1212
1313import httpx
1414import zarr
15- from pystac import Item , Link
15+ from pystac import Asset , Item , Link
1616from pystac .extensions .projection import ProjectionExtension
1717from pystac_client import Client
1818
@@ -139,17 +139,14 @@ def add_visualization_links(item: Item, raster_base: str, collection_id: str) ->
139139 )
140140 )
141141 elif coll_lower .startswith (("sentinel-2" , "sentinel2" )):
142- # S2: True color quicklook
143- var_path = "/quality/l2a_quicklook/r10m:tci"
144- query = (
145- f"variables={ urllib .parse .quote (var_path , safe = '' )} &bidx=1&bidx=2&bidx=3&assets=TCI_10m"
146- )
142+ # S2: True color from reflectance bands (B04=Red, B03=Green, B02=Blue)
143+ query = "rescale=0%2C1&color_formula=gamma+rgb+1.3%2C+sigmoidal+rgb+6+0.1%2C+saturation+1.2&variables=%2Fmeasurements%2Freflectance%2Fr60m%3Ab04&variables=%2Fmeasurements%2Freflectance%2Fr60m%3Ab03&variables=%2Fmeasurements%2Freflectance%2Fr60m%3Ab02&bidx=1"
147144 item .add_link (
148145 Link (
149146 "xyz" ,
150147 f"{ base_url } /tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}.png?{ query } " ,
151148 "image/png" ,
152- "Sentinel-2 L2A True Color" ,
149+ "Sentinel-2 L2A True Color (60m) " ,
153150 )
154151 )
155152 item .add_link (
@@ -171,6 +168,82 @@ def add_visualization_links(item: Item, raster_base: str, collection_id: str) ->
171168 )
172169
173170
171+ def add_thumbnail_asset (item : Item , raster_base : str , collection_id : str ) -> None :
172+ """Add thumbnail preview asset for STAC browsers."""
173+ if "thumbnail" in item .assets :
174+ return
175+
176+ base_url = f"{ raster_base } /collections/{ collection_id } /items/{ item .id } "
177+ coll_lower = collection_id .lower ()
178+
179+ # Mission-specific thumbnail parameters
180+ if coll_lower .startswith (("sentinel-2" , "sentinel2" )):
181+ params = "format=png&rescale=0%2C1&color_formula=gamma+rgb+1.3%2C+sigmoidal+rgb+6+0.1%2C+saturation+1.2&variables=%2Fmeasurements%2Freflectance%2Fr60m%3Ab04&variables=%2Fmeasurements%2Freflectance%2Fr60m%3Ab03&variables=%2Fmeasurements%2Freflectance%2Fr60m%3Ab02&bidx=1"
182+ title = "Sentinel-2 L2A True Color (60m)"
183+ elif coll_lower .startswith (("sentinel-1" , "sentinel1" )):
184+ # Use VH band for S-1 thumbnail
185+ if (vh := item .assets .get ("vh" )) and ".zarr/" in (vh .href or "" ):
186+ var_path = f"/{ vh .href .split ('.zarr/' )[1 ]} :grd"
187+ params = f"format=png&variables={ urllib .parse .quote (var_path , safe = '' )} &bidx=1&rescale=0%2C219&assets=vh"
188+ title = "Sentinel-1 GRD VH Preview"
189+ else :
190+ logger .debug ("No VH asset found for S-1 thumbnail" )
191+ return
192+ else :
193+ logger .debug (f"Unknown mission for thumbnail: { collection_id } " )
194+ return
195+
196+ thumbnail = Asset (
197+ href = f"{ base_url } /preview?{ params } " ,
198+ media_type = "image/png" ,
199+ roles = ["thumbnail" ],
200+ title = title ,
201+ )
202+ item .add_asset ("thumbnail" , thumbnail )
203+ logger .debug (f"Added thumbnail asset: { title } " )
204+
205+
206+ def add_derived_from_link (item : Item , source_url : str ) -> None :
207+ """Add derived_from link pointing to original source item."""
208+ # Remove existing derived_from links to avoid duplicates
209+ item .links = [link for link in item .links if link .rel != "derived_from" ]
210+
211+ item .add_link (
212+ Link (
213+ "derived_from" ,
214+ source_url ,
215+ "application/json" ,
216+ "Derived from original Zarr STAC item" ,
217+ )
218+ )
219+ logger .debug (f"Added derived_from link: { source_url } " )
220+
221+
222+ def remove_xarray_integration (item : Item ) -> None :
223+ """Remove XArray-specific fields from assets (ADR-111 compliance)."""
224+ removed_count = 0
225+ for asset in item .assets .values ():
226+ if hasattr (asset , "extra_fields" ):
227+ # Remove xarray-specific fields
228+ if asset .extra_fields .pop ("xarray:open_dataset_kwargs" , None ):
229+ removed_count += 1
230+ if asset .extra_fields .pop ("xarray:open_datatree_kwargs" , None ):
231+ removed_count += 1
232+
233+ # Remove alternate xarray configurations
234+ if "alternate" in asset .extra_fields and isinstance (
235+ asset .extra_fields ["alternate" ], dict
236+ ):
237+ if asset .extra_fields ["alternate" ].pop ("xarray" , None ):
238+ removed_count += 1
239+ # Remove empty alternate section
240+ if not asset .extra_fields ["alternate" ]:
241+ asset .extra_fields .pop ("alternate" )
242+
243+ if removed_count > 0 :
244+ logger .debug (f"Removed { removed_count } XArray integration field(s)" )
245+
246+
174247# === Registration Workflow ===
175248
176249
@@ -233,11 +306,20 @@ def run_registration(
233306 # 3. Add projection metadata from zarr
234307 add_projection_from_zarr (item )
235308
236- # 4. Add visualization links (viewer, xyz, tilejson)
309+ # 4. Remove XArray integration fields (ADR-111 compliance)
310+ remove_xarray_integration (item )
311+
312+ # 5. Add visualization links (viewer, xyz, tilejson)
237313 add_visualization_links (item , raster_api_url , collection )
238314 logger .info (" 🎨 Added visualization links" )
239315
240- # 5. Register to STAC API
316+ # 6. Add thumbnail asset for STAC browsers
317+ add_thumbnail_asset (item , raster_api_url , collection )
318+
319+ # 7. Add derived_from link to source item
320+ add_derived_from_link (item , source_url )
321+
322+ # 8. Register to STAC API
241323 client = Client .open (stac_api_url )
242324 upsert_item (client , collection , item )
243325
0 commit comments