diff --git a/stackstac/accumulate_metadata.py b/stackstac/accumulate_metadata.py index eb96404..113feaf 100644 --- a/stackstac/accumulate_metadata.py +++ b/stackstac/accumulate_metadata.py @@ -1,196 +1,165 @@ -from typing import ( - Container, - Dict, - Hashable, - Iterable, - Literal, - Mapping, - Sequence, - Union, - TypeVar, -) - import numpy as np +import pandas as pd import xarray as xr +from itertools import chain -# properties can contain lists; need some way to tell them a singleton list -# apart from the list of properties we're collecting -class _ourlist(list): - pass - - -def metadata_to_coords( - items: Iterable[Mapping[str, object]], - dim_name: str, - fields: Union[str, Sequence[str], Literal[True]] = True, - skip_fields: Container[str] = (), -) -> Dict[str, xr.Variable]: - return dict_to_coords( - accumulate_metadata( - items, - fields=[fields] if isinstance(fields, str) else fields, - skip_fields=skip_fields, - ), - dim_name, - ) +def is_constant(arr): + """ + Return True if arr is constant along the first dimension + Parameters + ---------- + arr: + list or array + """ + is_constant = True + first_value = arr[0] + for i in arr: + if i != first_value: + is_constant = False + break + return is_constant -T = TypeVar("T", bound=Hashable) +def flatten(arr): + """ + Flatten a list -def accumulate_metadata( - items: Iterable[Mapping[T, object]], - fields: Union[Sequence[T], Literal[True]] = True, - skip_fields: Container[T] = (), -) -> Dict[T, object]: + Parameters + ---------- + arr: + list or array """ - Accumulate a sequence of multiple similar dicts into a single dict of lists. + return [item for sublist in arr for item in sublist] - Each field will contain a list of all the values for that field (equal length to ``items``). - For items where the field didn't exist, None is used. - Fields with only one unique value are flattened down to just that single value. +def accumulate_assets_coords(items, asset_ids, coords): + """ + Accumulate and deduplicate coordinnates (as per xarray nomenclature) from the items 'assets' Parameters ---------- items: - Iterable of dicts to accumulate - fields: - Only use these fields. If True, use all fields. - skip_fields: - Skip these fields. + list of stac items (in dictionnary format) + asset_ids: + list asset keys to be considered + coords: + dictionnary of existing coords to avoid overwriting them """ - all_fields: Dict[T, object] = {} - for i, item in enumerate(items): - # Inductive case: update existing fields - for existing_field, existing_value in all_fields.items(): - new_value = item.get(existing_field, None) - if new_value == existing_value: - # leave fields that are the same for every item as singletons - continue - if isinstance(existing_value, _ourlist): - # we already have a list going; add to it - existing_value.append(new_value) - else: - # all prior values were the same; this is the first different one - all_fields[existing_field] = _ourlist( - [existing_value] * i + [new_value] - ) - - # Base case 1: add any never-before-seen fields, when inferring field names - if fields is True: - for new_field in item.keys() - all_fields.keys(): - if new_field in skip_fields: - continue - value = item[new_field] - all_fields[new_field] = ( - value if i == 0 else _ourlist([None] * i + [value]) - ) - # Base case 2: initialize with predefined fields - elif i == 0: - all_fields.update( - (field, item.get(field, None)) - for field in fields - if field not in skip_fields + + # Selecting assets from 'asset_ids' and ordering them + assets_items = [ + [item["assets"][asset_id] for asset_id in asset_ids] for item in items + ] + + # List of all assets keys (deduplicated) + asset_keys = [ + key + for key in set(chain.from_iterable(flatten(assets_items))) + if key not in coords.keys() + ] + + # Flatening cases with 'eo:bands' + if "eo:bands" in asset_keys: + out = [] + for assets_item in assets_items: + for asset in assets_item: + eo = asset.pop("eo:bands", [{}]) + asset.update(eo[0] if isinstance(eo, list) else {}) + out.append(assets_item) + assets_items = out + asset_keys = set(chain.from_iterable(flatten(assets_items))) + + # Making sure all assets have all asset_keys and filling the others with np.nan + assets_items = [ + [[asset.get(key, np.nan) for key in asset_keys] for asset in assets_item] + for assets_item in assets_items + ] + + # Facilitating transposing via numpy for duplicates finding + assets_items = np.array(assets_items, dtype=object) + + # Dropping all nulls + nan_mask = pd.notnull(assets_items).any(axis=2).any(axis=0) + assets_items = assets_items[:, nan_mask, :] + + # Looping through assets_keys and determining what is unique and what is not + assets_coords = {} + for asset_key, band_oriented_coords, time_oriented_coords in zip( + asset_keys, assets_items.transpose(2, 1, 0), assets_items.transpose(2, 0, 1) + ): + is_band_dependant = is_constant(band_oriented_coords.tolist()) + is_time_dependant = is_constant(time_oriented_coords.tolist()) + + if is_time_dependant and is_band_dependant: + # same than band_oriented_coords[0, 0] + constant = time_oriented_coords[0, 0] + # xarray doesn't support passing a list as a constant coords for now + assets_coords[asset_key] = str(constant) if isinstance(constant, list) else constant + + elif is_time_dependant: + assets_coords[asset_key] = xr.Variable(["band"], time_oriented_coords[0]) + elif is_band_dependant: + assets_coords[asset_key] = xr.Variable(["time"], band_oriented_coords[0]) + else: + # rioxarray convention is ordered: time, band, y, x + assets_coords[asset_key] = xr.Variable( + ["time", "band"], time_oriented_coords ) - return all_fields + return assets_coords -def accumulate_metadata_only_allsame( - items: Iterable[Mapping[T, object]], - skip_fields: Container[T] = (), -) -> Dict[T, object]: +def accumulate_properties_coords(items, coords): """ - Accumulate multiple similar dicts into a single flattened dict of only consistent values. - - If the value of a field differs between items, the field is dropped. - If the value of a field is the same for all items that contain that field, the field is kept. - - Note this means that missing fields are ignored, not treated as different. + Accumulate and deduplicate coordinnates (as per xarray nomenclature) from the items 'properties' Parameters ---------- items: - Iterable of dicts to accumulate - skip_fields: - Skip these fields when ``fields`` is True. - """ - all_fields: Dict[T, object] = {} - for item in items: - for field, value in item.items(): - if field in skip_fields: - continue - if field not in all_fields: - all_fields[field] = value - else: - if value != all_fields[field]: - all_fields[field] = None - - return {field: value for field, value in all_fields.items() if value is not None} - - -def dict_to_coords( - metadata: Dict[str, object], dim_name: str -) -> Dict[str, xr.Variable]: + list of stac items (in dictionnary format) + coords: + dictionnary of existing coords to avoid overwriting them """ - Convert the output of `accumulate_metadata` into a dict of xarray Variables. - - 1-length lists and scalar values become 0D variables. - - Instances of ``_ourlist`` become 1D variables for ``dim_name``. - - Any other things with >= 1 dimension are dropped, because they probably don't line up - with the other dimensions of the final array. - """ - coords = {} - for field, props in metadata.items(): - while isinstance(props, list) and not isinstance(props, _ourlist): - # a list scalar (like `instruments = ['OLI', 'TIRS']`). - - # first, unpack (arbitrarily-nested) 1-element lists. - # keep re-checking if it's still a list - if len(props) == 1: - props = props[0] - continue - - # for now, treat multi-item lists as a set so xarray can interpret them as 0D variables. - # (numpy very much does not like to create object arrays containing python lists; - # `set` is basically a hack to make a 0D ndarray containing a Python object with multiple items.) - try: - props = set(props) - except TypeError: - # if it's not set-able, just give up - break - - props_arr = np.squeeze( - np.array( - props, - # Avoid DeprecationWarning creating ragged arrays when elements are lists/tuples of different lengths - dtype="object" - if ( - isinstance(props, _ourlist) - and len(set(len(x) if isinstance(x, (list, tuple)) else type(x) for x in props)) - > 1 - ) - else None, + # Selecting properties only + properties_items = [item["properties"] for item in items] + + # List of all assets keys (deduplicated) + properties_keys = [ + key + for key in set(chain.from_iterable(properties_items)) + if key not in coords.keys() + ] + + # Making sure all properties have all properties_keys and ordering them + properties_items = [ + [properties_item.get(key, np.nan) for key in properties_keys] + for properties_item in properties_items + ] + + # Facilitating transposing via numpy for duplicates finding + properties_items = np.array(properties_items, dtype=object) + + # Dropping all nulls + nan_mask = pd.notnull(properties_items).any(axis=0) + properties_items = properties_items[:, nan_mask] + + # Looping through properties_keys and determining what is unique and what is not + properties_coords = {} + for properties_key, time_oriented_coords in zip( + properties_keys, properties_items.T + ): + is_time_dependant = not is_constant(time_oriented_coords.tolist()) + + if is_time_dependant: + properties_coords[properties_key] = xr.Variable( + ["time"], time_oriented_coords ) - ) - - if ( - props_arr.ndim > 1 - or props_arr.ndim == 1 - and not isinstance(props, _ourlist) - ): - # probably a list-of-lists situation. the other dims likely don't correspond to - # our "bands", "y", and "x" dimensions, and xarray won't let us use unrelated - # dimensions. so just skip it for now. - continue - - coords[field] = xr.Variable( - (dim_name,) if props_arr.ndim == 1 else (), - props_arr, - ) - - return coords + else: + # rioxarray convention is ordered: time, band, y, x + constant = time_oriented_coords[0] + # xarray doesn't support passing a list as a constant coords for now + properties_coords[properties_key] = str(constant) if isinstance(constant, list) else constant + + return properties_coords diff --git a/stackstac/prepare.py b/stackstac/prepare.py index d9f68a0..f63ac7f 100644 --- a/stackstac/prepare.py +++ b/stackstac/prepare.py @@ -396,6 +396,7 @@ def to_coords( "time": times, "id": xr.Variable("time", [item["id"] for item in items]), "band": asset_ids, + "epsg": spec.epsg } if xy_coords is not False: @@ -436,100 +437,10 @@ def to_coords( coords["y"] = ys if properties: - coords.update( - accumulate_metadata.metadata_to_coords( - (item["properties"] for item in items), - "time", - fields=properties, - skip_fields={"datetime"}, - # skip_fields={"datetime", "providers"}, - ) - ) - - # Property-merging code using awkward array. Slightly shorter, not sure if it's faster, - # probably not worth the dependency - - # import awkward as ak - - # awk_props = ak.Array([item._data for item in items]).properties - # for field, props in zip(ak.fields(awk_props), ak.unzip(awk_props)): - # if field == "datetime": - # continue - - # # if all values are the same, collapse to a 0D coordinate - # try: - # if len(ak.run_lengths(props)) == 1: - # props = ak.to_list(props[0]) - # # ^ NOTE: `to_list` because `ak.to_numpy` on string scalars (`ak.CharBehavior`) - # # turns them into an int array of the characters! - # except NotImplementedError: - # # generally because it's an OptionArray (so there's >1 value anyway) - # pass - - # try: - # props = np.squeeze(ak.to_numpy(props)) - # except ValueError: - # continue - - # coords[field] = xr.Variable( - # (("time",) + tuple(f"dim_{i}" for i in range(1, props.ndim))) - # if np.ndim(props) > 0 - # else (), - # props, - # ) - # else: - # # For now don't use awkward when the field names are already known, - # # mostly so users don't have to have it installed. - # if isinstance(properties, str): - # properties = (properties,) - # for prop in properties: # type: ignore (`properties` cannot be True at this point) - # coords[prop] = xr.Variable( - # "time", [item["properties"].get(prop) for item in items] - # ) + coords.update(accumulate_metadata.accumulate_properties_coords(items, coords)) if band_coords: - flattened_metadata_by_asset = [ - accumulate_metadata.accumulate_metadata_only_allsame( - (item["assets"].get(asset_id, {}) for item in items), - skip_fields={"href", "type", "roles"}, - ) - for asset_id in asset_ids - ] - - eo_by_asset = [] - for meta in flattened_metadata_by_asset: - # NOTE: we look for `eo:bands` in each Asset's metadata, not as an Item-level list. - # This only became available in STAC 1.0.0-beta.1, so we'll fail on older collections. - # See https://github.com/radiantearth/stac-spec/tree/master/extensions/eo#item-fields - eo = meta.pop("eo:bands", {}) - if isinstance(eo, list): - eo = eo[0] if len(eo) == 1 else {} - # ^ `eo:bands` should be a list when present, but >1 item means it's probably a multi-band asset, - # which we can't currently handle, so we ignore it. we don't error here, because - # as long as you don't actually _use_ that asset, everything will be fine. we could - # warn, but that would probably just get annoying. - eo_by_asset.append(eo) - try: - meta["polarization"] = meta.pop("sar:polarizations") - except KeyError: - pass - - coords.update( - accumulate_metadata.metadata_to_coords( - flattened_metadata_by_asset, - "band", - skip_fields={"href"}, - # skip_fields={"href", "title", "description", "type", "roles"}, - ) - ) - if any(eo_by_asset): - coords.update( - accumulate_metadata.metadata_to_coords( - eo_by_asset, - "band", - fields=["common_name", "center_wavelength", "full_width_half_max"], - ) - ) + coords.update(accumulate_metadata.accumulate_assets_coords(items, asset_ids, coords)) # Add `epsg` last in case it's also a field in properties; our data model assumes it's a coordinate coords["epsg"] = spec.epsg diff --git a/stackstac/tests/test_accumulate_metadata.py b/stackstac/tests/test_accumulate_metadata.py new file mode 100644 index 0000000..a43ce0e --- /dev/null +++ b/stackstac/tests/test_accumulate_metadata.py @@ -0,0 +1,33 @@ +import json +from stackstac import accumulate_metadata + + +with open("stackstac/tests/test_stac_items.json") as json_data: + stac_items = json.load(json_data) + +properties_coords = accumulate_metadata.accumulate_properties_coords( + stac_items, coords={} +) +assert ( + properties_coords["view:off_nadir"] == 0 +), "the coord 'view:off_nadir' is not constant" +assert properties_coords["datetime"].dims == ( + "time", +), "the coord 'time' should vary along the time axis only" + +assets_coords = accumulate_metadata.accumulate_assets_coords( + stac_items, asset_ids=["B2", "B3", "B4"], coords={} +) +assert ( + assets_coords["type"] == "image/vnd.stac.geotiff; cloud-optimized=true" +), "the coord 'type' is not constant" +assert assets_coords["id2"].dims == ( + "time", +), "the coord 'id2' should vary along the time axis only" +assert assets_coords["title"].dims == ( + "band", +), "the coord 'title' should vary along the band axis only" +assert assets_coords["href"].dims == ( + "time", + "band", +), "the property 'href' should vary along both band and time axis" diff --git a/stackstac/tests/test_stac_items.json b/stackstac/tests/test_stac_items.json new file mode 100644 index 0000000..2a6b997 --- /dev/null +++ b/stackstac/tests/test_stac_items.json @@ -0,0 +1,422 @@ +[ + { + "id": "LC09_L1TP_199027_20221008_20230326_02_T1", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 0.8854253832019341, + 48.50593936854192 + ], + [ + 0.26285287053184364, + 46.79961884015438 + ], + [ + 2.6305324360568085, + 46.37468271392788 + ], + [ + 3.330195577538132, + 48.075774219295695 + ], + [ + 0.8854253832019341, + 48.50593936854192 + ] + ] + ] + }, + "bbox": [ + 0.26285287053184364, + 46.37468271392788, + 3.330195577538132, + 48.50593936854192 + ], + "assets": { + "B2": { + "href": "s3://usgs-landsat/collection02/level-1/standard/oli-tirs/2022/199/027/LC09_L1TP_199027_20221008_20230326_02_T1/LC09_L1TP_199027_20221008_20230326_02_T1_B2.TIF", + "id2": "LC09_L1TP_199027_20221008_20230326_02_T1", + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "title": "Blue Band (B2)" + }, + "B3": { + "href": "s3://usgs-landsat/collection02/level-1/standard/oli-tirs/2022/199/027/LC09_L1TP_199027_20221008_20230326_02_T1/LC09_L1TP_199027_20221008_20230326_02_T1_B3.TIF", + "id2": "LC09_L1TP_199027_20221008_20230326_02_T1", + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "title": "Green Band (B3)" + }, + "B4": { + "href": "s3://usgs-landsat/collection02/level-1/standard/oli-tirs/2022/199/027/LC09_L1TP_199027_20221008_20230326_02_T1/LC09_L1TP_199027_20221008_20230326_02_T1_B4.TIF", + "id2": "LC09_L1TP_199027_20221008_20230326_02_T1", + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "title": "Red Band (B4)" + } + }, + "properties": { + "datetime": "2022-10-08T10:41:35.845449Z", + "view:off_nadir": 0 + } + }, + { + "id": "LC08_L1TP_198027_20221009_20221013_02_T1", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 2.432095402737761, + 48.50857995792213 + ], + [ + 1.8098548469013855, + 46.8018730704457 + ], + [ + 4.1678629873299196, + 46.37633562463562 + ], + [ + 4.866830491224445, + 48.07781667425339 + ], + [ + 2.432095402737761, + 48.50857995792213 + ] + ] + ] + }, + "bbox": [ + 1.8098548469013855, + 46.37633562463562, + 4.866830491224445, + 48.50857995792213 + ], + "assets": { + "B2": { + "href": "s3://usgs-landsat/collection02/level-1/standard/oli-tirs/2022/198/027/LC08_L1TP_198027_20221009_20221013_02_T1/LC08_L1TP_198027_20221009_20221013_02_T1_B2.TIF", + "id2": "LC08_L1TP_198027_20221009_20221013_02_T1", + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "title": "Blue Band (B2)" + }, + "B3": { + "href": "s3://usgs-landsat/collection02/level-1/standard/oli-tirs/2022/198/027/LC08_L1TP_198027_20221009_20221013_02_T1/LC08_L1TP_198027_20221009_20221013_02_T1_B3.TIF", + "id2": "LC08_L1TP_198027_20221009_20221013_02_T1", + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "title": "Green Band (B3)" + }, + "B4": { + "href": "s3://usgs-landsat/collection02/level-1/standard/oli-tirs/2022/198/027/LC08_L1TP_198027_20221009_20221013_02_T1/LC08_L1TP_198027_20221009_20221013_02_T1_B4.TIF", + "id2": "LC08_L1TP_198027_20221009_20221013_02_T1", + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "title": "Red Band (B4)" + } + }, + "properties": { + "datetime": "2022-10-09T10:35:36.466949Z", + "view:off_nadir": 0 + } + }, + { + "id": "LC08_L1TP_198028_20221009_20221013_02_T1", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 1.911418446833477, + 47.08716132420589 + ], + [ + 1.3143898002373042, + 45.378092264745284 + ], + [ + 3.612713846748114, + 44.959779380090914 + ], + [ + 4.283056840990693, + 46.66371945193676 + ], + [ + 1.911418446833477, + 47.08716132420589 + ] + ] + ] + }, + "bbox": [ + 1.3143898002373042, + 44.959779380090914, + 4.283056840990693, + 47.08716132420589 + ], + "assets": { + "B2": { + "href": "s3://usgs-landsat/collection02/level-1/standard/oli-tirs/2022/198/028/LC08_L1TP_198028_20221009_20221013_02_T1/LC08_L1TP_198028_20221009_20221013_02_T1_B2.TIF", + "id2": "LC08_L1TP_198028_20221009_20221013_02_T1", + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "title": "Blue Band (B2)" + }, + "B3": { + "href": "s3://usgs-landsat/collection02/level-1/standard/oli-tirs/2022/198/028/LC08_L1TP_198028_20221009_20221013_02_T1/LC08_L1TP_198028_20221009_20221013_02_T1_B3.TIF", + "id2": "LC08_L1TP_198028_20221009_20221013_02_T1", + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "title": "Green Band (B3)" + }, + "B4": { + "href": "s3://usgs-landsat/collection02/level-1/standard/oli-tirs/2022/198/028/LC08_L1TP_198028_20221009_20221013_02_T1/LC08_L1TP_198028_20221009_20221013_02_T1_B4.TIF", + "id2": "LC08_L1TP_198028_20221009_20221013_02_T1", + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "title": "Red Band (B4)" + } + }, + "properties": { + "datetime": "2022-10-09T10:36:00.349517Z", + "view:off_nadir": 0 + } + }, + { + "id": "LC08_L1TP_198027_20221025_20221107_02_T1", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 2.414221416451175, + 48.50876028226318 + ], + [ + 1.7921547851933306, + 46.80222757830497 + ], + [ + 4.150708266299432, + 46.376779548043636 + ], + [ + 4.849510562220403, + 48.07782691958504 + ], + [ + 2.414221416451175, + 48.50876028226318 + ] + ] + ] + }, + "bbox": [ + 1.7921547851933306, + 46.376779548043636, + 4.849510562220403, + 48.50876028226318 + ], + "assets": { + "B2": { + "href": "s3://usgs-landsat/collection02/level-1/standard/oli-tirs/2022/198/027/LC08_L1TP_198027_20221025_20221107_02_T1/LC08_L1TP_198027_20221025_20221107_02_T1_B2.TIF", + "id2": "LC08_L1TP_198027_20221025_20221107_02_T1", + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "title": "Blue Band (B2)" + }, + "B3": { + "href": "s3://usgs-landsat/collection02/level-1/standard/oli-tirs/2022/198/027/LC08_L1TP_198027_20221025_20221107_02_T1/LC08_L1TP_198027_20221025_20221107_02_T1_B3.TIF", + "id2": "LC08_L1TP_198027_20221025_20221107_02_T1", + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "title": "Green Band (B3)" + }, + "B4": { + "href": "s3://usgs-landsat/collection02/level-1/standard/oli-tirs/2022/198/027/LC08_L1TP_198027_20221025_20221107_02_T1/LC08_L1TP_198027_20221025_20221107_02_T1_B4.TIF", + "id2": "LC08_L1TP_198027_20221025_20221107_02_T1", + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "title": "Red Band (B4)" + } + }, + "properties": { + "datetime": "2022-10-25T10:35:40.013503Z", + "view:off_nadir": 0 + } + }, + { + "id": "LC08_L1TP_199027_20221101_20221108_02_T1", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 0.858512670869317, + 48.50841068423348 + ], + [ + 0.23639125871657043, + 46.80195238918447 + ], + [ + 2.595412435299337, + 46.37645380222518 + ], + [ + 3.2943612885583877, + 48.077760990232186 + ], + [ + 0.858512670869317, + 48.50841068423348 + ] + ] + ] + }, + "bbox": [ + 0.23639125871657043, + 46.37645380222518, + 3.2943612885583877, + 48.50841068423348 + ], + "assets": { + "B2": { + "href": "s3://usgs-landsat/collection02/level-1/standard/oli-tirs/2022/199/027/LC08_L1TP_199027_20221101_20221108_02_T1/LC08_L1TP_199027_20221101_20221108_02_T1_B2.TIF", + "id2": "LC08_L1TP_199027_20221101_20221108_02_T1", + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "title": "Blue Band (B2)" + }, + "B3": { + "href": "s3://usgs-landsat/collection02/level-1/standard/oli-tirs/2022/199/027/LC08_L1TP_199027_20221101_20221108_02_T1/LC08_L1TP_199027_20221101_20221108_02_T1_B3.TIF", + "id2": "LC08_L1TP_199027_20221101_20221108_02_T1", + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "title": "Green Band (B3)" + }, + "B4": { + "href": "s3://usgs-landsat/collection02/level-1/standard/oli-tirs/2022/199/027/LC08_L1TP_199027_20221101_20221108_02_T1/LC08_L1TP_199027_20221101_20221108_02_T1_B4.TIF", + "id2": "LC08_L1TP_199027_20221101_20221108_02_T1", + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "title": "Red Band (B4)" + } + }, + "properties": { + "datetime": "2022-11-01T10:41:52.520710Z", + "view:off_nadir": 0 + } + }, + { + "id": "LC09_L1TP_198028_20221102_20230323_02_T1", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 1.909102504047156, + 47.08443966376098 + ], + [ + 1.3125551130299975, + 45.375364754038515 + ], + [ + 3.6191594427192393, + 44.95785419834833 + ], + [ + 4.289291154449699, + 46.66202946589008 + ], + [ + 1.909102504047156, + 47.08443966376098 + ] + ] + ] + }, + "bbox": [ + 1.3125551130299975, + 44.95785419834833, + 4.289291154449699, + 47.08443966376098 + ], + "assets": { + "B2": { + "href": "s3://usgs-landsat/collection02/level-1/standard/oli-tirs/2022/198/028/LC09_L1TP_198028_20221102_20230323_02_T1/LC09_L1TP_198028_20221102_20230323_02_T1_B2.TIF", + "id2": "LC09_L1TP_198028_20221102_20230323_02_T1", + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "title": "Blue Band (B2)" + }, + "B3": { + "href": "s3://usgs-landsat/collection02/level-1/standard/oli-tirs/2022/198/028/LC09_L1TP_198028_20221102_20230323_02_T1/LC09_L1TP_198028_20221102_20230323_02_T1_B3.TIF", + "id2": "LC09_L1TP_198028_20221102_20230323_02_T1", + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "title": "Green Band (B3)" + }, + "B4": { + "href": "s3://usgs-landsat/collection02/level-1/standard/oli-tirs/2022/198/028/LC09_L1TP_198028_20221102_20230323_02_T1/LC09_L1TP_198028_20221102_20230323_02_T1_B4.TIF", + "id2": "LC09_L1TP_198028_20221102_20230323_02_T1", + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "title": "Red Band (B4)" + } + }, + "properties": { + "datetime": "2022-11-02T10:35:51.513949Z", + "view:off_nadir": 0 + } + }, + { + "id": "LC09_L1TP_198027_20230206_20230311_02_T1", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 2.3667383094782544, + 48.505540026652916 + ], + [ + 1.745842356574468, + 46.79875966532977 + ], + [ + 4.112442783811361, + 46.37499827920248 + ], + [ + 4.810787907026006, + 48.07628444541995 + ], + [ + 2.3667383094782544, + 48.505540026652916 + ] + ] + ] + }, + "bbox": [ + 1.745842356574468, + 46.37499827920248, + 4.810787907026006, + 48.505540026652916 + ], + "assets": { + "B2": { + "href": "s3://usgs-landsat/collection02/level-1/standard/oli-tirs/2023/198/027/LC09_L1TP_198027_20230206_20230311_02_T1/LC09_L1TP_198027_20230206_20230311_02_T1_B2.TIF", + "id2": "LC09_L1TP_198027_20230206_20230311_02_T1", + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "title": "Blue Band (B2)" + }, + "B3": { + "href": "s3://usgs-landsat/collection02/level-1/standard/oli-tirs/2023/198/027/LC09_L1TP_198027_20230206_20230311_02_T1/LC09_L1TP_198027_20230206_20230311_02_T1_B3.TIF", + "id2": "LC09_L1TP_198027_20230206_20230311_02_T1", + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "title": "Green Band (B3)" + }, + "B4": { + "href": "s3://usgs-landsat/collection02/level-1/standard/oli-tirs/2023/198/027/LC09_L1TP_198027_20230206_20230311_02_T1/LC09_L1TP_198027_20230206_20230311_02_T1_B4.TIF", + "id2": "LC09_L1TP_198027_20230206_20230311_02_T1", + "type": "image/vnd.stac.geotiff; cloud-optimized=true", + "title": "Red Band (B4)" + } + }, + "properties": { + "datetime": "2023-02-06T10:35:33.461417Z", + "view:off_nadir": 0 + } + } +] \ No newline at end of file