diff --git a/CHANGES b/CHANGES index f1b2ab9b..219971b7 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,7 @@ * Updates to product ingestions: - GOME-2 L2: added support for O3MARS aerosol layer height and added support for O3MARP V5 + - S5P PAL L2: added support for SRON/SICOR HDO (L2__HDO__S) product 1.23 2024-07-30 ~~~~~~~~~~~~~~~ diff --git a/definitions/S5P_PAL b/definitions/S5P_PAL index 6db9bb08..d30f2404 160000 --- a/definitions/S5P_PAL +++ b/definitions/S5P_PAL @@ -1 +1 @@ -Subproject commit 6db9bb0895447ea7482ca40eceeaecacf0452317 +Subproject commit d30f2404d066e5eccded7bff75656bd6bbfe5d96 diff --git a/doc/html/ingestions/GOME2_L2_O3MARP.html b/doc/html/ingestions/GOME2_L2_O3MARP.html index e6f443f2..bcd5b7d0 100644 --- a/doc/html/ingestions/GOME2_L2_O3MARP.html +++ b/doc/html/ingestions/GOME2_L2_O3MARP.html @@ -157,24 +157,24 @@

Variables

[degree]

absorbing aerosol index

-

cloud_fraction

-

double

-

{time}

-

[]

-

cloud fraction

- -

scan_direction_type

+

scan_direction_type

int8

{time}

scan direction for each measurement; enumeration values: forward (0), backward (1)

-

scene_type

+

scene_type

int32

{time}

sun glint condition

+

cloud_fraction

+

double

+

{time}

+

[]

+

cloud fraction

+

index

int32

{time}

@@ -240,21 +240,21 @@

Mapping description

path

/DATA/AAI[]

-

cloud_fraction

-

path

-

/DATA/PMD_CloudFraction[]

- -

scan_direction_type

+

scan_direction_type

path

/GEOLOCATION/ScanDirection[]

-

description

+

description

subtract 1 to turn values 1,2 into 0,1

-

scene_type

+

scene_type

path

/DATA/SunGlintFlag[]

+

cloud_fraction

+

path

+

/DATA/PMD_CloudFraction[]

+ diff --git a/doc/html/ingestions/index.html b/doc/html/ingestions/index.html index d2517a1e..3e535e03 100644 --- a/doc/html/ingestions/index.html +++ b/doc/html/ingestions/index.html @@ -1985,13 +1985,17 @@

Ingestion definitions

GOME2_L2_O3MARP

ACSAF/O3MARP

-

GOME2 offline absorbing aerosol index product

+

GOME2 offline absorbing aerosol index from PMD product

-

GOME2_L2_O3MNTO

+

GOME2_L2_O3MARS

+

ACSAF/O3MARS

+

GOME2 offline absorbing layer height and absorbing aerosol index product

+ +

GOME2_L2_O3MNTO

ACSAF/O3MNTO

GOME2 near-real-time total column trace gas product

-

GOME2_L2_O3MOTO

+

GOME2_L2_O3MOTO

ACSAF/O3MOTO

GOME2 offline total column trace gas product

@@ -3219,19 +3223,23 @@

Ingestion definitions

S5P_PAL/L2__CHOCHO

Sentinel-5P L2 Glyoxal (CHOCHO) product

-

S5P_PAL_L2_OCLO

+

S5P_PAL_L2_HDO_S

+

S5P_PAL/L2__HDO__S

+

Sentinel-5P L2 Heavy Water (HDO) product from SRON/SICOR algorithm

+ +

S5P_PAL_L2_OCLO

S5P_PAL/L2__OCLO__

Sentinel-5P L2 Chlorine Dioxide (OClO) product

-

S5P_PAL_L2_SIF

+

S5P_PAL_L2_SIF

S5P_PAL/L2__SIF___

Sentinel-5P L2 Solar Induced Fluorescence product

-

S5P_PAL_L2_SO2CBR

+

S5P_PAL_L2_SO2CBR

S5P_PAL/L2__SO2CBR

Sentinel-5P L2 SO2 COBRA product

-

S5P_PAL_L2_TCWV

+

S5P_PAL_L2_TCWV

S5P_PAL/L2__TCWV__

Sentinel-5P L2 Total Column Water Vapor product

diff --git a/doc/html/objects.inv b/doc/html/objects.inv index 8aaca964..fca3e15c 100644 Binary files a/doc/html/objects.inv and b/doc/html/objects.inv differ diff --git a/harpdoc.mk b/harpdoc.mk index 556317e0..e2f25ef7 100644 --- a/harpdoc.mk +++ b/harpdoc.mk @@ -409,6 +409,7 @@ HARP_DOCFILES = \ doc/html/ingestions/GOME2_L1_sun_reference.html \ doc/html/ingestions/GOME2_L1_transmission.html \ doc/html/ingestions/GOME2_L2_O3MARP.html \ + doc/html/ingestions/GOME2_L2_O3MARS.html \ doc/html/ingestions/GOME2_L2_O3MNTO.html \ doc/html/ingestions/GOME2_L2_O3MOTO.html \ doc/html/ingestions/GOME_L1_EXTRACTED.html \ @@ -535,6 +536,7 @@ HARP_DOCFILES = \ doc/html/ingestions/S5P_PAL_L2_AER_OT.html \ doc/html/ingestions/S5P_PAL_L2_BRO.html \ doc/html/ingestions/S5P_PAL_L2_CHOCHO.html \ + doc/html/ingestions/S5P_PAL_L2_HDO_S.html \ doc/html/ingestions/S5P_PAL_L2_OCLO.html \ doc/html/ingestions/S5P_PAL_L2_SIF.html \ doc/html/ingestions/S5P_PAL_L2_SO2CBR.html \ @@ -1417,6 +1419,8 @@ doc/html/ingestions/GOME2_L1_transmission.html: $(MAKE) harp_doc doc/html/ingestions/GOME2_L2_O3MARP.html: $(MAKE) harp_doc +doc/html/ingestions/GOME2_L2_O3MARS.html: + $(MAKE) harp_doc doc/html/ingestions/GOME2_L2_O3MNTO.html: $(MAKE) harp_doc doc/html/ingestions/GOME2_L2_O3MOTO.html: @@ -1669,6 +1673,8 @@ doc/html/ingestions/S5P_PAL_L2_BRO.html: $(MAKE) harp_doc doc/html/ingestions/S5P_PAL_L2_CHOCHO.html: $(MAKE) harp_doc +doc/html/ingestions/S5P_PAL_L2_HDO_S.html: + $(MAKE) harp_doc doc/html/ingestions/S5P_PAL_L2_OCLO.html: $(MAKE) harp_doc doc/html/ingestions/S5P_PAL_L2_SIF.html: diff --git a/libharp/harp-ingest-pal_s5p_l2.c b/libharp/harp-ingest-pal_s5p_l2.c index 9da99b34..a5faeb9a 100644 --- a/libharp/harp-ingest-pal_s5p_l2.c +++ b/libharp/harp-ingest-pal_s5p_l2.c @@ -47,6 +47,7 @@ typedef enum pal_s5p_product_type_enum pal_s5p_type_aer_ot, pal_s5p_type_bro, pal_s5p_type_chocho, + pal_s5p_type_hdo_s, pal_s5p_type_oclo, pal_s5p_type_sif, pal_s5p_type_so2cbr, @@ -74,6 +75,7 @@ static const char *pal_s5p_dimension_name[PAL_S5P_NUM_PRODUCT_TYPES][PAL_S5P_NUM {"time", "scanline", "ground_pixel", "corner", "wavelength", NULL}, /* pal_s5p_type_aer_ot */ {"time", "scanline", "ground_pixel", "corner", NULL, NULL}, /* pal_s5p_type_bro */ {"time", "scanline", "ground_pixel", "corner", NULL, NULL}, /* pal_s5p_type_chocho */ + {"time", "scanline", "ground_pixel", "corner", NULL, "layer"}, /* pal_s5p_type_hdo_s */ {"time", "scanline", "ground_pixel", "corner", NULL, NULL}, /* pal_s5p_type_oclo */ {"time", "scanline", "ground_pixel", "corner", NULL, NULL}, /* pal_s5p_type_sif */ {"time", "scanline", "ground_pixel", "corner", NULL, "layer"}, /* pal_s5p_type_so2cbr */ @@ -276,8 +278,8 @@ static int read_dataset(coda_cursor cursor, return 0; } -static int read_array(coda_cursor cursor, - const char *path, harp_data_type data_type, long num_elements, harp_array data) +static int read_array(coda_cursor cursor, const char *path, harp_data_type data_type, long num_elements, + harp_array data) { long coda_num_elements; @@ -337,6 +339,46 @@ static int read_array(coda_cursor cursor, return 0; } +static int read_layer_bounds_from_levels(ingest_info *info, coda_cursor cursor, const char *path, harp_array data) +{ + long dimension[2]; + long num_layers; + long i, j; + + if (read_array(cursor, path, harp_type_float, info->num_scanlines * info->num_pixels * (info->num_layers + 1), + data) != 0) + { + return -1; + } + /* invert axis */ + dimension[0] = info->num_scanlines * info->num_pixels; + dimension[1] = info->num_layers + 1; + if (harp_array_invert(harp_type_float, 1, 2, dimension, data) != 0) + { + return -1; + } + + /* Convert from #levels (== #layers + 1) consecutive altitudes to #layers x 2 altitude bounds. Iterate in reverse to + * ensure correct results (conversion is performed in place). + */ + num_layers = info->num_layers; + + for (i = info->num_scanlines * info->num_pixels - 1; i >= 0; i--) + { + float *level = &data.float_data[i * (num_layers + 1)]; + float *layer_bounds = &data.float_data[i * num_layers * 2]; + + for (j = num_layers - 1; j >= 0; j--) + { + /* NB. The order of the following two lines is important to ensure correct results. */ + layer_bounds[j * 2 + 1] = level[j + 1]; + layer_bounds[j * 2] = level[j]; + } + } + + return 0; +} + static const char *get_product_type_name(pal_s5p_product_type product_type) { switch (product_type) @@ -349,6 +391,8 @@ static const char *get_product_type_name(pal_s5p_product_type product_type) return "L2__AER_OT"; case pal_s5p_type_chocho: return "L2__CHOCHO"; + case pal_s5p_type_hdo_s: + return "L2__HDO__S"; case pal_s5p_type_oclo: return "L2__OCLO__"; case pal_s5p_type_so2cbr: @@ -677,7 +721,8 @@ static int read_dimensions(void *user_data, long dimension[HARP_NUM_DIM_TYPES]) dimension[harp_dimension_spectral] = info->num_wavelengths; } - if ((info->product_type == pal_s5p_type_so2cbr) || (info->product_type == pal_s5p_type_tcwv)) + if ((info->product_type == pal_s5p_type_hdo_s) || (info->product_type == pal_s5p_type_so2cbr) || + (info->product_type == pal_s5p_type_tcwv)) { dimension[harp_dimension_vertical] = info->num_layers; } @@ -963,6 +1008,13 @@ static int read_input_aerosol_index_354_388(void *user_data, harp_array data) info->num_scanlines * info->num_pixels, data); } +static int read_input_altitude_bounds(void *user_data, harp_array data) +{ + ingest_info *info = (ingest_info *)user_data; + + return read_layer_bounds_from_levels(info, info->input_data_cursor, "altitude_levels", data); +} + static int read_input_cloud_albedo(void *user_data, harp_array data) { ingest_info *info = (ingest_info *)user_data; @@ -1091,6 +1143,13 @@ static int read_input_ozone_total_vertical_column_precision(void *user_data, har info->num_scanlines * info->num_pixels, data); } +static int read_input_pressure_bounds(void *user_data, harp_array data) +{ + ingest_info *info = (ingest_info *)user_data; + + return read_layer_bounds_from_levels(info, info->input_data_cursor, "pressure_levels", data); +} + static int read_input_surface_albedo(void *user_data, harp_array data) { ingest_info *info = (ingest_info *)user_data; @@ -1266,6 +1325,82 @@ static int read_product_glyoxal_tropospheric_vertical_column_precision(void *use info->num_scanlines * info->num_pixels, data); } +static int read_product_h2o_column(void *user_data, harp_array data) +{ + ingest_info *info = (ingest_info *)user_data; + + return read_array(info->product_cursor, "h2o_column", harp_type_float, info->num_scanlines * info->num_pixels, + data); +} + +static int read_product_h2o_column_precision(void *user_data, harp_array data) +{ + ingest_info *info = (ingest_info *)user_data; + + return read_array(info->product_cursor, "h2o_column_precision", harp_type_float, + info->num_scanlines * info->num_pixels, data); +} + +static int read_product_h2o_profile_apriori(void *user_data, harp_array data) +{ + ingest_info *info = (ingest_info *)user_data; + long dimension[2]; + + if (read_array(info->product_cursor, "h2o_profile_apriori", harp_type_float, + info->num_scanlines * info->num_pixels * info->num_layers, data) != 0) + { + return -1; + } + + /* invert axis */ + dimension[0] = info->num_scanlines * info->num_pixels; + dimension[1] = info->num_layers; + if (harp_array_invert(harp_type_float, 1, 2, dimension, data) != 0) + { + return -1; + } + + return 0; +} + +static int read_product_hdo_column(void *user_data, harp_array data) +{ + ingest_info *info = (ingest_info *)user_data; + + return read_array(info->product_cursor, "hdo_column", harp_type_float, info->num_scanlines * info->num_pixels, + data); +} + +static int read_product_hdo_column_precision(void *user_data, harp_array data) +{ + ingest_info *info = (ingest_info *)user_data; + + return read_array(info->product_cursor, "hdo_column_precision", harp_type_float, + info->num_scanlines * info->num_pixels, data); +} + +static int read_product_hdo_profile_apriori(void *user_data, harp_array data) +{ + ingest_info *info = (ingest_info *)user_data; + long dimension[2]; + + if (read_array(info->product_cursor, "hdo_profile_apriori", harp_type_float, + info->num_scanlines * info->num_pixels * info->num_layers, data) != 0) + { + return -1; + } + + /* invert axis */ + dimension[0] = info->num_scanlines * info->num_pixels; + dimension[1] = info->num_layers; + if (harp_array_invert(harp_type_float, 1, 2, dimension, data) != 0) + { + return -1; + } + + return 0; +} + static int read_product_latitude(void *user_data, harp_array data) { ingest_info *info = (ingest_info *)user_data; @@ -1560,6 +1695,50 @@ static int read_results_brominemonoxide_total_vertical_column_trueness(void *use variable_name, harp_type_float, info->num_scanlines * info->num_pixels, data); } +static int read_results_h2o_column_averaging_kernel(void *user_data, harp_array data) +{ + ingest_info *info = (ingest_info *)user_data; + long dimension[2]; + + if (read_array(info->detailed_results_cursor, "h2o_column_averaging_kernel", harp_type_float, + info->num_scanlines * info->num_pixels * info->num_layers, data) != 0) + { + return -1; + } + + /* invert axis */ + dimension[0] = info->num_scanlines * info->num_pixels; + dimension[1] = info->num_layers; + if (harp_array_invert(harp_type_float, 1, 2, dimension, data) != 0) + { + return -1; + } + + return 0; +} + +static int read_results_hdo_column_averaging_kernel(void *user_data, harp_array data) +{ + ingest_info *info = (ingest_info *)user_data; + long dimension[2]; + + if (read_array(info->detailed_results_cursor, "hdo_column_averaging_kernel", harp_type_float, + info->num_scanlines * info->num_pixels * info->num_layers, data) != 0) + { + return -1; + } + + /* invert axis */ + dimension[0] = info->num_scanlines * info->num_pixels; + dimension[1] = info->num_layers; + if (harp_array_invert(harp_type_float, 1, 2, dimension, data) != 0) + { + return -1; + } + + return 0; +} + static int read_results_water_vapor_profile_apriori(void *user_data, harp_array data) { ingest_info *info = (ingest_info *)user_data; @@ -3001,6 +3180,128 @@ static void register_chocho_product(void) harp_variable_definition_add_mapping(variable_definition, NULL, NULL, "/PRODUCT/qa_value", NULL); } +static void register_hdo_s_product(void) +{ + harp_ingestion_module *module; + harp_product_definition *product_definition; + const char *path; + const char *description; + harp_variable_definition *variable_definition; + + harp_dimension_type dimension_type[3] = { harp_dimension_time, harp_dimension_vertical, + harp_dimension_independent + }; + long dimension[3] = { -1, -1, 2 }; + + module = harp_ingestion_register_module("S5P_PAL_L2_HDO_S", "Sentinel-5P PAL", "S5P_PAL", "L2__HDO__S", + "Sentinel-5P L2 Heavy Water (HDO) product from SRON/SICOR algorithm", + ingestion_init, ingestion_done); + + product_definition = harp_ingestion_register_product(module, "S5P_PAL_L2_HDO_S", NULL, read_dimensions); + + register_common_variables(product_definition, 0); + + /* altitude_bounds */ + description = "altitude bounds per profile layer"; + variable_definition = + harp_ingestion_register_variable_full_read(product_definition, "altitude_bounds", harp_type_float, 3, + dimension_type, dimension, description, "m", NULL, + read_input_altitude_bounds); + path = "/PRODUCT/SUPPORT_DATA/INPUT_DATA/altitude_levels[]"; + description = "derived from height per level (layer boundary) by repeating the inner levels; the upper bound of " + "layer k is equal to the lower bound of layer k+1; the vertical grid is inverted to make it ascending"; + harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, description); + + /* pressure_bounds */ + description = "pressure bounds per profile layer"; + variable_definition = + harp_ingestion_register_variable_full_read(product_definition, "pressure_bounds", harp_type_float, 3, + dimension_type, dimension, description, "Pa", NULL, + read_input_pressure_bounds); + path = "/PRODUCT/SUPPORT_DATA/INPUT_DATA/pressure_levels[]"; + description = "derived from pressure per level (layer boundary) by repeating the inner levels; the upper bound of " + "layer k is equal to the lower bound of layer k+1; the vertical grid is inverted to make it ascending"; + harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, description); + + /* h2o_column_number_density */ + description = "Vertically integrated column of water"; + variable_definition = + harp_ingestion_register_variable_full_read(product_definition, "h2o_column_number_density", harp_type_float, 1, + dimension_type, NULL, description, "molec/cm2", NULL, + read_product_h2o_column); + path = "/PRODUCT/h2o_column"; + harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL); + + /* h2o_column_number_density_uncertainty */ + description = "Standard error of vertically integrated column of water"; + variable_definition = + harp_ingestion_register_variable_full_read(product_definition, "h2o_column_number_density_uncertainty", + harp_type_float, 1, dimension_type, NULL, description, "molec/cm2", + NULL, read_product_h2o_column_precision); + path = "/PRODUCT/h2o_column"; + harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL); + + /* h2o_column_number_density_apriori */ + description = "A-priori vertically integrated partial column of water in layers"; + variable_definition = + harp_ingestion_register_variable_full_read(product_definition, "h2o_column_number_density_apriori", + harp_type_float, 2, dimension_type, NULL, description, + "molec/cm2", NULL, read_product_h2o_profile_apriori); + path = "/PRODUCT/h2o_column"; + description = "the vertical grid is inverted to make it ascending"; + harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, description); + + /* h2o_column_number_density_avk */ + description = "Column averaging kernel of the vertically integrated column of water"; + variable_definition = + harp_ingestion_register_variable_full_read(product_definition, "h2o_column_number_density_avk", + harp_type_float, 2, dimension_type, NULL, description, + "(molec/cm2)/(molec/cm2)", NULL, + read_results_h2o_column_averaging_kernel); + path = "/PRODUCT/SUPPORT_DATA/DETAILED_RESULTS/h2o_column_averaging_kernel[]"; + description = "the vertical grid is inverted to make it ascending"; + harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, description); + + /* hdo_column_number_density */ + description = "Vertically integrated column of heavy water"; + variable_definition = + harp_ingestion_register_variable_full_read(product_definition, "hdo_column_number_density", harp_type_float, 1, + dimension_type, NULL, description, "molec/cm2", NULL, + read_product_hdo_column); + path = "/PRODUCT/hdo_column"; + harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL); + + /* hdo_column_number_density_uncertainty */ + description = "Standard error of vertically integrated column of heavy water"; + variable_definition = + harp_ingestion_register_variable_full_read(product_definition, "hdo_column_number_density_uncertainty", + harp_type_float, 1, dimension_type, NULL, description, "molec/cm2", + NULL, read_product_hdo_column_precision); + path = "/PRODUCT/hdo_column"; + harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, NULL); + + /* hdo_column_number_density_apriori */ + description = "A-priori vertically integrated partial column of heavy water in layers"; + variable_definition = + harp_ingestion_register_variable_full_read(product_definition, "hdo_column_number_density_apriori", + harp_type_float, 2, dimension_type, NULL, description, + "molec/cm2", NULL, read_product_hdo_profile_apriori); + path = "/PRODUCT/hdo_column"; + description = "the vertical grid is inverted to make it ascending"; + harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, description); + + /* hdo_column_number_density_avk */ + description = "Column averaging kernel of the vertically integrated column of heavy water"; + variable_definition = + harp_ingestion_register_variable_full_read(product_definition, "hdo_column_number_density_avk", + harp_type_float, 2, dimension_type, NULL, description, + "(molec/cm2)/(molec/cm2)", NULL, + read_results_hdo_column_averaging_kernel); + path = "/PRODUCT/SUPPORT_DATA/DETAILED_RESULTS/hdo_column_averaging_kernel[]"; + description = "the vertical grid is inverted to make it ascending"; + harp_variable_definition_add_mapping(variable_definition, NULL, NULL, path, description); +} + static void register_oclo_product(void) { harp_ingestion_module *module; @@ -3641,6 +3942,7 @@ int harp_ingestion_module_pal_s5p_l2_init(void) register_aer_ot_product(); register_bro_product(); register_chocho_product(); + register_hdo_s_product(); register_oclo_product(); register_sif_product(); register_so2cbr_product();