/*
 * Copyright (C) 2015-2022 S[&]T, The Netherlands.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "coda.h"
#include "harp-ingestion.h"
#include "harp-geometry.h"

#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>

#define SECONDS_FROM_1993_TO_2000 (220838400 + 5)

typedef enum omi_dim_type_enum
{
    omi_dim_time,
    omi_dim_xtrack,
    omi_dim_vertical,
    omi_dim_spectral
} omi_dim_type;

#define OMI_NUM_DIM_TYPES  (((int)omi_dim_spectral) + 1)

typedef struct variable_descriptor_struct
{
    coda_cursor cursor;
    long (*get_offset)(const long *dimension, long index);
    long length;
    double missing_value;
    double scale_factor;
    double offset;
} variable_descriptor;

typedef struct ingest_info_struct
{
    /* product */
    coda_product *product;

    /* product version */
    int product_version;        /* 2 for V2, 3 for V3, -1 for unknown */

    /* product dimensions */
    long dimension[OMI_NUM_DIM_TYPES];

    /* cursors */
    coda_cursor swath_cursor;
    coda_cursor geo_cursor;

    /* geolocation buffers */
    double *longitude_grid;
    double *latitude_grid;

    /* options */
    int clipped_cloud_fraction;
    int so2_column_level;       /* 0, 1, 2, 3 */
    int clear_sky;      /* for UVB */
    int wavelength;     /* 305, 308, 324, 380 for UVB */
    int destriped;
    int radiative_cloud_fraction;       /* use RadiativeCloudFraction */

    variable_descriptor omo3pr_pressure;
    variable_descriptor omo3pr_o3;
    variable_descriptor omo3pr_o3_precision;
    variable_descriptor omaeruv_aod;
    variable_descriptor omaeruv_aaod;
} ingest_info;

static void calculate_corner_coordinates(long num_time, long num_xtrack, const double *longitude,
                                         const double *latitude, double *longitude_grid, double *latitude_grid)
{
    double center_longitude[4]; /* the four center coordinates needed to calculate a corner coordinate */
    double center_latitude[4];
    long i;
    long j;

    /* corner coordinates lying at the outer edges are calculated by means of extrapolation. */

    /* enumerate all corner coordinates (num_xtrack + 1) x (num_time + 1) and calculate the coordinates */
    for (i = 0; i < num_time + 1; i++)
    {
        for (j = 0; j < num_xtrack + 1; j++)
        {
            long id1;   /* id of first center coordinate for extrapolation */
            long id2;   /* id of second center coordinate for extrapolation */

            if (i == 0)
            {
                /* extrapolate */
                id1 = i * num_xtrack + j - 1 + (j == 0);
                id2 = id1 + num_xtrack + (j == 0);
                harp_geographic_extrapolation(latitude[id1], longitude[id1], latitude[id2], longitude[id2],
                                              &center_latitude[0], &center_longitude[0]);

                id1 = i * num_xtrack + j - (j == num_xtrack);
                id2 = id1 + num_xtrack - (j == num_xtrack);
                harp_geographic_extrapolation(latitude[id1], longitude[id1], latitude[id2], longitude[id2],
                                              &center_latitude[1], &center_longitude[1]);
            }
            else
            {
                if (j == 0)
                {
                    /* extrapolate */
                    id1 = (i - 1) * num_xtrack + j;
                    id2 = id1 + 1;
                    harp_geographic_extrapolation(latitude[id1], longitude[id1], latitude[id2], longitude[id2],
                                                  &center_latitude[0], &center_longitude[0]);
                }
                else
                {
                    center_longitude[0] = longitude[(i - 1) * num_xtrack + j - 1];
                    center_latitude[0] = latitude[(i - 1) * num_xtrack + j - 1];
                }

                if (j == num_xtrack)
                {
                    /* extrapolate */
                    id1 = (i - 1) * num_xtrack + j - 1;
                    id2 = id1 - 1;
                    harp_geographic_extrapolation(latitude[id1], longitude[id1], latitude[id2], longitude[id2],
                                                  &center_latitude[1], &center_longitude[1]);
                }
                else
                {
                    center_longitude[1] = longitude[(i - 1) * num_xtrack + j];
                    center_latitude[1] = latitude[(i - 1) * num_xtrack + j];
                }
            }

            if (i == num_time)
            {
                /* extrapolate */
                id1 = (i - 1) * num_xtrack + j - (j == num_xtrack);
                id2 = id1 - num_xtrack - (j == num_xtrack);
                harp_geographic_extrapolation(latitude[id1], longitude[id1], latitude[id2], longitude[id2],
                                              &center_latitude[2], &center_longitude[2]);

                id1 = (i - 1) * num_xtrack + j - 1 + (j == 0);
                id2 = id1 - num_xtrack + (j == 0);
                harp_geographic_extrapolation(latitude[id1], longitude[id1], latitude[id2], longitude[id2],
                                              &center_latitude[3], &center_longitude[3]);
            }
            else
            {
                if (j == num_xtrack)
                {
                    /* extrapolate */
                    id1 = i * num_xtrack + j - 1;
                    id2 = id1 - 1;
                    harp_geographic_extrapolation(latitude[id1], longitude[id1], latitude[id2], longitude[id2],
                                                  &center_latitude[2], &center_longitude[2]);
                }
                else
                {
                    center_longitude[2] = longitude[i * num_xtrack + j];
                    center_latitude[2] = latitude[i * num_xtrack + j];
                }

                if (j == 0)
                {
                    /* extrapolate */
                    id1 = i * num_xtrack + j;
                    id2 = id1 + 1;
                    harp_geographic_extrapolation(latitude[id1], longitude[id1], latitude[id2], longitude[id2],
                                                  &center_latitude[3], &center_longitude[3]);
                }
                else
                {
                    center_longitude[3] = longitude[i * num_xtrack + j - 1];
                    center_latitude[3] = latitude[i * num_xtrack + j - 1];
                }
            }

            harp_geographic_intersection(center_latitude[0], center_longitude[0],
                                         center_latitude[2], center_longitude[2],
                                         center_latitude[1], center_longitude[1],
                                         center_latitude[3], center_longitude[3],
                                         &latitude_grid[i * (num_xtrack + 1) + j],
                                         &longitude_grid[i * (num_xtrack + 1) + j]);
        }
    }
}

static void transform_array_double(long num_elements, double *data, double missing_value, double scale_factor,
                                   double offset)
{
    double *data_end;

    for (data_end = data + num_elements; data != data_end; data++)
    {
        if (*data == missing_value)
        {
            *data = coda_NaN();
        }
        else
        {
            *data = offset + scale_factor * (*data);
        }
    }
}

static void broadcast_array_double(long num_time, long num_xtrack, double *data)
{
    long i;

    /* Repeat the value for each time for all across track samples. Iterate in reverse to avoid overwriting values. */
    for (i = num_time - 1; i >= 0; --i)
    {
        double *xtrack = data + i * num_xtrack;
        double *xtrack_end = xtrack + num_xtrack;
        const double value = data[i];

        for (; xtrack != xtrack_end; xtrack++)
        {
            *xtrack = value;
        }
    }
}

static long get_offset_pressure(const long *dimension, long index)
{
    /* OMI profile products store pressure per level instead of per layer, whereas the corresponding profiles are stored
     * per layer (where num_levels = num_layers + 1). The ingested HARP product uses num_layers as the length of the
     * vertical dimension, i.e. dimension[omi_dim_vertical] = num_layers in this case. The offset calculation below
     * accounts for this difference.
     */
    return index * (dimension[omi_dim_vertical] + 1);
}

static int has_swath_variable(ingest_info *info, const char *name)
{
    long index;

    if (coda_cursor_get_record_field_index_from_name(&info->swath_cursor, name, &index) != 0)
    {
        return 0;
    }

    return 1;
}

static const char *get_variable_name_from_cursor(coda_cursor *cursor)
{
    coda_cursor parent_cursor;
    coda_type *parent_type;
    const char *variable_name;
    long index;

    variable_name = "<unknown variable name>";
    if (coda_cursor_get_index(cursor, &index) != 0)
    {
        return variable_name;
    }

    parent_cursor = *cursor;
    if (coda_cursor_goto_parent(&parent_cursor) != 0)
    {
        return variable_name;
    }
    if (coda_cursor_get_type(&parent_cursor, &parent_type) != 0)
    {
        return variable_name;
    }
    if (coda_type_get_record_field_real_name(parent_type, index, &variable_name) != 0)
    {
        return variable_name;
    }

    return variable_name;
}

static int verify_variable_dimensions(coda_cursor *cursor, int num_dimensions, const long *dimension)
{
    long coda_dimension[CODA_MAX_NUM_DIMS];
    int num_coda_dimensions;
    int i;

    if (coda_cursor_get_array_dim(cursor, &num_coda_dimensions, coda_dimension) != 0)
    {
        harp_set_error(HARP_ERROR_CODA, NULL);
        return -1;
    }
    if (num_coda_dimensions != num_dimensions)
    {
        harp_set_error(HARP_ERROR_INGESTION, "product error detected (variable '%s' has %d dimensions, expected %d)",
                       get_variable_name_from_cursor(cursor), num_coda_dimensions, num_dimensions);
        return -1;
    }

    for (i = 0; i < num_dimensions; i++)
    {
        if (coda_dimension[i] != dimension[i])
        {
            harp_set_error(HARP_ERROR_INGESTION,
                           "product error detected (dimension %d of variable '%s' has %ld elements," " expected %ld)",
                           i, get_variable_name_from_cursor(cursor), coda_dimension[i], dimension[i]);
            return -1;
        }
    }

    return 0;
}

static int get_variable_attributes(coda_cursor *cursor, double *missing_value, double *scale_factor, double *offset)
{
    if (coda_cursor_goto_attributes(cursor) != 0)
    {
        harp_set_error(HARP_ERROR_CODA, NULL);
        return -1;
    }
    if (missing_value != NULL)
    {
        if (coda_cursor_goto_record_field_by_name(cursor, "MissingValue") != 0)
        {
            harp_set_error(HARP_ERROR_CODA, NULL);
            return -1;
        }
        if (coda_cursor_goto_first_array_element(cursor) != 0)
        {
            harp_set_error(HARP_ERROR_CODA, NULL);
            return -1;
        }
        if (coda_cursor_read_double(cursor, missing_value) != 0)
        {
            harp_set_error(HARP_ERROR_CODA, NULL);
            return -1;
        }
        coda_cursor_goto_parent(cursor);
        coda_cursor_goto_parent(cursor);
    }
    if (scale_factor != NULL)
    {
        if (coda_cursor_goto_record_field_by_name(cursor, "ScaleFactor") != 0)
        {
            /* use a scale factor of 1 */
            *scale_factor = 1;
        }
        else
        {
            if (coda_cursor_goto_first_array_element(cursor) != 0)
            {
                harp_set_error(HARP_ERROR_CODA, NULL);
                return -1;
            }
            if (coda_cursor_read_double(cursor, scale_factor) != 0)
            {
                harp_set_error(HARP_ERROR_CODA, NULL);
                return -1;
            }
            coda_cursor_goto_parent(cursor);
            coda_cursor_goto_parent(cursor);
        }
    }
    if (offset != NULL)
    {
        if (coda_cursor_goto_record_field_by_name(cursor, "Offset") != 0)
        {
            /* use an offset of 0 */
            *offset = 0;
        }
        else
        {
            if (coda_cursor_goto_first_array_element(cursor) != 0)
            {
                harp_set_error(HARP_ERROR_CODA, NULL);
                return -1;
            }
            if (coda_cursor_read_double(cursor, offset) != 0)
            {
                harp_set_error(HARP_ERROR_CODA, NULL);
                return -1;
            }
            coda_cursor_goto_parent(cursor);
            coda_cursor_goto_parent(cursor);
        }
    }
    coda_cursor_goto_parent(cursor);

    return 0;
}

static int variable_descriptor_init(coda_cursor *cursor, const char *name, int num_dimensions, const long *dimension,
                                    long (*get_offset)(const long *dimension, long index), long length,
                                    variable_descriptor *descriptor)
{
    descriptor->cursor = *cursor;
    descriptor->get_offset = get_offset;
    descriptor->length = length;

    if (coda_cursor_goto(&descriptor->cursor, name) != 0)
    {
        harp_set_error(HARP_ERROR_CODA, NULL);
        return -1;
    }
    if (verify_variable_dimensions(&descriptor->cursor, num_dimensions, dimension) != 0)
    {
        return -1;
    }
    if (get_variable_attributes(&descriptor->cursor, &descriptor->missing_value, &descriptor->scale_factor,
                                &descriptor->offset) != 0)
    {
        return -1;
    }

    return 0;
}

static int read_variable_int16(ingest_info *info, coda_cursor *cursor, const char *name, harp_array data)
{
    long dimension[2];

    dimension[0] = info->dimension[omi_dim_time];
    dimension[1] = info->dimension[omi_dim_xtrack];
    if (coda_cursor_goto_record_field_by_name(cursor, name) != 0)
    {
        harp_set_error(HARP_ERROR_CODA, NULL);
        return -1;
    }
    if (verify_variable_dimensions(cursor, 2, dimension) != 0)
    {
        return -1;
    }
    if (coda_cursor_read_int16_array(cursor, data.int16_data, coda_array_ordering_c) != 0)
    {
        harp_set_error(HARP_ERROR_CODA, NULL);
        return -1;
    }
    coda_cursor_goto_parent(cursor);

    return 0;
}

static int read_variable_int32(ingest_info *info, coda_cursor *cursor, const char *name, harp_array data)
{
    long dimension[2];

    dimension[0] = info->dimension[omi_dim_time];
    dimension[1] = info->dimension[omi_dim_xtrack];
    if (coda_cursor_goto_record_field_by_name(cursor, name) != 0)
    {
        harp_set_error(HARP_ERROR_CODA, NULL);
        return -1;
    }
    if (verify_variable_dimensions(cursor, 2, dimension) != 0)
    {
        return -1;
    }
    if (coda_cursor_read_int32_array(cursor, data.int32_data, coda_array_ordering_c) != 0)
    {
        harp_set_error(HARP_ERROR_CODA, NULL);
        return -1;
    }
    coda_cursor_goto_parent(cursor);

    return 0;
}

static int read_variable_double(ingest_info *info, coda_cursor *cursor, const char *name, int num_dimensions,
                                const long *dimension, harp_array data)
{
    long default_dimension[2];
    long num_elements;
    double missing_value;
    double scale_factor;
    double offset;

    default_dimension[0] = info->dimension[omi_dim_time];
    default_dimension[1] = info->dimension[omi_dim_xtrack];
    if (coda_cursor_goto_record_field_by_name(cursor, name) != 0)
    {
        harp_set_error(HARP_ERROR_CODA, NULL);
        return -1;
    }
    assert(dimension != NULL || num_dimensions <= 2);
    if (verify_variable_dimensions(cursor, num_dimensions, (dimension == NULL ? default_dimension : dimension)) != 0)
    {
        return -1;
    }
    if (get_variable_attributes(cursor, &missing_value, &scale_factor, &offset) != 0)
    {
        return -1;
    }
    if (coda_cursor_read_double_array(cursor, data.double_data, coda_array_ordering_c) != 0)
    {
        harp_set_error(HARP_ERROR_CODA, NULL);
        return -1;
    }
    coda_cursor_goto_parent(cursor);

    /* apply scaling and filter for NaN */
    num_elements = harp_get_num_elements(num_dimensions, (dimension == NULL ? default_dimension : dimension));
    transform_array_double(num_elements, data.double_data, missing_value, scale_factor, offset);

    return 0;
}

static int read_variable_partial_double(ingest_info *info, variable_descriptor *descriptor, long index, harp_array data)
{
    long offset;

    if (descriptor->get_offset == NULL)
    {
        offset = index * descriptor->length;
    }
    else
    {
        offset = descriptor->get_offset(info->dimension, index);
    }

    if (coda_cursor_read_double_partial_array(&descriptor->cursor, offset, descriptor->length, data.double_data) != 0)
    {
        harp_set_error(HARP_ERROR_CODA, NULL);
        return -1;
    }

    /* apply scaling and filter for NaN */
    transform_array_double(descriptor->length, data.double_data, descriptor->missing_value, descriptor->scale_factor,
                           descriptor->offset);

    return 0;
}

static int verify_dimensions(ingest_info *info)
{
    /* The time and xtrack dimensions should be >1 because we need to calculate corner coordinates. */
    if (info->dimension[omi_dim_time] == 1)
    {
        harp_set_error(HARP_ERROR_INGESTION, "product error detected ('time' dimension should be larger than 1)");
        return -1;
    }
    if (info->dimension[omi_dim_xtrack] == 1)
    {
        harp_set_error(HARP_ERROR_INGESTION, "product error detected ('xtrack' dimension should be larger than 1)");
        return -1;
    }

    return 0;
}

static int verify_dimensions_omaeruv(ingest_info *info)
{
    if (verify_dimensions(info) != 0)
    {
        return -1;
    }

    if (info->dimension[omi_dim_spectral] != 3)
    {
        harp_set_error(HARP_ERROR_INGESTION, "product error detected ('spectral' dimension has length %ld, expected 3)",
                       info->dimension[omi_dim_spectral]);
        return -1;
    }

    return 0;
}

static int verify_dimensions_omo3pr(ingest_info *info)
{
    if (verify_dimensions(info) != 0)
    {
        return -1;
    }

    if (info->dimension[omi_dim_vertical] <= 0)
    {
        harp_set_error(HARP_ERROR_INGESTION, "product error detected ('vertical' dimension should be larger than 0)");
        return -1;
    }

    return 0;
}

static int init_cursors(ingest_info *info)
{
    if (coda_cursor_set_product(&info->swath_cursor, info->product) != 0)
    {
        harp_set_error(HARP_ERROR_CODA, NULL);
        return -1;
    }
    if (coda_cursor_goto_record_field_by_name(&info->swath_cursor, "HDFEOS") != 0)
    {
        harp_set_error(HARP_ERROR_CODA, NULL);
        return -1;
    }
    if (coda_cursor_goto_record_field_by_name(&info->swath_cursor, "SWATHS") != 0)
    {
        harp_set_error(HARP_ERROR_CODA, NULL);
        return -1;
    }
    if (coda_cursor_goto_first_record_field(&info->swath_cursor) != 0)
    {
        harp_set_error(HARP_ERROR_CODA, NULL);
        return -1;
    }
    info->geo_cursor = info->swath_cursor;
    if (coda_cursor_goto_record_field_by_name(&info->swath_cursor, "Data_Fields") != 0)
    {
        harp_set_error(HARP_ERROR_CODA, NULL);
        return -1;
    }
    if (coda_cursor_goto_record_field_by_name(&info->geo_cursor, "Geolocation_Fields") != 0)
    {
        harp_set_error(HARP_ERROR_CODA, NULL);
        return -1;
    }

    return 0;
}

static int init_dimensions(ingest_info *info)
{
    coda_cursor cursor;
    long dim[CODA_MAX_NUM_DIMS];
    int num_dims;

    cursor = info->geo_cursor;
    if (coda_cursor_goto_record_field_by_name(&cursor, "Latitude") != 0)
    {
        harp_set_error(HARP_ERROR_CODA, NULL);
        return -1;
    }
    if (coda_cursor_get_array_dim(&cursor, &num_dims, dim) != 0)
    {
        harp_set_error(HARP_ERROR_CODA, NULL);
        return -1;
    }
    if (num_dims != 2)
    {
        harp_set_error(HARP_ERROR_INGESTION, "product error detected (product has %d dimensions, expected 2)",
                       num_dims);
        return -1;
    }

    info->dimension[omi_dim_time] = dim[0];
    info->dimension[omi_dim_xtrack] = dim[1];

    return 0;
}

static int init_dimensions_omaeruv(ingest_info *info)
{
    coda_cursor cursor;
    long dim[CODA_MAX_NUM_DIMS];
    int num_dims;

    cursor = info->swath_cursor;
    if (coda_cursor_goto_record_field_by_name(&cursor, "FinalAerosolOpticalDepth") != 0)
    {
        harp_set_error(HARP_ERROR_CODA, NULL);
        return -1;
    }
    if (coda_cursor_get_array_dim(&cursor, &num_dims, dim) != 0)
    {
        harp_set_error(HARP_ERROR_CODA, NULL);
        return -1;
    }
    if (num_dims != 3)
    {
        harp_set_error(HARP_ERROR_INGESTION, "product error detected (product has %d dimensions, expected 3)",
                       num_dims);
        return -1;
    }

    info->dimension[omi_dim_time] = dim[0];
    info->dimension[omi_dim_xtrack] = dim[1];
    info->dimension[omi_dim_spectral] = dim[2];

    return 0;
}

static int init_dimensions_omo3pr(ingest_info *info)
{
    coda_cursor cursor;
    long dim[CODA_MAX_NUM_DIMS];
    int num_dims;

    cursor = info->geo_cursor;
    if (coda_cursor_goto_record_field_by_name(&cursor, "Pressure") != 0)
    {
        harp_set_error(HARP_ERROR_CODA, NULL);
        return -1;
    }
    if (coda_cursor_get_array_dim(&cursor, &num_dims, dim) != 0)
    {
        harp_set_error(HARP_ERROR_CODA, NULL);
        return -1;
    }
    if (num_dims != 3)
    {
        harp_set_error(HARP_ERROR_INGESTION, "product error detected (product has %d dimensions, expected 3)",
                       num_dims);
        return -1;
    }

    info->dimension[omi_dim_time] = dim[0];
    info->dimension[omi_dim_xtrack] = dim[1];
    /* Pressure data is given per level and the amount of layers is the amount of levels minus 1. */
    info->dimension[omi_dim_vertical] = dim[2] - 1;

    return 0;
}

static int init_geolocation(ingest_info *info)
{
    long num_time = info->dimension[omi_dim_time];
    long num_xtrack = info->dimension[omi_dim_xtrack];
    harp_array longitude;
    harp_array latitude;

    /* read longitude information */
    longitude.ptr = malloc(num_xtrack * num_time * sizeof(double));
    if (longitude.ptr == NULL)
    {
        harp_set_error(HARP_ERROR_OUT_OF_MEMORY, "out of memory (could not allocate %lu bytes) (%s:%u)",
                       num_xtrack * num_time * sizeof(double), __FILE__, __LINE__);
        return -1;
    }
    if (read_variable_double(info, &info->geo_cursor, "Longitude", 2, NULL, longitude) != 0)
    {
        free(longitude.ptr);
        return -1;
    }

    /* read latitude information */
    latitude.ptr = malloc(num_xtrack * num_time * sizeof(double));
    if (latitude.ptr == NULL)
    {
        harp_set_error(HARP_ERROR_OUT_OF_MEMORY, "out of memory (could not allocate %lu bytes) (%s:%u)",
                       num_xtrack * num_time * sizeof(double), __FILE__, __LINE__);
        free(longitude.ptr);
        return -1;
    }
    if (read_variable_double(info, &info->geo_cursor, "Latitude", 2, NULL, latitude) != 0)
    {
        free(latitude.ptr);
        free(longitude.ptr);
        return -1;
    }

    /* calculate corner coordinates */
    info->longitude_grid = malloc((num_xtrack + 1) * (num_time + 1) * sizeof(double));
    if (info->longitude_grid == NULL)
    {
        harp_set_error(HARP_ERROR_OUT_OF_MEMORY, "out of memory (could not allocate %lu bytes) (%s:%u)",
                       (num_xtrack + 1) * (num_time + 1) * sizeof(double), __FILE__, __LINE__);
        free(latitude.ptr);
        free(longitude.ptr);
        return -1;
    }
    info->latitude_grid = malloc((num_xtrack + 1) * (num_time + 1) * sizeof(double));
    if (info->latitude_grid == NULL)
    {
        harp_set_error(HARP_ERROR_OUT_OF_MEMORY, "out of memory (could not allocate %lu bytes) (%s:%u)",
                       (num_xtrack + 1) * (num_time + 1) * sizeof(double), __FILE__, __LINE__);
        free(latitude.ptr);
        free(longitude.ptr);
        return -1;
    }

    calculate_corner_coordinates(num_time, num_xtrack, longitude.double_data, latitude.double_data,
                                 info->longitude_grid, info->latitude_grid);

    free(latitude.ptr);
    free(longitude.ptr);

    return 0;
}

static int ingest_info_new(ingest_info **new_info)
{
    ingest_info *info;

    info = malloc(sizeof(ingest_info));
    if (info == NULL)
    {
        harp_set_error(HARP_ERROR_OUT_OF_MEMORY, "out of memory (could not allocate %lu bytes) (%s:%u)",
                       sizeof(ingest_info), __FILE__, __LINE__);
        return -1;
    }
    info->product = NULL;
    info->product_version = -1;
    memset(info->dimension, 0, OMI_NUM_DIM_TYPES * sizeof(long));
    info->longitude_grid = NULL;
    info->latitude_grid = NULL;
    info->clipped_cloud_fraction = 1;
    info->so2_column_level = 0;
    info->destriped = 0;
    info->clear_sky = 0;
    info->wavelength = 0;
    info->radiative_cloud_fraction = 0;

    *new_info = info;

    return 0;
}

static void ingest_info_delete(ingest_info *info)
{
    if (info->longitude_grid != NULL)
    {
        free(info->longitude_grid);
    }
    if (info->latitude_grid != NULL)
    {
        free(info->latitude_grid);
    }

    free(info);
}

static int read_dimensions(void *user_data, long dimension[HARP_NUM_DIM_TYPES])
{
    ingest_info *info = (ingest_info *)user_data;

    dimension[harp_dimension_time] = info->dimension[omi_dim_time] * info->dimension[omi_dim_xtrack];

    return 0;
}

static int read_dimensions_omaeruv(void *user_data, long dimension[HARP_NUM_DIM_TYPES])
{
    ingest_info *info = (ingest_info *)user_data;

    dimension[harp_dimension_time] = info->dimension[omi_dim_time] * info->dimension[omi_dim_xtrack];
    dimension[harp_dimension_spectral] = info->dimension[omi_dim_spectral];

    return 0;
}

static int read_dimensions_omo3pr(void *user_data, long dimension[HARP_NUM_DIM_TYPES])
{
    ingest_info *info = (ingest_info *)user_data;

    dimension[harp_dimension_time] = info->dimension[omi_dim_time] * info->dimension[omi_dim_xtrack];
    dimension[harp_dimension_vertical] = info->dimension[omi_dim_vertical];

    return 0;
}

static int read_datetime(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;
    long i;

    /* read datetime */
    if (read_variable_double(info, &info->geo_cursor, "Time", 1, NULL, data) != 0)
    {
        return -1;
    }

    /* convert datetime values from TAI93 to seconds since 2000-01-01 */
    for (i = 0; i < info->dimension[omi_dim_time]; i++)
    {
        data.double_data[i] -= SECONDS_FROM_1993_TO_2000;
    }

    /* broadcast the result along the xtrack dimension */
    broadcast_array_double(info->dimension[omi_dim_time], info->dimension[omi_dim_xtrack], data.double_data);

    return 0;
}

static int read_longitude_bounds(void *user_data, long index, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;
    long num_xtrack;
    int i;
    int j;

    if (info->longitude_grid == NULL)
    {
        if (init_geolocation(info) != 0)
        {
            return -1;
        }
    }

    num_xtrack = info->dimension[omi_dim_xtrack];
    i = index / num_xtrack;     /* 0 <= i < num_time */
    j = index - i * num_xtrack; /* 0 <= j < num_xtrack */

    data.double_data[0] = info->longitude_grid[i * (num_xtrack + 1) + j];
    data.double_data[1] = info->longitude_grid[i * (num_xtrack + 1) + j + 1];
    data.double_data[2] = info->longitude_grid[(i + 1) * (num_xtrack + 1) + j + 1];
    data.double_data[3] = info->longitude_grid[(i + 1) * (num_xtrack + 1) + j];

    return 0;
}

static int read_latitude_bounds(void *user_data, long index, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;
    long num_xtrack;
    int i;
    int j;

    if (info->latitude_grid == NULL)
    {
        if (init_geolocation(info) != 0)
        {
            return -1;
        }
    }

    num_xtrack = info->dimension[omi_dim_xtrack];
    i = index / num_xtrack;     /* 0 <= i < num_time */
    j = index - i * num_xtrack; /* 0 <= j < num_xtrack */

    data.double_data[0] = info->latitude_grid[i * (num_xtrack + 1) + j];
    data.double_data[1] = info->latitude_grid[i * (num_xtrack + 1) + j + 1];
    data.double_data[2] = info->latitude_grid[(i + 1) * (num_xtrack + 1) + j + 1];
    data.double_data[3] = info->latitude_grid[(i + 1) * (num_xtrack + 1) + j];

    return 0;
}

static int read_longitude(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->geo_cursor, "Longitude", 2, NULL, data);
}

static int read_latitude(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->geo_cursor, "Latitude", 2, NULL, data);
}

static int read_longitude_bounds_domino(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;
    long dimension[3];
    long dimension_transpose[2];
    long i;

    dimension[0] = 4;
    dimension[1] = info->dimension[omi_dim_time];
    dimension[2] = info->dimension[omi_dim_xtrack];
    dimension_transpose[0] = 4;
    dimension_transpose[1] = info->dimension[omi_dim_time] * info->dimension[omi_dim_xtrack];
    if (read_variable_double(info, &info->geo_cursor, "LongitudeCornerpoints", 3, dimension, data) != 0)
    {
        return -1;
    }

    /* reorder array dimensions from [4, num_time, num_xtrack] to [num_time, num_xtrack, 4] */
    if (harp_array_transpose(harp_type_double, 2, dimension_transpose, NULL, data) != 0)
    {
        return -1;
    }

    /* reorder corner coordinates from {a,b,c,d} to {d,b,a,c} */
    for (i = 0; i < info->dimension[omi_dim_time] * info->dimension[omi_dim_xtrack]; i++)
    {
        double temp = data.double_data[i * 4];

        data.double_data[i * 4] = data.double_data[i * 4 + 3];
        data.double_data[i * 4 + 3] = data.double_data[i * 4 + 2];
        data.double_data[i * 4 + 2] = temp;
    }

    return 0;
}

static int read_latitude_bounds_domino(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;
    long dimension[3];
    long dimension_transpose[2];
    long i;

    dimension[0] = 4;
    dimension[1] = info->dimension[omi_dim_time];
    dimension[2] = info->dimension[omi_dim_xtrack];
    dimension_transpose[0] = 4;
    dimension_transpose[1] = info->dimension[omi_dim_time] * info->dimension[omi_dim_xtrack];
    if (read_variable_double(info, &info->geo_cursor, "LatitudeCornerpoints", 3, dimension, data) != 0)
    {
        return -1;
    }

    /* reorder array dimensions from [4, num_time, num_xtrack] to [num_time, num_xtrack, 4] */
    if (harp_array_transpose(harp_type_double, 2, dimension_transpose, NULL, data) != 0)
    {
        return -1;
    }

    /* reorder corner coordinates from {a,b,c,d} to {d,b,a,c} */
    for (i = 0; i < info->dimension[omi_dim_time] * info->dimension[omi_dim_xtrack]; i++)
    {
        double temp = data.double_data[i * 4];

        data.double_data[i * 4] = data.double_data[i * 4 + 3];
        data.double_data[i * 4 + 3] = data.double_data[i * 4 + 2];
        data.double_data[i * 4 + 2] = temp;
    }

    return 0;
}

static int read_processing_quality_flags(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_int32(info, &info->swath_cursor, "ProcessingQualityFlags", data);
}

static int read_processing_quality_flags_o3(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_int32(info, &info->swath_cursor, "ProcessingQualityFlagsforO3", data);
}

static int read_quality_flags(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_int32(info, &info->swath_cursor, "QualityFlags", data);
}

static int read_vcd_quality_flags(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_int32(info, &info->swath_cursor, "VcdQualityFlags", data);
}

static int read_pressure(void *user_data, long index, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_partial_double(info, &info->omo3pr_pressure, index, data);
}

static int read_o3(void *user_data, long index, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_partial_double(info, &info->omo3pr_o3, index, data);
}

static int read_o3_error(void *user_data, long index, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_partial_double(info, &info->omo3pr_o3_precision, index, data);
}

static int read_o3_column(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "ColumnAmountO3", 2, NULL, data);
}

static int read_o3_column_error(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "ColumnAmountO3Precision", 2, NULL, data);
}

static int read_so2_column(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;
    int status = -1;

    if (info->product_version == 2)
    {
        if (info->so2_column_level == 1)
        {
            status = read_variable_double(info, &info->swath_cursor, "SO2ColumnAmount05KM", 2, NULL, data);
        }
        else if (info->so2_column_level == 2)
        {
            status = read_variable_double(info, &info->swath_cursor, "SO2ColumnAmount15KM", 2, NULL, data);
        }
        else
        {
            assert(info->so2_column_level == 0);
            status = read_variable_double(info, &info->swath_cursor, "SO2ColumnAmountPBL", 2, NULL, data);
        }
    }
    else
    {
        if (info->so2_column_level == 1)
        {
            status = read_variable_double(info, &info->swath_cursor, "ColumnAmountSO2_TRL", 2, NULL, data);
        }
        else if (info->so2_column_level == 2)
        {
            status = read_variable_double(info, &info->swath_cursor, "ColumnAmountSO2_TRM", 2, NULL, data);
        }
        else if (info->so2_column_level == 3)
        {
            status = read_variable_double(info, &info->swath_cursor, "ColumnAmountSO2_STL", 2, NULL, data);
        }
        else
        {
            assert(info->so2_column_level == 0);
            status = read_variable_double(info, &info->swath_cursor, "ColumnAmountSO2_PBL", 2, NULL, data);
        }
    }

    return status;
}

static int read_no2_column(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "ColumnAmountNO2", 2, NULL, data);
}

static int read_no2_column_error(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "ColumnAmountNO2Std", 2, NULL, data);
}

static int read_no2_column_tropospheric(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "ColumnAmountNO2Trop", 2, NULL, data);
}

static int read_no2_column_tropospheric_error(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "ColumnAmountNO2TropStd", 2, NULL, data);
}

static int read_no2_column_tropospheric_amf(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "AmfTrop", 2, NULL, data);
}

static int read_no2_column_tropospheric_apriori(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "VcdApTrop", 2, NULL, data);
}

static int read_no2_column_stratospheric(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "ColumnAmountNO2Strat", 2, NULL, data);
}

static int read_no2_column_stratospheric_error(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "ColumnAmountNO2StratStd", 2, NULL, data);
}

static int read_no2_column_stratospheric_amf(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "AmfStrat", 2, NULL, data);
}

static int read_no2_column_stratospheric_apriori(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "VcdApStrat", 2, NULL, data);
}

static int read_no2_column_domino(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "TotalVerticalColumn", 2, NULL, data);
}

static int read_no2_column_error_domino(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "TotalVerticalColumnError", 2, NULL, data);
}

static int read_no2_column_tropospheric_domino(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "TroposphericVerticalColumn", 2, NULL, data);
}

static int read_no2_column_tropospheric_error_domino(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "TroposphericVerticalColumnError", 2, NULL, data);
}

static int read_no2_column_tropospheric_validity_domino(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_int16(info, &info->swath_cursor, "TroposphericColumnFlag", data);
}

static int read_no2_slant_column(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "SlantColumnAmountNO2", 2, NULL, data);
}

static int read_no2_slant_column_error(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "SlantColumnAmountNO2Std", 2, NULL, data);
}

static int read_bro_column(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;
    int status = -1;

    if (info->destriped)
    {
        status = read_variable_double(info, &info->swath_cursor, "ColumnAmountDestriped", 2, NULL, data);
    }
    else
    {
        status = read_variable_double(info, &info->swath_cursor, "ColumnAmount", 2, NULL, data);
    }

    return status;
}

static int read_bro_column_error(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "ColumnUncertainty", 2, NULL, data);
}

static int read_chocho_column(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;
    int status = -1;

    if (info->destriped)
    {
        status = read_variable_double(info, &info->swath_cursor, "ColumnAmountDestriped", 2, NULL, data);
    }
    else
    {
        status = read_variable_double(info, &info->swath_cursor, "ColumnAmount", 2, NULL, data);
    }

    return status;
}

static int read_chocho_column_error(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "ColumnUncertainty", 2, NULL, data);
}

static int read_hcho_column(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;
    int status = -1;

    if (info->destriped)
    {
        status = read_variable_double(info, &info->swath_cursor, "ColumnAmountDestriped", 2, NULL, data);
    }
    else
    {
        status = read_variable_double(info, &info->swath_cursor, "ColumnAmount", 2, NULL, data);
    }

    return status;
}

static int read_hcho_column_error(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "ColumnUncertainty", 2, NULL, data);
}

static int read_oclo_column(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;
    int status = -1;

    if (info->destriped)
    {
        status = read_variable_double(info, &info->swath_cursor, "ColumnAmountDestriped", 2, NULL, data);
    }
    else
    {
        status = read_variable_double(info, &info->swath_cursor, "ColumnAmount", 2, NULL, data);
    }

    return status;
}

static int read_oclo_column_error(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "ColumnUncertainty", 2, NULL, data);
}

static int read_cloud_fraction(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;
    int status = -1;

    if (info->radiative_cloud_fraction)
    {
        status = read_variable_double(info, &info->swath_cursor, "RadiativeCloudFraction", 2, NULL, data);
    }
    else if (info->clipped_cloud_fraction)
    {
        if (has_swath_variable(info, "fc"))
        {
            status = read_variable_double(info, &info->swath_cursor, "fc", 2, NULL, data);
        }
        else
        {
            status = read_variable_double(info, &info->swath_cursor, "CloudFraction", 2, NULL, data);
        }
    }
    else
    {
        status = read_variable_double(info, &info->swath_cursor, "CloudFractionNotClipped", 2, NULL, data);
    }

    return status;
}

static int read_cloud_fraction_for_o3(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "CloudFractionforO3", 2, NULL, data);
}

static int read_cloud_fraction_precision(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "CloudFractionPrecision", 2, NULL, data);
}

static int read_cloud_fraction_std(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "CloudFractionStd", 2, NULL, data);
}

static int read_pressure_cloud(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "CloudPressure", 2, NULL, data);
}

static int read_pressure_cloud_for_o3(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "CloudPressureforO3", 2, NULL, data);
}

static int read_pressure_cloud_top(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "CloudTopPressure", 2, NULL, data);
}

static int read_pressure_cloud_precision(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "CloudPressurePrecision", 2, NULL, data);
}

static int read_pressure_cloud_std(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "CloudPressureStd", 2, NULL, data);
}

static int read_tropopause_pressure(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "TropopausePressure", 2, NULL, data);
}

static int read_uv_irradiance_surface(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;
    const char *variable_name = NULL;

    switch (info->wavelength)
    {
        case 305:
            variable_name = info->clear_sky ? "CSIrradiance305" : "Irradiance305";
            break;
        case 310:
            variable_name = info->clear_sky ? "CSIrradiance310" : "Irradiance310";
            break;
        case 324:
            variable_name = info->clear_sky ? "CSIrradiance324" : "Irradiance324";
            break;
        case 380:
            variable_name = info->clear_sky ? "CSIrradiance380" : "Irradiance380";
            break;
        default:
            assert(0);
    }

    return read_variable_double(info, &info->swath_cursor, variable_name, 2, NULL, data);
}

static int read_aerosol_wavelength(void *user_data, harp_array data)
{
    (void)user_data;

    data.double_data[0] = 354.0;
    data.double_data[1] = 388.0;
    data.double_data[2] = 500.0;

    return 0;
}

static int read_aerosol_optical_depth(void *user_data, long index, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_partial_double(info, &info->omaeruv_aod, index, data);
}

static int read_aerosol_absorbing_optical_depth(void *user_data, long index, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_partial_double(info, &info->omaeruv_aaod, index, data);
}

static int read_uv_aerosol_index(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "UVAerosolIndex", 2, NULL, data);
}

static int read_solar_zenith_angle_wgs84(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->geo_cursor, "SolarZenithAngle", 2, NULL, data);
}

static int read_solar_azimuth_angle_wgs84(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->geo_cursor, "SolarAzimuthAngle", 2, NULL, data);
}

static int read_viewing_zenith_angle_wgs84(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->geo_cursor, "ViewingZenithAngle", 2, NULL, data);
}

static int read_viewing_azimuth_angle_wgs84(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->geo_cursor, "ViewingAzimuthAngle", 2, NULL, data);
}

static int read_relative_azimuth_angle_wgs84(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->geo_cursor, "RelativeAzimuthAngle", 2, NULL, data);
}

static int read_spacecraft_altitude(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    /* read SpacecraftAltitude */
    if (read_variable_double(info, &info->geo_cursor, "SpacecraftAltitude", 1, NULL, data) != 0)
    {
        return -1;
    }

    /* broadcast the result along the xtrack dimension */
    broadcast_array_double(info->dimension[omi_dim_time], info->dimension[omi_dim_xtrack], data.double_data);

    return 0;
}

static int read_spacecraft_latitude(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    /* read SpacecraftLatitude */
    if (read_variable_double(info, &info->geo_cursor, "SpacecraftLatitude", 1, NULL, data) != 0)
    {
        return -1;
    }

    /* broadcast the result along the xtrack dimension */
    broadcast_array_double(info->dimension[omi_dim_time], info->dimension[omi_dim_xtrack], data.double_data);

    return 0;
}

static int read_spacecraft_longitude(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    /* read SpacecraftLongitude */
    if (read_variable_double(info, &info->geo_cursor, "SpacecraftLongitude", 1, NULL, data) != 0)
    {
        return -1;
    }

    /* broadcast the result along the xtrack dimension */
    broadcast_array_double(info->dimension[omi_dim_time], info->dimension[omi_dim_xtrack], data.double_data);

    return 0;
}

static int read_terrain_height_from_data(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "TerrainHeight", 2, NULL, data);
}

static int read_terrain_height_from_geolocation(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->geo_cursor, "TerrainHeight", 2, NULL, data);
}

static int read_terrain_pressure_from_data(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->swath_cursor, "TerrainPressure", 2, NULL, data);
}

static int read_terrain_pressure_from_geolocation(void *user_data, harp_array data)
{
    ingest_info *info = (ingest_info *)user_data;

    return read_variable_double(info, &info->geo_cursor, "TerrainPressure", 2, NULL, data);
}

static int include_no_destriped(void *user_data)
{
    return !((ingest_info *)user_data)->destriped;
}

static int include_cloud_fraction(void *user_data)
{
    ingest_info *info = (ingest_info *)user_data;

    return !info->radiative_cloud_fraction || has_swath_variable(info, "RadiativeCloudFraction");
}

static int include_cloud_pressure(void *user_data)
{
    return has_swath_variable((ingest_info *)user_data, "CloudPressure");
}

static int include_cloud_top_pressure(void *user_data)
{
    return has_swath_variable((ingest_info *)user_data, "CloudTopPressure");
}

static int include_tropopause_pressure(void *user_data)
{
    return has_swath_variable((ingest_info *)user_data, "TropopausePressure");
}

static int include_stratno2(void *user_data)
{
    return has_swath_variable((ingest_info *)user_data, "ColumnAmountNO2Strat");
}

static int include_vcd_quality_flags(void *user_data)
{
    return has_swath_variable((ingest_info *)user_data, "VcdQualityFlags");
}

static int include_amf_strat(void *user_data)
{
    return has_swath_variable((ingest_info *)user_data, "AmfStrat");
}

static int include_amf_trop(void *user_data)
{
    return has_swath_variable((ingest_info *)user_data, "AmfTrop");
}

static int include_vcd_ap_strat(void *user_data)
{
    return has_swath_variable((ingest_info *)user_data, "VcdApStrat");
}

static int include_vcd_ap_trop(void *user_data)
{
    return has_swath_variable((ingest_info *)user_data, "VcdApTrop");
}

static int parse_option_clipped_cloud_fraction(ingest_info *info, const harp_ingestion_options *options)
{
    const char *value;

    if (harp_ingestion_options_get_option(options, "clipped_cloud_fraction", &value) == 0)
    {
        info->clipped_cloud_fraction = 0;
    }

    return 0;
}

static int parse_option_cloud_fraction_variant(ingest_info *info, const harp_ingestion_options *options)
{
    const char *value;

    if (harp_ingestion_options_get_option(options, "cloud_fraction_variant", &value) == 0)
    {
        info->radiative_cloud_fraction = (strcmp(value, "radiative") == 0);
    }

    return 0;
}

static int parse_option_so2_column_variant(ingest_info *info, const harp_ingestion_options *options)
{
    const char *value;

    if (harp_ingestion_options_get_option(options, "so2_column_variant", &value) == 0)
    {
        if (info->product_version == 2)
        {
            if (strcmp(value, "pbl") == 0)
            {
                info->so2_column_level = 0;
            }
            else if (strcmp(value, "5km") == 0)
            {
                info->so2_column_level = 1;
            }
            else if (strcmp(value, "15km") == 0)
            {
                info->so2_column_level = 2;
            }
            else
            {
                harp_set_error(HARP_ERROR_INVALID_INGESTION_OPTION_VALUE, "value '%s' for ingestion option "
                               "'so2_column_variant' not supported for product version %d", value,
                               info->product_version);
                return -1;
            }
        }
        else
        {
            if (strcmp(value, "pbl") == 0)
            {
                info->so2_column_level = 0;
            }
            else if (strcmp(value, "trl") == 0)
            {
                info->so2_column_level = 1;
            }
            else if (strcmp(value, "trm") == 0)
            {
                info->so2_column_level = 2;
            }
            else if (strcmp(value, "stl") == 0)
            {
                info->so2_column_level = 3;
            }
            else
            {
                harp_set_error(HARP_ERROR_INVALID_INGESTION_OPTION_VALUE, "value '%s' for ingestion option "
                               "'so2_column_variant' not supported for product version %d", value,
                               info->product_version);
                return -1;
            }
        }
    }

    return 0;
}

static int parse_option_destriped(ingest_info *info, const harp_ingestion_options *options)
{
    const char *value;

    if (harp_ingestion_options_get_option(options, "destriped", &value) == 0)
    {
        info->destriped = 1;
    }

    return 0;
}

static int parse_option_clear_sky(ingest_info *info, const harp_ingestion_options *options)
{
    const char *value;

    if (harp_ingestion_options_get_option(options, "clear_sky", &value) == 0)
    {
        info->clear_sky = 1;
    }

    return 0;
}

static int parse_option_wavelength_omuvb(ingest_info *info, const harp_ingestion_options *options)
{
    const char *value;

    if (harp_ingestion_options_get_option(options, "wavelength", &value) == 0)
    {
        if (strcmp(value, "305nm") == 0)
        {
            info->wavelength = 305;
        }
        else if (strcmp(value, "310nm") == 0)
        {
            info->wavelength = 310;
        }
        else if (strcmp(value, "324nm") == 0)
        {
            info->wavelength = 324;
        }
        else
        {
            /* Option values are guaranteed to be legal if present. */
            assert(strcmp(value, "380nm") == 0);
            info->wavelength = 380;
        }
    }

    return 0;
}

static void ingestion_done(void *user_data)
{
    ingest_info_delete((ingest_info *)user_data);
}

static int ingestion_init_helper(coda_product *product, ingest_info **new_info)
{
    ingest_info *info;

    if (ingest_info_new(&info) != 0)
    {
        return -1;
    }
    info->product = product;
    if (init_cursors(info) != 0)
    {
        ingest_info_delete(info);
        return -1;
    }
    if (init_dimensions(info) != 0)
    {
        ingest_info_delete(info);
        return -1;
    }
    if (verify_dimensions(info) != 0)
    {
        ingest_info_delete(info);
        return -1;
    }

    *new_info = info;

    return 0;
}

static int ingestion_init(const harp_ingestion_module *module, coda_product *product,
                          const harp_ingestion_options *options, harp_product_definition **definition, void **user_data)
{
    ingest_info *info;

    (void)options;

    if (ingestion_init_helper(product, &info) != 0)
    {
        return -1;
    }

    *definition = *module->product_definition;
    *user_data = info;

    return 0;
}

static int ingestion_init_omaeruv(const harp_ingestion_module *module, coda_product *product,
                                  const harp_ingestion_options *options, harp_product_definition **definition,
                                  void **user_data)
{
    ingest_info *info;
    long dimension[3];

    (void)options;

    if (ingest_info_new(&info) != 0)
    {
        return -1;
    }
    info->product = product;
    if (init_cursors(info) != 0)
    {
        ingest_info_delete(info);
        return -1;
    }
    if (init_dimensions_omaeruv(info) != 0)
    {
        ingest_info_delete(info);
        return -1;
    }
    if (verify_dimensions_omaeruv(info) != 0)
    {
        ingest_info_delete(info);
        return -1;
    }

    dimension[0] = info->dimension[omi_dim_time];
    dimension[1] = info->dimension[omi_dim_xtrack];
    dimension[2] = info->dimension[omi_dim_spectral];
    if (variable_descriptor_init(&info->swath_cursor, "FinalAerosolOpticalDepth", 3, dimension, NULL,
                                 info->dimension[omi_dim_spectral], &info->omaeruv_aod) != 0)
    {
        ingest_info_delete(info);
        return -1;
    }
    if (variable_descriptor_init(&info->swath_cursor, "FinalAerosolAbsOpticalDepth", 3, dimension, NULL,
                                 info->dimension[omi_dim_spectral], &info->omaeruv_aaod) != 0)
    {
        ingest_info_delete(info);
        return -1;
    }

    *definition = *module->product_definition;
    *user_data = info;

    return 0;
}

static int ingestion_init_ombro(const harp_ingestion_module *module, coda_product *product,
                                const harp_ingestion_options *options, harp_product_definition **definition,
                                void **user_data)
{
    ingest_info *info;

    if (ingestion_init_helper(product, &info) != 0)
    {
        return -1;
    }

    if (parse_option_destriped(info, options) != 0)
    {
        ingest_info_delete(info);
        return -1;
    }

    *definition = *module->product_definition;
    *user_data = info;

    return 0;
}

static int ingestion_init_omchocho(const harp_ingestion_module *module, coda_product *product,
                                   const harp_ingestion_options *options, harp_product_definition **definition,
                                   void **user_data)
{
    ingest_info *info;

    if (ingestion_init_helper(product, &info) != 0)
    {
        return -1;
    }

    if (parse_option_destriped(info, options) != 0)
    {
        ingest_info_delete(info);
        return -1;
    }

    *definition = *module->product_definition;
    *user_data = info;

    return 0;
}

static int ingestion_init_omcldo2(const harp_ingestion_module *module, coda_product *product,
                                  const harp_ingestion_options *options, harp_product_definition **definition,
                                  void **user_data)
{
    ingest_info *info;

    if (ingestion_init_helper(product, &info) != 0)
    {
        return -1;
    }

    if (parse_option_clipped_cloud_fraction(info, options) != 0)
    {
        ingest_info_delete(info);
        return -1;
    }

    *definition = *module->product_definition;
    *user_data = info;

    return 0;
}

static int ingestion_init_omdomino(const harp_ingestion_module *module, coda_product *product,
                                   const harp_ingestion_options *options, harp_product_definition **definition,
                                   void **user_data)
{
    ingest_info *info;

    (void)options;

    if (ingest_info_new(&info) != 0)
    {
        return -1;
    }
    info->product = product;
    if (init_cursors(info) != 0)
    {
        ingest_info_delete(info);
        return -1;
    }
    if (init_dimensions(info) != 0)
    {
        ingest_info_delete(info);
        return -1;
    }
    if (verify_dimensions(info) != 0)
    {
        ingest_info_delete(info);
        return -1;
    }

    *definition = *module->product_definition;
    *user_data = info;

    return 0;
}

static int ingestion_init_omhcho(const harp_ingestion_module *module, coda_product *product,
                                 const harp_ingestion_options *options, harp_product_definition **definition,
                                 void **user_data)
{
    ingest_info *info;

    if (ingestion_init_helper(product, &info) != 0)
    {
        return -1;
    }

    if (parse_option_destriped(info, options) != 0)
    {
        ingest_info_delete(info);
        return -1;
    }

    *definition = *module->product_definition;
    *user_data = info;

    return 0;
}

static int ingestion_init_no2(const harp_ingestion_module *module, coda_product *product,
                              const harp_ingestion_options *options, harp_product_definition **definition,
                              void **user_data)
{
    ingest_info *info;

    if (ingestion_init_helper(product, &info) != 0)
    {
        return -1;
    }

    if (parse_option_destriped(info, options) != 0)
    {
        ingest_info_delete(info);
        return -1;
    }
    if (info->destriped)
    {
        if (!has_swath_variable(info, "SlantColumnAmountNO2Destriped"))
        {
            harp_set_error(HARP_ERROR_INVALID_INGESTION_OPTION_VALUE, "ingestion option 'destriped' not "
                           "supported when product does not contain destriped data");
            return -1;
        }
    }

    *definition = *module->product_definition;
    *user_data = info;

    return 0;
}

static int ingestion_init_omo3pr(const harp_ingestion_module *module, coda_product *product,
                                 const harp_ingestion_options *options, harp_product_definition **definition,
                                 void **user_data)
{
    ingest_info *info;
    long dimension[3];

    (void)options;

    if (ingest_info_new(&info) != 0)
    {
        return -1;
    }
    info->product = product;
    if (init_cursors(info) != 0)
    {
        ingest_info_delete(info);
        return -1;
    }
    if (init_dimensions_omo3pr(info) != 0)
    {
        ingest_info_delete(info);
        return -1;
    }
    if (verify_dimensions_omo3pr(info) != 0)
    {
        ingest_info_delete(info);
        return -1;
    }

    /* OMI profile products store pressure per level instead of per layer, whereas the corresponding profiles are stored
     * per layer (where num_levels = num_layers + 1). The ingested HARP product uses num_layers as the length of the
     * vertical dimension, i.e. info->dimension[omi_dim_vertical] = num_layers in this case.
     */
    dimension[0] = info->dimension[omi_dim_time];
    dimension[1] = info->dimension[omi_dim_xtrack];
    dimension[2] = info->dimension[omi_dim_vertical] + 1;
    if (variable_descriptor_init(&info->geo_cursor, "Pressure", 3, dimension, get_offset_pressure,
                                 info->dimension[omi_dim_vertical], &info->omo3pr_pressure) != 0)
    {
        ingest_info_delete(info);
        return -1;
    }

    dimension[0] = info->dimension[omi_dim_time];
    dimension[1] = info->dimension[omi_dim_xtrack];
    dimension[2] = info->dimension[omi_dim_vertical];
    if (variable_descriptor_init(&info->swath_cursor, "O3", 3, dimension, NULL, info->dimension[omi_dim_vertical],
                                 &info->omo3pr_o3) != 0)
    {
        ingest_info_delete(info);
        return -1;
    }
    if (variable_descriptor_init(&info->swath_cursor, "O3Precision", 3, dimension, NULL,
                                 info->dimension[omi_dim_vertical], &info->omo3pr_o3_precision) != 0)
    {
        ingest_info_delete(info);
        return -1;
    }

    *definition = *module->product_definition;
    *user_data = info;

    return 0;
}

static int ingestion_init_omoclo(const harp_ingestion_module *module, coda_product *product,
                                 const harp_ingestion_options *options, harp_product_definition **definition,
                                 void **user_data)
{
    ingest_info *info;

    if (ingestion_init_helper(product, &info) != 0)
    {
        return -1;
    }

    if (parse_option_destriped(info, options) != 0)
    {
        ingest_info_delete(info);
        return -1;
    }

    *definition = *module->product_definition;
    *user_data = info;

    return 0;
}

static int ingestion_init_omso2(const harp_ingestion_module *module, coda_product *product,
                                const harp_ingestion_options *options, harp_product_definition **definition,
                                void **user_data)
{
    ingest_info *info;

    if (ingestion_init_helper(product, &info) != 0)
    {
        return -1;
    }

    if (has_swath_variable(info, "SO2ColumnAmountPBL"))
    {
        info->product_version = 2;
    }

    /* Requires product version to be set. */
    if (parse_option_so2_column_variant(info, options) != 0)
    {
        ingest_info_delete(info);
        return -1;
    }

    *definition = *module->product_definition;
    *user_data = info;

    return 0;
}

static int ingestion_init_omto3(const harp_ingestion_module *module, coda_product *product,
                                const harp_ingestion_options *options, harp_product_definition **definition,
                                void **user_data)
{
    ingest_info *info;

    if (ingestion_init_helper(product, &info) != 0)
    {
        return -1;
    }

    if (parse_option_cloud_fraction_variant(info, options) != 0)
    {
        ingest_info_delete(info);
        return -1;
    }

    *definition = *module->product_definition;
    *user_data = info;

    return 0;
}

static int ingestion_init_omuvb(const harp_ingestion_module *module, coda_product *product,
                                const harp_ingestion_options *options, harp_product_definition **definition,
                                void **user_data)
{
    ingest_info *info;

    if (ingestion_init_helper(product, &info) != 0)
    {
        return -1;
    }

    if (parse_option_clear_sky(info, options) != 0)
    {
        ingest_info_delete(info);
        return -1;
    }

    info->wavelength = 305;

    if (parse_option_wavelength_omuvb(info, options) != 0)
    {
        ingest_info_delete(info);
        return -1;
    }

    *definition = *module->product_definition;
    *user_data = info;

    return 0;
}

static void register_datetime_variable(harp_product_definition *product_definition, const char *path)
{
    harp_variable_definition *variable_definition;
    harp_dimension_type dimension_type[1] = { harp_dimension_time };
    const char *description;

    description = "time of the measurement";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "datetime", harp_type_double,
                                                                     1, dimension_type, NULL, description,
                                                                     "seconds since 2000-01-01", NULL, read_datetime);

    description = "the time of the measurement converted from TAI93 to seconds since 2000-01-01T00:00:00";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, description);
}

static void register_longitude_variable(harp_product_definition *product_definition, const char *path)
{
    harp_variable_definition *variable_definition;
    harp_dimension_type dimension_type[1] = { harp_dimension_time };
    const char *description;

    description = "longitude of the ground pixel center (WGS84)";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "longitude",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "degree_east", NULL, read_longitude);
    harp_variable_definition_set_valid_range_double(variable_definition, -180.0, 180.0);
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);
}

static void register_latitude_variable(harp_product_definition *product_definition, const char *path)
{
    harp_variable_definition *variable_definition;
    harp_dimension_type dimension_type[1] = { harp_dimension_time };
    const char *description;

    description = "latitude of the ground pixel center (WGS84)";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "latitude", harp_type_double,
                                                                     1, dimension_type, NULL, description,
                                                                     "degree_north", NULL, read_latitude);
    harp_variable_definition_set_valid_range_double(variable_definition, -90.0, 90.0);
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);
}

static void add_footprint_mapping(harp_product_definition *product_definition)
{
    const char *description;

    /* split description in parts since official max string length that C90 compilers are required to support is 509 */
    description = "The shape and size of each ground pixel is not included in the product. HARP therefore "
        "provides its own approximation which is based on interpolation of the available center coordinates for each "
        "of the ground pixels. Each corner coordinate is determined by its four surrounding center coordinates. "
        "The corner coordinate is exactly at the intersection of the cross that can be made with these four points "
        "(each line of the cross is the great-circle distance from one center coordinate to the other). ";
    harp_product_definition_add_mapping(product_definition, description, NULL);

    description = "In situations where a corner coordinate is not surrounded by four center coordinates (i.e. at "
        "the boundaries) virtual center coordinates are created by means of extrapolation. The virtual center "
        "coordinate is placed such that the distance to its nearest real center coordinate equals the distance "
        "between that nearest real center coordinate and the next center coordinate going further inwards. ";
    harp_product_definition_add_mapping(product_definition, description, NULL);

    description = "In mathematical notation: when c(i,m+1) is the virtual center coordinate and c(i,m) "
        "and c(i,m-1) are real center coordinates, then ||c(i,m+1) - c(i,m)|| = ||c(i,m) - c(i,m-1)|| and all three "
        "coordinates should lie on the same great circle. The four virtual coordinates that lie in the utmost corners "
        "of the boundaries are calculated by extrapolating in a diagonal direction (e.g. c(n+1,m+1) is calculated "
        "from c(n,m) and c(n-1,m-1)).";
    harp_product_definition_add_mapping(product_definition, description, NULL);
}

static void register_footprint_variables(harp_product_definition *product_definition)
{
    harp_variable_definition *variable_definition;
    harp_dimension_type dimension_type[2] = { harp_dimension_time, harp_dimension_independent };
    long dimension[2] = { -1, 4 };
    const char *description;
    const char *mapping_description;

    mapping_description = "interpolated from the available center coordinates for each of the ground pixels";

    description = "longitudes of the ground pixel corners (WGS84)";
    variable_definition = harp_ingestion_register_variable_block_read(product_definition, "longitude_bounds",
                                                                      harp_type_double, 2, dimension_type, dimension,
                                                                      description, "degree_east", NULL,
                                                                      read_longitude_bounds);
    harp_variable_definition_set_valid_range_double(variable_definition, -180.0, 180.0);
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, NULL, mapping_description);

    description = "latitudes of the ground pixel corners (WGS84)";
    variable_definition = harp_ingestion_register_variable_block_read(product_definition, "latitude_bounds",
                                                                      harp_type_double, 2, dimension_type, dimension,
                                                                      description, "degree_north", NULL,
                                                                      read_latitude_bounds);
    harp_variable_definition_set_valid_range_double(variable_definition, -90.0, 90.0);
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, NULL, mapping_description);
}

static void register_solar_zenith_angle_variable(harp_product_definition *product_definition, const char *path)
{
    harp_variable_definition *variable_definition;
    harp_dimension_type dimension_type[1] = { harp_dimension_time };
    const char *description;

    description = "solar zenith angle at WGS84 ellipsoid for center co-ordinate of the ground pixel";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "solar_zenith_angle",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "degree", NULL,
                                                                     read_solar_zenith_angle_wgs84);
    harp_variable_definition_set_valid_range_double(variable_definition, 0.0, 180.0);
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);
}

static void register_solar_azimuth_angle_variable(harp_product_definition *product_definition, const char *path)
{
    harp_variable_definition *variable_definition;
    harp_dimension_type dimension_type[1] = { harp_dimension_time };
    const char *description;

    description = "solar azimuth angle at WGS84 ellipsoid for center co-ordinate of the ground pixel, defined East-of"
        "-North";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "solar_azimuth_angle",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "degree", NULL,
                                                                     read_solar_azimuth_angle_wgs84);
    harp_variable_definition_set_valid_range_double(variable_definition, 0.0, 360.0);
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);
}

static void register_viewing_zenith_angle_variable(harp_product_definition *product_definition, const char *path)
{
    harp_variable_definition *variable_definition;
    harp_dimension_type dimension_type[1] = { harp_dimension_time };
    const char *description;

    description = "viewing zenith angle at WGS84 ellipsoid for center co-ordinate of the ground pixel";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "viewing_zenith_angle",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "degree", NULL,
                                                                     read_viewing_zenith_angle_wgs84);
    harp_variable_definition_set_valid_range_double(variable_definition, 0.0, 180.0);
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);
}

static void register_viewing_azimuth_angle_variable(harp_product_definition *product_definition, const char *path)
{
    harp_variable_definition *variable_definition;
    harp_dimension_type dimension_type[1] = { harp_dimension_time };
    const char *description;

    description = "viewing azimuth angle at WGS84 ellipsoid for center co-ordinate of the ground pixel, defined East-of"
        "-North";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "viewing_azimuth_angle",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "degree", NULL,
                                                                     read_viewing_azimuth_angle_wgs84);
    harp_variable_definition_set_valid_range_double(variable_definition, 0.0, 360.0);
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);
}

static void register_spacecraft_altitude_variable(harp_product_definition *product_definition, const char *path)
{
    harp_variable_definition *variable_definition;
    harp_dimension_type dimension_type[1] = { harp_dimension_time };
    const char *description;

    description = "altitude of Aura spacecraft";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "sensor_altitude",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "m", NULL, read_spacecraft_altitude);
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);
}

static void register_spacecraft_latitude_variable(harp_product_definition *product_definition, const char *path)
{
    harp_variable_definition *variable_definition;
    harp_dimension_type dimension_type[1] = { harp_dimension_time };
    const char *description;

    description = "geodetic latitude above WGS84 ellipsoid";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "sensor_latitude",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "degree_north", NULL,
                                                                     read_spacecraft_latitude);
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);
}

static void register_spacecraft_longitude_variable(harp_product_definition *product_definition, const char *path)
{
    harp_variable_definition *variable_definition;
    harp_dimension_type dimension_type[1] = { harp_dimension_time };
    const char *description;

    description = "geodetic longitude above WGS84 ellipsoid";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "sensor_longitude",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "degree_east", NULL,
                                                                     read_spacecraft_longitude);
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);
}

static void register_terrain_height_variable(harp_product_definition *product_definition, const char *path)
{
    harp_variable_definition *variable_definition;
    harp_dimension_type dimension_type[1] = { harp_dimension_time };
    int (*read_func)(void *, harp_array) = read_terrain_height_from_geolocation;
    const char *description;

    if (strstr(path, "Data_Fields") != NULL)
    {
        read_func = read_terrain_height_from_data;
    }
    description = "terrain height";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "surface_altitude",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "m", NULL, read_func);
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);
}

static void register_terrain_pressure_variable(harp_product_definition *product_definition, const char *path)
{
    harp_variable_definition *variable_definition;
    harp_dimension_type dimension_type[1] = { harp_dimension_time };
    int (*read_func)(void *, harp_array) = read_terrain_pressure_from_geolocation;
    const char *description;

    if (strstr(path, "Data_Fields") != NULL)
    {
        read_func = read_terrain_pressure_from_data;
    }
    description = "terrain pressure";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "surface_pressure",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "hPa", NULL, read_func);
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);
}

static void register_omaeruv_product(void)
{
    harp_ingestion_module *module;
    harp_product_definition *product_definition;
    harp_variable_definition *variable_definition;
    harp_dimension_type dimension_type[2] = { harp_dimension_time, harp_dimension_spectral };
    harp_dimension_type dimension_type_wavelength[1] = { harp_dimension_spectral };
    const char *description;
    const char *path;

    module = harp_ingestion_register_module("OMI_L2_OMAERUV", "OMI", "AURA_OMI", "OMAERUV",
                                            "OMI L2 aerosol product (AOD and AAOD)", ingestion_init_omaeruv,
                                            ingestion_done);

    /* OMAERUV product */
    product_definition = harp_ingestion_register_product(module, "OMI_L2_OMAERUV", NULL, read_dimensions_omaeruv);
    add_footprint_mapping(product_definition);

    /* datetime */
    path = "/HDFEOS/SWATHS/Aerosol_NearUV_Swath/Geolocation_Fields/Time[]";
    register_datetime_variable(product_definition, path);

    /* longitude and latitude */
    path = "/HDFEOS/SWATHS/Aerosol_NearUV_Swath/Geolocation_Fields/Longitude[]";
    register_longitude_variable(product_definition, path);
    path = "/HDFEOS/SWATHS/Aerosol_NearUV_Swath/Geolocation_Fields/Latitude[]";
    register_latitude_variable(product_definition, path);

    /* longitude_bounds and latitude_bounds */
    register_footprint_variables(product_definition);

    /* surface_pressure */
    path = "/HDFEOS/SWATHS/ColumnAmountO3/Geolocation_Fields/TerrainPressure[]";
    register_terrain_pressure_variable(product_definition, path);

    /* wavelength */
    description = "wavelength";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "wavelength",
                                                                     harp_type_double, 1, dimension_type_wavelength,
                                                                     NULL, description, "nm", NULL,
                                                                     read_aerosol_wavelength);
    description = "three fixed wavelengths (354, 388, and 500 nm) as defined by the OMI OMAERUV product specification";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, NULL, description);

    /* aerosol_optical_depth */
    description = "aerosol optical depth";
    variable_definition = harp_ingestion_register_variable_block_read(product_definition, "aerosol_optical_depth",
                                                                      harp_type_double, 2, dimension_type, NULL,
                                                                      description, HARP_UNIT_DIMENSIONLESS, NULL,
                                                                      read_aerosol_optical_depth);
    path = "/HDFEOS/SWATHS/Aerosol_NearUV_Swath/Data_Fields/FinalAerosolOpticalDepth[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* aerosol_absorbing_optical_depth */
    description = "aerosol absorbing optical depth";
    variable_definition = harp_ingestion_register_variable_block_read(product_definition,
                                                                      "aerosol_absorbing_optical_depth",
                                                                      harp_type_double, 2, dimension_type, NULL,
                                                                      description, HARP_UNIT_DIMENSIONLESS, NULL,
                                                                      read_aerosol_absorbing_optical_depth);

    path = "/HDFEOS/SWATHS/Aerosol_NearUV_Swath/Data_Fields/FinalAerosolAbsOpticalDepth[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* uv_aerosol_index */
    description = "UV aerosol index";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "uv_aerosol_index",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, HARP_UNIT_DIMENSIONLESS, NULL,
                                                                     read_uv_aerosol_index);
    path = "/HDFEOS/SWATHS/Aerosol_NearUV_Swath/Data_Fields/UVAerosolIndex[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);
}

static void register_ombro_product(void)
{
    harp_ingestion_module *module;
    harp_product_definition *product_definition;
    harp_variable_definition *variable_definition;
    harp_dimension_type dimension_type[1] = { harp_dimension_time };
    const char *destriped_option_values[] = { "true" };
    const char *description;
    const char *path;

    module = harp_ingestion_register_module("OMI_L2_OMBRO", "OMI", "AURA_OMI", "OMBRO", "OMI L2 BrO total column",
                                            ingestion_init_ombro, ingestion_done);

    /* destriped ingestion option */
    harp_ingestion_register_option(module, "destriped", "ingest column densities without destriping correction "
                                   "(default) or with destriping correction (destriped=true)", 1,
                                   destriped_option_values);

    /* OMBRO product */
    product_definition = harp_ingestion_register_product(module, "OMI_L2_OMBRO", NULL, read_dimensions);
    add_footprint_mapping(product_definition);

    /* datetime */
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_BRO/Geolocation_Fields/Time[]";
    register_datetime_variable(product_definition, path);

    /* longitude and latitude */
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_BRO/Geolocation_Fields/Longitude[]";
    register_longitude_variable(product_definition, path);
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_BRO/Geolocation_Fields/Latitude[]";
    register_latitude_variable(product_definition, path);

    /* longitude_bounds and latitude_bounds */
    register_footprint_variables(product_definition);

    /* sensor_altitude */
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_BRO/Geolocation_Fields/SpacecraftAltitude[]";
    register_spacecraft_altitude_variable(product_definition, path);

    /* surface_altitude */
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_BRO/Geolocation_Fields/TerrainHeight[]";
    register_terrain_height_variable(product_definition, path);

    /* BrO_column_number_density */
    description = "BrO vertical column density";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "BrO_column_number_density",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "molec/cm^2", NULL, read_bro_column);
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_BRO/Data_Fields/ColumnAmount[]";
    harp_variable_definition_add_mapping(variable_definition, "destriped unset", NULL, path, NULL);
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_BRO/Data_Fields/ColumnAmountDestriped[]";
    harp_variable_definition_add_mapping(variable_definition, "destriped=true", NULL, path, NULL);

    /* BrO_column_number_density_uncertainty */
    description = "uncertainty of the BrO vertical column density";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition,
                                                                     "BrO_column_number_density_uncertainty",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "molec/cm^2", include_no_destriped,
                                                                     read_bro_column_error);
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_BRO/Data_Fields/ColumnUncertainty[]";
    harp_variable_definition_add_mapping(variable_definition, "destriped unset", NULL, path, NULL);
}

static void register_omchocho_product(void)
{
    harp_ingestion_module *module;
    harp_product_definition *product_definition;
    harp_variable_definition *variable_definition;
    harp_dimension_type dimension_type[1] = { harp_dimension_time };
    const char *destriped_option_values[] = { "true" };
    const char *description;
    const char *path;

    module = harp_ingestion_register_module("OMI_L2_OMCHOCHO", "OMI", "AURA_OMI", "OMCHOCHO",
                                            "OMI L2 Glyoxal total column", ingestion_init_omchocho, ingestion_done);

    /* destriped ingestion option */
    harp_ingestion_register_option(module, "destriped", "ingest column densities without destriping correction "
                                   "(default) or with destriping correction (destriped=true)", 1,
                                   destriped_option_values);

    /* OMCHOCHO product */
    product_definition = harp_ingestion_register_product(module, "OMI_L2_OMCHOCHO", NULL, read_dimensions);
    add_footprint_mapping(product_definition);

    /* datetime */
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_CHOCHO/Geolocation_Fields/Time[]";
    register_datetime_variable(product_definition, path);

    /* longitude and latitude */
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_CHOCHO/Geolocation_Fields/Longitude[]";
    register_longitude_variable(product_definition, path);
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_CHOCHO/Geolocation_Fields/Latitude[]";
    register_latitude_variable(product_definition, path);

    /* longitude_bounds and latitude_bounds */
    register_footprint_variables(product_definition);

    /* C2H2O2_column_number_density */
    description = "CHOCHO vertical column density";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "C2H2O2_column_number_density",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "molec/cm^2", NULL,
                                                                     read_chocho_column);
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_CHOCHO/Data_Fields/ColumnAmount[]";
    harp_variable_definition_add_mapping(variable_definition, "destriped unset", NULL, path, NULL);
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_CHOCHO/Data_Fields/ColumnAmountDestriped[]";
    harp_variable_definition_add_mapping(variable_definition, "destriped=true", NULL, path, NULL);

    /* BrO_column_number_density_uncertainty */
    description = "uncertainty of the CHOCHO vertical column density";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition,
                                                                     "C2H2O2_column_number_density_uncertainty",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "molec/cm^2", include_no_destriped,
                                                                     read_chocho_column_error);
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_CHOCHO/Data_Fields/ColumnUncertainty[]";
    harp_variable_definition_add_mapping(variable_definition, "destriped unset", NULL, path, NULL);
}

static void register_omcldo2_product(void)
{
    harp_ingestion_module *module;
    harp_product_definition *product_definition;
    harp_variable_definition *variable_definition;
    harp_dimension_type dimension_type[1] = { harp_dimension_time };
    const char *clipped_cloud_fraction_option_values[] = { "false" };
    const char *description;
    const char *path;

    module = harp_ingestion_register_module("OMI_L2_OMCLDO2", "OMI", "AURA_OMI", "OMCLDO2",
                                            "OMI L2 cloud pressure and cloud fraction (O2-O2 absorption)",
                                            ingestion_init_omcldo2, ingestion_done);

    /* clipped_cloud_fraction ingestion option */
    harp_ingestion_register_option(module, "clipped_cloud_fraction", "ingest regular cloud fraction values "
                                   "(clipped_cloud_fraction=false) or clipped to the range [0.0, 1.0] (default)", 1,
                                   clipped_cloud_fraction_option_values);

    /* OMCLDO2 product */
    product_definition = harp_ingestion_register_product(module, "OMI_L2_OMCLDO2", NULL, read_dimensions);
    add_footprint_mapping(product_definition);

    /* datetime */
    path = "/HDFEOS/SWATHS/CloudFractionAndPressure/Geolocation_Fields/Time[]";
    register_datetime_variable(product_definition, path);

    /* longitude and latitude */
    path = "/HDFEOS/SWATHS/CloudFractionAndPressure/Geolocation_Fields/Longitude[]";
    register_longitude_variable(product_definition, path);
    path = "/HDFEOS/SWATHS/CloudFractionAndPressure/Geolocation_Fields/Latitude[]";
    register_latitude_variable(product_definition, path);

    /* longitude_bounds and latitude_bounds */
    register_footprint_variables(product_definition);

    /* solar_zenith_angle */
    path = "/HDFEOS/SWATHS/CloudFractionAndPressure/Geolocation_Fields/SolarZenithAngle[]";
    register_solar_zenith_angle_variable(product_definition, path);

    /* solar_azimuth_angle */
    path = "/HDFEOS/SWATHS/CloudFractionAndPressure/Geolocation_Fields/SolarAzimuthAngle[]";
    register_solar_azimuth_angle_variable(product_definition, path);

    /* viewing_zenith_angle */
    path = "/HDFEOS/SWATHS/CloudFractionAndPressure/Geolocation_Fields/ViewingZenithAngle[]";
    register_viewing_zenith_angle_variable(product_definition, path);

    /* viewing_azimuth_angle */
    path = "/HDFEOS/SWATHS/CloudFractionAndPressure/Geolocation_Fields/ViewingAzimuthAngle[]";
    register_viewing_azimuth_angle_variable(product_definition, path);

    /* sensor_altitude */
    path = "/HDFEOS/SWATHS/CloudFractionAndPressure/Geolocation_Fields/SpacecraftAltitude[]";
    register_spacecraft_altitude_variable(product_definition, path);

    /* sensor_latitude */
    path = "/HDFEOS/SWATHS/CloudFractionAndPressure/Geolocation_Fields/SpacecraftLatitude[]";
    register_spacecraft_latitude_variable(product_definition, path);

    /* sensor_longitude */
    path = "/HDFEOS/SWATHS/CloudFractionAndPressure/Geolocation_Fields/SpacecraftLongitude[]";
    register_spacecraft_longitude_variable(product_definition, path);

    /* surface_altitude */
    path = "/HDFEOS/SWATHS/CloudFractionAndPressure/Geolocation_Fields/TerrainHeight[]";
    register_terrain_height_variable(product_definition, path);

    /* surface_pressure */
    path = "/HDFEOS/SWATHS/CloudFractionAndPressure/Data_Fields/TerrainPressure[]";
    register_terrain_pressure_variable(product_definition, path);

    /* cloud_fraction */
    description = "effective cloud fraction";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "cloud_fraction",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, HARP_UNIT_DIMENSIONLESS, NULL,
                                                                     read_cloud_fraction);
    path = "/HDFEOS/SWATHS/CloudFractionAndPressure/Data_Fields/CloudFraction[]";
    harp_variable_definition_add_mapping(variable_definition, "clipped_cloud_fraction unset", NULL, path, NULL);
    path = "/HDFEOS/SWATHS/CloudFractionAndPressure/Data_Fields/CloudFractionNotClipped[]";
    harp_variable_definition_add_mapping(variable_definition, "clipped_cloud_fraction=false", NULL, path, NULL);

    /* cloud_fraction_uncertainty */
    description = "uncertainty of the effective cloud fraction";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "cloud_fraction_uncertainty",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, HARP_UNIT_DIMENSIONLESS, NULL,
                                                                     read_cloud_fraction_precision);
    path = "/HDFEOS/SWATHS/CloudFractionAndPressure/Data_Fields/CloudFractionPrecision[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* cloud_pressure */
    description = "effective cloud pressure";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "cloud_pressure",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "hPa", NULL, read_pressure_cloud);
    path = "/HDFEOS/SWATHS/CloudFractionAndPressure/Data_Fields/CloudPressure[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* cloud_pressure_uncertainty */
    description = "uncertainty of the effective cloud pressure";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "cloud_pressure_uncertainty",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "hPa", NULL,
                                                                     read_pressure_cloud_precision);
    path = "/HDFEOS/SWATHS/CloudFractionAndPressure/Data_Fields/CloudPressurePrecision[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* validity */
    description = "flags describing the processing quality";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "validity",
                                                                     harp_type_int32, 1, dimension_type, NULL,
                                                                     description, NULL, NULL,
                                                                     read_processing_quality_flags);
    path = "/HDFEOS/SWATHS/CloudFractionAndPressure/Data_Fields/ProcessingQualityFlags[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);
}

static void register_omcldrr_product(void)
{
    harp_ingestion_module *module;
    harp_product_definition *product_definition;
    harp_variable_definition *variable_definition;
    harp_dimension_type dimension_type[1] = { harp_dimension_time };
    const char *description;
    const char *path;

    module = harp_ingestion_register_module("OMI_L2_OMCLDRR", "OMI", "AURA_OMI", "OMCLDRR",
                                            "OMI L2 cloud pressure and cloud fraction (Raman scattering)",
                                            ingestion_init, ingestion_done);

    /* OMCLDRR product */
    product_definition = harp_ingestion_register_product(module, "OMI_L2_OMCLDRR", NULL, read_dimensions);
    add_footprint_mapping(product_definition);

    /* datetime */
    path = "/HDFEOS/SWATHS/Cloud_Product/Geolocation_Fields/Time[]";
    register_datetime_variable(product_definition, path);

    /* longitude and latitude */
    path = "/HDFEOS/SWATHS/Cloud_Product/Geolocation_Fields/Longitude[]";
    register_longitude_variable(product_definition, path);
    path = "/HDFEOS/SWATHS/Cloud_Product/Geolocation_Fields/Latitude[]";
    register_latitude_variable(product_definition, path);

    /* longitude_bounds and latitude_bounds */
    register_footprint_variables(product_definition);

    /* solar_zenith_angle */
    path = "/HDFEOS/SWATHS/Cloud_Product/Geolocation_Fields/SolarZenithAngle[]";
    register_solar_zenith_angle_variable(product_definition, path);

    /* viewing_zenith_angle */
    path = "/HDFEOS/SWATHS/Cloud_Product/Geolocation_Fields/ViewingZenithAngle[]";
    register_viewing_zenith_angle_variable(product_definition, path);

    /* relative_azimuth_angle */
    description = "relative (sun + 180 - view) azimuth angle at WGS84 ellipsoid for center co-ordinate of the ground"
        " pixel";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "relative_azimuth_angle",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "degree", NULL,
                                                                     read_relative_azimuth_angle_wgs84);
    harp_variable_definition_set_valid_range_double(variable_definition, 0.0, 360.0);
    path = "/HDFEOS/SWATHS/Cloud_Product/Geolocation_Fields/RelativeAzimuthAngle[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* surface_altitude */
    path = "/HDFEOS/SWATHS/Cloud_Product/Geolocation_Fields/TerrainHeight[]";
    register_terrain_height_variable(product_definition, path);

    /* surface_pressure */
    path = "/HDFEOS/SWATHS/Cloud_Product/Data_Fields/TerrainPressure[]";
    register_terrain_pressure_variable(product_definition, path);

    /* cloud_fraction */
    description = "effective cloud fraction";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "cloud_fraction",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, HARP_UNIT_DIMENSIONLESS, NULL,
                                                                     read_cloud_fraction_for_o3);
    path = "/HDFEOS/SWATHS/Cloud_Product/Data_Fields/CloudFractionforO3[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* cloud_pressure */
    description = "effective cloud pressure";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "cloud_pressure",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "hPa", NULL,
                                                                     read_pressure_cloud_for_o3);
    path = "/HDFEOS/SWATHS/Cloud_Product/Data_Fields/CloudPressureforO3[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* validity */
    description = "flags describing the processing quality";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "validity",
                                                                     harp_type_int32, 1, dimension_type, NULL,
                                                                     description, NULL, NULL,
                                                                     read_processing_quality_flags_o3);
    path = "/HDFEOS/SWATHS/Cloud_Product/Data_Fields/ProcessingQualityFlagsforO3[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);
}

static void register_omdoao3_product(void)
{
    harp_ingestion_module *module;
    harp_product_definition *product_definition;
    harp_variable_definition *variable_definition;
    harp_dimension_type dimension_type[1] = { harp_dimension_time };
    const char *description;
    const char *path;

    module = harp_ingestion_register_module("OMI_L2_OMDOAO3", "OMI", "AURA_OMI", "OMDOAO3",
                                            "OMI L2 O3 total column (DOAS)", ingestion_init, ingestion_done);

    /* OMDOAO3 product */
    product_definition = harp_ingestion_register_product(module, "OMI_L2_OMDOAO3", NULL, read_dimensions);
    add_footprint_mapping(product_definition);

    /* datetime */
    path = "/HDFEOS/SWATHS/ColumnAmountO3/Geolocation_Fields/Time[]";
    register_datetime_variable(product_definition, path);

    /* longitude and latitude */
    path = "/HDFEOS/SWATHS/ColumnAmountO3/Geolocation_Fields/Longitude[]";
    register_longitude_variable(product_definition, path);
    path = "/HDFEOS/SWATHS/ColumnAmountO3/Geolocation_Fields/Latitude[]";
    register_latitude_variable(product_definition, path);

    /* longitude_bounds and latitude_bounds */
    register_footprint_variables(product_definition);

    /* solar_zenith_angle */
    path = "/HDFEOS/SWATHS/ColumnAmountO3/Geolocation_Fields/SolarZenithAngle[]";
    register_solar_zenith_angle_variable(product_definition, path);

    /* solar_azimuth_angle */
    path = "/HDFEOS/SWATHS/ColumnAmountO3/Geolocation_Fields/SolarAzimuthAngle[]";
    register_solar_azimuth_angle_variable(product_definition, path);

    /* viewing_zenith_angle */
    path = "/HDFEOS/SWATHS/ColumnAmountO3/Geolocation_Fields/ViewingZenithAngle[]";
    register_viewing_zenith_angle_variable(product_definition, path);

    /* viewing_azimuth_angle */
    path = "/HDFEOS/SWATHS/ColumnAmountO3/Geolocation_Fields/ViewingAzimuthAngle[]";
    register_viewing_azimuth_angle_variable(product_definition, path);

    /* sensor_altitude */
    path = "/HDFEOS/SWATHS/ColumnAmountO3/Geolocation_Fields/SpacecraftAltitude[]";
    register_spacecraft_altitude_variable(product_definition, path);

    /* sensor_latitude */
    path = "/HDFEOS/SWATHS/ColumnAmountO3/Geolocation_Fields/SpacecraftLatitude[]";
    register_spacecraft_latitude_variable(product_definition, path);

    /* sensor_longitude */
    path = "/HDFEOS/SWATHS/ColumnAmountO3/Geolocation_Fields/SpacecraftLongitude[]";
    register_spacecraft_longitude_variable(product_definition, path);

    /* surface_altitude */
    path = "/HDFEOS/SWATHS/ColumnAmountO3/Geolocation_Fields/TerrainHeight[]";
    register_terrain_height_variable(product_definition, path);

    /* surface_pressure */
    path = "/HDFEOS/SWATHS/ColumnAmountO3/Data_Fields/TerrainPressure[]";
    register_terrain_pressure_variable(product_definition, path);

    /* O3_column_number_density */
    description = "O3 vertical column density";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "O3_column_number_density",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "DU", NULL, read_o3_column);
    path = "/HDFEOS/SWATHS/ColumnAmountO3/Data_Fields/ColumnAmountO3[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* O3_column_number_density_uncertainty */
    description = "uncertainty of the O3 vertical column density";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition,
                                                                     "O3_column_number_density_uncertainty",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "DU", NULL, read_o3_column_error);
    path = "/HDFEOS/SWATHS/ColumnAmountO3/Data_Fields/ColumnAmountO3Precision[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);


    /* O3_column_number_density_validity */
    description = "flags describing the O3 vertical column processing quality";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition,
                                                                     "O3_column_number_density_validity",
                                                                     harp_type_int32, 1, dimension_type, NULL,
                                                                     description, NULL, NULL,
                                                                     read_processing_quality_flags);
    path = "/HDFEOS/SWATHS/ColumnAmountO3/Data_Fields/ProcessingQualityFlags[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* cloud_fraction */
    description = "effective cloud fraction";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "cloud_fraction",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, HARP_UNIT_DIMENSIONLESS, NULL,
                                                                     read_cloud_fraction);
    path = "/HDFEOS/SWATHS/ColumnAmountO3/Data_Fields/CloudFraction[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* cloud_pressure */
    description = "effective cloud pressure";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "cloud_pressure",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "hPa", NULL, read_pressure_cloud);
    path = "/HDFEOS/SWATHS/ColumnAmountO3/Data_Fields/CloudPressure[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* cloud_pressure_uncertainty */
    description = "uncertainty of the effective cloud pressure";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "cloud_pressure_uncertainty",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "hPa", NULL,
                                                                     read_pressure_cloud_precision);
    path = "/HDFEOS/SWATHS/ColumnAmountO3/Data_Fields/CloudPressurePrecision[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);
}

static void register_omdomino_product(void)
{
    harp_ingestion_module *module;
    harp_product_definition *product_definition;
    harp_variable_definition *variable_definition;
    harp_dimension_type dimension_type[1] = { harp_dimension_time };
    harp_dimension_type dimension_type_bounds[2] = { harp_dimension_time, harp_dimension_independent };
    long dimension_bounds[2] = { -1, 4 };
    const char *description;
    const char *path;

    module = harp_ingestion_register_module("OMI_L2_OMDOMINO", "OMI", "AURA_OMI", "OMDOMINO",
                                            "OMI L2 DOMINO NO2 product", ingestion_init_omdomino, ingestion_done);

    /* OMDOMINO product */
    product_definition = harp_ingestion_register_product(module, "OMI_L2_OMDOMINO", NULL, read_dimensions);
    add_footprint_mapping(product_definition);

    /* datetime */
    path = "/HDFEOS/SWATHS/DominoNO2/Geolocation_Fields/Time[]";
    register_datetime_variable(product_definition, path);

    /* longitude */
    description = "longitude of the ground pixel center";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "longitude", harp_type_double,
                                                                     1, dimension_type, NULL, description,
                                                                     "degree_east", NULL, read_longitude);
    harp_variable_definition_set_valid_range_double(variable_definition, -180.0, 180.0);
    path = "/HDFEOS/SWATHS/DominoNO2/Geolocation_Fields/Longitude[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* latitude */
    description = "latitude of the ground pixel center";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "latitude", harp_type_double,
                                                                     1, dimension_type, NULL, description,
                                                                     "degree_north", NULL, read_latitude);
    harp_variable_definition_set_valid_range_double(variable_definition, -90.0, 90.0);
    path = "/HDFEOS/SWATHS/DominoNO2/Geolocation_Fields/Latitude[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* longitude_bounds */
    description = "longitudes of the ground pixel corners";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "longitude_bounds",
                                                                     harp_type_double, 2, dimension_type_bounds,
                                                                     dimension_bounds, description, "degree_east",
                                                                     NULL, read_longitude_bounds_domino);
    harp_variable_definition_set_valid_range_double(variable_definition, -180.0, 180.0);
    path = "/HDFEOS/SWATHS/DominoNO2/Geolocation_Fields/LongitudeCornerpoints[]";
    description = "corners are reordered from {a,b,c,d} to {d,b,a,c}";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, description);

    /* latitude_bounds */
    description = "latitudes of the ground pixel corners";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "latitude_bounds",
                                                                     harp_type_double, 2, dimension_type_bounds,
                                                                     dimension_bounds, description, "degree_north",
                                                                     NULL, read_latitude_bounds_domino);
    harp_variable_definition_set_valid_range_double(variable_definition, -90.0, 90.0);
    path = "/HDFEOS/SWATHS/DominoNO2/Geolocation_Fields/LatitudeCornerpoints[]";
    description = "corners are reordered from {a,b,c,d} to {d,b,a,c}";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, description);

    /* solar_zenith_angle */
    path = "/HDFEOS/SWATHS/DominoNO2/Geolocation_Fields/SolarZenithAngle[]";
    register_solar_zenith_angle_variable(product_definition, path);

    /* solar_azimuth_angle */
    path = "/HDFEOS/SWATHS/DominoNO2/Geolocation_Fields/SolarAzimuthAngle[]";
    register_solar_azimuth_angle_variable(product_definition, path);

    /* viewing_zenith_angle */
    path = "/HDFEOS/SWATHS/DominoNO2/Geolocation_Fields/ViewingZenithAngle[]";
    register_viewing_zenith_angle_variable(product_definition, path);

    /* viewing_azimuth_angle */
    path = "/HDFEOS/SWATHS/DominoNO2/Geolocation_Fields/ViewingAzimuthAngle[]";
    register_viewing_azimuth_angle_variable(product_definition, path);

    /* NO2_column_number_density */
    description = "NO2 vertical column density";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "NO2_column_number_density",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "molec/cm^2", NULL,
                                                                     read_no2_column_domino);
    path = "/HDFEOS/SWATHS/DominoNO2/Data_Fields/TotalVerticalColumn[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* NO2_column_number_density_uncertainty */
    description = "uncertainty of the NO2 vertical column density";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition,
                                                                     "NO2_column_number_density_uncertainty",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "molec/cm^2", NULL,
                                                                     read_no2_column_error_domino);
    path = "/HDFEOS/SWATHS/DominoNO2/Data_Fields/TotalVerticalColumnError[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* tropospheric_NO2_column_number_density */
    description = "NO2 tropospheric column density";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition,
                                                                     "tropospheric_NO2_column_number_density",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "molec/cm^2", NULL,
                                                                     read_no2_column_tropospheric_domino);
    path = "/HDFEOS/SWATHS/DominoNO2/Data_Fields/TroposphericVerticalColumn[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* tropospheric_NO2_column_number_density_uncertainty */
    description = "uncertainty of the NO2 tropospheric column density";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition,
                                                                     "tropospheric_NO2_column_number_density_uncertainty",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "molec/cm^2", NULL,
                                                                     read_no2_column_tropospheric_error_domino);
    path = "/HDFEOS/SWATHS/DominoNO2/Data_Fields/TroposphericVerticalColumnError[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* tropospheric_NO2_column_number_density_validity */
    description = "flags describing the NO2 tropospheric column processing quality";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition,
                                                                     "tropospheric_NO2_column_number_density_validity",
                                                                     harp_type_int16, 1, dimension_type, NULL,
                                                                     description, NULL, NULL,
                                                                     read_no2_column_tropospheric_validity_domino);
    path = "/HDFEOS/SWATHS/DominoNO2/Data_Fields/TroposphericColumnFlag[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path,
                                         "data is converted from uint8 to int16");

    /* cloud_fraction */
    description = "effective cloud fraction";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "cloud_fraction",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, HARP_UNIT_DIMENSIONLESS, NULL,
                                                                     read_cloud_fraction);
    path = "/HDFEOS/SWATHS/DominoNO2/Data_Fields/CloudFraction[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* cloud_fraction_uncertainty */
    description = "uncertainty of the effective cloud fraction";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "cloud_fraction_uncertainty",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, HARP_UNIT_DIMENSIONLESS, NULL,
                                                                     read_cloud_fraction_std);
    path = "/HDFEOS/SWATHS/DominoNO2/Data_Fields/CloudFractionStd[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* cloud_pressure */
    description = "effective cloud pressure";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "cloud_pressure",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "hPa", NULL, read_pressure_cloud);
    path = "/HDFEOS/SWATHS/DominoNO2/Data_Fields/CloudPressure[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* cloud_pressure_uncertainty */
    description = "uncertainty of the effective cloud pressure";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "cloud_pressure_uncertainty",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "hPa", NULL, read_pressure_cloud_std);
    path = "/HDFEOS/SWATHS/DominoNO2/Data_Fields/CloudPressureStd[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);
}

static void register_omhcho_product(void)
{
    harp_ingestion_module *module;
    harp_product_definition *product_definition;
    harp_variable_definition *variable_definition;
    harp_dimension_type dimension_type[1] = { harp_dimension_time };
    const char *destriped_option_values[] = { "true" };
    const char *description;
    const char *path;

    module = harp_ingestion_register_module("OMI_L2_OMHCHO", "OMI", "AURA_OMI", "OMHCHO",
                                            "OMI L2 HCHO total column", ingestion_init_omhcho, ingestion_done);

    /* destriped ingestion option */
    harp_ingestion_register_option(module, "destriped", "ingest column densities without destriping correction "
                                   "(default) or with destriping correction (destriped=true)", 1,
                                   destriped_option_values);

    /* OMHCHO product */
    product_definition = harp_ingestion_register_product(module, "OMI_L2_OMHCHO", NULL, read_dimensions);
    add_footprint_mapping(product_definition);

    /* datetime */
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_HCHO/Geolocation_Fields/Time[]";
    register_datetime_variable(product_definition, path);

    /* longitude and latitude */
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_HCHO/Geolocation_Fields/Longitude[]";
    register_longitude_variable(product_definition, path);
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_HCHO/Geolocation_Fields/Latitude[]";
    register_latitude_variable(product_definition, path);

    /* longitude_bounds and latitude_bounds */
    register_footprint_variables(product_definition);

    /* sensor_altitude */
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_HCHO/Geolocation_Fields/SpacecraftAltitude[]";
    register_spacecraft_altitude_variable(product_definition, path);

    /* surface_altitude */
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_HCHO/Geolocation_Fields/TerrainHeight[]";
    register_terrain_height_variable(product_definition, path);

    /* HCHO_column_number_density */
    description = "HCHO vertical column density";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "HCHO_column_number_density",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "molec/cm^2", NULL, read_hcho_column);
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_HCHO/Data_Fields/ColumnAmount[]";
    harp_variable_definition_add_mapping(variable_definition, "destriped unset", NULL, path, NULL);
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_HCHO/Data_Fields/ColumnAmountDestriped[]";
    harp_variable_definition_add_mapping(variable_definition, "destriped=true", NULL, path, NULL);

    /* HCHO_column_number_density_uncertainty */
    description = "uncertainty of the HCHO vertical column density";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition,
                                                                     "HCHO_column_number_density_uncertainty",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "molec/cm^2", include_no_destriped,
                                                                     read_hcho_column_error);
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_HCHO/Data_Fields/ColumnUncertainty[]";
    harp_variable_definition_add_mapping(variable_definition, "destriped unset", NULL, path, NULL);
}

static void register_omno2_product(void)
{
    harp_ingestion_module *module;
    harp_product_definition *product_definition;
    harp_variable_definition *variable_definition;
    harp_dimension_type dimension_type[1] = { harp_dimension_time };
    const char *destriped_option_values[] = { "true" };
    const char *description;
    const char *path;

    module = harp_ingestion_register_module("OMI_L2_OMNO2", "OMI", "AURA_OMI", "OMNO2",
                                            "OMI L2 NO2 total and tropospheric column", ingestion_init_no2,
                                            ingestion_done);

    /* destriped ingestion option */
    harp_ingestion_register_option(module, "destriped", "ingest slant column densities without destriping correction "
                                   "(default) or with destriping correction (destriped=true)", 1,
                                   destriped_option_values);

    /* OMNO2 product */
    product_definition = harp_ingestion_register_product(module, "OMI_L2_OMNO2", NULL, read_dimensions);
    add_footprint_mapping(product_definition);

    /* datetime */
    path = "/HDFEOS/SWATHS/ColumnAmountNO2/Geolocation_Fields/Time[]";
    register_datetime_variable(product_definition, path);

    /* longitude and latitude */
    path = "/HDFEOS/SWATHS/ColumnAmountNO2/Geolocation_Fields/Longitude[]";
    register_longitude_variable(product_definition, path);
    path = "/HDFEOS/SWATHS/ColumnAmountNO2/Geolocation_Fields/Latitude[]";
    register_latitude_variable(product_definition, path);

    /* longitude_bounds and latitude_bounds */
    register_footprint_variables(product_definition);

    /* solar_zenith_angle */
    path = "/HDFEOS/SWATHS/ColumnAmountNO2/Geolocation_Fields/SolarZenithAngle[]";
    register_solar_zenith_angle_variable(product_definition, path);

    /* solar_azimuth_angle */
    path = "/HDFEOS/SWATHS/ColumnAmountNO2/Geolocation_Fields/SolarAzimuthAngle[]";
    register_solar_azimuth_angle_variable(product_definition, path);

    /* viewing_zenith_angle */
    path = "/HDFEOS/SWATHS/ColumnAmountNO2/Geolocation_Fields/ViewingZenithAngle[]";
    register_viewing_zenith_angle_variable(product_definition, path);

    /* viewing_azimuth_angle */
    path = "/HDFEOS/SWATHS/ColumnAmountNO2/Geolocation_Fields/ViewingAzimuthAngle[]";
    register_viewing_azimuth_angle_variable(product_definition, path);

    /* sensor_altitude */
    path = "/HDFEOS/SWATHS/ColumnAmountNO2/Geolocation_Fields/SpacecraftAltitude[]";
    register_spacecraft_altitude_variable(product_definition, path);

    /* sensor_latitude */
    path = "/HDFEOS/SWATHS/ColumnAmountNO2/Geolocation_Fields/SpacecraftLatitude[]";
    register_spacecraft_latitude_variable(product_definition, path);

    /* sensor_longitude */
    path = "/HDFEOS/SWATHS/ColumnAmountNO2/Geolocation_Fields/SpacecraftLongitude[]";
    register_spacecraft_longitude_variable(product_definition, path);

    /* surface_altitude */
    path = "/HDFEOS/SWATHS/ColumnAmountNO2/Data_Fields/TerrainHeight[]";
    register_terrain_height_variable(product_definition, path);

    /* surface_pressure */
    path = "/HDFEOS/SWATHS/ColumnAmountNO2/Data_Fields/TerrainPressure[]";
    register_terrain_pressure_variable(product_definition, path);

    /* NO2_column_number_density */
    description = "NO2 vertical column density";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "NO2_column_number_density",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "molec/cm^2", NULL, read_no2_column);
    path = "/HDFEOS/SWATHS/ColumnAmountNO2/Data_Fields/ColumnAmountNO2[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* NO2_column_number_density_uncertainty */
    description = "uncertainty of the NO2 vertical column density";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition,
                                                                     "NO2_column_number_density_uncertainty",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "molec/cm^2", NULL,
                                                                     read_no2_column_error);
    path = "/HDFEOS/SWATHS/ColumnAmountNO2/Data_Fields/ColumnAmountNO2Std[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* tropospheric_NO2_column_number_density */
    description = "NO2 tropospheric column density";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition,
                                                                     "tropospheric_NO2_column_number_density",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "molec/cm^2", NULL,
                                                                     read_no2_column_tropospheric);
    path = "/HDFEOS/SWATHS/ColumnAmountNO2/Data_Fields/ColumnAmountNO2Trop[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* tropospheric_NO2_column_number_density_uncertainty */
    description = "uncertainty of the NO2 tropospheric column density";
    variable_definition =
        harp_ingestion_register_variable_full_read(product_definition,
                                                   "tropospheric_NO2_column_number_density_uncertainty",
                                                   harp_type_double, 1, dimension_type, NULL, description, "molec/cm^2",
                                                   NULL, read_no2_column_tropospheric_error);
    path = "/HDFEOS/SWATHS/ColumnAmountNO2/Data_Fields/ColumnAmountNO2TropStd[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* tropospheric_NO2_column_number_density_amf */
    description = "air mass factor of the NO2 tropospheric column density";
    variable_definition =
        harp_ingestion_register_variable_full_read(product_definition, "tropospheric_NO2_column_number_density_amf",
                                                   harp_type_double, 1, dimension_type, NULL, description,
                                                   HARP_UNIT_DIMENSIONLESS, include_amf_trop,
                                                   read_no2_column_tropospheric_amf);
    path = "/HDFEOS/SWATHS/ColumnAmountNO2/Data_Fields/AmfTrop[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* tropospheric_NO2_column_number_density_apriori */
    description = "apriori of the NO2 tropospheric column density";
    variable_definition =
        harp_ingestion_register_variable_full_read(product_definition, "tropospheric_NO2_column_number_density_apriori",
                                                   harp_type_double, 1, dimension_type, NULL, description, "molec/cm^2",
                                                   include_vcd_ap_trop, read_no2_column_tropospheric_apriori);
    path = "/HDFEOS/SWATHS/ColumnAmountNO2/Data_Fields/VcdApTrop[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* stratospheric_NO2_column_number_density */
    description = "NO2 stratospheric column density";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition,
                                                                     "stratospheric_NO2_column_number_density",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "molec/cm^2", include_stratno2,
                                                                     read_no2_column_stratospheric);
    path = "/HDFEOS/SWATHS/ColumnAmountNO2/Data_Fields/ColumnAmountNO2Strat[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* stratospheric_NO2_column_number_density_uncertainty */
    description = "uncertainty of the NO2 stratospheric column density";
    variable_definition =
        harp_ingestion_register_variable_full_read(product_definition,
                                                   "stratospheric_NO2_column_number_density_uncertainty",
                                                   harp_type_double, 1, dimension_type, NULL, description, "molec/cm^2",
                                                   include_stratno2, read_no2_column_stratospheric_error);
    path = "/HDFEOS/SWATHS/ColumnAmountNO2/Data_Fields/ColumnAmountNO2StratStd[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* stratospheric_NO2_column_number_density_amf */
    description = "air mass factor of the NO2 stratospheric column density";
    variable_definition =
        harp_ingestion_register_variable_full_read(product_definition, "stratospheric_NO2_column_number_density_amf",
                                                   harp_type_double, 1, dimension_type, NULL, description,
                                                   HARP_UNIT_DIMENSIONLESS, include_amf_strat,
                                                   read_no2_column_stratospheric_amf);
    path = "/HDFEOS/SWATHS/ColumnAmountNO2/Data_Fields/AmfStrat[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* stratospheric_NO2_column_number_density_apriori */
    description = "apriori of the NO2 stratospheric column density";
    variable_definition =
        harp_ingestion_register_variable_full_read(product_definition,
                                                   "stratospheric_NO2_column_number_density_apriori", harp_type_double,
                                                   1, dimension_type, NULL, description, "molec/cm^2",
                                                   include_vcd_ap_strat, read_no2_column_stratospheric_apriori);
    path = "/HDFEOS/SWATHS/ColumnAmountNO2/Data_Fields/VcdApStrat[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* NO2_slant_column_number_density */
    description = "NO2 slant column density";
    variable_definition =
        harp_ingestion_register_variable_full_read(product_definition, "NO2_slant_column_number_density",
                                                   harp_type_double, 1, dimension_type, NULL, description, "molec/cm^2",
                                                   NULL, read_no2_slant_column);
    path = "/HDFEOS/SWATHS/ColumnAmountNO2/Data_Fields/SlantColumnAmountNO2[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, "destriped unset", path, NULL);
    path = "/HDFEOS/SWATHS/ColumnAmountNO2/Data_Fields/SlantColumnAmountNO2Destriped[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, "destriped=true", path, NULL);

    /* NO2_slant_column_number_density_uncertainty */
    description = "uncertainty of the NO2 slant column density";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition,
                                                                     "NO2_slant_column_number_density_uncertainty",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "molec/cm^2", NULL,
                                                                     read_no2_slant_column_error);
    path = "/HDFEOS/SWATHS/ColumnAmountNO2/Data_Fields/SlantColumnAmountNO2Std[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* validity */
    description = "Vertical column density quality flags";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "validity", harp_type_int32, 1,
                                                                     dimension_type, NULL, description, NULL,
                                                                     include_vcd_quality_flags, read_vcd_quality_flags);
    path = "/HDFEOS/SWATHS/ColumnAmountNO2/Data_Fields/VcdQualityFlags[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* tropopause_pressure */
    description = "Pressure of the tropopause";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "tropopause_pressure",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "hPa", include_tropopause_pressure,
                                                                     read_tropopause_pressure);
    path = "/HDFEOS/SWATHS/ColumnAmountNO2/Data_Fields/TropopausePressure[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* cloud_fraction */
    description = "effective cloud fraction";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "cloud_fraction",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, HARP_UNIT_DIMENSIONLESS, NULL,
                                                                     read_cloud_fraction);
    path = "/HDFEOS/SWATHS/ColumnAmountNO2/Data_Fields/CloudFraction[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* cloud_fraction_uncertainty */
    description = "uncertainty of the effective cloud fraction";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "cloud_fraction_uncertainty",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, HARP_UNIT_DIMENSIONLESS, NULL,
                                                                     read_cloud_fraction_std);
    path = "/HDFEOS/SWATHS/ColumnAmountNO2/Data_Fields/CloudFractionStd[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* cloud_pressure */
    description = "effective cloud pressure";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "cloud_pressure",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "hPa", NULL, read_pressure_cloud);
    path = "/HDFEOS/SWATHS/ColumnAmountNO2/Data_Fields/CloudPressure[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* cloud_pressure_uncertainty */
    description = "uncertainty of the effective cloud pressure";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "cloud_pressure_uncertainty",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "hPa", NULL, read_pressure_cloud_std);
    path = "/HDFEOS/SWATHS/ColumnAmountNO2/Data_Fields/CloudPressureStd[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);
}

static void register_omo3pr_product(void)
{
    harp_ingestion_module *module;
    harp_product_definition *product_definition;
    harp_variable_definition *variable_definition;
    harp_dimension_type dimension_type[2] = { harp_dimension_time, harp_dimension_vertical };
    const char *description;
    const char *path;

    module = harp_ingestion_register_module("OMI_L2_OMO3PR", "OMI", "AURA_OMI", "OMO3PR", "OMI L2 O3 profile",
                                            ingestion_init_omo3pr, ingestion_done);

    /* OMO3PR product */
    product_definition = harp_ingestion_register_product(module, "OMI_L2_OMO3PR", NULL, read_dimensions_omo3pr);
    add_footprint_mapping(product_definition);

    /* datetime */
    path = "/HDFEOS/SWATHS/O3Profile/Geolocation_Fields/Time[]";
    register_datetime_variable(product_definition, path);

    /* longitude and latitude */
    path = "/HDFEOS/SWATHS/O3Profile/Geolocation_Fields/Longitude[]";
    register_longitude_variable(product_definition, path);
    path = "/HDFEOS/SWATHS/O3Profile/Geolocation_Fields/Latitude[]";
    register_latitude_variable(product_definition, path);

    /* longitude_bounds and latitude_bounds */
    register_footprint_variables(product_definition);

    /* solar_zenith_angle */
    path = "/HDFEOS/SWATHS/O3Profile/Geolocation_Fields/SolarZenithAngle[]";
    register_solar_zenith_angle_variable(product_definition, path);

    /* solar_azimuth_angle */
    path = "/HDFEOS/SWATHS/O3Profile/Geolocation_Fields/SolarAzimuthAngle[]";
    register_solar_azimuth_angle_variable(product_definition, path);

    /* viewing_zenith_angle */
    path = "/HDFEOS/SWATHS/O3Profile/Geolocation_Fields/ViewingZenithAngle[]";
    register_viewing_zenith_angle_variable(product_definition, path);

    /* viewing_azimuth_angle */
    path = "/HDFEOS/SWATHS/O3Profile/Geolocation_Fields/ViewingAzimuthAngle[]";
    register_viewing_azimuth_angle_variable(product_definition, path);

    /* sensor_altitude */
    path = "/HDFEOS/SWATHS/O3Profile/Geolocation_Fields/SpacecraftAltitude[]";
    register_spacecraft_altitude_variable(product_definition, path);

    /* sensor_latitude */
    path = "/HDFEOS/SWATHS/O3Profile/Geolocation_Fields/SpacecraftLatitude[]";
    register_spacecraft_latitude_variable(product_definition, path);

    /* sensor_longitude */
    path = "/HDFEOS/SWATHS/O3Profile/Geolocation_Fields/SpacecraftLongitude[]";
    register_spacecraft_longitude_variable(product_definition, path);

    /* surface_altitude */
    path = "/HDFEOS/SWATHS/O3Profile/Geolocation_Fields/TerrainHeight[]";
    register_terrain_height_variable(product_definition, path);

    /* pressure */
    description = "pressure level for each profile element";
    variable_definition = harp_ingestion_register_variable_block_read(product_definition, "pressure", harp_type_double,
                                                                      2, dimension_type, NULL, description, "hPa",
                                                                      NULL, read_pressure);
    path = "/HDFEOS/SWATHS/O3Profile/Geolocation_Fields/Pressure[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* O3_number_density */
    description = "O3 concentration";
    variable_definition = harp_ingestion_register_variable_block_read(product_definition, "O3_number_density",
                                                                      harp_type_double, 2, dimension_type, NULL,
                                                                      description, "DU", NULL, read_o3);
    path = "/HDFEOS/SWATHS/O3Profile/Data_Fields/O3[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* O3_number_density_uncertainty */
    description = "uncertainty of the O3 concentration";
    variable_definition = harp_ingestion_register_variable_block_read(product_definition,
                                                                      "O3_number_density_uncertainty",
                                                                      harp_type_double, 2, dimension_type, NULL,
                                                                      description, "DU", NULL, read_o3_error);
    path = "/HDFEOS/SWATHS/O3Profile/Data_Fields/O3Precision[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* O3_number_density_validity */
    description = "flags describing the O3 profile processing quality";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "O3_number_density_validity",
                                                                     harp_type_int32, 1, dimension_type, NULL,
                                                                     description, NULL, NULL,
                                                                     read_processing_quality_flags);
    path = "/HDFEOS/SWATHS/O3Profile/Data_Fields/ProcessingQualityFlags[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);
}

static void register_omoclo_product(void)
{
    harp_ingestion_module *module;
    harp_product_definition *product_definition;
    harp_variable_definition *variable_definition;
    harp_dimension_type dimension_type[1] = { harp_dimension_time };
    const char *destriped_option_values[] = { "true" };
    const char *description;
    const char *path;

    module = harp_ingestion_register_module("OMI_L2_OMOCLO", "OMI", "AURA_OMI", "OMOCLO",
                                            "OMI L2 OClO slant column", ingestion_init_omoclo, ingestion_done);

    /* destriped ingestion option */
    harp_ingestion_register_option(module, "destriped", "ingest column densities without destriping correction "
                                   "(default) or with destriping correction (destriped=true)", 1,
                                   destriped_option_values);

    /* OMOCLO product */
    product_definition = harp_ingestion_register_product(module, "OMI_L2_OMOCLO", NULL, read_dimensions);
    add_footprint_mapping(product_definition);

    /* datetime */
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_OClO/Geolocation_Fields/Time[]";
    register_datetime_variable(product_definition, path);

    /* longitude and latitude */
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_OClO/Geolocation_Fields/Longitude[]";
    register_longitude_variable(product_definition, path);
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_OClO/Geolocation_Fields/Latitude[]";
    register_latitude_variable(product_definition, path);

    /* longitude_bounds and latitude_bounds */
    register_footprint_variables(product_definition);

    /* sensor_altitude */
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_OClO/Geolocation_Fields/SpacecraftAltitude[]";
    register_spacecraft_altitude_variable(product_definition, path);

    /* surface_altitude */
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_OClO/Geolocation_Fields/TerrainHeight[]";
    register_terrain_height_variable(product_definition, path);

    /* OClO_column_number_density */
    description = "OClO vertical column density";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "OClO_column_number_density",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "molec/cm^2", NULL, read_oclo_column);
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_OClO/Data_Fields/ColumnAmount[]";
    harp_variable_definition_add_mapping(variable_definition, "destriped unset", NULL, path, NULL);
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_OClO/Data_Fields/ColumnAmountDestriped[]";
    harp_variable_definition_add_mapping(variable_definition, "destriped=true", NULL, path, NULL);

    /* OClO_column_number_density_uncertainty */
    description = "uncertainty of the OClO vertical column density";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition,
                                                                     "OClO_column_number_density_uncertainty",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "molec/cm^2", include_no_destriped,
                                                                     read_oclo_column_error);
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_OClO/Data_Fields/ColumnUncertainty[]";
    harp_variable_definition_add_mapping(variable_definition, "destriped unset", NULL, path, description);
}

static void register_omso2_product(void)
{
    harp_ingestion_module *module;
    harp_product_definition *product_definition;
    harp_variable_definition *variable_definition;
    harp_dimension_type dimension_type[1] = { harp_dimension_time };
    const char *so2_column_variant_option_values[] = { "pbl", "5km", "15km", "trl", "trm", "stl" };
    const char *description;
    const char *path;

    module = harp_ingestion_register_module("OMI_L2_OMSO2", "OMI", "AURA_OMI", "OMSO2", "OMI L2 SO2 total column",
                                            ingestion_init_omso2, ingestion_done);

    /* so2_column_variant ingestion option */
    description = "for V2 products: anthropogenic SO2 pollution at the planet boundary layer (so2_column_variant=pbl, "
        "default), showing passive degassing at 5km altitude (so2_column_variant=5km), or showing explosive eruptions "
        "at 15km (so2_column_variant=15km); for V3 products: planet boundary layer - 0.9km (so2_column_variant=pbl, "
        "default), lower troposphere - 2.5km (so2_column_variant=trl), middle troposphere - 7.5km "
        "(so2_column_variant=trm), or upper tropospheric and stratospheric - 17km (so2_column_variant=stl)";
    harp_ingestion_register_option(module, "so2_column_variant", description, 6, so2_column_variant_option_values);

    /* OMSO2 product */
    product_definition = harp_ingestion_register_product(module, "OMI_L2_OMSO2", NULL, read_dimensions);
    add_footprint_mapping(product_definition);

    /* datetime */
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_SO2/Geolocation_Fields/Time[]";
    register_datetime_variable(product_definition, path);

    /* longitude and latitude */
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_SO2/Geolocation_Fields/Longitude[]";
    register_longitude_variable(product_definition, path);
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_SO2/Geolocation_Fields/Latitude[]";
    register_latitude_variable(product_definition, path);

    /* longitude_bounds and latitude_bounds */
    register_footprint_variables(product_definition);

    /* solar_zenith_angle */
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_SO2/Geolocation_Fields/SolarZenithAngle[]";
    register_solar_zenith_angle_variable(product_definition, path);

    /* solar_azimuth_angle */
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_SO2/Geolocation_Fields/SolarAzimuthAngle[]";
    register_solar_azimuth_angle_variable(product_definition, path);

    /* viewing_zenith_angle */
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_SO2/Geolocation_Fields/ViewingZenithAngle[]";
    register_viewing_zenith_angle_variable(product_definition, path);

    /* viewing_azimuth_angle */
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_SO2/Geolocation_Fields/ViewingAzimuthAngle[]";
    register_viewing_azimuth_angle_variable(product_definition, path);

    /* sensor_altitude */
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_SO2/Geolocation_Fields/SpacecraftAltitude[]";
    register_spacecraft_altitude_variable(product_definition, path);

    /* sensor_latitude */
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_SO2/Geolocation_Fields/SpacecraftLatitude[]";
    register_spacecraft_latitude_variable(product_definition, path);

    /* sensor_longitude */
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_SO2/Geolocation_Fields/SpacecraftLongitude[]";
    register_spacecraft_longitude_variable(product_definition, path);

    /* surface_altitude */
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_SO2/Geolocation_Fields/TerrainHeight[]";
    register_terrain_height_variable(product_definition, path);

    /* surface_pressure */
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_SO2/Data_Fields/TerrainPressure[]";
    register_terrain_pressure_variable(product_definition, path);

    /* SO2_column_number_density */
    description = "SO2 vertical column density";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "SO2_column_number_density",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "DU", NULL, read_so2_column);
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_SO2/Data_Fields/SO2ColumnAmountPBL[]";
    harp_variable_definition_add_mapping(variable_definition, "so2_column_variant=pbl or so2_column_variant unset",
                                         "V2 product", path, NULL);
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_SO2/Data_Fields/SO2ColumnAmount05KM[]";
    harp_variable_definition_add_mapping(variable_definition, "so2_column_variant=5km", "V2 product", path, NULL);
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_SO2/Data_Fields/SO2ColumnAmount15KM[]";
    harp_variable_definition_add_mapping(variable_definition, "so2_column_variant=15km", "V2 product", path, NULL);
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_SO2/Data_Fields/ColumnAmountSO2_PBL[]";
    harp_variable_definition_add_mapping(variable_definition, "so2_column_variant=pbl or so2_column_variant unset",
                                         "V3 product", path, NULL);
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_SO2/Data_Fields/ColumnAmountSO2_TRL[]";
    harp_variable_definition_add_mapping(variable_definition, "so2_column_variant=trl", "V3 product", path, NULL);
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_SO2/Data_Fields/ColumnAmountSO2_TRM[]";
    harp_variable_definition_add_mapping(variable_definition, "so2_column_variant=trm", "V3 product", path, NULL);
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_SO2/Data_Fields/ColumnAmountSO2_STL[]";
    harp_variable_definition_add_mapping(variable_definition, "so2_column_variant=stl", "V3 product", path, NULL);

    /* cloud_fraction */
    description = "effective cloud fraction";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "cloud_fraction",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, HARP_UNIT_DIMENSIONLESS, NULL,
                                                                     read_cloud_fraction);
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_SO2/Data_Fields/CloudFraction[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* cloud_pressure */
    description = "effective cloud pressure";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "cloud_pressure",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "hPa", include_cloud_pressure,
                                                                     read_pressure_cloud);
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_SO2/Data_Fields/CloudPressure[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, "V3 product", path, NULL);


    /* cloud_top_pressure */
    description = "cloud top pressure";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "cloud_top_pressure",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "hPa", include_cloud_top_pressure,
                                                                     read_pressure_cloud_top);
    path = "/HDFEOS/SWATHS/OMI_Total_Column_Amount_SO2/Data_Fields/CloudTopPressure[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, "V2 product", path, NULL);
}

static void register_omto3_product(void)
{
    harp_ingestion_module *module;
    harp_product_definition *product_definition;
    harp_variable_definition *variable_definition;
    harp_dimension_type dimension_type[1] = { harp_dimension_time };
    const char *cloud_fraction_variant_option_values[] = { "effective", "radiative" };
    const char *description;
    const char *path;

    module = harp_ingestion_register_module("OMI_L2_OMTO3", "OMI", "AURA_OMI", "OMTO3",
                                            "OMI L2 O3 total column (TOMS)", ingestion_init_omto3, ingestion_done);

    /* cloud_fraction_variant ingestion option */
    description = "ingest effective (cloud_fraction_variant=effective, default) or radiative cloud fraction "
        "(cloud_fraction_variant=radiative); only applicable for V3 products";
    harp_ingestion_register_option(module, "cloud_fraction_variant", description, 2,
                                   cloud_fraction_variant_option_values);

    /* OMTO3 product */
    product_definition = harp_ingestion_register_product(module, "OMI_L2_OMTO3", NULL, read_dimensions);
    add_footprint_mapping(product_definition);

    /* datetime */
    path = "/HDFEOS/SWATHS/OMI_Column_Amount_O3/Geolocation_Fields/Time[]";
    register_datetime_variable(product_definition, path);

    /* longitude and latitude */
    path = "/HDFEOS/SWATHS/OMI_Column_Amount_O3/Geolocation_Fields/Longitude[]";
    register_longitude_variable(product_definition, path);
    path = "/HDFEOS/SWATHS/OMI_Column_Amount_O3/Geolocation_Fields/Latitude[]";
    register_latitude_variable(product_definition, path);

    /* longitude_bounds and latitude_bounds */
    register_footprint_variables(product_definition);

    /* solar_zenith_angle */
    path = "/HDFEOS/SWATHS/OMI_Column_Amount_O3/Geolocation_Fields/SolarZenithAngle[]";
    register_solar_zenith_angle_variable(product_definition, path);

    /* solar_azimuth_angle */
    path = "/HDFEOS/SWATHS/OMI_Column_Amount_O3/Geolocation_Fields/SolarAzimuthAngle[]";
    register_solar_azimuth_angle_variable(product_definition, path);

    /* viewing_zenith_angle */
    path = "/HDFEOS/SWATHS/OMI_Column_Amount_O3/Geolocation_Fields/ViewingZenithAngle[]";
    register_viewing_zenith_angle_variable(product_definition, path);

    /* viewing_azimuth_angle */
    path = "/HDFEOS/SWATHS/OMI_Column_Amount_O3/Geolocation_Fields/ViewingAzimuthAngle[]";
    register_viewing_azimuth_angle_variable(product_definition, path);

    /* sensor_altitude */
    path = "/HDFEOS/SWATHS/OMI_Column_Amount_O3/Geolocation_Fields/SpacecraftAltitude[]";
    register_spacecraft_altitude_variable(product_definition, path);

    /* sensor_latitude */
    path = "/HDFEOS/SWATHS/OMI_Column_Amount_O3/Geolocation_Fields/SpacecraftLatitude[]";
    register_spacecraft_latitude_variable(product_definition, path);

    /* sensor_longitude */
    path = "/HDFEOS/SWATHS/OMI_Column_Amount_O3/Geolocation_Fields/SpacecraftLongitude[]";
    register_spacecraft_longitude_variable(product_definition, path);

    /* O3_column_number_density */
    description = "ozone vertical column density";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "O3_column_number_density",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "DU", NULL, read_o3_column);
    path = "/HDFEOS/SWATHS/OMI_Column_Amount_O3/Data_Fields/ColumnAmountO3[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* O3_column_number_density_validity */
    description = "flags describing the O3 vertical column processing quality";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition,
                                                                     "O3_column_number_density_validity",
                                                                     harp_type_int32, 1, dimension_type, NULL,
                                                                     description, NULL, NULL, read_quality_flags);
    path = "/HDFEOS/SWATHS/OMI_Column_Amount_O3/Data_Fields/QualityFlags[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL);

    /* cloud_fraction */
    description = "effective or radiative cloud fraction";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "cloud_fraction",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, HARP_UNIT_DIMENSIONLESS,
                                                                     include_cloud_fraction, read_cloud_fraction);
    path = "/HDFEOS/SWATHS/OMI_Column_Amount_O3/Data_Fields/CloudFraction[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, "V2 product", path, NULL);
    path = "/HDFEOS/SWATHS/OMI_Column_Amount_O3/Data_Fields/fc[]";
    harp_variable_definition_add_mapping(variable_definition,
                                         "cloud_fraction_variant=effective or cloud_fraction_variant unset",
                                         "V3 product", path, NULL);
    path = "/HDFEOS/SWATHS/OMI_Column_Amount_O3/Data_Fields/RadiativeCloudFraction[]";
    harp_variable_definition_add_mapping(variable_definition, "cloud_fraction_variant=radiative", "V3 product", path,
                                         NULL);

    /* cloud_pressure */
    description = "effective cloud pressure";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "cloud_pressure",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "hPa", include_cloud_pressure,
                                                                     read_pressure_cloud);
    path = "/HDFEOS/SWATHS/OMI_Column_Amount_O3/Data_Fields/CloudPressure[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, "V3 product", path, NULL);

    /* cloud_top_pressure */
    description = "cloud top pressure";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "cloud_top_pressure",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "hPa", include_cloud_top_pressure,
                                                                     read_pressure_cloud_top);
    path = "/HDFEOS/SWATHS/OMI_Column_Amount_O3/Data_Fields/CloudTopPressure[]";
    harp_variable_definition_add_mapping(variable_definition, NULL, "V2 product", path, NULL);
}

static void register_omuvb_product(void)
{
    harp_ingestion_module *module;
    harp_product_definition *product_definition;
    harp_variable_definition *variable_definition;
    harp_dimension_type dimension_type[1] = { harp_dimension_time };
    const char *clear_sky_option_values[] = { "true" };
    const char *wavelength_option_values[] = { "305nm", "310nm", "324nm", "380nm" };
    const char *description;
    const char *path;

    module = harp_ingestion_register_module("OMI_L2_OMUVB", "OMI", "AURA_OMI", "OMUVB",
                                            "OMI L2 UV-B surface irradiance and erythemal dose rate",
                                            ingestion_init_omuvb, ingestion_done);

    /* clear_sky ingestion option */
    description = "ingest clear sky surface UV irradiance";
    harp_ingestion_register_option(module, "clear_sky", description, 1, clear_sky_option_values);

    /* wavelength ingestion option */
    description = "wavelength for which to ingest the surface UV irradiance";
    harp_ingestion_register_option(module, "wavelength", description, 4, wavelength_option_values);

    /* OMUVB product */
    product_definition = harp_ingestion_register_product(module, "OMI_L2_OMUVB", NULL, read_dimensions);
    add_footprint_mapping(product_definition);

    /* datetime */
    path = "/HDFEOS/SWATHS/UVB/Geolocation_Fields/Time[]";
    register_datetime_variable(product_definition, path);

    /* longitude and latitude */
    path = "/HDFEOS/SWATHS/UVB/Geolocation_Fields/Longitude[]";
    register_longitude_variable(product_definition, path);
    path = "/HDFEOS/SWATHS/UVB/Geolocation_Fields/Latitude[]";
    register_latitude_variable(product_definition, path);

    /* longitude_bounds and latitude_bounds */
    register_footprint_variables(product_definition);

    /* solar_zenith_angle */
    path = "/HDFEOS/SWATHS/UVB/Geolocation_Fields/SolarZenithAngle[]";
    register_solar_zenith_angle_variable(product_definition, path);

    /* surface_altitude */
    path = "/HDFEOS/SWATHS/UVB/Geolocation_Fields/TerrainHeight[]";
    register_terrain_height_variable(product_definition, path);

    /* surface_irradiance */
    description = "surface irradiance";
    variable_definition = harp_ingestion_register_variable_full_read(product_definition, "surface_irradiance",
                                                                     harp_type_double, 1, dimension_type, NULL,
                                                                     description, "W/(m^2.nm)", NULL,
                                                                     read_uv_irradiance_surface);
    path = "/HDFEOS/SWATHS/UVB/Data_Fields/Irradiance305[]";
    harp_variable_definition_add_mapping(variable_definition, "clear_sky unset and wavelength=305nm", NULL, path, NULL);
    path = "/HDFEOS/SWATHS/UVB/Data_Fields/Irradiance310[]";
    harp_variable_definition_add_mapping(variable_definition, "clear_sky unset and wavelength=310nm", NULL, path, NULL);
    path = "/HDFEOS/SWATHS/UVB/Data_Fields/Irradiance324[]";
    harp_variable_definition_add_mapping(variable_definition, "clear_sky unset and wavelength=324nm", NULL, path, NULL);
    path = "/HDFEOS/SWATHS/UVB/Data_Fields/Irradiance380[]";
    harp_variable_definition_add_mapping(variable_definition, "clear_sky unset and wavelength=380nm", NULL, path, NULL);
    path = "/HDFEOS/SWATHS/UVB/Data_Fields/CSIrradiance305[]";
    harp_variable_definition_add_mapping(variable_definition, "clear_sky=true and wavelength=305nm", NULL, path, NULL);
    path = "/HDFEOS/SWATHS/UVB/Data_Fields/CSIrradiance310[]";
    harp_variable_definition_add_mapping(variable_definition, "clear_sky=true and wavelength=310nm", NULL, path, NULL);
    path = "/HDFEOS/SWATHS/UVB/Data_Fields/CSIrradiance324[]";
    harp_variable_definition_add_mapping(variable_definition, "clear_sky=true and wavelength=324nm", NULL, path, NULL);
    path = "/HDFEOS/SWATHS/UVB/Data_Fields/CSIrradiance380[]";
    harp_variable_definition_add_mapping(variable_definition, "clear_sky=true and wavelength=380nm", NULL, path, NULL);
    path = "/HDFEOS/SWATHS/UVB/Geolocation_Fields/SolarZenithAngle[]";
}

int harp_ingestion_module_omi_l2_init(void)
{
    register_omaeruv_product();
    register_ombro_product();
    register_omchocho_product();
    register_omcldo2_product();
    register_omcldrr_product();
    register_omdoao3_product();
    register_omdomino_product();
    register_omhcho_product();
    register_omno2_product();
    register_omo3pr_product();
    register_omoclo_product();
    register_omso2_product();
    register_omto3_product();
    register_omuvb_product();

    return 0;
}
