def test_item_ext_add_to(self) -> None: item = pystac.Item.from_file(self.example_uri) item.stac_extensions.remove(ViewExtension.get_schema_uri()) self.assertNotIn(ViewExtension.get_schema_uri(), item.stac_extensions) _ = ViewExtension.ext(item, add_if_missing=True) self.assertIn(ViewExtension.get_schema_uri(), item.stac_extensions)
def test_migrates_added_extension(self) -> None: item = pystac.Item.from_file( TestCases.get_path("data-files/examples/0.8.1/item-spec/" "examples/planet-sample.json")) self.assertTrue(ViewExtension.has_extension(item)) view_ext = ViewExtension.ext(item) self.assertEqual(view_ext.sun_azimuth, 101.8) self.assertEqual(view_ext.sun_elevation, 58.8) self.assertEqual(view_ext.off_nadir, 1)
def test_azimuth(self) -> None: view_item = pystac.Item.from_file(self.example_uri) # Get self.assertIn("view:azimuth", view_item.properties) view_azimuth = ViewExtension.ext(view_item).azimuth assert view_azimuth is not None self.assertEqual(view_azimuth, view_item.properties["view:azimuth"]) # Set ViewExtension.ext(view_item).azimuth = view_azimuth + 100 self.assertEqual(view_azimuth + 100, view_item.properties["view:azimuth"]) # Get from Asset asset_no_prop = view_item.assets["blue"] asset_prop = view_item.assets["red"] self.assertEqual( ViewExtension.ext(asset_no_prop).azimuth, ViewExtension.ext(view_item).azimuth, ) self.assertEqual(ViewExtension.ext(asset_prop).azimuth, 5.0) # Set to Asset asset_value = 15.0 ViewExtension.ext(asset_no_prop).azimuth = asset_value self.assertNotEqual( ViewExtension.ext(asset_no_prop).azimuth, ViewExtension.ext(view_item).azimuth, ) self.assertEqual(ViewExtension.ext(asset_no_prop).azimuth, asset_value) # Validate view_item.validate()
def process_view_geometry(item, granule): view_extension = ViewExtension.ext(item, add_if_missing=True) for attribute in granule.AdditionalAttributes.AdditionalAttribute: if attribute.Name == "MEAN_SUN_AZIMUTH_ANGLE": view_extension.sun_azimuth = float(attribute.Values.Value.cdata) if attribute.Name == "MEAN_VIEW_AZIMUTH_ANGLE": view_extension.azimuth = float(attribute.Values.Value.cdata)
def test_extension_not_implemented(self) -> None: # Should raise exception if Item does not include extension URI item = pystac.Item.from_file(self.example_uri) item.stac_extensions.remove(ViewExtension.get_schema_uri()) with self.assertRaises(pystac.ExtensionNotImplemented): _ = ViewExtension.ext(item) # Should raise exception if owning Item does not include extension URI asset = item.assets["blue"] with self.assertRaises(pystac.ExtensionNotImplemented): _ = ViewExtension.ext(asset) # Should succeed if Asset has no owner ownerless_asset = pystac.Asset.from_dict(asset.to_dict()) _ = ViewExtension.ext(ownerless_asset)
def test_set_sun_elevation_summaries(self) -> None: collection = self.collection.clone() view_summaries = ViewExtension.summaries(collection, True) view_summaries.sun_elevation = RangeSummary(-10, 38) self.assertDictEqual({ "minimum": -10, "maximum": 38 }, view_summaries.sun_elevation.to_dict())
def test_set_incidence_angle_summaries(self) -> None: collection = self.collection.clone() view_summaries = ViewExtension.summaries(collection, True) view_summaries.incidence_angle = RangeSummary(5, 15) self.assertDictEqual({ "minimum": 5, "maximum": 15 }, view_summaries.incidence_angle.to_dict())
def test_set_off_nadir_summaries(self) -> None: collection = self.collection.clone() view_summaries = ViewExtension.summaries(collection, True) view_summaries.off_nadir = RangeSummary(0, 10) self.assertDictEqual({ "minimum": 0, "maximum": 10 }, view_summaries.off_nadir.to_dict())
def test_get_azimuth_summaries(self) -> None: azimuths = ViewExtension.summaries(self.collection, True).azimuth assert azimuths is not None self.assertDictEqual({ "minimum": 20, "maximum": 186 }, azimuths.to_dict())
def test_get_off_nadir_summaries(self) -> None: off_nadirs = ViewExtension.summaries(self.collection, True).off_nadir assert off_nadirs is not None self.assertDictEqual({ "minimum": 0.5, "maximum": 7.3 }, off_nadirs.to_dict())
def test_apply(self) -> None: item = next(iter(TestCases.test_case_2().get_all_items())) self.assertFalse(ViewExtension.has_extension(item)) ViewExtension.add_to(item) ViewExtension.ext(item).apply( off_nadir=1.0, incidence_angle=2.0, azimuth=3.0, sun_azimuth=4.0, sun_elevation=5.0, ) self.assertEqual(ViewExtension.ext(item).off_nadir, 1.0) self.assertEqual(ViewExtension.ext(item).incidence_angle, 2.0) self.assertEqual(ViewExtension.ext(item).azimuth, 3.0) self.assertEqual(ViewExtension.ext(item).sun_azimuth, 4.0) self.assertEqual(ViewExtension.ext(item).sun_elevation, 5.0)
def test_summaries_adds_uri(self) -> None: collection = self.collection.clone() collection.stac_extensions = [] self.assertRaisesRegex( pystac.ExtensionNotImplemented, r"Could not find extension schema URI.*", ViewExtension.summaries, collection, False, ) _ = ViewExtension.summaries(collection, True) self.assertIn(ViewExtension.get_schema_uri(), collection.stac_extensions) ViewExtension.remove_from(collection) self.assertNotIn(ViewExtension.get_schema_uri(), collection.stac_extensions)
def test_set_sun_azimuth_summaries(self) -> None: collection = self.collection.clone() view_summaries = ViewExtension.summaries(collection, True) view_summaries.sun_azimuth = RangeSummary(210, 275) self.assertDictEqual({ "minimum": 210, "maximum": 275 }, view_summaries.sun_azimuth.to_dict())
def test_get_sun_azimuth_summaries(self) -> None: sun_azimuths = ViewExtension.summaries(self.collection, True).sun_azimuth assert sun_azimuths is not None self.assertDictEqual({ "minimum": 48, "maximum": 78 }, sun_azimuths.to_dict())
def test_get_incidence_angle_summaries(self) -> None: incidence_angles = ViewExtension.summaries(self.collection, True).incidence_angle assert incidence_angles is not None self.assertDictEqual({ "minimum": 23, "maximum": 35 }, incidence_angles.to_dict())
def test_get_sun_elevation_summaries(self) -> None: sun_elevations = ViewExtension.summaries(self.collection, True).sun_elevation assert sun_elevations is not None self.assertDictEqual({ "minimum": 10, "maximum": 45 }, sun_elevations.to_dict())
def test_incidence_angle(self) -> None: view_item = pystac.Item.from_file(self.example_uri) # Get self.assertIn("view:incidence_angle", view_item.properties) view_incidence_angle = ViewExtension.ext(view_item).incidence_angle assert view_incidence_angle is not None self.assertEqual(view_incidence_angle, view_item.properties["view:incidence_angle"]) # Set ViewExtension.ext( view_item).incidence_angle = view_incidence_angle + 10 self.assertEqual(view_incidence_angle + 10, view_item.properties["view:incidence_angle"]) # Get from Asset asset_no_prop = view_item.assets["blue"] asset_prop = view_item.assets["red"] self.assertEqual( ViewExtension.ext(asset_no_prop).incidence_angle, ViewExtension.ext(view_item).incidence_angle, ) self.assertEqual(ViewExtension.ext(asset_prop).incidence_angle, 4.0) # Set to Asset asset_value = 14.0 ViewExtension.ext(asset_no_prop).incidence_angle = asset_value self.assertNotEqual( ViewExtension.ext(asset_no_prop).incidence_angle, ViewExtension.ext(view_item).incidence_angle, ) self.assertEqual( ViewExtension.ext(asset_no_prop).incidence_angle, asset_value) # Validate view_item.validate()
def test_off_nadir(self) -> None: view_item = pystac.Item.from_file(self.example_uri) # Get self.assertIn("view:off_nadir", view_item.properties) view_off_nadir = ViewExtension.ext(view_item).off_nadir assert view_off_nadir is not None self.assertEqual(view_off_nadir, view_item.properties["view:off_nadir"]) # Set ViewExtension.ext(view_item).off_nadir = view_off_nadir + 10 self.assertEqual(view_off_nadir + 10, view_item.properties["view:off_nadir"]) # Get from Asset asset_no_prop = view_item.assets["blue"] asset_prop = view_item.assets["red"] self.assertEqual( ViewExtension.ext(asset_no_prop).off_nadir, ViewExtension.ext(view_item).off_nadir, ) self.assertEqual(ViewExtension.ext(asset_prop).off_nadir, 3.0) # Set to Asset asset_value = 13.0 ViewExtension.ext(asset_no_prop).off_nadir = asset_value self.assertNotEqual( ViewExtension.ext(asset_no_prop).off_nadir, ViewExtension.ext(view_item).off_nadir, ) self.assertEqual( ViewExtension.ext(asset_no_prop).off_nadir, asset_value) # Validate view_item.validate()
def test_sun_elevation(self) -> None: view_item = pystac.Item.from_file(self.example_uri) # Get self.assertIn("view:sun_elevation", view_item.properties) view_sun_elevation = ViewExtension.ext(view_item).sun_elevation assert view_sun_elevation is not None self.assertEqual(view_sun_elevation, view_item.properties["view:sun_elevation"]) # Set ViewExtension.ext(view_item).sun_elevation = view_sun_elevation + 10 self.assertEqual(view_sun_elevation + 10, view_item.properties["view:sun_elevation"]) # Get from Asset asset_no_prop = view_item.assets["blue"] asset_prop = view_item.assets["red"] self.assertEqual( ViewExtension.ext(asset_no_prop).sun_elevation, ViewExtension.ext(view_item).sun_elevation, ) self.assertEqual(ViewExtension.ext(asset_prop).sun_elevation, 2.0) # Set to Asset asset_value = 12.0 ViewExtension.ext(asset_no_prop).sun_elevation = asset_value self.assertNotEqual( ViewExtension.ext(asset_no_prop).sun_elevation, ViewExtension.ext(view_item).sun_elevation, ) self.assertEqual( ViewExtension.ext(asset_no_prop).sun_elevation, asset_value) # Validate view_item.validate()
def to_pystac_item( dataset: DatasetDoc, stac_item_destination_url: str, dataset_location: Optional[str] = None, odc_dataset_metadata_url: Optional[str] = None, explorer_base_url: Optional[str] = None, collection_url: Optional[str] = None, ) -> pystac.Item: """ Convert the given ODC Dataset into a Stac Item document. Note: You may want to call `validate_item(doc)` on the outputs to find any incomplete properties. :param collection_url: URL to the Stac Collection. Either this or an explorer_base_url should be specified for Stac compliance. :param stac_item_destination_url: Public 'self' URL where the stac document will be findable. :param dataset_location: Use this location instead of picking from dataset.locations (for calculating relative band paths) :param odc_dataset_metadata_url: Public URL for the original ODC dataset yaml document :param explorer_base_url: An Explorer instance that contains this dataset. Will allow links to things such as the product definition. """ if dataset.geometry is not None: geom = Geometry(dataset.geometry, CRS(dataset.crs)) wgs84_geometry = geom.to_crs(CRS("epsg:4326"), math.inf) geometry = wgs84_geometry.json bbox = wgs84_geometry.boundingbox else: geometry = None bbox = None properties = eo3_to_stac_properties(dataset, title=dataset.label) properties.update(_lineage_fields(dataset.lineage)) dt = properties["datetime"] del properties["datetime"] # TODO: choose remote if there's multiple locations? # Without a dataset location, all paths will be relative. dataset_location = dataset_location or (dataset.locations[0] if dataset.locations else None) item = Item( id=str(dataset.id), datetime=dt, properties=properties, geometry=geometry, bbox=bbox, collection=dataset.product.name, ) # Add links if stac_item_destination_url: item.links.append( Link( rel="self", media_type=MediaType.JSON, target=stac_item_destination_url, )) if odc_dataset_metadata_url: item.links.append( Link( title="ODC Dataset YAML", rel="odc_yaml", media_type="text/yaml", target=odc_dataset_metadata_url, )) for link in _odc_links(explorer_base_url, dataset, collection_url): item.links.append(link) EOExtension.ext(item, add_if_missing=True) if dataset.geometry: proj = ProjectionExtension.ext(item, add_if_missing=True) epsg, wkt = _get_projection(dataset) if epsg is not None: proj.apply(epsg=epsg, **_proj_fields(dataset.grids)) elif wkt is not None: proj.apply(wkt2=wkt, **_proj_fields(dataset.grids)) else: raise STACError( "Projection extension requires either epsg or wkt for crs.") # To pass validation, only add 'view' extension when we're using it somewhere. if any(k.startswith("view:") for k in properties.keys()): ViewExtension.ext(item, add_if_missing=True) # Add assets that are data for name, measurement in dataset.measurements.items(): if not dataset_location and not measurement.path: # No URL to link to. URL is mandatory for Stac validation. continue asset = Asset( href=_uri_resolve(dataset_location, measurement.path), media_type=_media_type(Path(measurement.path)), title=name, roles=["data"], ) eo = EOExtension.ext(asset) # TODO: pull out more information about the band band = Band.create(name) eo.apply(bands=[band]) if dataset.grids: proj_fields = _proj_fields(dataset.grids, measurement.grid) if proj_fields is not None: proj = ProjectionExtension.ext(asset) # Not sure how this handles None for an EPSG code proj.apply( shape=proj_fields["shape"], transform=proj_fields["transform"], epsg=epsg, ) item.add_asset(name, asset=asset) # Add assets that are accessories for name, measurement in dataset.accessories.items(): if not dataset_location and not measurement.path: # No URL to link to. URL is mandatory for Stac validation. continue asset = Asset( href=_uri_resolve(dataset_location, measurement.path), media_type=_media_type(Path(measurement.path)), title=_asset_title_fields(name), roles=_asset_roles_fields(name), ) item.add_asset(name, asset=asset) return item
def test_validate_view(self) -> None: item = pystac.Item.from_file(self.example_uri) self.assertTrue(ViewExtension.has_extension(item)) item.validate()
def test_set_azimuth_summaries(self) -> None: collection = self.collection.clone() view_summaries = ViewExtension.summaries(collection, True) view_summaries.azimuth = None self.assertIsNone(view_summaries.azimuth)