|
| 1 | +import os |
| 2 | + |
| 3 | +import pystac |
| 4 | +from pystac.extensions.eo import EOExtension |
| 5 | +from pystac.extensions.sat import OrbitState, SatExtension |
| 6 | +from pystac.extensions.timestamps import TimestampsExtension |
| 7 | +from pystac.utils import now_in_utc, str_to_datetime |
| 8 | + |
| 9 | +from eopf_stac.common.constants import ( |
| 10 | + EOPF_EXTENSION_SCHEMA_URI, |
| 11 | + PROCESSING_EXTENSION_SCHEMA_URI, |
| 12 | + PRODUCT_EXTENSION_SCHEMA_URI, |
| 13 | +) |
| 14 | + |
| 15 | + |
| 16 | +def validate_metadata(metadata: dict) -> dict: |
| 17 | + stac_discovery = metadata.get("metadata", {}).get(".zattrs", {}).get("stac_discovery") |
| 18 | + if stac_discovery is None: |
| 19 | + raise ValueError("JSON object 'stac_discovery' not found in .zmetadata file") |
| 20 | + |
| 21 | + other_metadata = metadata.get("metadata", {}).get(".zattrs", {}).get("other_metadata") |
| 22 | + if other_metadata is None: |
| 23 | + raise ValueError("JSON object 'other_metadata' not found in .zmetadata file") |
| 24 | + |
| 25 | + return metadata["metadata"] |
| 26 | + |
| 27 | + |
| 28 | +def rearrange_bbox(bbox): |
| 29 | + longitudes = [bbox[0], bbox[2]] |
| 30 | + latitudes = [bbox[1], bbox[3]] |
| 31 | + |
| 32 | + corrected_bbox = [min(longitudes), min(latitudes), max(longitudes), max(latitudes)] |
| 33 | + return corrected_bbox |
| 34 | + |
| 35 | + |
| 36 | +def get_identifier(stac_discovery: dict): |
| 37 | + item_id = stac_discovery.get("id") |
| 38 | + # CPM workaround for https://gitlab.eopf.copernicus.eu/cpm/eopf-cpm/-/issues/690 |
| 39 | + if item_id.lower().endswith(".safe") or item_id.lower().endswith(".sen3"): |
| 40 | + item_id = os.path.splitext(item_id)[0] |
| 41 | + return item_id |
| 42 | + |
| 43 | + |
| 44 | +def get_datetimes(properties: dict): |
| 45 | + datetime = None |
| 46 | + start_datetime = None |
| 47 | + end_datetime = None |
| 48 | + datetime_str = properties.get("datetime") |
| 49 | + if datetime_str is not None: |
| 50 | + # CPM workaround for https://gitlab.eopf.copernicus.eu/cpm/eopf-cpm/-/issues/643 |
| 51 | + if datetime_str == "null": |
| 52 | + datetime = None |
| 53 | + else: |
| 54 | + datetime = str_to_datetime(datetime_str) |
| 55 | + |
| 56 | + if datetime is None: |
| 57 | + # start_datetime and end_datetime must be supplied |
| 58 | + start_datetime_str = properties.get("start_datetime") |
| 59 | + if start_datetime_str is not None: |
| 60 | + start_datetime = str_to_datetime(start_datetime_str) |
| 61 | + datetime = start_datetime |
| 62 | + end_datetime_str = properties.get("end_datetime") |
| 63 | + if end_datetime_str is not None: |
| 64 | + end_datetime = str_to_datetime(end_datetime_str) |
| 65 | + |
| 66 | + return (datetime, start_datetime, end_datetime) |
| 67 | + |
| 68 | + |
| 69 | +def fill_timestamp_properties(item: pystac.Item, properties: dict) -> None: |
| 70 | + created_datetime = properties.get("created") |
| 71 | + if created_datetime is None: |
| 72 | + created_datetime = now_in_utc() |
| 73 | + else: |
| 74 | + created_datetime = str_to_datetime(created_datetime) |
| 75 | + item.common_metadata.created = created_datetime |
| 76 | + item.common_metadata.updated = created_datetime |
| 77 | + |
| 78 | + ts_ext = TimestampsExtension.ext(item, add_if_missing=True) |
| 79 | + ts_ext.apply(published=created_datetime) |
| 80 | + |
| 81 | + |
| 82 | +def fill_sat_properties(item: pystac.Item, properties: dict) -> None: |
| 83 | + orbit_state = properties.get("sat:orbit_state") |
| 84 | + abs_orbit = properties.get("sat:absolute_orbit") |
| 85 | + rel_orbit = properties.get("sat:relative_orbit") |
| 86 | + anx_datetime = properties.get("sat:anx_datetime") |
| 87 | + platform_international_designator = properties.get("sat:platform_international_designator") |
| 88 | + |
| 89 | + if any_not_none([orbit_state, abs_orbit, rel_orbit, anx_datetime, platform_international_designator]): |
| 90 | + sat_ext = SatExtension.ext(item, add_if_missing=True) |
| 91 | + if orbit_state: |
| 92 | + sat_ext.orbit_state = OrbitState(orbit_state.lower()) |
| 93 | + if abs_orbit: |
| 94 | + sat_ext.absolute_orbit = int(abs_orbit) |
| 95 | + if rel_orbit: |
| 96 | + sat_ext.relative_orbit = int(rel_orbit) |
| 97 | + if anx_datetime: |
| 98 | + sat_ext.anx_datetime = str_to_datetime(anx_datetime) |
| 99 | + if platform_international_designator: |
| 100 | + sat_ext.platform_international_designator = platform_international_designator |
| 101 | + |
| 102 | + |
| 103 | +def fill_eo_properties(item: pystac.Item, properties: dict) -> None: |
| 104 | + cloud_cover = properties.get("eo:cloud_cover") |
| 105 | + snow_cover = properties.get("eo:snow_cover") |
| 106 | + |
| 107 | + if any_not_none([cloud_cover, snow_cover]): |
| 108 | + eo = EOExtension.ext(item, add_if_missing=True) |
| 109 | + if cloud_cover is not None: |
| 110 | + eo.cloud_cover = cloud_cover |
| 111 | + if snow_cover is not None: |
| 112 | + eo.snow_cover = snow_cover |
| 113 | + |
| 114 | + |
| 115 | +def fill_processing_properties(item: pystac.Item, properties: dict) -> None: |
| 116 | + # CPM workaround: following invalid values are ignored: |
| 117 | + # "processing:expression": "systematic", |
| 118 | + # "processing:facility": "OPE,OPE,OPE", |
| 119 | + # "processing:version": "", |
| 120 | + |
| 121 | + proc_expression = properties.get("processing:expression") |
| 122 | + proc_lineage = properties.get("processing:lineage") |
| 123 | + proc_level = properties.get("processing:level") |
| 124 | + proc_facility = properties.get("processing:facility") |
| 125 | + proc_datetime = properties.get("processing:datetime") |
| 126 | + proc_version = properties.get("processing:version") |
| 127 | + proc_software = properties.get("processing:software") |
| 128 | + if any_not_none( |
| 129 | + [proc_expression, proc_facility, proc_level, proc_lineage, proc_software, proc_datetime, proc_version] |
| 130 | + ): |
| 131 | + item.stac_extensions.append(PROCESSING_EXTENSION_SCHEMA_URI) |
| 132 | + if proc_expression is not None and proc_expression != "systematic": |
| 133 | + item.properties["processing:expression"] = proc_expression |
| 134 | + if proc_software is not None: |
| 135 | + item.properties["processing:software"] = proc_software |
| 136 | + if proc_datetime is not None: |
| 137 | + item.properties["processing:datetime"] = proc_datetime |
| 138 | + if is_valid_string(proc_facility) and proc_facility != "OPE,OPE,OPE": |
| 139 | + item.properties["processing:facility"] = proc_facility |
| 140 | + if is_valid_string(proc_level): |
| 141 | + item.properties["processing:level"] = proc_level |
| 142 | + if is_valid_string(proc_lineage): |
| 143 | + item.properties["processing:lineage"] = proc_lineage |
| 144 | + if is_valid_string(proc_version): |
| 145 | + item.properties["processing:version"] = proc_version |
| 146 | + |
| 147 | + |
| 148 | +def fill_product_properties(item: pystac.Item, product_type: str, properties: dict) -> None: |
| 149 | + product_timeliness = properties.get("product:timeliness") |
| 150 | + product_timeliness_category = properties.get("product:timeliness_category") |
| 151 | + product_acquisition_type = properties.get("product:acquisition_type") |
| 152 | + if any([product_type, product_acquisition_type, all([product_timeliness, product_timeliness_category])]): |
| 153 | + item.stac_extensions.append(PRODUCT_EXTENSION_SCHEMA_URI) |
| 154 | + if is_valid_string(product_type): |
| 155 | + item.properties["product:type"] = product_type |
| 156 | + if is_valid_string(product_acquisition_type): |
| 157 | + item.properties["product:acquisition_type"] = product_acquisition_type |
| 158 | + if all([is_valid_string(product_timeliness), is_valid_string(product_timeliness_category)]): |
| 159 | + # CPM workaround for https://gitlab.eopf.copernicus.eu/cpm/eopf-cpm/-/issues/706 |
| 160 | + if product_timeliness != "MISSING": |
| 161 | + item.properties["product:timeliness"] = product_timeliness |
| 162 | + item.properties["product:timeliness_category"] = product_timeliness_category |
| 163 | + |
| 164 | + |
| 165 | +def fill_eopf_properties(item: pystac.Item, properties: dict) -> None: |
| 166 | + """Fills the item with values of the EOPF STAC extension |
| 167 | + See also: https://github.com/CS-SI/eopf-stac-extension |
| 168 | + """ |
| 169 | + datatake_id = properties.get("eopf:datatake_id") |
| 170 | + # CPM workaround for https://gitlab.eopf.copernicus.eu/cpm/eopf-cpm/-/issues/689 |
| 171 | + if datatake_id is None: |
| 172 | + datatake_id = properties.get("eopf:data_take_id") |
| 173 | + datastrip_id = properties.get("eopf:datastrip_id") |
| 174 | + instrument_mode = properties.get("eopf:instrument_mode") |
| 175 | + origin_datetime = properties.get("eopf:origin_datetime") |
| 176 | + instrument_configuration_id = properties.get("eopf:instrument_configuration_id") |
| 177 | + |
| 178 | + if any_not_none( |
| 179 | + [ |
| 180 | + datatake_id, |
| 181 | + datastrip_id, |
| 182 | + instrument_mode, |
| 183 | + origin_datetime, |
| 184 | + instrument_configuration_id, |
| 185 | + ] |
| 186 | + ): |
| 187 | + item.stac_extensions.append(EOPF_EXTENSION_SCHEMA_URI) |
| 188 | + if is_valid_string(datatake_id): |
| 189 | + item.properties["eopf:datatake_id"] = datatake_id |
| 190 | + if is_valid_string(instrument_mode): |
| 191 | + # CPM workaround |
| 192 | + if instrument_mode != "Earth Observation": |
| 193 | + item.properties["eopf:instrument_mode"] = instrument_mode |
| 194 | + if origin_datetime: |
| 195 | + item.properties["eopf:origin_datetime"] = origin_datetime |
| 196 | + if is_valid_string(datastrip_id): |
| 197 | + item.properties["eopf:datastrip_id"] = datastrip_id |
| 198 | + if instrument_configuration_id is not None: |
| 199 | + item.properties["eopf:instrument_configuration_id"] = instrument_configuration_id |
| 200 | + |
| 201 | + |
| 202 | +def is_valid_string(value: str) -> bool: |
| 203 | + return value is not None and len(value) > 0 |
| 204 | + |
| 205 | + |
| 206 | +def any_not_none(values: list) -> bool: |
| 207 | + for v in values: |
| 208 | + if v is not None: |
| 209 | + return True |
0 commit comments