Skip to content

Commit

Permalink
Merge pull request #163 from stac-utils/feature/add-render-support
Browse files Browse the repository at this point in the history
add render extension support
  • Loading branch information
vincentsarago authored May 16, 2024
2 parents 684d7cb + c79c43c commit a1ace2f
Show file tree
Hide file tree
Showing 14 changed files with 390 additions and 75 deletions.
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
* update `titiler.pgstac.model.Link` to match the OGC specification
* use `{tileMatrixSetId}` in templated URL links
* add support for `render` and `item-assets` STAC Collection extensions
* add `/info` endpoint to the `Collections` endpoints
* add `/collections` and `/collections/{collection_id}` endpoints when `TITILER_PGSTAC_API_DEBUG=TRUE`
## 1.2.3 (2024-03-25)
Expand Down
6 changes: 3 additions & 3 deletions docs/src/advanced/custom_tilejson.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,16 +135,16 @@ class MosaicTilerFactory(TitilerPgSTACFactory.MosaicTilerFactory):
]

if layer:
config = search_info.defaults.get(layer)
config = search_info.metadata.defaults_params.get(layer)
if not config:
raise HTTPException(status_code=404, detail=f"Invalid {layer} configuration.")

# This assume the default configuration follows the endpoint expected format
# as `"true_color": [("assets", "B4"), ("assets", "B3"), ("assets", "B2")]`
# as `"true_color": {"assets": ["B4", "B3", "B2"]}`
qs = QueryParams(config)

if qs:
tiles_url += f"?{urlencode(qs)}"
tiles_url += f"?{urlencode(qs, doseq=True)}"

minzoom = _first_value([minzoom, search_info.metadata.minzoom], tms.minzoom)
maxzoom = _first_value([maxzoom, search_info.metadata.maxzoom], tms.maxzoom)
Expand Down
2 changes: 1 addition & 1 deletion docs/src/advanced/metadata.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
},
"ndvi": {
"expression": "(B4-B3)/(B4+B3)",
"rescale": "-1,1",
"rescale": [[-1, 1]],
"colormap_name": "viridis"
}
}
Expand Down
4 changes: 3 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

DATA_DIR = os.path.join(os.path.dirname(__file__), "fixtures")
collection = os.path.join(DATA_DIR, "noaa-emergency-response.json")
collection_maxar = os.path.join(DATA_DIR, "maxar_BayOfBengal.json")
items = os.path.join(DATA_DIR, "noaa-eri-nashville2020.json")

test_db = pytest_pgsql.TransactedPostgreSQLTestDB.create_fixture(
Expand Down Expand Up @@ -67,14 +68,15 @@ def database_url(test_db):
print("Load items and collection into PgSTAC")
loader = Loader(db=db)
loader.load_collections(collection)
loader.load_collections(collection_maxar)
loader.load_items(items)

# Make sure we have 1 collection and 163 items in pgstac
with psycopg.connect(str(test_db.connection.engine.url)) as conn:
with conn.cursor() as cur:
cur.execute("SELECT COUNT(*) FROM pgstac.collections")
val = cur.fetchone()[0]
assert val == 1
assert val == 2

cur.execute("SELECT COUNT(*) FROM pgstac.items")
val = cur.fetchone()[0]
Expand Down
133 changes: 133 additions & 0 deletions tests/fixtures/maxar_BayOfBengal.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
{
"id": "MAXAR_BayofBengal_Cyclone_Mocha_May_23",
"type": "Collection",
"links": [],
"title": "Bay of Bengal Cyclone Mocha 2023",
"extent": {
"spatial": {
"bbox": [
[
91.831615,
19.982078842323997,
92.97426268500965,
21.666101
],
[
92.567815,
20.18811887678192,
92.74417544237298,
20.62968532404085
],
[
92.72278776887262,
20.104801,
92.893524,
20.630214
],
[
92.75855246040959,
19.982078842323997,
92.89682495377032,
20.514473160464657
],
[
92.84253515935835,
19.984656587012033,
92.97426268500965,
20.514418665444474
],
[
91.831615,
21.518411,
91.957078,
21.666101
]
]
},
"temporal": {
"interval": [
[
"2023-01-03T04:30:17Z",
"2023-05-22T04:35:25Z"
]
]
}
},
"license": "CC-BY-NC-4.0",
"renders": {
"visual": {
"title": "Visual Image",
"assets": [
"visual"
],
"asset_bidx": ["visual|1,2,3"],
"minmax_zoom": [
8,
22
],
"tilematrixsets": {
"WebMercatorQuad": [
8,
22
]
}
},
"color": {
"title": "Colored Image",
"assets": [
"visual"
],
"asset_bidx": ["visual|1"],
"colormap": {
"1": [0, 0, 0, 255],
"1000": [255, 255, 255, 255]
}
},
"visualr": {
"title": "Rescaled Image",
"assets": [
"visual"
],
"asset_bidx": ["visual|1"],
"rescale": [
[0, 100]
]
}
},
"description": "Maxar OpenData | Cyclone Mocha, a category five cyclone with 130 mph winds and torrential rain, hit parts of Myanmar and Bangladesh, forcing mass evacuations ahead of the storm. The cyclone, one of the most powerful to hit the region in the last decade, made landfall on Sunday, May 14, 2023, near Sittwe in Myanmar's Rakhine state. Rain and a storm surge caused widespread flooding in low-lying areas. The United National Office Coordination of Humanitarian Affairs stated that there had been extensive damage among already vulnerable communities and that communications with the affected areas have been difficult.",
"item_assets": {
"visual": {
"type": "image/tiff; application=geotiff; profile=cloud-optimized",
"roles": [
"visual"
],
"title": "Visual Image"
},
"data-mask": {
"type": "application/geopackage+sqlite3",
"roles": [
"data-mask"
],
"title": "Data Mask"
},
"ms_analytic": {
"type": "image/tiff; application=geotiff; profile=cloud-optimized",
"roles": [
"data"
],
"title": "Multispectral Image"
},
"pan_analytic": {
"type": "image/tiff; application=geotiff; profile=cloud-optimized",
"roles": [
"data"
],
"title": "Panchromatic Image"
}
},
"stac_version": "1.0.0",
"stac_extensions": [
"https://stac-extensions.github.io/item-assets/v1.0.0/schema.json",
"https://stac-extensions.github.io/render/v1.0.0/schema.json"
]
}
31 changes: 30 additions & 1 deletion tests/fixtures/noaa-emergency-response.json
Original file line number Diff line number Diff line change
@@ -1 +1,30 @@
{"id":"noaa-emergency-response", "title": "NOAA Emergency Response Imagery", "description":"NOAA Emergency Response Imagery hosted on AWS Public Dataset.","stac_version":"1.0.0","license":"public-domain","links":[],"extent":{"spatial":{"bbox":[[-180,-90,180,90]]},"temporal":{"interval":[["2005-01-01T00:00:00Z",null]]}}}
{
"id": "noaa-emergency-response",
"type": "Collection",
"title": "NOAA Emergency Response Imagery",
"description": "NOAA Emergency Response Imagery hosted on AWS Public Dataset.",
"stac_version": "1.0.0",
"license": "public-domain",
"links": [],
"extent": {
"spatial": {
"bbox": [
[
-180,
-90,
180,
90
]
]
},
"temporal": {
"interval": [
[
"2005-01-01T00:00:00Z",
null
]
]
}
},
"stac_extensions": []
}
29 changes: 29 additions & 0 deletions tests/test_collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -518,3 +518,32 @@ def test_query_point_collections(app):
)

assert response.status_code == 204 # (no content)


def test_collections_render(app, tmp_path):
"""Create wmts document."""
response = app.get(
"/collections/MAXAR_BayofBengal_Cyclone_Mocha_May_23/WebMercatorQuad/WMTSCapabilities.xml"
)
assert response.status_code == 200
assert response.headers["content-type"] == "application/xml"

wmts = tmp_path / "WMTSCapabilities.xml"
with open(wmts, "wb") as f:
f.write(response.content)

# Validate it's a good WMTS
with rasterio.open(wmts) as src:
assert not src.crs
assert src.profile["driver"] == "WMTS"
assert len(src.subdatasets) == 3
assert ["color", "visual", "visualr"] == [
s.split(",layer=")[1] for s in src.subdatasets
]
with rasterio.open(src.subdatasets[0]) as sub:
assert sub.crs == CRS.from_epsg(3857)
assert sub.profile["driver"] == "WMTS"

response = app.get("/collections/MAXAR_BayofBengal_Cyclone_Mocha_May_23/info")
assert response.status_code == 200
assert len(response.json()["links"]) == 10 # self, tilejson (4), map (4), wmts (1)
42 changes: 23 additions & 19 deletions tests/test_searches.py
Original file line number Diff line number Diff line change
Expand Up @@ -552,16 +552,28 @@ def test_query_with_metadata(app):
"maxzoom": 2,
"defaults": {
"one_band": {
"assets": "cog",
"asset_bidx": "cog|1",
"assets": ["cog"],
"asset_bidx": ["cog|1"],
},
"three_bands": {
"assets": "cog",
"asset_bidx": "cog|1,2,3",
"assets": ["cog"],
"asset_bidx": ["cog|1,2,3"],
},
"rescale": {
"assets": ["cog"],
"asset_bidx": ["cog|1"],
"rescale": [
[-1, 1],
],
},
"colormap": {
"assets": ["cog"],
"asset_bidx": ["cog|1"],
"colormap": {"1": [0, 0, 0, 255], "1000": [255, 255, 255, 255]},
},
# missing `assets`
"bad_layer": {
"asset_bidx": "cog|1,2,3",
"asset_bidx": ["cog|1,2,3"],
},
},
},
Expand All @@ -572,9 +584,7 @@ def test_query_with_metadata(app):
assert response.status_code == 200
resp = response.json()
assert resp["id"]
assert (
len(resp["links"]) == 6
) # info, tilejson, map, wmts tilejson for one_band, tilejson for three_bands
assert len(resp["links"]) == 8 # info, tilejson, map, wmts, tilejson layers
link = resp["links"][-2]

mosaic_id_metadata = resp["id"]
Expand Down Expand Up @@ -616,9 +626,7 @@ def test_query_with_metadata(app):

with rasterio.open(io.BytesIO(response.content)) as src:
assert src.profile["driver"] == "WMTS"
assert len(src.subdatasets) == 2
assert src.subdatasets[0].endswith(",layer=one_band")
assert src.subdatasets[1].endswith(",layer=three_bands")
assert len(src.subdatasets) == 4

# 4. assets and metadata layers
with pytest.warns(UserWarning):
Expand All @@ -631,26 +639,22 @@ def test_query_with_metadata(app):

with rasterio.open(io.BytesIO(response.content)) as src:
assert src.profile["driver"] == "WMTS"
assert len(src.subdatasets) == 3
assert src.subdatasets[0].endswith(",layer=one_band")
assert src.subdatasets[1].endswith(",layer=three_bands")
assert src.subdatasets[2].endswith(",layer=default")
assert len(src.subdatasets) == 5

with pytest.warns(UserWarning):
response = app.get(f"/searches/{mosaic_id_metadata}/info")
assert response.status_code == 200
resp = response.json()
assert resp["search"]["hash"] == mosaic_id_metadata
assert len(resp["links"]) == 10 # self, tilejson (3), map (3), wmts (3)
assert len(resp["links"]) == 12 # self, tilejson (5), map (5), wmts (1)

assert resp["links"][1]["title"] == "TileJSON link (Template URL)."
assert (
resp["links"][2]["title"]
== "TileJSON link for `one_band` layer (Template URL)."
resp["links"][2]["title"] == "TileJSON link for `rescale` layer (Template URL)."
)
assert (
resp["links"][3]["title"]
== "TileJSON link for `three_bands` layer (Template URL)."
== "TileJSON link for `colormap` layer (Template URL)."
)

assert "asset_bidx=cog%7C1" in resp["links"][2]["href"]
Expand Down
Loading

0 comments on commit a1ace2f

Please sign in to comment.