diff --git a/CHANGELOG.md b/CHANGELOG.md index 125bf72f4..ab25b0aad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Updated Link to obtain stac_io from owner root ([#762](https://github.com/stac-utils/pystac/pull/762)) - Replace test.com with special-use domain name. ([#769](https://github.com/stac-utils/pystac/pull/769)) - Updated AssetDefinition to have create, apply methods ([#768](https://github.com/stac-utils/pystac/pull/768)) +- Add Grid Extension support ([#799](https://github.com/stac-utils/pystac/pull/799)) ### Removed diff --git a/pystac/extensions/grid.py b/pystac/extensions/grid.py new file mode 100644 index 000000000..928897ebc --- /dev/null +++ b/pystac/extensions/grid.py @@ -0,0 +1,107 @@ +"""Implements the :stac-ext:`Grid Extension `.""" + +import re +from typing import Any, Dict, Optional, Pattern, Set, Union + +import pystac +from pystac.extensions.base import ExtensionManagementMixin, PropertiesExtension +from pystac.extensions.hooks import ExtensionHooks + +SCHEMA_URI: str = "https://stac-extensions.github.io/grid/v1.0.0/schema.json" +PREFIX: str = "grid:" + +# Field names +CODE_PROP: str = PREFIX + "code" # required + +CODE_REGEX: str = r"[A-Z]+-[-_.A-Za-z0-9]+" +CODE_PATTERN: Pattern[str] = re.compile(CODE_REGEX) + + +def validated_code(v: str) -> str: + if not isinstance(v, str): + raise ValueError("Invalid Grid code: must be str") + if not CODE_PATTERN.fullmatch(v): + raise ValueError( + f"Invalid Grid code: {v}" f" does not match the regex {CODE_REGEX}" + ) + return v + + +class GridExtension( + PropertiesExtension, + ExtensionManagementMixin[Union[pystac.Item, pystac.Collection]], +): + """A concrete implementation of :class:`GridExtension` on an :class:`~pystac.Item` + that extends the properties of the Item to include properties defined in the + :stac-ext:`Grid Extension `. + + This class should generally not be instantiated directly. Instead, call + :meth:`GridExtension.ext` on an :class:`~pystac.Item` to extend it. + + .. code-block:: python + + >>> item: pystac.Item = ... + >>> proj_ext = GridExtension.ext(item) + """ + + item: pystac.Item + """The :class:`~pystac.Item` being extended.""" + + properties: Dict[str, Any] + """The :class:`~pystac.Item` properties, including extension properties.""" + + def __init__(self, item: pystac.Item): + self.item = item + self.properties = item.properties + + def __repr__(self) -> str: + return "".format(self.item.id) + + def apply(self, code: str) -> None: + """Applies Grid extension properties to the extended Item. + + Args: + code : REQUIRED. The code of the Item's grid location. + """ + self.code = validated_code(code) + + @property + def code(self) -> Optional[str]: + """Get or sets the latitude band of the datasource.""" + return self._get_property(CODE_PROP, str) + + @code.setter + def code(self, v: str) -> None: + self._set_property(CODE_PROP, validated_code(v), pop_if_none=False) + + @classmethod + def get_schema_uri(cls) -> str: + return SCHEMA_URI + + @classmethod + def ext(cls, obj: pystac.Item, add_if_missing: bool = False) -> "GridExtension": + """Extends the given STAC Object with properties from the :stac-ext:`Grid + Extension `. + + This extension can be applied to instances of :class:`~pystac.Item`. + + Raises: + + pystac.ExtensionTypeError : If an invalid object type is passed. + """ + if isinstance(obj, pystac.Item): + cls.validate_has_extension(obj, add_if_missing) + return GridExtension(obj) + else: + raise pystac.ExtensionTypeError( + f"Grid Extension does not apply to type '{type(obj).__name__}'" + ) + + +class GridExtensionHooks(ExtensionHooks): + schema_uri: str = SCHEMA_URI + prev_extension_ids: Set[str] = set() + stac_object_types = {pystac.STACObjectType.ITEM} + + +Grid_EXTENSION_HOOKS: ExtensionHooks = GridExtensionHooks() diff --git a/setup.cfg b/setup.cfg index ff51dc013..36013b9a7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,9 @@ [metadata] version = attr: pystac.version.__version__ + +[tool:pytest] +minversion = 6.0 +addopts = -ra -q +testpaths = + tests +asyncio_mode = auto \ No newline at end of file diff --git a/tests/data-files/examples/1.0.0/example-sentinel2.json b/tests/data-files/examples/1.0.0/example-sentinel2.json new file mode 100644 index 000000000..7d6bf667e --- /dev/null +++ b/tests/data-files/examples/1.0.0/example-sentinel2.json @@ -0,0 +1,818 @@ +{ + "type": "Feature", + "stac_version": "1.0.0", + "id": "S2A_MSIL1C_20210908T042701_R133_T46RER_20210908T070248", + "properties": { + "providers": [ + { + "name": "ESA", + "roles": [ + "producer", + "processor", + "licensor" + ], + "url": "https://earth.esa.int/web/guest/home" + } + ], + "platform": "sentinel-2a", + "constellation": "sentinel-2", + "instruments": [ + "msi" + ], + "eo:cloud_cover": 88.2972, + "sat:orbit_state": "descending", + "sat:relative_orbit": 133, + "proj:epsg": 32646, + "mgrs:utm_zone": 46, + "mgrs:latitude_band": "R", + "mgrs:grid_square": "ER", + "grid:code": "MGRS-46RER", + "view:sun_azimuth": 142.987598836457, + "view:sun_elevation": 63.5068357330561, + "sentinel2:product_uri": "S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE", + "sentinel2:generation_time": "2021-09-08T07:02:48.000000Z", + "sentinel2:processing_baseline": "03.01", + "sentinel2:product_type": "S2MSI1C", + "sentinel2:datatake_id": "GS2A_20210908T042701_032448_N03.01", + "sentinel2:datatake_type": "INS-NOBS", + "sentinel2:datastrip_id": "S2A_OPER_MSI_L1C_DS_VGS4_20210908T070248_S20210908T043714_N03.01", + "sentinel2:granule_id": "S2A_OPER_MSI_L1C_TL_VGS4_20210908T070248_A032448_T46RER_N03.01", + "sentinel2:mgrs_tile": "46RER", + "sentinel2:reflectance_conversion_factor": 0.983841990384341, + "sentinel2:degraded_msi_data_percentage": 0.0, + "datetime": "2021-09-08T04:27:01.024000Z" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 93.43341763893636, + 28.023673957735582 + ], + [ + 93.40196468575007, + 27.9143684592852 + ], + [ + 93.36091831507485, + 27.766687360630367 + ], + [ + 93.31695738599002, + 27.619769241040945 + ], + [ + 93.2783823780312, + 27.471306867624268 + ], + [ + 93.23722635567444, + 27.32359772615637 + ], + [ + 93.19755659090528, + 27.17555811144318 + ], + [ + 93.16090624762064, + 27.033537745066344 + ], + [ + 92.99979835697575, + 27.03417096113827 + ], + [ + 92.99979654001324, + 28.025435531419042 + ], + [ + 93.43341763893636, + 28.023673957735582 + ] + ] + ] + }, + "links": [ + { + "rel": "license", + "href": "https://sentinel.esa.int/documents/247904/690755/Sentinel_Data_Legal_Notice" + }, + { + "rel": "self", + "href": "/var/folders/3q/jbg6x0zx3194zq6_2jbwygjw0000gn/T/tmp9ewihdpa/S2A_MSIL1C_20210908T042701_R133_T46RER_20210908T070248.json", + "type": "application/json" + } + ], + "assets": { + "coastal": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/GRANULE/L1C_T46RER_A032448_20210908T043714/IMG_DATA/T46RER_20210908T042701_B01.jp2", + "type": "image/jp2", + "title": "Coastal aerosol (band 1) - 60m", + "eo:bands": [ + { + "name": "coastal", + "common_name": "coastal", + "description": "Coastal aerosol (band 1)", + "center_wavelength": 0.443, + "full_width_half_max": 0.027 + } + ], + "gsd": 60, + "proj:shape": [ + 1830, + 1830 + ], + "proj:bbox": [ + 499980.0, + 2990220.0, + 609780.0, + 3100020.0 + ], + "proj:transform": [ + 60.0, + 0.0, + 499980.0, + 0.0, + -60.0, + 3100020.0 + ], + "raster:bands": [ + { + "nodata": 0, + "data_type": "uint16", + "bits_per_sample": 15, + "spatial_resolution": 60, + "unit": "none", + "scale": 0.0001, + "offset": 0 + } + ], + "roles": [ + "data" + ] + }, + "blue": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/GRANULE/L1C_T46RER_A032448_20210908T043714/IMG_DATA/T46RER_20210908T042701_B02.jp2", + "type": "image/jp2", + "title": "Blue (band 2) - 10m", + "eo:bands": [ + { + "name": "blue", + "common_name": "blue", + "description": "Blue (band 2)", + "center_wavelength": 0.49, + "full_width_half_max": 0.098 + } + ], + "gsd": 10, + "proj:shape": [ + 10980, + 10980 + ], + "proj:bbox": [ + 499980.0, + 2990220.0, + 609780.0, + 3100020.0 + ], + "proj:transform": [ + 10.0, + 0.0, + 499980.0, + 0.0, + -10.0, + 3100020.0 + ], + "raster:bands": [ + { + "nodata": 0, + "data_type": "uint16", + "bits_per_sample": 15, + "spatial_resolution": 10, + "unit": "none", + "scale": 0.0001, + "offset": 0 + } + ], + "roles": [ + "data" + ] + }, + "green": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/GRANULE/L1C_T46RER_A032448_20210908T043714/IMG_DATA/T46RER_20210908T042701_B03.jp2", + "type": "image/jp2", + "title": "Green (band 3) - 10m", + "eo:bands": [ + { + "name": "green", + "common_name": "green", + "description": "Green (band 3)", + "center_wavelength": 0.56, + "full_width_half_max": 0.045 + } + ], + "gsd": 10, + "proj:shape": [ + 10980, + 10980 + ], + "proj:bbox": [ + 499980.0, + 2990220.0, + 609780.0, + 3100020.0 + ], + "proj:transform": [ + 10.0, + 0.0, + 499980.0, + 0.0, + -10.0, + 3100020.0 + ], + "raster:bands": [ + { + "nodata": 0, + "data_type": "uint16", + "bits_per_sample": 15, + "spatial_resolution": 10, + "unit": "none", + "scale": 0.0001, + "offset": 0 + } + ], + "roles": [ + "data" + ] + }, + "red": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/GRANULE/L1C_T46RER_A032448_20210908T043714/IMG_DATA/T46RER_20210908T042701_B04.jp2", + "type": "image/jp2", + "title": "Red (band 4) - 10m", + "eo:bands": [ + { + "name": "red", + "common_name": "red", + "description": "Red (band 4)", + "center_wavelength": 0.665, + "full_width_half_max": 0.038 + } + ], + "gsd": 10, + "proj:shape": [ + 10980, + 10980 + ], + "proj:bbox": [ + 499980.0, + 2990220.0, + 609780.0, + 3100020.0 + ], + "proj:transform": [ + 10.0, + 0.0, + 499980.0, + 0.0, + -10.0, + 3100020.0 + ], + "raster:bands": [ + { + "nodata": 0, + "data_type": "uint16", + "bits_per_sample": 15, + "spatial_resolution": 10, + "unit": "none", + "scale": 0.0001, + "offset": 0 + } + ], + "roles": [ + "data" + ] + }, + "rededge1": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/GRANULE/L1C_T46RER_A032448_20210908T043714/IMG_DATA/T46RER_20210908T042701_B05.jp2", + "type": "image/jp2", + "title": "Red edge 1 (band 5) - 20m", + "eo:bands": [ + { + "name": "rededge1", + "common_name": "rededge", + "description": "Red edge 1 (band 5)", + "center_wavelength": 0.704, + "full_width_half_max": 0.019 + } + ], + "gsd": 20, + "proj:shape": [ + 5490, + 5490 + ], + "proj:bbox": [ + 499980.0, + 2990220.0, + 609780.0, + 3100020.0 + ], + "proj:transform": [ + 20.0, + 0.0, + 499980.0, + 0.0, + -20.0, + 3100020.0 + ], + "raster:bands": [ + { + "nodata": 0, + "data_type": "uint16", + "bits_per_sample": 15, + "spatial_resolution": 20, + "unit": "none", + "scale": 0.0001, + "offset": 0 + } + ], + "roles": [ + "data" + ] + }, + "rededge2": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/GRANULE/L1C_T46RER_A032448_20210908T043714/IMG_DATA/T46RER_20210908T042701_B06.jp2", + "type": "image/jp2", + "title": "Red edge 2 (band 6) - 20m", + "eo:bands": [ + { + "name": "rededge2", + "common_name": "rededge", + "description": "Red edge 2 (band 6)", + "center_wavelength": 0.74, + "full_width_half_max": 0.018 + } + ], + "gsd": 20, + "proj:shape": [ + 5490, + 5490 + ], + "proj:bbox": [ + 499980.0, + 2990220.0, + 609780.0, + 3100020.0 + ], + "proj:transform": [ + 20.0, + 0.0, + 499980.0, + 0.0, + -20.0, + 3100020.0 + ], + "raster:bands": [ + { + "nodata": 0, + "data_type": "uint16", + "bits_per_sample": 15, + "spatial_resolution": 20, + "unit": "none", + "scale": 0.0001, + "offset": 0 + } + ], + "roles": [ + "data" + ] + }, + "rededge3": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/GRANULE/L1C_T46RER_A032448_20210908T043714/IMG_DATA/T46RER_20210908T042701_B07.jp2", + "type": "image/jp2", + "title": "Red edge 3 (band 7) - 20m", + "eo:bands": [ + { + "name": "rededge3", + "common_name": "rededge", + "description": "Red edge 3 (band 7)", + "center_wavelength": 0.783, + "full_width_half_max": 0.028 + } + ], + "gsd": 20, + "proj:shape": [ + 5490, + 5490 + ], + "proj:bbox": [ + 499980.0, + 2990220.0, + 609780.0, + 3100020.0 + ], + "proj:transform": [ + 20.0, + 0.0, + 499980.0, + 0.0, + -20.0, + 3100020.0 + ], + "raster:bands": [ + { + "nodata": 0, + "data_type": "uint16", + "bits_per_sample": 15, + "spatial_resolution": 20, + "unit": "none", + "scale": 0.0001, + "offset": 0 + } + ], + "roles": [ + "data" + ] + }, + "nir": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/GRANULE/L1C_T46RER_A032448_20210908T043714/IMG_DATA/T46RER_20210908T042701_B08.jp2", + "type": "image/jp2", + "title": "NIR 1 (band 8) - 10m", + "eo:bands": [ + { + "name": "nir", + "common_name": "nir", + "description": "NIR 1 (band 8)", + "center_wavelength": 0.842, + "full_width_half_max": 0.145 + } + ], + "gsd": 10, + "proj:shape": [ + 10980, + 10980 + ], + "proj:bbox": [ + 499980.0, + 2990220.0, + 609780.0, + 3100020.0 + ], + "proj:transform": [ + 10.0, + 0.0, + 499980.0, + 0.0, + -10.0, + 3100020.0 + ], + "raster:bands": [ + { + "nodata": 0, + "data_type": "uint16", + "bits_per_sample": 15, + "spatial_resolution": 10, + "unit": "none", + "scale": 0.0001, + "offset": 0 + } + ], + "roles": [ + "data" + ] + }, + "nir08": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/GRANULE/L1C_T46RER_A032448_20210908T043714/IMG_DATA/T46RER_20210908T042701_B8A.jp2", + "type": "image/jp2", + "title": "NIR 2 (band 8A) - 20m", + "eo:bands": [ + { + "name": "nir08", + "common_name": "nir08", + "description": "NIR 2 (band 8A)", + "center_wavelength": 0.865, + "full_width_half_max": 0.033 + } + ], + "gsd": 20, + "proj:shape": [ + 5490, + 5490 + ], + "proj:bbox": [ + 499980.0, + 2990220.0, + 609780.0, + 3100020.0 + ], + "proj:transform": [ + 20.0, + 0.0, + 499980.0, + 0.0, + -20.0, + 3100020.0 + ], + "raster:bands": [ + { + "nodata": 0, + "data_type": "uint16", + "bits_per_sample": 15, + "spatial_resolution": 20, + "unit": "none", + "scale": 0.0001, + "offset": 0 + } + ], + "roles": [ + "data" + ] + }, + "nir09": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/GRANULE/L1C_T46RER_A032448_20210908T043714/IMG_DATA/T46RER_20210908T042701_B09.jp2", + "type": "image/jp2", + "title": "NIR 3 (band 9) - 60m", + "eo:bands": [ + { + "name": "nir09", + "common_name": "nir09", + "description": "NIR 3 (band 9)", + "center_wavelength": 0.945, + "full_width_half_max": 0.026 + } + ], + "gsd": 60, + "proj:shape": [ + 1830, + 1830 + ], + "proj:bbox": [ + 499980.0, + 2990220.0, + 609780.0, + 3100020.0 + ], + "proj:transform": [ + 60.0, + 0.0, + 499980.0, + 0.0, + -60.0, + 3100020.0 + ], + "raster:bands": [ + { + "nodata": 0, + "data_type": "uint16", + "bits_per_sample": 15, + "spatial_resolution": 60, + "unit": "none", + "scale": 0.0001, + "offset": 0 + } + ], + "roles": [ + "data" + ] + }, + "cirrus": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/GRANULE/L1C_T46RER_A032448_20210908T043714/IMG_DATA/T46RER_20210908T042701_B10.jp2", + "type": "image/jp2", + "title": "Cirrus (band 10) - 60m", + "eo:bands": [ + { + "name": "cirrus", + "common_name": "cirrus", + "description": "Cirrus (band 10)", + "center_wavelength": 1.3735, + "full_width_half_max": 0.075 + } + ], + "gsd": 60, + "proj:shape": [ + 1830, + 1830 + ], + "proj:bbox": [ + 499980.0, + 2990220.0, + 609780.0, + 3100020.0 + ], + "proj:transform": [ + 60.0, + 0.0, + 499980.0, + 0.0, + -60.0, + 3100020.0 + ], + "raster:bands": [ + { + "nodata": 0, + "data_type": "uint16", + "bits_per_sample": 15, + "spatial_resolution": 60, + "unit": "none", + "scale": 0.0001, + "offset": 0 + } + ], + "roles": [ + "data" + ] + }, + "swir16": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/GRANULE/L1C_T46RER_A032448_20210908T043714/IMG_DATA/T46RER_20210908T042701_B11.jp2", + "type": "image/jp2", + "title": "SWIR 1 (band 11) - 20m", + "eo:bands": [ + { + "name": "swir16", + "common_name": "swir16", + "description": "SWIR 1 (band 11)", + "center_wavelength": 1.61, + "full_width_half_max": 0.143 + } + ], + "gsd": 20, + "proj:shape": [ + 5490, + 5490 + ], + "proj:bbox": [ + 499980.0, + 2990220.0, + 609780.0, + 3100020.0 + ], + "proj:transform": [ + 20.0, + 0.0, + 499980.0, + 0.0, + -20.0, + 3100020.0 + ], + "raster:bands": [ + { + "nodata": 0, + "data_type": "uint16", + "bits_per_sample": 15, + "spatial_resolution": 20, + "unit": "none", + "scale": 0.0001, + "offset": 0 + } + ], + "roles": [ + "data" + ] + }, + "swir22": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/GRANULE/L1C_T46RER_A032448_20210908T043714/IMG_DATA/T46RER_20210908T042701_B12.jp2", + "type": "image/jp2", + "title": "SWIR 2 (band 12) - 20m", + "eo:bands": [ + { + "name": "swir22", + "common_name": "swir22", + "description": "SWIR 2 (band 12)", + "center_wavelength": 2.19, + "full_width_half_max": 0.242 + } + ], + "gsd": 20, + "proj:shape": [ + 5490, + 5490 + ], + "proj:bbox": [ + 499980.0, + 2990220.0, + 609780.0, + 3100020.0 + ], + "proj:transform": [ + 20.0, + 0.0, + 499980.0, + 0.0, + -20.0, + 3100020.0 + ], + "raster:bands": [ + { + "nodata": 0, + "data_type": "uint16", + "bits_per_sample": 15, + "spatial_resolution": 20, + "unit": "none", + "scale": 0.0001, + "offset": 0 + } + ], + "roles": [ + "data" + ] + }, + "visual": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/GRANULE/L1C_T46RER_A032448_20210908T043714/IMG_DATA/T46RER_20210908T042701_TCI.jp2", + "type": "image/jp2", + "title": "True color image", + "eo:bands": [ + { + "name": "red", + "common_name": "red", + "description": "Red (band 4)", + "center_wavelength": 0.665, + "full_width_half_max": 0.038 + }, + { + "name": "green", + "common_name": "green", + "description": "Green (band 3)", + "center_wavelength": 0.56, + "full_width_half_max": 0.045 + }, + { + "name": "blue", + "common_name": "blue", + "description": "Blue (band 2)", + "center_wavelength": 0.49, + "full_width_half_max": 0.098 + } + ], + "proj:shape": [ + 10980, + 10980 + ], + "proj:bbox": [ + 499980.0, + 2990220.0, + 609780.0, + 3100020.0 + ], + "proj:transform": [ + 10.0, + 0.0, + 499980.0, + 0.0, + -10.0, + 3100020.0 + ], + "roles": [ + "visual" + ] + }, + "safe_manifest": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/manifest.safe", + "type": "application/xml", + "roles": [ + "metadata" + ] + }, + "product_metadata": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/MTD_MSIL1C.xml", + "type": "application/xml", + "roles": [ + "metadata" + ] + }, + "granule_metadata": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/GRANULE/L1C_T46RER_A032448_20210908T043714/MTD_TL.xml", + "type": "application/xml", + "roles": [ + "metadata" + ] + }, + "inspire_metadata": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/INSPIRE.xml", + "type": "application/xml", + "roles": [ + "metadata" + ] + }, + "datastrip_metadata": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/DATASTRIP/DS_VGS4_20210908T070248_S20210908T043714/MTD_DS.xml", + "type": "application/xml", + "roles": [ + "metadata" + ] + } + }, + "bbox": [ + 92.99979654001324, + 27.033537745066344, + 93.43341763893636, + 28.025435531419042 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/sat/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json", + "https://stac-extensions.github.io/mgrs/v1.0.0/schema.json", + "https://stac-extensions.github.io/grid/v1.0.0/schema.json", + "https://stac-extensions.github.io/view/v1.0.0/schema.json" + ] +} \ No newline at end of file diff --git a/tests/data-files/examples/example-info.csv b/tests/data-files/examples/example-info.csv index 4e980338e..faf65b02a 100644 --- a/tests/data-files/examples/example-info.csv +++ b/tests/data-files/examples/example-info.csv @@ -119,6 +119,7 @@ "1.0.0/collection.json","Collection","1.0.0","https://stac-extensions.github.io/eo/v1.0.0/schema.json|https://stac-extensions.github.io/view/v1.0.0/schema.json" "1.0.0/collectionless-item.json","Feature","1.0.0","https://stac-extensions.github.io/eo/v1.0.0/schema.json|https://stac-extensions.github.io/view/v1.0.0/schema.json" "1.0.0/core-item.json","Feature","1.0.0","" +"1.0.0/example-sentinel2.json","Feature","1.0.0","https://stac-extensions.github.io/eo/v1.0.0/schema.json|https://stac-extensions.github.io/sat/v1.0.0/schema.json|https://stac-extensions.github.io/projection/v1.0.0/schema.json|https://stac-extensions.github.io/mgrs/v1.0.0/schema.json|https://stac-extensions.github.io/grid/v1.0.0/schema.json|https://stac-extensions.github.io/view/v1.0.0/schema.json" "1.0.0/extended-item.json","Feature","1.0.0","https://stac-extensions.github.io/eo/v1.0.0/schema.json|https://stac-extensions.github.io/projection/v1.0.0/schema.json|https://stac-extensions.github.io/scientific/v1.0.0/schema.json|https://stac-extensions.github.io/view/v1.0.0/schema.json|https://stac-extensions.github.io/remote-data/v1.0.0/schema.json" "1.0.0/extensions-collection/collection.json","Collection","1.0.0","" "1.0.0/extensions-collection/proj-example/proj-example.json","Feature","1.0.0","https://stac-extensions.github.io/eo/v1.0.0/schema.json|https://stac-extensions.github.io/projection/v1.0.0/schema.json" diff --git a/tests/data-files/grid/example-sentinel2.json b/tests/data-files/grid/example-sentinel2.json new file mode 100644 index 000000000..7d6bf667e --- /dev/null +++ b/tests/data-files/grid/example-sentinel2.json @@ -0,0 +1,818 @@ +{ + "type": "Feature", + "stac_version": "1.0.0", + "id": "S2A_MSIL1C_20210908T042701_R133_T46RER_20210908T070248", + "properties": { + "providers": [ + { + "name": "ESA", + "roles": [ + "producer", + "processor", + "licensor" + ], + "url": "https://earth.esa.int/web/guest/home" + } + ], + "platform": "sentinel-2a", + "constellation": "sentinel-2", + "instruments": [ + "msi" + ], + "eo:cloud_cover": 88.2972, + "sat:orbit_state": "descending", + "sat:relative_orbit": 133, + "proj:epsg": 32646, + "mgrs:utm_zone": 46, + "mgrs:latitude_band": "R", + "mgrs:grid_square": "ER", + "grid:code": "MGRS-46RER", + "view:sun_azimuth": 142.987598836457, + "view:sun_elevation": 63.5068357330561, + "sentinel2:product_uri": "S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE", + "sentinel2:generation_time": "2021-09-08T07:02:48.000000Z", + "sentinel2:processing_baseline": "03.01", + "sentinel2:product_type": "S2MSI1C", + "sentinel2:datatake_id": "GS2A_20210908T042701_032448_N03.01", + "sentinel2:datatake_type": "INS-NOBS", + "sentinel2:datastrip_id": "S2A_OPER_MSI_L1C_DS_VGS4_20210908T070248_S20210908T043714_N03.01", + "sentinel2:granule_id": "S2A_OPER_MSI_L1C_TL_VGS4_20210908T070248_A032448_T46RER_N03.01", + "sentinel2:mgrs_tile": "46RER", + "sentinel2:reflectance_conversion_factor": 0.983841990384341, + "sentinel2:degraded_msi_data_percentage": 0.0, + "datetime": "2021-09-08T04:27:01.024000Z" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 93.43341763893636, + 28.023673957735582 + ], + [ + 93.40196468575007, + 27.9143684592852 + ], + [ + 93.36091831507485, + 27.766687360630367 + ], + [ + 93.31695738599002, + 27.619769241040945 + ], + [ + 93.2783823780312, + 27.471306867624268 + ], + [ + 93.23722635567444, + 27.32359772615637 + ], + [ + 93.19755659090528, + 27.17555811144318 + ], + [ + 93.16090624762064, + 27.033537745066344 + ], + [ + 92.99979835697575, + 27.03417096113827 + ], + [ + 92.99979654001324, + 28.025435531419042 + ], + [ + 93.43341763893636, + 28.023673957735582 + ] + ] + ] + }, + "links": [ + { + "rel": "license", + "href": "https://sentinel.esa.int/documents/247904/690755/Sentinel_Data_Legal_Notice" + }, + { + "rel": "self", + "href": "/var/folders/3q/jbg6x0zx3194zq6_2jbwygjw0000gn/T/tmp9ewihdpa/S2A_MSIL1C_20210908T042701_R133_T46RER_20210908T070248.json", + "type": "application/json" + } + ], + "assets": { + "coastal": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/GRANULE/L1C_T46RER_A032448_20210908T043714/IMG_DATA/T46RER_20210908T042701_B01.jp2", + "type": "image/jp2", + "title": "Coastal aerosol (band 1) - 60m", + "eo:bands": [ + { + "name": "coastal", + "common_name": "coastal", + "description": "Coastal aerosol (band 1)", + "center_wavelength": 0.443, + "full_width_half_max": 0.027 + } + ], + "gsd": 60, + "proj:shape": [ + 1830, + 1830 + ], + "proj:bbox": [ + 499980.0, + 2990220.0, + 609780.0, + 3100020.0 + ], + "proj:transform": [ + 60.0, + 0.0, + 499980.0, + 0.0, + -60.0, + 3100020.0 + ], + "raster:bands": [ + { + "nodata": 0, + "data_type": "uint16", + "bits_per_sample": 15, + "spatial_resolution": 60, + "unit": "none", + "scale": 0.0001, + "offset": 0 + } + ], + "roles": [ + "data" + ] + }, + "blue": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/GRANULE/L1C_T46RER_A032448_20210908T043714/IMG_DATA/T46RER_20210908T042701_B02.jp2", + "type": "image/jp2", + "title": "Blue (band 2) - 10m", + "eo:bands": [ + { + "name": "blue", + "common_name": "blue", + "description": "Blue (band 2)", + "center_wavelength": 0.49, + "full_width_half_max": 0.098 + } + ], + "gsd": 10, + "proj:shape": [ + 10980, + 10980 + ], + "proj:bbox": [ + 499980.0, + 2990220.0, + 609780.0, + 3100020.0 + ], + "proj:transform": [ + 10.0, + 0.0, + 499980.0, + 0.0, + -10.0, + 3100020.0 + ], + "raster:bands": [ + { + "nodata": 0, + "data_type": "uint16", + "bits_per_sample": 15, + "spatial_resolution": 10, + "unit": "none", + "scale": 0.0001, + "offset": 0 + } + ], + "roles": [ + "data" + ] + }, + "green": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/GRANULE/L1C_T46RER_A032448_20210908T043714/IMG_DATA/T46RER_20210908T042701_B03.jp2", + "type": "image/jp2", + "title": "Green (band 3) - 10m", + "eo:bands": [ + { + "name": "green", + "common_name": "green", + "description": "Green (band 3)", + "center_wavelength": 0.56, + "full_width_half_max": 0.045 + } + ], + "gsd": 10, + "proj:shape": [ + 10980, + 10980 + ], + "proj:bbox": [ + 499980.0, + 2990220.0, + 609780.0, + 3100020.0 + ], + "proj:transform": [ + 10.0, + 0.0, + 499980.0, + 0.0, + -10.0, + 3100020.0 + ], + "raster:bands": [ + { + "nodata": 0, + "data_type": "uint16", + "bits_per_sample": 15, + "spatial_resolution": 10, + "unit": "none", + "scale": 0.0001, + "offset": 0 + } + ], + "roles": [ + "data" + ] + }, + "red": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/GRANULE/L1C_T46RER_A032448_20210908T043714/IMG_DATA/T46RER_20210908T042701_B04.jp2", + "type": "image/jp2", + "title": "Red (band 4) - 10m", + "eo:bands": [ + { + "name": "red", + "common_name": "red", + "description": "Red (band 4)", + "center_wavelength": 0.665, + "full_width_half_max": 0.038 + } + ], + "gsd": 10, + "proj:shape": [ + 10980, + 10980 + ], + "proj:bbox": [ + 499980.0, + 2990220.0, + 609780.0, + 3100020.0 + ], + "proj:transform": [ + 10.0, + 0.0, + 499980.0, + 0.0, + -10.0, + 3100020.0 + ], + "raster:bands": [ + { + "nodata": 0, + "data_type": "uint16", + "bits_per_sample": 15, + "spatial_resolution": 10, + "unit": "none", + "scale": 0.0001, + "offset": 0 + } + ], + "roles": [ + "data" + ] + }, + "rededge1": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/GRANULE/L1C_T46RER_A032448_20210908T043714/IMG_DATA/T46RER_20210908T042701_B05.jp2", + "type": "image/jp2", + "title": "Red edge 1 (band 5) - 20m", + "eo:bands": [ + { + "name": "rededge1", + "common_name": "rededge", + "description": "Red edge 1 (band 5)", + "center_wavelength": 0.704, + "full_width_half_max": 0.019 + } + ], + "gsd": 20, + "proj:shape": [ + 5490, + 5490 + ], + "proj:bbox": [ + 499980.0, + 2990220.0, + 609780.0, + 3100020.0 + ], + "proj:transform": [ + 20.0, + 0.0, + 499980.0, + 0.0, + -20.0, + 3100020.0 + ], + "raster:bands": [ + { + "nodata": 0, + "data_type": "uint16", + "bits_per_sample": 15, + "spatial_resolution": 20, + "unit": "none", + "scale": 0.0001, + "offset": 0 + } + ], + "roles": [ + "data" + ] + }, + "rededge2": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/GRANULE/L1C_T46RER_A032448_20210908T043714/IMG_DATA/T46RER_20210908T042701_B06.jp2", + "type": "image/jp2", + "title": "Red edge 2 (band 6) - 20m", + "eo:bands": [ + { + "name": "rededge2", + "common_name": "rededge", + "description": "Red edge 2 (band 6)", + "center_wavelength": 0.74, + "full_width_half_max": 0.018 + } + ], + "gsd": 20, + "proj:shape": [ + 5490, + 5490 + ], + "proj:bbox": [ + 499980.0, + 2990220.0, + 609780.0, + 3100020.0 + ], + "proj:transform": [ + 20.0, + 0.0, + 499980.0, + 0.0, + -20.0, + 3100020.0 + ], + "raster:bands": [ + { + "nodata": 0, + "data_type": "uint16", + "bits_per_sample": 15, + "spatial_resolution": 20, + "unit": "none", + "scale": 0.0001, + "offset": 0 + } + ], + "roles": [ + "data" + ] + }, + "rededge3": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/GRANULE/L1C_T46RER_A032448_20210908T043714/IMG_DATA/T46RER_20210908T042701_B07.jp2", + "type": "image/jp2", + "title": "Red edge 3 (band 7) - 20m", + "eo:bands": [ + { + "name": "rededge3", + "common_name": "rededge", + "description": "Red edge 3 (band 7)", + "center_wavelength": 0.783, + "full_width_half_max": 0.028 + } + ], + "gsd": 20, + "proj:shape": [ + 5490, + 5490 + ], + "proj:bbox": [ + 499980.0, + 2990220.0, + 609780.0, + 3100020.0 + ], + "proj:transform": [ + 20.0, + 0.0, + 499980.0, + 0.0, + -20.0, + 3100020.0 + ], + "raster:bands": [ + { + "nodata": 0, + "data_type": "uint16", + "bits_per_sample": 15, + "spatial_resolution": 20, + "unit": "none", + "scale": 0.0001, + "offset": 0 + } + ], + "roles": [ + "data" + ] + }, + "nir": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/GRANULE/L1C_T46RER_A032448_20210908T043714/IMG_DATA/T46RER_20210908T042701_B08.jp2", + "type": "image/jp2", + "title": "NIR 1 (band 8) - 10m", + "eo:bands": [ + { + "name": "nir", + "common_name": "nir", + "description": "NIR 1 (band 8)", + "center_wavelength": 0.842, + "full_width_half_max": 0.145 + } + ], + "gsd": 10, + "proj:shape": [ + 10980, + 10980 + ], + "proj:bbox": [ + 499980.0, + 2990220.0, + 609780.0, + 3100020.0 + ], + "proj:transform": [ + 10.0, + 0.0, + 499980.0, + 0.0, + -10.0, + 3100020.0 + ], + "raster:bands": [ + { + "nodata": 0, + "data_type": "uint16", + "bits_per_sample": 15, + "spatial_resolution": 10, + "unit": "none", + "scale": 0.0001, + "offset": 0 + } + ], + "roles": [ + "data" + ] + }, + "nir08": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/GRANULE/L1C_T46RER_A032448_20210908T043714/IMG_DATA/T46RER_20210908T042701_B8A.jp2", + "type": "image/jp2", + "title": "NIR 2 (band 8A) - 20m", + "eo:bands": [ + { + "name": "nir08", + "common_name": "nir08", + "description": "NIR 2 (band 8A)", + "center_wavelength": 0.865, + "full_width_half_max": 0.033 + } + ], + "gsd": 20, + "proj:shape": [ + 5490, + 5490 + ], + "proj:bbox": [ + 499980.0, + 2990220.0, + 609780.0, + 3100020.0 + ], + "proj:transform": [ + 20.0, + 0.0, + 499980.0, + 0.0, + -20.0, + 3100020.0 + ], + "raster:bands": [ + { + "nodata": 0, + "data_type": "uint16", + "bits_per_sample": 15, + "spatial_resolution": 20, + "unit": "none", + "scale": 0.0001, + "offset": 0 + } + ], + "roles": [ + "data" + ] + }, + "nir09": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/GRANULE/L1C_T46RER_A032448_20210908T043714/IMG_DATA/T46RER_20210908T042701_B09.jp2", + "type": "image/jp2", + "title": "NIR 3 (band 9) - 60m", + "eo:bands": [ + { + "name": "nir09", + "common_name": "nir09", + "description": "NIR 3 (band 9)", + "center_wavelength": 0.945, + "full_width_half_max": 0.026 + } + ], + "gsd": 60, + "proj:shape": [ + 1830, + 1830 + ], + "proj:bbox": [ + 499980.0, + 2990220.0, + 609780.0, + 3100020.0 + ], + "proj:transform": [ + 60.0, + 0.0, + 499980.0, + 0.0, + -60.0, + 3100020.0 + ], + "raster:bands": [ + { + "nodata": 0, + "data_type": "uint16", + "bits_per_sample": 15, + "spatial_resolution": 60, + "unit": "none", + "scale": 0.0001, + "offset": 0 + } + ], + "roles": [ + "data" + ] + }, + "cirrus": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/GRANULE/L1C_T46RER_A032448_20210908T043714/IMG_DATA/T46RER_20210908T042701_B10.jp2", + "type": "image/jp2", + "title": "Cirrus (band 10) - 60m", + "eo:bands": [ + { + "name": "cirrus", + "common_name": "cirrus", + "description": "Cirrus (band 10)", + "center_wavelength": 1.3735, + "full_width_half_max": 0.075 + } + ], + "gsd": 60, + "proj:shape": [ + 1830, + 1830 + ], + "proj:bbox": [ + 499980.0, + 2990220.0, + 609780.0, + 3100020.0 + ], + "proj:transform": [ + 60.0, + 0.0, + 499980.0, + 0.0, + -60.0, + 3100020.0 + ], + "raster:bands": [ + { + "nodata": 0, + "data_type": "uint16", + "bits_per_sample": 15, + "spatial_resolution": 60, + "unit": "none", + "scale": 0.0001, + "offset": 0 + } + ], + "roles": [ + "data" + ] + }, + "swir16": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/GRANULE/L1C_T46RER_A032448_20210908T043714/IMG_DATA/T46RER_20210908T042701_B11.jp2", + "type": "image/jp2", + "title": "SWIR 1 (band 11) - 20m", + "eo:bands": [ + { + "name": "swir16", + "common_name": "swir16", + "description": "SWIR 1 (band 11)", + "center_wavelength": 1.61, + "full_width_half_max": 0.143 + } + ], + "gsd": 20, + "proj:shape": [ + 5490, + 5490 + ], + "proj:bbox": [ + 499980.0, + 2990220.0, + 609780.0, + 3100020.0 + ], + "proj:transform": [ + 20.0, + 0.0, + 499980.0, + 0.0, + -20.0, + 3100020.0 + ], + "raster:bands": [ + { + "nodata": 0, + "data_type": "uint16", + "bits_per_sample": 15, + "spatial_resolution": 20, + "unit": "none", + "scale": 0.0001, + "offset": 0 + } + ], + "roles": [ + "data" + ] + }, + "swir22": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/GRANULE/L1C_T46RER_A032448_20210908T043714/IMG_DATA/T46RER_20210908T042701_B12.jp2", + "type": "image/jp2", + "title": "SWIR 2 (band 12) - 20m", + "eo:bands": [ + { + "name": "swir22", + "common_name": "swir22", + "description": "SWIR 2 (band 12)", + "center_wavelength": 2.19, + "full_width_half_max": 0.242 + } + ], + "gsd": 20, + "proj:shape": [ + 5490, + 5490 + ], + "proj:bbox": [ + 499980.0, + 2990220.0, + 609780.0, + 3100020.0 + ], + "proj:transform": [ + 20.0, + 0.0, + 499980.0, + 0.0, + -20.0, + 3100020.0 + ], + "raster:bands": [ + { + "nodata": 0, + "data_type": "uint16", + "bits_per_sample": 15, + "spatial_resolution": 20, + "unit": "none", + "scale": 0.0001, + "offset": 0 + } + ], + "roles": [ + "data" + ] + }, + "visual": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/GRANULE/L1C_T46RER_A032448_20210908T043714/IMG_DATA/T46RER_20210908T042701_TCI.jp2", + "type": "image/jp2", + "title": "True color image", + "eo:bands": [ + { + "name": "red", + "common_name": "red", + "description": "Red (band 4)", + "center_wavelength": 0.665, + "full_width_half_max": 0.038 + }, + { + "name": "green", + "common_name": "green", + "description": "Green (band 3)", + "center_wavelength": 0.56, + "full_width_half_max": 0.045 + }, + { + "name": "blue", + "common_name": "blue", + "description": "Blue (band 2)", + "center_wavelength": 0.49, + "full_width_half_max": 0.098 + } + ], + "proj:shape": [ + 10980, + 10980 + ], + "proj:bbox": [ + 499980.0, + 2990220.0, + 609780.0, + 3100020.0 + ], + "proj:transform": [ + 10.0, + 0.0, + 499980.0, + 0.0, + -10.0, + 3100020.0 + ], + "roles": [ + "visual" + ] + }, + "safe_manifest": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/manifest.safe", + "type": "application/xml", + "roles": [ + "metadata" + ] + }, + "product_metadata": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/MTD_MSIL1C.xml", + "type": "application/xml", + "roles": [ + "metadata" + ] + }, + "granule_metadata": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/GRANULE/L1C_T46RER_A032448_20210908T043714/MTD_TL.xml", + "type": "application/xml", + "roles": [ + "metadata" + ] + }, + "inspire_metadata": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/INSPIRE.xml", + "type": "application/xml", + "roles": [ + "metadata" + ] + }, + "datastrip_metadata": { + "href": "/Users/philvarner/code/sentinel2/tests/data-files/S2A_MSIL1C_20210908T042701_N0301_R133_T46RER_20210908T070248.SAFE/DATASTRIP/DS_VGS4_20210908T070248_S20210908T043714/MTD_DS.xml", + "type": "application/xml", + "roles": [ + "metadata" + ] + } + }, + "bbox": [ + 92.99979654001324, + 27.033537745066344, + 93.43341763893636, + 28.025435531419042 + ], + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/sat/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json", + "https://stac-extensions.github.io/mgrs/v1.0.0/schema.json", + "https://stac-extensions.github.io/grid/v1.0.0/schema.json", + "https://stac-extensions.github.io/view/v1.0.0/schema.json" + ] +} \ No newline at end of file diff --git a/tests/extensions/test_grid.py b/tests/extensions/test_grid.py new file mode 100644 index 000000000..e2edb9610 --- /dev/null +++ b/tests/extensions/test_grid.py @@ -0,0 +1,122 @@ +"""Tests for pystac.extensions.grid.""" + +import datetime +from typing import Any, Dict +import unittest + +import pystac +from pystac import ExtensionTypeError +from pystac.extensions import grid +from pystac.extensions.grid import GridExtension +from tests.utils import TestCases + +code = "MGRS-4CFJ" + + +def make_item() -> pystac.Item: + """Create basic test items that are only slightly different.""" + asset_id = "an/asset" + start = datetime.datetime(2018, 1, 2) + item = pystac.Item( + id=asset_id, geometry=None, bbox=None, datetime=start, properties={} + ) + + GridExtension.add_to(item) + return item + + +class GridTest(unittest.TestCase): + def setUp(self) -> None: + super().setUp() + self.item = make_item() + self.sentinel_example_uri = TestCases.get_path( + "data-files/grid/example-sentinel2.json" + ) + + def test_stac_extensions(self) -> None: + self.assertTrue(GridExtension.has_extension(self.item)) + + def test_item_repr(self) -> None: + grid_item_ext = GridExtension.ext(self.item) + self.assertEqual( + f"", grid_item_ext.__repr__() + ) + + def test_attributes(self) -> None: + GridExtension.ext(self.item).apply(code) + self.assertEqual(code, GridExtension.ext(self.item).code) + self.item.validate() + + def test_invalid_code_value(self) -> None: + with self.assertRaises(ValueError): + GridExtension.ext(self.item).apply("not_a_valid_code") + + def test_modify(self) -> None: + GridExtension.ext(self.item).apply(code) + GridExtension.ext(self.item).apply(code + "a") + self.assertEqual(code + "a", GridExtension.ext(self.item).code) + self.item.validate() + + def test_from_dict(self) -> None: + d: Dict[str, Any] = { + "type": "Feature", + "stac_version": "1.0.0", + "id": "an/asset", + "properties": { + "grid:code": code, + "datetime": "2018-01-02T00:00:00Z", + }, + "geometry": None, + "links": [], + "assets": {}, + "stac_extensions": [GridExtension.get_schema_uri()], + } + item = pystac.Item.from_dict(d) + self.assertEqual(code, GridExtension.ext(item).code) + + def test_to_from_dict(self) -> None: + GridExtension.ext(self.item).apply(code) + d = self.item.to_dict() + self.assertEqual(code, d["properties"][grid.CODE_PROP]) + + item = pystac.Item.from_dict(d) + self.assertEqual(code, GridExtension.ext(item).code) + + def test_clear_code(self) -> None: + GridExtension.ext(self.item).apply(code) + + with self.assertRaises(ValueError): + GridExtension.ext(self.item).code = None + + def test_extension_not_implemented(self) -> None: + # Should raise exception if Item does not include extension URI + item = pystac.Item.from_file(self.sentinel_example_uri) + item.stac_extensions.remove(GridExtension.get_schema_uri()) + + with self.assertRaises(pystac.ExtensionNotImplemented): + _ = GridExtension.ext(item) + + # Should raise exception if owning Item does not include extension URI + item.properties["grid:code"] = None + + with self.assertRaises(pystac.ExtensionNotImplemented): + _ = GridExtension.ext(item) + + def test_item_ext_add_to(self) -> None: + item = pystac.Item.from_file(self.sentinel_example_uri) + item.stac_extensions.remove(GridExtension.get_schema_uri()) + self.assertNotIn(GridExtension.get_schema_uri(), item.stac_extensions) + + _ = GridExtension.ext(item, add_if_missing=True) + + self.assertIn(GridExtension.get_schema_uri(), item.stac_extensions) + + def test_should_raise_exception_when_passing_invalid_extension_object( + self, + ) -> None: + self.assertRaisesRegex( + ExtensionTypeError, + r"^Grid Extension does not apply to type 'object'$", + GridExtension.ext, + object(), + )