Problem
Per-tile S1 RTC GeoZarr cubes store one acquisition per time slice (per orbit group, with overview levels r10m…r720m). The per-acquisition STAC items render their preview via titiler using sel=time={INTEGER index} — a positional index into the time axis.
This is fragile and not scalable:
- Cubes go non-monotonic.
ingest_s1tiling_acquisition (src/eopf_geozarr/conversion/s1_ingest.py:634-680) always appends a new acquisition at the physical end. A cross-run append of an earlier-dated scene therefore pushes the time axis out of chronological order.
- Once non-monotonic, titiler fails to render the out-of-order slice (HTTP 400), so the per-acquisition item has no preview.
- Keeping positional indices correct would require reordering the cube data and re-registering every item on every append — O(N) data rewrite + O(N) item churn per new acquisition.
Evidence
- Tile 31TEH, descending group: time axis is
[idx0 = 2026-06-08T05:43:23, idx1 = 2026-06-07T05:52:23] — decreasing.
- Item
s1-rtc-31TEH-20260607t055223 (idx1) renders HTTP 400 "Could not find any valid variables in '/descending:vv'", while idx0 renders fine.
- The idx1 slice is not empty (100% finite, real backscatter) — so it is purely an ordering/selection problem, not missing data.
- Root metadata gap:
…/descending/r10m/time/zarr.json has empty attributes — the time array is a bare int64 with no CF datetime metadata, so readers can't treat it as a datetime index.
Root cause
time arrays carry no CF datetime encoding (units/calendar), so xarray/titiler can't expose them as a datetime index — the time dimension falls back to an integer index (which is why sel=time=0 works but sel=time=<datetime> 500s).
ingest_s1tiling_acquisition appends at the physical end with no chronological guarantee.
Proposed resolution — render by datetime, not index
The deployed titiler v0.5.0 already does label-based selection: da.sel({dim: value}, method=method) (titiler/eopf/reader.py:589,597) with a method::value DSL. The only gap is the cube metadata.
Encode time as a CF datetime coordinate (units = "nanoseconds since 1970-01-01", calendar = "proleptic_gregorian", standard_name = "time"; values unchanged). Then each per-acquisition item can render via sel=time={its own acquisition datetime}. An exact-label .sel(time=<datetime>) works even on a non-monotonic axis (only nearest/slice need monotonicity), so:
- No cube data reorder, ever.
- Append a new acquisition → register only that one item with its datetime; existing items untouched.
- Order-immune and O(1) per append.
Tracking checklist
Fallback if the spike fails: sort the cube on append (reorder + re-register) — kept as a backup approach only.
Problem
Per-tile S1 RTC GeoZarr cubes store one acquisition per
timeslice (per orbit group, with overview levels r10m…r720m). The per-acquisition STAC items render their preview via titiler usingsel=time={INTEGER index}— a positional index into the time axis.This is fragile and not scalable:
ingest_s1tiling_acquisition(src/eopf_geozarr/conversion/s1_ingest.py:634-680) always appends a new acquisition at the physical end. A cross-run append of an earlier-dated scene therefore pushes the time axis out of chronological order.Evidence
[idx0 = 2026-06-08T05:43:23, idx1 = 2026-06-07T05:52:23]— decreasing.s1-rtc-31TEH-20260607t055223(idx1) renders HTTP 400"Could not find any valid variables in '/descending:vv'", while idx0 renders fine.…/descending/r10m/time/zarr.jsonhas emptyattributes— thetimearray is a bareint64with no CF datetime metadata, so readers can't treat it as a datetime index.Root cause
timearrays carry no CF datetime encoding (units/calendar), so xarray/titiler can't expose them as a datetime index — thetimedimension falls back to an integer index (which is whysel=time=0works butsel=time=<datetime>500s).ingest_s1tiling_acquisitionappends at the physical end with no chronological guarantee.Proposed resolution — render by datetime, not index
The deployed titiler v0.5.0 already does label-based selection:
da.sel({dim: value}, method=method)(titiler/eopf/reader.py:589,597) with amethod::valueDSL. The only gap is the cube metadata.Encode
timeas a CF datetime coordinate (units = "nanoseconds since 1970-01-01",calendar = "proleptic_gregorian",standard_name = "time"; values unchanged). Then each per-acquisition item can render viasel=time={its own acquisition datetime}. An exact-label.sel(time=<datetime>)works even on a non-monotonic axis (onlynearest/slice need monotonicity), so:Tracking checklist
selrenders the 31TEH 06-07 slice, correct, on the non-monotonic axis)time(create/append/conditions paths) + testssel=time={datetime}+ incremental registrationtimeattrs) + one-time item re-registrationFallback if the spike fails: sort the cube on append (reorder + re-register) — kept as a backup approach only.