Open rmg55 opened 3 years ago
Yeah, I recall something in the proj
extension about "default" assets. Right now the type on shapes
is List[Tuple]
, specifying the shape of each asset. I wonder if it should be a List[Dict[Tuple]]
, specifying the shape of each band for each asset... I'll need to play with this a bit.
Anyway, agreed that we should have some way to handle this case.
Sounds good @TomAugspurger
I updated the original post to change "target_band" to "target_asset". Quickly glancing at build_vrt, is it currently hardcoded to stac_item["assets"]["image"]
(line 225)?
In the Sentinel Catalog the assets look like:
stac_items[0]['assets'].keys()
dict_keys(['thumbnail', 'overview', 'info', 'metadata', 'visual', 'B01', 'B02', 'B03', 'B04', 'B05', 'B06', 'B07', 'B08', 'B8A', 'B09', 'B11', 'B12', 'AOT', 'WVP', 'SCL'])
Cheers
Thanks. Just a general comment that right now stac-vrt
bakes in many assumptions about the dataset I tested it on (NAIP on Azure: https://github.com/microsoft/AIforEarthDataSets/blob/main/data/naip.md). So we'll need to generalize things to handle that.
The NAIP dataset stores the four bands in a single COG. The AWS Sentinel data looks to have multiple assets (COGs) per STAC item. So we'll need some way of indicating which assets will be put into the VRT.
Right now we have something like
build_vrt(naip_data, asset_key="image")
which returns a VRT that has multiple bands since the COGs have multiple bands. Would a user ever do something like
build_vrt(sentinel_data, asset_key=["B01", "B02"])
which would return a similar VRT (multiple bands) that have been stacked together?
which would return a similar VRT (multiple bands) that have been stacked together?
Looking at gdalbuildvrt
, the -separate
parameter controls this. By default, they're considered tiles in a larger mosaic, but -separate
says to treat them as separate bands.
Now, my imagined use case for stac-vrt
is for creating a large mosaic from many items. But we can apply the same idea here... I guess we would want some kind of VRT of VRTs, where the "inner" VRTs use a -separate
-like behavior to stack the multiple files into separate bands of a single array. Then the outer VRT would mosaic those many VRTs into a single mosaic.
If you have API suggestions it'd be great to hear them. I think my initial preference is to preserve a single build_vrt
function, but expand it to handle the case where bands are split across multiple files. Something like concat_assets=["B02", "B03"]
would build up the inner VRTs? Or maybe even just assets=["B02", "B03"]
. In which case our "current default" would be the hardcoded assets="image"
.
cc @scottyhq, if you have thoughts on the API design as well. Anything we should consider from xarray?
My 2cents would be to use assets=["B02","B03"]
and have a concat=BOOL
. If concat=False, then return a list of vrt files, if True, then return the single multiband file (gdal -separate
version).
Not sure if this is the correct place to put these ideas, but I wanted to mention a few other functionalities that I think would be useful to think about including:
Mosaicing - stitching together a bunch of asset(s)
WarpedVRT
function would probably be really useful for this.Stacking Items: Stack single band asset from a STAC item and place it into a band in a vrt file (gdal -seperate
). This would replace the "band" dimension, with "time". This would not involve any spatial mosaicing. This would need to be a single file per band, because COGs are limited to 3 dimensions (unless there is some way to use the (Multidimensional VRT)[https://gdal.org/drivers/raster/vrt_multidimensional.html#vrt-multidimensional] and have x,y,band,time
dimensions).
Hi @rmg55 and @TomAugspurger . Very excited about the potential for stac-vrt for both mosaicing and stacking! These ideas are all great. A lot of this discussion has come up over in https://github.com/intake/intake-stac/issues/65, and I'm thinking I'll open a draft PR over there to more easily comment on API ideas. I've found working with several available catalogs has been key given how relevant info can be stored in different places via extensions, there is some churn in itemcollections returned by stac-search results (https://github.com/stac-utils/pystac/issues/256#issuecomment-765663796) versus static catalogs, and not everything is implemented yet in pystac (e.g. https://github.com/stac-utils/pystac/issues/132)
cc @scottyhq, if you have thoughts on the API design as well. Anything we should consider from xarray?
In brief, my recommendation would be to keep the keyword arguments in build_vrt() as close as possible to gdal's buildvrt. for example separate=False
by default. I think it's best for starters to focus on the case for data like NAIP, S2, HLS, that is already on a common grid (at least same CRS) to avoid reprojection/WarpedVRT/resampling complications. I've found -resolution='highest'
is also handy for the S2 case where different assets can have different ground-sample-distance. I think that is more natural than the default -resolution=average
...
As for 'asset' or 'asset_key'... 'asset_key' can be anything for better or worse ('B04', 'visual', 'random-name'). One of my favorite features of intake-stac is the mapping between eo:bands common names to assets though. For example it's really nice as a user to say assets=["red","nir"]
and let the code find the matching key.
Thanks @scottyhq great points (especially the -resolution
arg). Will switch over to the other thread, but just wanted to clarify one point:
focus on the case for data like NAIP, S2, HLS, that is already on a common grid (at least same CRS) to avoid reprojection/WarpedVRT/resampling complications
If working in the same tile (and temporally stacking assets), then yes, we should have a consistent CRS. However, if mosaicing, I don't think this is the case . For instance here is an example with the Sentinel STAC where we would probably want to reproject into a geographic projection (e.g. epsg:4326). Perhaps I am missing something her though?
import satsearch
URL='https://earth-search.aws.element84.com/v0'
results = satsearch.Search.search(url=URL,
collections=['sentinel-s2-l2a-cogs'],
bbox=[-110.791,39.783,-50.677,41.870],
datetime='2021-02-27/2021-02-27')
stac_catalog = results.items().geojson()
for assets in stac_items:
print('Projection:',
'epsg:'+str(assets['properties']['proj:epsg']),
'UTM Zone:',
assets['properties']['sentinel:utm_zone'],
'Lat. Band:',
assets['properties']['sentinel:latitude_band'],
'Grid Square:',
assets['properties']['sentinel:grid_square'])
Projection: epsg:32613 UTM Zone: 13 Lat. Band: T Grid Square: DE
Projection: epsg:32613 UTM Zone: 13 Lat. Band: T Grid Square: EE
Projection: epsg:32613 UTM Zone: 13 Lat. Band: T Grid Square: FE
Projection: epsg:32613 UTM Zone: 13 Lat. Band: T Grid Square: GE
Projection: epsg:32614 UTM Zone: 14 Lat. Band: T Grid Square: KK
Projection: epsg:32613 UTM Zone: 13 Lat. Band: T Grid Square: DF
Projection: epsg:32613 UTM Zone: 13 Lat. Band: T Grid Square: EF
Projection: epsg:32613 UTM Zone: 13 Lat. Band: T Grid Square: FF
Projection: epsg:32614 UTM Zone: 14 Lat. Band: T Grid Square: KL
Projection: epsg:32613 UTM Zone: 13 Lat. Band: T Grid Square: GF
Projection: epsg:32613 UTM Zone: 13 Lat. Band: T Grid Square: EG
Projection: epsg:32614 UTM Zone: 14 Lat. Band: T Grid Square: LL
Projection: epsg:32613 UTM Zone: 13 Lat. Band: T Grid Square: FG
Projection: epsg:32614 UTM Zone: 14 Lat. Band: T Grid Square: KM
Projection: epsg:32613 UTM Zone: 13 Lat. Band: T Grid Square: GG
Projection: epsg:32614 UTM Zone: 14 Lat. Band: T Grid Square: LM
Projection: epsg:32615 UTM Zone: 15 Lat. Band: T Grid Square: WE
Projection: epsg:32615 UTM Zone: 15 Lat. Band: T Grid Square: XE
Projection: epsg:32615 UTM Zone: 15 Lat. Band: T Grid Square: YE
Projection: epsg:32616 UTM Zone: 16 Lat. Band: T Grid Square: BK
Projection: epsg:32616 UTM Zone: 16 Lat. Band: T Grid Square: CK
Projection: epsg:32615 UTM Zone: 15 Lat. Band: T Grid Square: WF
Projection: epsg:32615 UTM Zone: 15 Lat. Band: T Grid Square: XF
Projection: epsg:32615 UTM Zone: 15 Lat. Band: T Grid Square: YF
Projection: epsg:32616 UTM Zone: 16 Lat. Band: T Grid Square: BL
Projection: epsg:32616 UTM Zone: 16 Lat. Band: T Grid Square: CL
Projection: epsg:32615 UTM Zone: 15 Lat. Band: T Grid Square: WG
Projection: epsg:32615 UTM Zone: 15 Lat. Band: T Grid Square: XG
Projection: epsg:32615 UTM Zone: 15 Lat. Band: T Grid Square: YG
Projection: epsg:32616 UTM Zone: 16 Lat. Band: T Grid Square: BM
Projection: epsg:32616 UTM Zone: 16 Lat. Band: T Grid Square: CM
Projection: epsg:32617 UTM Zone: 17 Lat. Band: T Grid Square: NE
Projection: epsg:32617 UTM Zone: 17 Lat. Band: T Grid Square: PE
Projection: epsg:32617 UTM Zone: 17 Lat. Band: T Grid Square: QE
Projection: epsg:32618 UTM Zone: 18 Lat. Band: T Grid Square: TK
Projection: epsg:32618 UTM Zone: 18 Lat. Band: T Grid Square: UK
Projection: epsg:32617 UTM Zone: 17 Lat. Band: T Grid Square: NF
Projection: epsg:32617 UTM Zone: 17 Lat. Band: T Grid Square: PF
Projection: epsg:32618 UTM Zone: 18 Lat. Band: T Grid Square: TL
Projection: epsg:32617 UTM Zone: 17 Lat. Band: T Grid Square: QF
Projection: epsg:32618 UTM Zone: 18 Lat. Band: T Grid Square: UL
Projection: epsg:32617 UTM Zone: 17 Lat. Band: T Grid Square: PG
Projection: epsg:32618 UTM Zone: 18 Lat. Band: T Grid Square: VL
Projection: epsg:32617 UTM Zone: 17 Lat. Band: T Grid Square: QG
Projection: epsg:32618 UTM Zone: 18 Lat. Band: T Grid Square: TM
Projection: epsg:32618 UTM Zone: 18 Lat. Band: T Grid Square: UM
Projection: epsg:32618 UTM Zone: 18 Lat. Band: T Grid Square: VM
These are some great features, some random thoughts:
Noting here that I'm creating Items where proj:epsg
, proj:bbox
live at the Item level as a default for any asset, while proj:shape
and proj:transform
may be Item specific.
PySTAC allows you to pull a property from an asset if available, and if not checks the properties of the Item. e.g. in
asset_key: str = ...
item: pystac.Item = ...
bbox: Optional[List[float]] = item.ext.projection.get_bbox(item.assets[asset_key])
bbox
will be pulled from the asset; if the asset doesn't have that property, it's pulled from the item; if the item doesn't have it, it's None.
I don't think resolution : average makes sense, either @scottyhq - not for multispectral satellite data, anyway. Landsat 8 - 30s, 15s and 100s for example.
Reprojection - not necessarily geographic @rmg55 - you could have say Australian or European or South American projected coordinate systems for larger scale areas you may want to use - so possibly an option?
It looks like some catalogs have the transform and shape in the assets (I think that is the correct term) rather than the properties. Probably due to the bands/assets have different resolutions. For instance:
output
``` {'type': 'Feature', 'stac_version': '1.0.0-beta.2', 'stac_extensions': ['eo', 'view', 'proj'], 'id': 'S2A_30VXK_20210305_0_L2A', 'bbox': [-0.29262403233012585, 58.46076357917498, 0.6099851126869734, 58.612152234653784], 'geometry': {'type': 'Polygon', 'coordinates': [[[0.5967195657827756, 58.46076357917498], [-0.2908733182739893, 58.61116185262608], [-0.29262403233012585, 58.612152234653784], [0.6099851126869734, 58.58996039069121], [0.5967195657827756, 58.46076357917498]]]}, 'properties': {'datetime': '2021-03-05T11:24:56Z', 'platform': 'sentinel-2a', 'constellation': 'sentinel-2', 'instruments': ['msi'], 'gsd': 10, 'view:off_nadir': 0, 'proj:epsg': 32630, 'sentinel:utm_zone': 30, 'sentinel:latitude_band': 'V', 'sentinel:grid_square': 'XK', 'sentinel:sequence': '0', 'sentinel:product_id': 'S2A_MSIL2A_20210305T112111_N0214_R037_T30VXK_20210305T121545', 'sentinel:data_coverage': 3.1, 'eo:cloud_cover': 4.65, 'sentinel:valid_cloud_cover': True, 'created': '2021-03-05T18:17:22.360Z', 'updated': '2021-03-05T18:17:22.360Z'}, 'collection': 'sentinel-s2-l2a-cogs', 'assets': {'thumbnail': {'title': 'Thumbnail', 'type': 'image/png', 'roles': ['thumbnail'], 'href': 'https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/30/V/XK/2021/3/5/0/preview.jpg'}, 'overview': {'title': 'True color image', 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 'roles': ['overview'], 'gsd': 10, 'eo:bands': [{'name': 'B04', 'common_name': 'red', 'center_wavelength': 0.6645, 'full_width_half_max': 0.038}, {'name': 'B03', 'common_name': 'green', 'center_wavelength': 0.56, 'full_width_half_max': 0.045}, {'name': 'B02', 'common_name': 'blue', 'center_wavelength': 0.4966, 'full_width_half_max': 0.098}], 'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/XK/2021/3/S2A_30VXK_20210305_0_L2A/L2A_PVI.tif', 'proj:shape': [343, 343], 'proj:transform': [320, 0, 600000, 0, -320, 6500040, 0, 0, 1]}, 'info': {'title': 'Original JSON metadata', 'type': 'application/json', 'roles': ['metadata'], 'href': 'https://roda.sentinel-hub.com/sentinel-s2-l2a/tiles/30/V/XK/2021/3/5/0/tileInfo.json'}, 'metadata': {'title': 'Original XML metadata', 'type': 'application/xml', 'roles': ['metadata'], 'href': 'https://roda.sentinel-hub.com/sentinel-s2-l2a/tiles/30/V/XK/2021/3/5/0/metadata.xml'}, 'visual': {'title': 'True color image', 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 'roles': ['overview'], 'gsd': 10, 'eo:bands': [{'name': 'B04', 'common_name': 'red', 'center_wavelength': 0.6645, 'full_width_half_max': 0.038}, {'name': 'B03', 'common_name': 'green', 'center_wavelength': 0.56, 'full_width_half_max': 0.045}, {'name': 'B02', 'common_name': 'blue', 'center_wavelength': 0.4966, 'full_width_half_max': 0.098}], 'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/XK/2021/3/S2A_30VXK_20210305_0_L2A/TCI.tif', 'proj:shape': [10980, 10980], 'proj:transform': [10, 0, 600000, 0, -10, 6500040, 0, 0, 1]}, 'B01': {'title': 'Band 1 (coastal)', 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 'roles': ['data'], 'gsd': 60, 'eo:bands': [{'name': 'B01', 'common_name': 'coastal', 'center_wavelength': 0.4439, 'full_width_half_max': 0.027}], 'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/XK/2021/3/S2A_30VXK_20210305_0_L2A/B01.tif', 'proj:shape': [1830, 1830], 'proj:transform': [60, 0, 600000, 0, -60, 6500040, 0, 0, 1]}, 'B02': {'title': 'Band 2 (blue)', 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 'roles': ['data'], 'gsd': 10, 'eo:bands': [{'name': 'B02', 'common_name': 'blue', 'center_wavelength': 0.4966, 'full_width_half_max': 0.098}], 'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/XK/2021/3/S2A_30VXK_20210305_0_L2A/B02.tif', 'proj:shape': [10980, 10980], 'proj:transform': [10, 0, 600000, 0, -10, 6500040, 0, 0, 1]}, 'B03': {'title': 'Band 3 (green)', 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 'roles': ['data'], 'gsd': 10, 'eo:bands': [{'name': 'B03', 'common_name': 'green', 'center_wavelength': 0.56, 'full_width_half_max': 0.045}], 'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/XK/2021/3/S2A_30VXK_20210305_0_L2A/B03.tif', 'proj:shape': [10980, 10980], 'proj:transform': [10, 0, 600000, 0, -10, 6500040, 0, 0, 1]}, 'B04': {'title': 'Band 4 (red)', 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 'roles': ['data'], 'gsd': 10, 'eo:bands': [{'name': 'B04', 'common_name': 'red', 'center_wavelength': 0.6645, 'full_width_half_max': 0.038}], 'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/XK/2021/3/S2A_30VXK_20210305_0_L2A/B04.tif', 'proj:shape': [10980, 10980], 'proj:transform': [10, 0, 600000, 0, -10, 6500040, 0, 0, 1]}, 'B05': {'title': 'Band 5', 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 'roles': ['data'], 'gsd': 20, 'eo:bands': [{'name': 'B05', 'center_wavelength': 0.7039, 'full_width_half_max': 0.019}], 'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/XK/2021/3/S2A_30VXK_20210305_0_L2A/B05.tif', 'proj:shape': [5490, 5490], 'proj:transform': [20, 0, 600000, 0, -20, 6500040, 0, 0, 1]}, 'B06': {'title': 'Band 6', 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 'roles': ['data'], 'gsd': 20, 'eo:bands': [{'name': 'B06', 'center_wavelength': 0.7402, 'full_width_half_max': 0.018}], 'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/XK/2021/3/S2A_30VXK_20210305_0_L2A/B06.tif', 'proj:shape': [5490, 5490], 'proj:transform': [20, 0, 600000, 0, -20, 6500040, 0, 0, 1]}, 'B07': {'title': 'Band 7', 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 'roles': ['data'], 'gsd': 20, 'eo:bands': [{'name': 'B07', 'center_wavelength': 0.7825, 'full_width_half_max': 0.028}], 'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/XK/2021/3/S2A_30VXK_20210305_0_L2A/B07.tif', 'proj:shape': [5490, 5490], 'proj:transform': [20, 0, 600000, 0, -20, 6500040, 0, 0, 1]}, 'B08': {'title': 'Band 8 (nir)', 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 'roles': ['data'], 'gsd': 10, 'eo:bands': [{'name': 'B08', 'common_name': 'nir', 'center_wavelength': 0.8351, 'full_width_half_max': 0.145}], 'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/XK/2021/3/S2A_30VXK_20210305_0_L2A/B08.tif', 'proj:shape': [10980, 10980], 'proj:transform': [10, 0, 600000, 0, -10, 6500040, 0, 0, 1]}, 'B8A': {'title': 'Band 8A', 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 'roles': ['data'], 'gsd': 20, 'eo:bands': [{'name': 'B8A', 'center_wavelength': 0.8648, 'full_width_half_max': 0.033}], 'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/XK/2021/3/S2A_30VXK_20210305_0_L2A/B8A.tif', 'proj:shape': [5490, 5490], 'proj:transform': [20, 0, 600000, 0, -20, 6500040, 0, 0, 1]}, 'B09': {'title': 'Band 9', 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 'roles': ['data'], 'gsd': 60, 'eo:bands': [{'name': 'B09', 'center_wavelength': 0.945, 'full_width_half_max': 0.026}], 'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/XK/2021/3/S2A_30VXK_20210305_0_L2A/B09.tif', 'proj:shape': [1830, 1830], 'proj:transform': [60, 0, 600000, 0, -60, 6500040, 0, 0, 1]}, 'B11': {'title': 'Band 11 (swir16)', 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 'roles': ['data'], 'gsd': 20, 'eo:bands': [{'name': 'B11', 'common_name': 'swir16', 'center_wavelength': 1.6137, 'full_width_half_max': 0.143}], 'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/XK/2021/3/S2A_30VXK_20210305_0_L2A/B11.tif', 'proj:shape': [5490, 5490], 'proj:transform': [20, 0, 600000, 0, -20, 6500040, 0, 0, 1]}, 'B12': {'title': 'Band 12 (swir22)', 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 'roles': ['data'], 'gsd': 20, 'eo:bands': [{'name': 'B12', 'common_name': 'swir22', 'center_wavelength': 2.22024, 'full_width_half_max': 0.242}], 'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/XK/2021/3/S2A_30VXK_20210305_0_L2A/B12.tif', 'proj:shape': [5490, 5490], 'proj:transform': [20, 0, 600000, 0, -20, 6500040, 0, 0, 1]}, 'AOT': {'title': 'Aerosol Optical Thickness (AOT)', 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 'roles': ['data'], 'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/XK/2021/3/S2A_30VXK_20210305_0_L2A/AOT.tif', 'proj:shape': [1830, 1830], 'proj:transform': [60, 0, 600000, 0, -60, 6500040, 0, 0, 1]}, 'WVP': {'title': 'Water Vapour (WVP)', 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 'roles': ['data'], 'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/XK/2021/3/S2A_30VXK_20210305_0_L2A/WVP.tif', 'proj:shape': [10980, 10980], 'proj:transform': [10, 0, 600000, 0, -10, 6500040, 0, 0, 1]}, 'SCL': {'title': 'Scene Classification Map (SCL)', 'type': 'image/tiff; application=geotiff; profile=cloud-optimized', 'roles': ['data'], 'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/XK/2021/3/S2A_30VXK_20210305_0_L2A/SCL.tif', 'proj:shape': [5490, 5490], 'proj:transform': [20, 0, 600000, 0, -20, 6500040, 0, 0, 1]}}, 'links': [{'rel': 'self', 'href': 'https://earth-search.aws.element84.com/v0/collections/sentinel-s2-l2a-cogs/items/S2A_30VXK_20210305_0_L2A'}, {'rel': 'canonical', 'href': 'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/XK/2021/3/S2A_30VXK_20210305_0_L2A/S2A_30VXK_20210305_0_L2A.json', 'type': 'application/json'}, {'title': 'sentinel-s2-l2a-aws/workflow-publish-sentinel/tiles-30-V-XK-2021-3-5-0', 'rel': 'via-cirrus', 'href': 'https://cirrus-earth-search.aws.element84.com/v0/catid/sentinel-s2-l2a-aws/workflow-publish-sentinel/tiles-30-V-XK-2021-3-5-0'}, {'title': 'Source STAC Item', 'rel': 'derived_from', 'href': 'https://cirrus-v0-data-1qm7gekzjucbq.s3.us-west-2.amazonaws.com/sentinel-s2-l2a/30/V/XK/2021/3/S2A_30VXK_20210305_0_L2A/S2A_30VXK_20210305_0_L2A.json', 'type': 'application/json'}, {'title': 'sentinel-s2-l2a/workflow-cog-archive/S2A_30VXK_20210305_0_L2A', 'rel': 'via-cirrus', 'href': 'https://cirrus-earth-search.aws.element84.com/v0/catid/sentinel-s2-l2a/workflow-cog-archive/S2A_30VXK_20210305_0_L2A'}, {'rel': 'parent', 'href': 'https://earth-search.aws.element84.com/v0/collections/sentinel-s2-l2a-cogs'}, {'rel': 'collection', 'href': 'https://earth-search.aws.element84.com/v0/collections/sentinel-s2-l2a-cogs'}, {'rel': 'root', 'href': 'https://earth-search.aws.element84.com/v0/'}]} ```Perhaps adding a "target_asset" keyword to build_vrt, then update the logic on shapes/transform to somthing like:
Happy to work up a PR if this seems like a good route.