def test_eo_items_are_heritable(self): item1 = Item(id='test-item-1', geometry=RANDOM_GEOM, bbox=RANDOM_BBOX, datetime=datetime.utcnow(), properties={'key': 'one'}, stac_extensions=['eo', 'commons']) item2 = Item(id='test-item-2', geometry=RANDOM_GEOM, bbox=RANDOM_BBOX, datetime=datetime.utcnow(), properties={'key': 'two'}, stac_extensions=['eo', 'commons']) wv3_bands = [ Band.create(name='Coastal', description='Coastal: 400 - 450 nm', common_name='coastal'), Band.create(name='Blue', description='Blue: 450 - 510 nm', common_name='blue'), Band.create(name='Green', description='Green: 510 - 580 nm', common_name='green'), Band.create(name='Yellow', description='Yellow: 585 - 625 nm', common_name='yellow'), Band.create(name='Red', description='Red: 630 - 690 nm', common_name='red'), Band.create(name='Red Edge', description='Red Edge: 705 - 745 nm', common_name='rededge'), Band.create(name='Near-IR1', description='Near-IR1: 770 - 895 nm', common_name='nir08'), Band.create(name='Near-IR2', description='Near-IR2: 860 - 1040 nm', common_name='nir09') ] spatial_extent = SpatialExtent(bboxes=[RANDOM_BBOX]) temporal_extent = TemporalExtent(intervals=[[item1.datetime, None]]) collection_extent = Extent(spatial=spatial_extent, temporal=temporal_extent) common_properties = { 'eo:bands': [b.to_dict() for b in wv3_bands], 'gsd': 0.3, 'eo:platform': 'Maxar', 'eo:instrument': 'WorldView3' } collection = Collection(id='test', description='test', extent=collection_extent, properties=common_properties, stac_extensions=['commons'], license='CC-BY-SA-4.0') collection.add_items([item1, item2]) with TemporaryDirectory() as tmp_dir: collection.normalize_hrefs(tmp_dir) collection.save(catalog_type=CatalogType.SELF_CONTAINED) read_col = Collection.from_file('{}/collection.json'.format(tmp_dir)) items = list(read_col.get_all_items()) self.assertEqual(len(items), 2) self.assertTrue(items[0].ext.implements('eo')) self.assertTrue(items[1].ext.implements('eo'))
def test_create(self) -> None: band = Band.create( name="B01", common_name="red", description=Band.band_description("red"), center_wavelength=0.65, full_width_half_max=0.1, solar_illumination=42.0, ) self.assertEqual(band.name, "B01") self.assertEqual(band.common_name, "red") self.assertEqual(band.description, "Common name: red, Range: 0.6 to 0.7") self.assertEqual(band.center_wavelength, 0.65) self.assertEqual(band.full_width_half_max, 0.1) self.assertEqual(band.solar_illumination, 42.0) self.assertEqual(band.__repr__(), "<Band name=B01>")
def test_bands(self) -> None: item = pystac.Item.from_file(self.BANDS_IN_ITEM_URI) # Get self.assertIn("eo:bands", item.properties) bands = EOExtension.ext(item).bands assert bands is not None self.assertEqual( list(map(lambda x: x.name, bands)), ["band1", "band2", "band3", "band4"] ) # Set new_bands = [ Band.create(name="red", description=Band.band_description("red")), Band.create(name="green", description=Band.band_description("green")), Band.create(name="blue", description=Band.band_description("blue")), ] EOExtension.ext(item).bands = new_bands self.assertEqual( "Common name: red, Range: 0.6 to 0.7", item.properties["eo:bands"][0]["description"], ) self.assertEqual(len(EOExtension.ext(item).bands or []), 3) item.validate()
def test_item_apply(self) -> None: item = pystac.Item.from_file(self.LANDSAT_EXAMPLE_URI) eo_ext = EOExtension.ext(item) test_band = Band.create(name="test") self.assertEqual(eo_ext.cloud_cover, 78) self.assertNotIn(test_band, eo_ext.bands or []) eo_ext.apply(bands=[test_band], cloud_cover=15) assert eo_ext.bands is not None self.assertEqual(test_band.to_dict(), eo_ext.bands[0].to_dict()) self.assertEqual(eo_ext.cloud_cover, 15)
def test_asset_bands(self) -> None: item = pystac.Item.from_file(self.LANDSAT_EXAMPLE_URI) # Get b1_asset = item.assets["B1"] asset_bands = EOExtension.ext(b1_asset).bands assert asset_bands is not None self.assertEqual(len(asset_bands), 1) self.assertEqual(asset_bands[0].name, "B1") self.assertEqual(asset_bands[0].solar_illumination, 2000) index_asset = item.assets["index"] asset_bands = EOExtension.ext(index_asset).bands self.assertIs(None, asset_bands) # No asset specified item_bands = EOExtension.ext(item).bands self.assertIsNot(None, item_bands) # Set b2_asset = item.assets["B2"] self.assertEqual(get_opt(EOExtension.ext(b2_asset).bands)[0].name, "B2") EOExtension.ext(b2_asset).bands = EOExtension.ext(b1_asset).bands new_b2_asset_bands = EOExtension.ext(item.assets["B2"]).bands self.assertEqual(get_opt(new_b2_asset_bands)[0].name, "B1") item.validate() # Check adding a new asset new_bands = [ Band.create( name="red", description=Band.band_description("red"), solar_illumination=1900, ), Band.create( name="green", description=Band.band_description("green"), solar_illumination=1950, ), Band.create( name="blue", description=Band.band_description("blue"), solar_illumination=2000, ), ] asset = pystac.Asset(href="some/path.tif", media_type=pystac.MediaType.GEOTIFF) EOExtension.ext(asset).bands = new_bands item.add_asset("test", asset) self.assertEqual(len(item.assets["test"].extra_fields["eo:bands"]), 3)
def test_summaries(self) -> None: col = pystac.Collection.from_file(self.EO_COLLECTION_URI) eo_summaries = EOExtension.summaries(col) # Get cloud_cover_summaries = eo_summaries.cloud_cover assert cloud_cover_summaries is not None self.assertEqual(cloud_cover_summaries.minimum, 0.0) self.assertEqual(cloud_cover_summaries.maximum, 80.0) bands = eo_summaries.bands assert bands is not None self.assertEqual(len(bands), 11) # Set eo_summaries.cloud_cover = RangeSummary(1.0, 2.0) eo_summaries.bands = [Band.create(name="test")] col_dict = col.to_dict() self.assertEqual(len(col_dict["summaries"]["eo:bands"]), 1) self.assertEqual(col_dict["summaries"]["eo:cloud_cover"]["minimum"], 1.0)
def test_asset_bands(self): eo_item = pystac.read_file(self.LANDSAT_EXAMPLE_URI) # Get b1_asset = eo_item.assets['B1'] asset_bands = eo_item.ext.eo.get_bands(b1_asset) self.assertIsNot(None, asset_bands) self.assertEqual(len(asset_bands), 1) self.assertEqual(asset_bands[0].name, 'B1') index_asset = eo_item.assets['index'] asset_bands = eo_item.ext.eo.get_bands(index_asset) self.assertIs(None, asset_bands) # Set b2_asset = eo_item.assets['B2'] self.assertEqual(eo_item.ext.eo.get_bands(b2_asset)[0].name, "B2") eo_item.ext.eo.set_bands(eo_item.ext.eo.get_bands(b1_asset), b2_asset) new_b2_asset_bands = eo_item.ext.eo.get_bands(eo_item.assets['B2']) self.assertEqual(new_b2_asset_bands[0].name, 'B1') eo_item.validate() # Check adding a new asset new_bands = [ Band.create(name="red", description=Band.band_description("red")), Band.create(name="green", description=Band.band_description("green")), Band.create(name="blue", description=Band.band_description("blue")), ] asset = pystac.Asset(href="some/path.tif", media_type=pystac.MediaType.GEOTIFF) eo_item.ext.eo.set_bands(new_bands, asset) eo_item.add_asset("test", asset) self.assertEqual(len(eo_item.assets["test"].properties["eo:bands"]), 3)
def test_bands(self): eo_item = pystac.read_file(self.BANDS_IN_ITEM_URI) # Get self.assertIn("eo:bands", eo_item.properties) bands = eo_item.ext.eo.bands self.assertEqual(list(map(lambda x: x.name, bands)), ['band1', 'band2', 'band3', 'band4']) # Set new_bands = [ Band.create(name="red", description=Band.band_description("red")), Band.create(name="green", description=Band.band_description("green")), Band.create(name="blue", description=Band.band_description("blue")), ] eo_item.ext.eo.bands = new_bands self.assertEqual('Common name: red, Range: 0.6 to 0.7', eo_item.properties['eo:bands'][0]['description']) self.assertEqual(len(eo_item.ext.eo.bands), 3) eo_item.validate()
def test_bands(self): eo_item = pystac.read_file( TestCases.get_path('data-files/eo/eo-landsat-example.json')) # Get self.assertIn("eo:bands", eo_item.properties) bands = eo_item.ext.eo.bands self.assertEqual(list(map(lambda x: x.name, bands)), [ 'B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B9', 'B10', 'B11' ]) for band in bands: self.assertIsInstance(band.common_name, str) self.assertIn(type(band.center_wavelength), [float, int]) self.assertIn(type(band.full_width_half_max), [float, int]) self.assertIs(None, band.description) # Ensure modifying the bands make changes on the item. bands_dict = {band.name: band for band in bands} bands_dict['B1'].description = "Band 1" self.assertTrue( [x for x in eo_item.properties['eo:bands'] if x['name'] == 'B1'][0]['description'] == "Band 1") # Set new_bands = [ Band.create(name="red", description=Band.band_description("red")), Band.create(name="green", description=Band.band_description("green")), Band.create(name="blue", description=Band.band_description("blue")), ] eo_item.ext.eo.bands = new_bands self.assertEqual('Common name: red, Range: 0.6 to 0.7', eo_item.properties['eo:bands'][0]['description']) self.validator.validate_object(eo_item)
SENTINEL_PROVIDER = pystac.Provider( name='ESA', roles=['producer', 'processor', 'licensor'], url='https://earth.esa.int/web/guest/home') SAFE_MANIFEST_ASSET_KEY = "safe-manifest" INSPIRE_METADATA_ASSET_KEY = "inspire-metadata" PRODUCT_METADATA_ASSET_KEY = "product-metadata" GRANULE_METADATA_ASSET_KEY = "granule-metadata" DATASTRIP_METADATA_ASSET_KEY = "datastrip-metadata" SENTINEL_BANDS = { 'B01': Band.create(name='B01', common_name='coastal', description='Band 1 - Coastal aerosol', center_wavelength=0.443, full_width_half_max=0.027), 'B02': Band.create(name='B02', common_name='blue', description='Band 2 - Blue', center_wavelength=0.490, full_width_half_max=0.098), 'B03': Band.create(name='B03', common_name='green', description='Band 3 - Green', center_wavelength=0.560, full_width_half_max=0.045), 'B04':
class STACWriter: """Writes a product as a stac item.""" s2_bands = [ Band.create(name="B01", description="", common_name="Coastal aerosol", center_wavelength=0.443), Band.create(name="B02", description="", common_name="Blue", center_wavelength=0.49), Band.create(name="B03", description="", common_name="Green", center_wavelength=0.56), Band.create(name="B04", description="", common_name="Red", center_wavelength=0.665), Band.create(name="B05", description="", common_name="Red Edge 1", center_wavelength=0.705), Band.create(name="B06", description="", common_name="Red Edge 2", center_wavelength=0.740), Band.create(name="B07", description="", common_name="Red Edge 3", center_wavelength=0.783), Band.create(name="B08", description="", common_name="NIR", center_wavelength=0.842), Band.create(name="B8A", description="", common_name="Narrow NIR", center_wavelength=0.865), Band.create(name="B9", description="", common_name="Water", center_wavelength=0.945), Band.create(name="B10", description="", common_name="SWIR - Cirrus", center_wavelength=1.373), Band.create(name="B11", description="", common_name="SWIR 1", center_wavelength=1.61), Band.create(name="B12", description="", common_name="SWIR 2", center_wavelength=2.190), ] s2_index = [band.name for band in s2_bands] def __init__(self, catalog_path=None, sid=None, title=None, cog=False, with_bbox=True): self.catalog_path = catalog_path self._catalog = None self.sid = sid self.title = title self.cog = cog self.with_bbox = with_bbox @property def catalog(self): """Check if catalog exists and create it otherwise.""" if self.catalog_path is not None and self._catalog is None: if os.path.isfile(self.catalog_path): os.remove(self.catalog_path) if self.with_bbox: self._catalog = pystac.Collection(id="Sen2Like_catalog" if self.sid is None else self.sid, title="Sen2Like Catalog" if self.title is None else self.title, href=self.catalog_path, description="Catalog containing Sen2Like generated products", extent=pystac.Extent(pystac.SpatialExtent([180, -56, 180, 83]), pystac.TemporalExtent([None, None]))) else: self._catalog = pystac.Catalog(id="Sen2Like_catalog" if self.sid is None else self.sid, title="Sen2Like Catalog" if self.title is None else self.title, href=self.catalog_path, description="Catalog containing Sen2Like generated products") return self._catalog def _create_item(self, product, product_id, output_name, ref_image): # Get common properties from B04 # If file is not found, it may have been generated with old version where image format was not correclty managed if not os.path.exists(ref_image) and ref_image.endswith('.jp2'): ref_image = f"{ref_image[:-4]}.TIF" bbox, footprint = get_bbox_and_footprint(ref_image) # Create item eo_item = pystac.Item(id=product_id, geometry=footprint, bbox=bbox, datetime=product.acqdate, properties={}, href=os.path.normpath(output_name)) eo_item.ext.enable(pystac.Extensions.EO) eo_item.ext.eo.apply(bands=self.s2_bands) eo_item.properties["Platform"] = product.sensor eo_item.properties["Instrument"] = product.mtl.sensor eo_item.properties["Sun azimuth"] = f"{float(product.mtl.sun_azimuth_angle):.3f}\u00b0" eo_item.properties["Sun elevation"] = f"{float(product.mtl.sun_zenith_angle):.3f}\u00b0" eo_item.properties["Processing level"] = ref_image.split('_')[0] eo_item.properties[ "Cloud cover"] = f"{float(product.mtl.cloud_cover):.2}%" if product.mtl.cloud_cover is not None else None return eo_item def write_product(self, product, output_dir, bands, ql_name, granule_compact_name): product_id = os.path.basename(output_dir).split('.')[0] output_name = f"{os.path.join(output_dir, product_id)}.json" item = self._create_item(product, product_id, output_name, bands[0]) for image in set(bands): band = image.split('_')[-2] if not os.path.exists(image) and image.endswith('.jp2'): log.warning("Overwrite .jp2 extension from metadata -> image file is a TIF !!!!!") image = f"{image[:-4]}.TIF" asset = pystac.Asset(href=os.path.normpath(image), media_type=pystac.MediaType.COG if self.cog else ( pystac.MediaType.GEOTIFF if image.endswith( ".TIF") else pystac.MediaType.JPEG2000), properties={ 'eo:bands': [self.s2_index.index(band)] if band in self.s2_index else []}) item.add_asset(band, asset) # Get Quicklook ql_path = os.path.join(output_dir, 'GRANULE', granule_compact_name, 'QI_DATA', ql_name) if os.path.isfile(ql_path): ql_asset = pystac.Asset(href=os.path.normpath(ql_path), media_type=pystac.MediaType.JPEG) item.add_asset("thumbnail", ql_asset) else: log.warning("%s not found: No thumbnail for band %s" % (ql_path, band)) item.save_object() logging.debug("STAC file generated: %s" % output_name) if self.catalog_path is not None: try: self.catalog.add_item(item, title=product_id) except urllib.error.URLError as error: log.error("Cannot write to catalog: %s" % error) def write_catalog(self): if self.catalog is None: log.error("Cannot write an empty catalog.") else: if self.with_bbox and isinstance(self.catalog, pystac.Collection) and len( list(self.catalog.get_all_items())): self.catalog.update_extent_from_items() self.catalog.save(catalog_type=pystac.CatalogType.ABSOLUTE_PUBLISHED) logging.debug("STAC catalog generated: %s" % self.catalog_path)
from pystac.extensions.eo import Band HDF_ASSET_KEY = 'L1T' ASTER_FILE_NAME_REGEX = (r'AST_L1T_(?P<start>[\d]+)_' r'(?P<production>[\d]+)_' r'(?P<processing>[\d]+)') ASTER_BANDS = [ Band.create( name="B01", description="VNIR_Band1 (visible green/yellow)", center_wavelength=0.56, ), Band.create( name="B02", description="VNIR_Band2 (visible red)", center_wavelength=0.66, ), Band.create( name="B3N", description="VNIR_Band3N (near infrared, nadir pointing)", center_wavelength=0.82, ), Band.create( name="B04", description="SWIR_Band4 (short-wave infrared)", center_wavelength=1.65, ), Band.create( name="B05",
from pystac.utils import (str_to_datetime, make_absolute_href) from pystac.extensions.eo import Band from shapely.geometry import shape from stactools.planet import PLANET_PROVIDER from stactools.planet.constants import PLANET_EXTENSION_PREFIX import rasterio from rasterio.enums import Resampling logger = logging.getLogger(__name__) SKYSAT_BANDS = { 'PAN': Band.create('PAN', common_name='pan', center_wavelength=655, full_width_half_max=440), 'BLUE': Band.create('BLUE', common_name='blue', center_wavelength=470, full_width_half_max=70), 'GREEN': Band.create('GREEN', common_name='green', center_wavelength=560, full_width_half_max=80), 'RED': Band.create('RED', common_name='red', center_wavelength=645,
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_band_description_unknown_band(self) -> None: desc = Band.band_description("rainbow") self.assertIsNone(desc)
during the agricultural growing seasons in the continental U.S. NAIP projects are contracted each year based upon available funding and the FSA imagery acquisition cycle. Beginning in 2003, NAIP was acquired on a 5-year cycle. 2008 was a transition year, and a three-year cycle began in 2009. NAIP imagery is acquired at a one-meter ground sample distance (GSD) with a horizontal accuracy that matches within six meters of photo-identifiable ground control points, which are used during image inspection. Older images were collected using 3 bands (Red, Green, and Blue: RGB), but newer imagery is usually collected with an additional near-infrared band (RGBN). """.strip('\n') NAIP_LICENSE = 'PDDL-1.0' USDA_PROVIDER = pystac.Provider( name='USDA Farm Service Agency', url=('https://www.fsa.usda.gov/programs-and-services/aerial-photography' '/imagery-programs/naip-imagery/'), roles=['producer', 'licensor']) NAIP_BANDS = [ Band.create(name="Red", common_name='red'), Band.create(name="Green", common_name='green'), Band.create(name="Blue", common_name='blue'), Band.create(name="NIR", common_name='nir', description="near-infrared") ]
return self.item.properties.get('landsat:wrs_row') @wrs_row.setter def wrs_row(self, val): self.item.properties['landsat:wrs_row'] = val landsat_def = ExtensionDefinition('landsat', [ExtendedObject(pystac.Item, LandsatExt)]) pystac.STAC_EXTENSIONS.add_extension(landsat_def) band_info = { 'B1': { 'band': Band.create(name="B1", common_name="coastal", center_wavelength=0.48, full_width_half_max=0.02), 'gsd': 30.0 }, 'B2': { 'band': Band.create(name="B2", common_name="blue", center_wavelength=0.44, full_width_half_max=0.06), 'gsd': 30.0 }, 'B3': { 'band':
import pystac import rasterio import untangle from geojson import MultiPolygon from pystac.extensions.eo import Band, EOExtension from pystac.extensions.projection import ProjectionExtension from pystac.extensions.scientific import ScientificExtension from pystac.extensions.view import ViewExtension from shapely.geometry import shape landsat_band_info = { "B01": { "band": Band.create( name="B01", common_name="coastal", center_wavelength=0.48, full_width_half_max=0.02, ), "gsd": 30.0, }, "B02": { "band": Band.create( name="B02", common_name="blue", center_wavelength=0.44, full_width_half_max=0.06, ), "gsd": 30.0,
r'(?P<production>[\d]+)_' r'(?P<processing>[\d]+)') ASTER_PLATFORM = "terra" ASTER_INSTRUMENT = "aster" VNIR_SENSOR = 'VNIR' SWIR_SENSOR = 'SWIR' TIR_SENSOR = 'TIR' ASTER_SENSORS = [VNIR_SENSOR, SWIR_SENSOR, TIR_SENSOR] ASTER_BANDS = [ Band.create(name="VNIR_Band1", common_name="yellow/green", description="visible yellow/", center_wavelength=0.56, full_width_half_max=0.08), Band.create(name="VNIR_Band2", common_name="red", description="visible red", center_wavelength=0.66, full_width_half_max=0.06), Band.create(name="VNIR_Band3N", common_name="near infrared", description="near infrared", center_wavelength=0.82, full_width_half_max=0.08), Band.create(name="SWIR_Band4", common_name="swir", description="short-wave infrared",
from pystac.extensions.eo import Band L8_PLATFORM = "landsat-8" L8_INSTRUMENTS = ["oli", "tirs"] L8_EXTENSION_SCHEMA = "https://landsat.usgs.gov/stac/landsat-extension/schema.json" L8_ITEM_DESCRIPTION = "Landsat Collection 2 Level-2 Surface Reflectance Product" L8_SR_BANDS = { "SR_B1": Band({ "name": "SR_B1", "common_name": "coastal", "gsd": 30, "center_wavelength": 0.44, "full_width_half_max": 0.02 }), "SR_B2": Band({ "name": "SR_B2", "common_name": "blue", "gsd": 30, "center_wavelength": 0.48, "full_width_half_max": 0.06 }), "SR_B3": Band({ "name": "SR_B3", "common_name": "green", "gsd": 30, "center_wavelength": 0.56,