def test_partial_apply(self) -> None: proj_item = pystac.Item.from_file(self.example_uri) ProjectionExtension.ext(proj_item).apply(epsg=1111) self.assertEqual(ProjectionExtension.ext(proj_item).epsg, 1111) proj_item.validate()
def test_item_ext_add_to(self) -> None: item = pystac.Item.from_file(self.example_uri) item.stac_extensions.remove(ProjectionExtension.get_schema_uri()) self.assertNotIn(ProjectionExtension.get_schema_uri(), item.stac_extensions) _ = ProjectionExtension.ext(item, add_if_missing=True) self.assertIn(ProjectionExtension.get_schema_uri(), item.stac_extensions)
def test_bbox(self) -> None: proj_item = pystac.Item.from_file(self.example_uri) # Get self.assertIn("proj:bbox", proj_item.properties) proj_bbox = ProjectionExtension.ext(proj_item).bbox self.assertEqual(proj_bbox, proj_item.properties["proj:bbox"]) # Set ProjectionExtension.ext(proj_item).bbox = [1.0, 2.0, 3.0, 4.0] self.assertEqual(proj_item.properties["proj:bbox"], [1.0, 2.0, 3.0, 4.0]) # Get from Asset asset_no_prop = proj_item.assets["B1"] asset_prop = proj_item.assets["B8"] self.assertEqual( ProjectionExtension.ext(asset_no_prop).bbox, ProjectionExtension.ext(proj_item).bbox, ) self.assertEqual(ProjectionExtension.ext(asset_prop).bbox, [1.0, 2.0, 3.0, 4.0]) # Set to Asset asset_value = [10.0, 20.0, 30.0, 40.0] ProjectionExtension.ext(asset_no_prop).bbox = asset_value self.assertNotEqual( ProjectionExtension.ext(asset_no_prop).bbox, ProjectionExtension.ext(proj_item).bbox, ) self.assertEqual(ProjectionExtension.ext(asset_no_prop).bbox, asset_value) # Validate proj_item.validate()
def test_wkt2(self) -> None: proj_item = pystac.Item.from_file(self.example_uri) # Get self.assertIn("proj:wkt2", proj_item.properties) proj_wkt2 = ProjectionExtension.ext(proj_item).wkt2 self.assertEqual(proj_wkt2, proj_item.properties["proj:wkt2"]) # Set ProjectionExtension.ext(proj_item).wkt2 = WKT2 self.assertEqual(WKT2, proj_item.properties["proj:wkt2"]) # Get from Asset asset_no_prop = proj_item.assets["B1"] asset_prop = proj_item.assets["B8"] self.assertEqual( ProjectionExtension.ext(asset_no_prop).wkt2, ProjectionExtension.ext(proj_item).wkt2, ) self.assertTrue( "TEST_TEXT" in get_opt(ProjectionExtension.ext(asset_prop).wkt2) ) # Set to Asset asset_value = "TEST TEXT 2" ProjectionExtension.ext(asset_no_prop).wkt2 = asset_value self.assertNotEqual( ProjectionExtension.ext(asset_no_prop).wkt2, ProjectionExtension.ext(proj_item).wkt2, ) self.assertEqual(ProjectionExtension.ext(asset_no_prop).wkt2, asset_value) # Validate proj_item.validate()
def test_epsg(self) -> None: proj_item = pystac.Item.from_file(self.example_uri) # Get self.assertIn("proj:epsg", proj_item.properties) proj_epsg = ProjectionExtension.ext(proj_item).epsg self.assertEqual(proj_epsg, proj_item.properties["proj:epsg"]) # Set assert proj_epsg is not None ProjectionExtension.ext(proj_item).epsg = proj_epsg + 100 self.assertEqual(proj_epsg + 100, proj_item.properties["proj:epsg"]) # Get from Asset asset_no_prop = proj_item.assets["B1"] asset_prop = proj_item.assets["B8"] self.assertEqual( ProjectionExtension.ext(asset_no_prop).epsg, ProjectionExtension.ext(proj_item).epsg, ) self.assertEqual(ProjectionExtension.ext(asset_prop).epsg, 9999) # Set to Asset ProjectionExtension.ext(asset_no_prop).epsg = 8888 self.assertNotEqual( ProjectionExtension.ext(asset_no_prop).epsg, ProjectionExtension.ext(proj_item).epsg, ) self.assertEqual(ProjectionExtension.ext(asset_no_prop).epsg, 8888) # Validate proj_item.validate()
def test_shape(self) -> None: proj_item = pystac.Item.from_file(self.example_uri) # Get self.assertIn("proj:shape", proj_item.properties) proj_shape = ProjectionExtension.ext(proj_item).shape self.assertEqual(proj_shape, proj_item.properties["proj:shape"]) # Set new_val = [100, 200] ProjectionExtension.ext(proj_item).shape = new_val self.assertEqual(proj_item.properties["proj:shape"], new_val) # Get from Asset asset_no_prop = proj_item.assets["B1"] asset_prop = proj_item.assets["B8"] self.assertEqual( ProjectionExtension.ext(asset_no_prop).shape, ProjectionExtension.ext(proj_item).shape, ) self.assertEqual(ProjectionExtension.ext(asset_prop).shape, [16781, 16621]) # Set to Asset asset_value = [1, 2] ProjectionExtension.ext(asset_no_prop).shape = asset_value self.assertNotEqual( ProjectionExtension.ext(asset_no_prop).shape, ProjectionExtension.ext(proj_item).shape, ) self.assertEqual(ProjectionExtension.ext(asset_no_prop).shape, asset_value) # Validate proj_item.validate()
def test_apply(self) -> None: item = next(iter(TestCases.test_case_2().get_all_items())) self.assertFalse(ProjectionExtension.has_extension(item)) ProjectionExtension.add_to(item) ProjectionExtension.ext(item).apply( 4326, wkt2=WKT2, projjson=PROJJSON, geometry=item.geometry, bbox=item.bbox, centroid={"lat": 0.0, "lon": 1.0}, shape=[100, 100], transform=[30.0, 0.0, 224985.0, 0.0, -30.0, 6790215.0, 0.0, 0.0, 1.0], )
def test_one_datetime(self) -> None: item = create.item(self.path) self.assertEqual(item.id, os.path.splitext(os.path.basename(self.path))[0]) self.assertIsNotNone(item.datetime) self.assertEqual( item.geometry, { 'type': 'Polygon', 'coordinates': [[[-95.780872, 29.517294], [-95.783782, 29.623358], [-96.041791, 29.617689], [-96.038613, 29.511649], [-95.780872, 29.517294]]] }) self.assertEqual(item.bbox, [-96.041791, 29.511649, -95.780872, 29.623358]) self.assertIsNone(item.common_metadata.start_datetime) self.assertIsNone(item.common_metadata.end_datetime) projection = ProjectionExtension.ext(item) self.assertEqual(projection.epsg, 32615) self.assertIsNone(projection.wkt2) self.assertIsNone(projection.projjson) self.assertEqual( projection.transform, [97.69921875, 0.0, 205437.0, 0.0, -45.9609375, 3280290.0]) self.assertEqual(projection.shape, (256, 256)) data = item.assets['data'] self.assertEqual(data.href, self.path) self.assertIsNone(data.title) self.assertIsNone(data.description) self.assertEqual(data.roles, ['data']) self.assertEqual(data.media_type, None) item.validate()
def test_summaries_adds_uri(self) -> None: col = pystac.Collection.from_file(self.example_uri) col.stac_extensions = [] self.assertRaisesRegex( pystac.ExtensionNotImplemented, r"Could not find extension schema URI.*", ProjectionExtension.summaries, col, False, ) _ = ProjectionExtension.summaries(col, True) self.assertIn(ProjectionExtension.get_schema_uri(), col.stac_extensions) ProjectionExtension.remove_from(col) self.assertNotIn(ProjectionExtension.get_schema_uri(), col.stac_extensions)
def item(href: str, read_href_modifier: Optional[ReadHrefModifier] = None) -> Item: """Creates a STAC Item from the asset at the provided href. The `read_href_modifer` argument can be used to modify the href for the rasterio read, e.g. if you need to sign a url. This function is intentionally minimal in its signature and capabilities. If you need to customize your Item, do so after creation. This function sets: - id - geometry - bbox - datetime (to the time of item creation): you'll probably want to change this - the proj extension - either the EPSG code or, if not available, the WKT2 - transform - shape - a single asset with key 'data' - asset href - asset roles to ['data'] In particular, the datetime and asset media type fields most likely need to be updated. """ id = os.path.splitext(os.path.basename(href))[0] if read_href_modifier: modified_href = read_href_modifier(href) else: modified_href = href with rasterio.open(modified_href) as dataset: crs = dataset.crs proj_bbox = dataset.bounds proj_transform = list(dataset.transform)[0:6] proj_shape = dataset.shape proj_geometry = shapely.geometry.mapping(shapely.geometry.box(*proj_bbox)) geometry = stactools.core.projection.reproject_geom(crs, 'EPSG:4326', proj_geometry, precision=6) bbox = list(shapely.geometry.shape(geometry).bounds) item = Item(id=id, geometry=geometry, bbox=bbox, datetime=datetime.datetime.now(), properties={}) projection = ProjectionExtension.ext(item, add_if_missing=True) epsg = crs.to_epsg() if epsg: projection.epsg = epsg else: projection.wkt2 = crs.to_wkt('WKT2') projection.transform = proj_transform projection.shape = proj_shape item.add_asset('data', Asset(href=href, roles=['data'])) return item
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(ProjectionExtension.get_schema_uri()) with self.assertRaises(pystac.ExtensionNotImplemented): _ = ProjectionExtension.ext(item) # Should raise exception if owning Item does not include extension URI asset = item.assets["B8"] with self.assertRaises(pystac.ExtensionNotImplemented): _ = ProjectionExtension.ext(asset) # Should succeed if Asset has no owner ownerless_asset = pystac.Asset.from_dict(asset.to_dict()) _ = ProjectionExtension.ext(ownerless_asset)
def test_get_summaries(self) -> None: col = pystac.Collection.from_file(self.example_uri) proj_summaries = ProjectionExtension.summaries(col) # Get epsg_summaries = proj_summaries.epsg assert epsg_summaries is not None self.assertListEqual(epsg_summaries, [32614])
def test_projjson(self) -> None: proj_item = pystac.Item.from_file(self.example_uri) # Get self.assertIn("proj:projjson", proj_item.properties) proj_projjson = ProjectionExtension.ext(proj_item).projjson self.assertEqual(proj_projjson, proj_item.properties["proj:projjson"]) # Set ProjectionExtension.ext(proj_item).projjson = PROJJSON self.assertEqual(PROJJSON, proj_item.properties["proj:projjson"]) # Get from Asset asset_no_prop = proj_item.assets["B1"] asset_prop = proj_item.assets["B8"] self.assertEqual( ProjectionExtension.ext(asset_no_prop).projjson, ProjectionExtension.ext(proj_item).projjson, ) asset_prop_json = ProjectionExtension.ext(asset_prop).projjson assert asset_prop_json is not None self.assertEqual(asset_prop_json["id"]["code"], 9999) # Set to Asset asset_value = deepcopy(PROJJSON) asset_value["id"]["code"] = 7777 ProjectionExtension.ext(asset_no_prop).projjson = asset_value self.assertNotEqual( ProjectionExtension.ext(asset_no_prop).projjson, ProjectionExtension.ext(proj_item).projjson, ) asset_no_prop_json = ProjectionExtension.ext(asset_no_prop).projjson assert asset_no_prop_json is not None self.assertEqual(asset_no_prop_json["id"]["code"], 7777) # Validate proj_item.validate() # Ensure setting bad projjson fails validation with self.assertRaises(pystac.STACValidationError): ProjectionExtension.ext(proj_item).projjson = {"bad": "data"} proj_item.validate()
def test_migration(self) -> None: with open(self.item_0_8_path) as src: item_dict = json.load(src) self.assertIn("eo:epsg", item_dict["properties"]) item = Item.from_file(self.item_0_8_path) self.assertNotIn("eo:epsg", item.properties) self.assertIn("proj:epsg", item.properties) self.assertIn(ProjectionExtension.get_schema_uri(), item.stac_extensions)
def test_set_summaries(self) -> None: col = pystac.Collection.from_file(self.example_uri) proj_summaries = ProjectionExtension.summaries(col) # Set proj_summaries.epsg = [4326] col_dict = col.to_dict() self.assertEqual(len(col_dict["summaries"]["proj:epsg"]), 1) self.assertEqual(col_dict["summaries"]["proj:epsg"][0], 4326)
def add_stac(self, tile): if not tile.poly: return None item = pystac.Item( tile.name, mapping(tile.poly), list(tile.poly.bounds), datetime.datetime.now(), {'description': 'A USGS Lidar pointcloud in Entwine/EPT format'}) #item.ext.enable(pystac.Extensions.POINTCLOUD) # icky s = tile.ept['schema'] p = [] for d in s: p.append(Schema(d)) PointcloudExtension.add_to(item) PointcloudExtension.ext(item).apply( tile.num_points, PhenomenologyType.LIDAR, "ept", p, ) ProjectionExtension.add_to(item) ProjectionExtension.ext(item).apply(3857, projjson=PROJJSON) # item.ext.pointcloud.apply(tile.num_points, 'lidar', 'ept', p, epsg='EPSG:3857') asset = pystac.Asset(tile.url, 'entwine', 'The ept.json for accessing data') item.add_asset('ept.json', asset) item_link = pystac.Link('self', f'{self.args.stac_base_url}{tile.name}.json') item_parent = pystac.Link('parent', f'{self.args.stac_base_url}catalog.json') item.add_links([item_link, item_parent]) return item
def test_crs_string(self) -> None: item = pystac.Item.from_file(self.example_uri) ProjectionExtension.remove_from(item) for key in list(item.properties.keys()): if key.startswith("proj:"): item.properties.pop(key) self.assertIsNone(item.properties.get("proj:epsg")) self.assertIsNone(item.properties.get("proj:wkt2")) self.assertIsNone(item.properties.get("proj:projjson")) projection = ProjectionExtension.ext(item, add_if_missing=True) self.assertIsNone(projection.crs_string) projection.projjson = PROJJSON self.assertEqual(projection.crs_string, json.dumps(PROJJSON)) projection.wkt2 = WKT2 self.assertEqual(projection.crs_string, WKT2) projection.epsg = 4326 self.assertEqual(projection.crs_string, "EPSG:4326")
def test_centroid(self) -> None: proj_item = pystac.Item.from_file(self.example_uri) # Get self.assertIn("proj:centroid", proj_item.properties) proj_centroid = ProjectionExtension.ext(proj_item).centroid self.assertEqual(proj_centroid, proj_item.properties["proj:centroid"]) # Set new_val = {"lat": 2.0, "lon": 3.0} ProjectionExtension.ext(proj_item).centroid = new_val self.assertEqual(proj_item.properties["proj:centroid"], new_val) # Get from Asset asset_no_prop = proj_item.assets["B1"] asset_prop = proj_item.assets["B8"] self.assertEqual( ProjectionExtension.ext(asset_no_prop).centroid, ProjectionExtension.ext(proj_item).centroid, ) self.assertEqual( ProjectionExtension.ext(asset_prop).centroid, {"lat": 0.5, "lon": 0.3} ) # Set to Asset asset_value = {"lat": 1.5, "lon": 1.3} ProjectionExtension.ext(asset_no_prop).centroid = asset_value self.assertNotEqual( ProjectionExtension.ext(asset_no_prop).centroid, ProjectionExtension.ext(proj_item).centroid, ) self.assertEqual(ProjectionExtension.ext(asset_no_prop).centroid, asset_value) # Validate proj_item.validate() # Ensure setting bad centroid fails validation with self.assertRaises(pystac.STACValidationError): ProjectionExtension.ext(proj_item).centroid = {"lat": 2.0, "lng": 3.0} proj_item.validate()
def test_geometry(self) -> None: proj_item = pystac.Item.from_file(self.example_uri) # Get self.assertIn("proj:geometry", proj_item.properties) proj_geometry = ProjectionExtension.ext(proj_item).geometry self.assertEqual(proj_geometry, proj_item.properties["proj:geometry"]) # Set ProjectionExtension.ext(proj_item).geometry = proj_item.geometry self.assertEqual(proj_item.geometry, proj_item.properties["proj:geometry"]) # Get from Asset asset_no_prop = proj_item.assets["B1"] asset_prop = proj_item.assets["B8"] self.assertEqual( ProjectionExtension.ext(asset_no_prop).geometry, ProjectionExtension.ext(proj_item).geometry, ) asset_prop_geometry = ProjectionExtension.ext(asset_prop).geometry assert asset_prop_geometry is not None self.assertEqual(asset_prop_geometry["coordinates"][0][0], [0.0, 0.0]) # Set to Asset asset_value: Dict[str, Any] = {"type": "Point", "coordinates": [1.0, 2.0]} ProjectionExtension.ext(asset_no_prop).geometry = asset_value self.assertNotEqual( ProjectionExtension.ext(asset_no_prop).geometry, ProjectionExtension.ext(proj_item).geometry, ) self.assertEqual(ProjectionExtension.ext(asset_no_prop).geometry, asset_value) # Validate proj_item.validate() # Ensure setting bad geometry fails validation with self.assertRaises(pystac.STACValidationError): ProjectionExtension.ext(proj_item).geometry = {"bad": "data"} proj_item.validate()
def process_projection(item, granule, band1_file): proj_ext = ProjectionExtension.ext(item, add_if_missing=True) with rasterio.open(band1_file) as band1_dataset: proj_ext.transform = band1_dataset.transform height, width = band1_dataset.shape proj_ext.shape = [height, width] for attribute in granule.AdditionalAttributes.AdditionalAttribute: if attribute.Name == "MGRS_TILE_ID": value = attribute.Values.Value.cdata lat_band = value[3] # Case is important for ordinal comparison if lat_band.casefold() > "m": hemi = "326" else: hemi = "327" epsg = int(hemi + value[0:2]) proj_ext.epsg = epsg
def test_transform(self) -> None: proj_item = pystac.Item.from_file(self.example_uri) # Get self.assertIn("proj:transform", proj_item.properties) proj_transform = ProjectionExtension.ext(proj_item).transform self.assertEqual(proj_transform, proj_item.properties["proj:transform"]) # Set new_val = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0] ProjectionExtension.ext(proj_item).transform = new_val self.assertEqual(proj_item.properties["proj:transform"], new_val) # Get from Asset asset_no_prop = proj_item.assets["B1"] asset_prop = proj_item.assets["B8"] self.assertEqual( ProjectionExtension.ext(asset_no_prop).transform, ProjectionExtension.ext(proj_item).transform, ) self.assertEqual( ProjectionExtension.ext(asset_prop).transform, [15.0, 0.0, 224992.5, 0.0, -15.0, 6790207.5, 0.0, 0.0, 1.0], ) # Set to Asset asset_value = [2.0, 4.0, 6.0, 8.0, 10.0, 12.0] ProjectionExtension.ext(asset_no_prop).transform = asset_value self.assertNotEqual( ProjectionExtension.ext(asset_no_prop).transform, ProjectionExtension.ext(proj_item).transform, ) self.assertEqual(ProjectionExtension.ext(asset_no_prop).transform, asset_value) # Validate proj_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