STAC Items for raster cases¶
Some consumers cannot read a file. stackstac.stack and odc.stac.load both
take STAC Items, so pointing either of them at a bundled GeoTIFF requires an
adapter first. geocase.stac is that adapter.
from geocase.stac import item_for_case, items_for_cases
item = item_for_case("dem_small") # one Item, as a plain dict
items = items_for_cases(category="raster") # every raster case, in catalog order
Output is a plain dict, not a pystac.Item. A STAC Item is JSON, and
requiring pystac to produce one would put a dependency between this catalog
and every consumer that does not use it. pystac.Item.from_dict accepts what
this emits.
Why this ships instead of being an example¶
A validation run on 2026-08-31 read the corpus with six consumers. Two of them
needed Items, so the run synthesised one per raster case with
rio_stac.create_stac_item — and that synthesiser was wrong for
bottom_up_dem_small, writing an inverted proj:bbox (south greater than
north) because it trusted the transform's row order on a bottom-up affine.
The resulting Item is invalid. A consumer that trusts it computes an empty or inverted intersection and reports nothing, which reads as the case is fine. Every user who writes that adapter hits the same bug, and most misattribute it to the consumer. So the catalog owns it.
Three things the hand-built version got wrong¶
Both proj:epsg and proj:code¶
The projection extension renamed the key in v2.0. stackstac reads only
proj:epsg; pystac ≥ 1.13 rewrites it to proj:code on from_dict. An
adapter emitting one of them silently excludes a consumer — no error, just a
grid derived from nothing.
Emitting both is spec-legal, and it is the only way one Item serves both:
properties = item_for_case("dem_small")["properties"]
properties["proj:epsg"] # 32633
properties["proj:code"] # "EPSG:32633"
A normalised proj:bbox¶
Every bbox this module emits is [west, south, east, north] with min before
max on both axes, regardless of what the file's affine implies. That is the
bottom_up_dem_small fix, and it is gated for the north-up, bottom-up and
rotated cases in tests/unit/test_stac.py.
Per-band and whole-file assets¶
The two consumers disagree about what an asset is. stackstac's model is one
band per asset, and it refuses a multi-band raster; odc-stac reads the whole
file from one asset. Both shapes are reachable, so the difference is a choice
your harness records rather than a failure it trips over:
assets= |
Shape | Suits |
|---|---|---|
"whole_file" (default) |
one data asset naming the file |
odc-stac |
"per_band" |
one asset per band, each with band_index |
stackstac |
"both" |
the file asset and the band assets | either, from one Item |
item = item_for_case("geotiff_multiband_small", assets="per_band")
sorted(asset["band_index"] for asset in item["assets"].values()) # [1, 2, 3]
Hrefs¶
href_style="file_url" (the default) emits a file:// URI, which is what
pystac and stackstac expect. href_style="path" emits a bare filesystem path,
for the consumers that mishandle file://.
Groups of Items¶
items_for_cases is the reason the overlap
group exists. Both libraries
take a sequence of Items and what happens across that sequence is their whole
reason for existing — stacking order, mosaic compositing, band-alias
resolution. Until the group landed, the corpus could only ever hand them a list
of one.
items = items_for_cases(
include_ids=[
"overlap_group_north",
"overlap_group_centre",
"overlap_group_south",
],
assets="per_band",
)
Two of those three declare common_name: red — the ordinary Sentinel-2 shape —
so a consumer resolving the alias silently to the first candidate is visible
rather than invisible.
Non-raster cases in a selection are skipped, not an error. A sweep helper that dies on the first vector case cannot be pointed at the corpus, which is the only thing anyone wants to point it at.
Related docs¶
- Differential testing — the harness these Items feed
- Case discovery — the selectors
items_for_casesforwards to - Dataset catalog — what is in the raster set