Skip to content

Commit ecddb6d

Browse files
Merge pull request #26 from jverrydt/main
Support cube:dimensions and overriding of colormap/expression
2 parents 090bf07 + 216e930 commit ecddb6d

File tree

5 files changed

+261
-9
lines changed

5 files changed

+261
-9
lines changed

tests/fixtures/catalog.json

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,157 @@
153153
"https://stac-extensions.github.io/item-assets/v1.0.0/schema.json",
154154
"https://stac-extensions.github.io/render/v1.0.0/schema.json"
155155
]
156+
},
157+
{
158+
"id": "MAXAR_BayofBengal_Cyclone_Mocha_May_23_cube_dimensions",
159+
"type": "Collection",
160+
"links": [
161+
{
162+
"rel": "items",
163+
"type": "application/geo+json",
164+
"href": "https://stac.endpoint.io/collections/MAXAR_BayofBengal_Cyclone_Mocha_May_23/items"
165+
},
166+
{
167+
"rel": "parent",
168+
"type": "application/json",
169+
"href": "https://stac.endpoint.io/"
170+
},
171+
{
172+
"rel": "root",
173+
"type": "application/json",
174+
"href": "https://stac.endpoint.io/"
175+
},
176+
{
177+
"rel": "self",
178+
"type": "application/json",
179+
"href": "https://stac.endpoint.io/collections/MAXAR_BayofBengal_Cyclone_Mocha_May_23"
180+
}
181+
],
182+
"title": "Bay of Bengal Cyclone Mocha 2023",
183+
"cube:dimensions": {
184+
"time": {
185+
"type": "temporal",
186+
"extent": [
187+
"2023-01-03T04:30:17Z",
188+
"2023-05-22T04:35:25Z"
189+
],
190+
"values": [
191+
"2023-01-03T04:30:17Z",
192+
"2023-02-03T04:30:17Z",
193+
"2023-03-03T04:30:17Z",
194+
"2023-04-03T04:30:17Z",
195+
"2023-05-03T04:30:17Z",
196+
"2023-05-22T04:35:25Z"
197+
]
198+
}
199+
},
200+
"extent": {
201+
"spatial": {
202+
"bbox": [
203+
[
204+
91.831615,
205+
19.982078842323997,
206+
92.97426268500965,
207+
21.666101
208+
],
209+
[
210+
92.567815,
211+
20.18811887678192,
212+
92.74417544237298,
213+
20.62968532404085
214+
],
215+
[
216+
92.72278776887262,
217+
20.104801,
218+
92.893524,
219+
20.630214
220+
],
221+
[
222+
92.75855246040959,
223+
19.982078842323997,
224+
92.89682495377032,
225+
20.514473160464657
226+
],
227+
[
228+
92.84253515935835,
229+
19.984656587012033,
230+
92.97426268500965,
231+
20.514418665444474
232+
],
233+
[
234+
91.831615,
235+
21.518411,
236+
91.957078,
237+
21.666101
238+
]
239+
]
240+
},
241+
"temporal": {
242+
"interval": [
243+
[
244+
"2023-01-03T04:30:17Z",
245+
"2023-05-22T04:35:25Z"
246+
]
247+
]
248+
}
249+
},
250+
"license": "CC-BY-NC-4.0",
251+
"renders": {
252+
"visual": {
253+
"title": "Visual Image",
254+
"assets": [
255+
"visual"
256+
],
257+
"asset_bidx": "visual|1,2,3",
258+
"minmax_zoom": [
259+
8,
260+
22
261+
],
262+
"tilematrixsets": {
263+
"WebMercatorQuad": [
264+
8,
265+
22
266+
]
267+
}
268+
}
269+
},
270+
"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.",
271+
"item_assets": {
272+
"visual": {
273+
"type": "image/tiff; application=geotiff; profile=cloud-optimized",
274+
"roles": [
275+
"visual"
276+
],
277+
"title": "Visual Image"
278+
},
279+
"data-mask": {
280+
"type": "application/geopackage+sqlite3",
281+
"roles": [
282+
"data-mask"
283+
],
284+
"title": "Data Mask"
285+
},
286+
"ms_analytic": {
287+
"type": "image/tiff; application=geotiff; profile=cloud-optimized",
288+
"roles": [
289+
"data"
290+
],
291+
"title": "Multispectral Image"
292+
},
293+
"pan_analytic": {
294+
"type": "image/tiff; application=geotiff; profile=cloud-optimized",
295+
"roles": [
296+
"data"
297+
],
298+
"title": "Panchromatic Image"
299+
}
300+
},
301+
"stac_version": "1.0.0",
302+
"stac_extensions": [
303+
"https://stac-extensions.github.io/item-assets/v1.0.0/schema.json",
304+
"https://stac-extensions.github.io/render/v1.0.0/schema.json",
305+
"https://stac-extensions.github.io/datacube/v2.2.0/schema.json"
306+
]
156307
}
157308
],
158309
"links": [

tests/test_render.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def test_render(client):
2525
collections_render = get_layer_from_collections(
2626
"https://something.stac", None, None
2727
)
28-
assert len(collections_render) == 3
28+
assert len(collections_render) == 4
2929

3030
visual = collections_render["MAXAR_BayofBengal_Cyclone_Mocha_May_23_visual"]
3131
assert visual["bbox"]

tests/test_wmts.py

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,11 @@ def test_wmts_getcapabilities(client, app):
8888
assert response.status_code == 200
8989
wmts = WebMapTileService(url="/wmts", xml=response.text.encode())
9090
layers = list(wmts.contents)
91-
assert len(layers) == 3
91+
assert len(layers) == 4
9292
assert "MAXAR_BayofBengal_Cyclone_Mocha_May_23_visual" in layers
9393
assert "MAXAR_BayofBengal_Cyclone_Mocha_May_23_color" in layers
9494
assert "MAXAR_BayofBengal_Cyclone_Mocha_May_23_visualr" in layers
95+
assert "MAXAR_BayofBengal_Cyclone_Mocha_May_23_cube_dimensions_visual" in layers
9596

9697
layer = wmts["MAXAR_BayofBengal_Cyclone_Mocha_May_23_visual"]
9798
assert "WebMercatorQuad" in layer.tilematrixsetlinks
@@ -104,6 +105,11 @@ def test_wmts_getcapabilities(client, app):
104105
assert query["assets"] == ["visual"]
105106
assert query["asset_bidx"] == ["visual|1,2,3"]
106107

108+
layer = wmts["MAXAR_BayofBengal_Cyclone_Mocha_May_23_cube_dimensions_visual"]
109+
assert "TIME" in layer.dimensions
110+
times = layer.dimensions["TIME"]["values"]
111+
assert len(times) == 6
112+
107113

108114
@patch("rio_tiler.io.rasterio.rasterio")
109115
@patch("titiler.stacapi.factory.STACAPIBackend.get_assets")
@@ -287,6 +293,63 @@ def test_wmts_gettile(client, get_assets, rio, app):
287293
assert response.status_code == 200
288294

289295

296+
@patch("rio_tiler.io.rasterio.rasterio")
297+
@patch("titiler.stacapi.factory.STACAPIBackend.get_assets")
298+
@patch("titiler.stacapi.factory.Client")
299+
def test_wmts_gettile_param_override(client, get_assets, rio, app):
300+
"""test STAC items endpoints."""
301+
rio.open = mock_rasterio_open
302+
303+
with open(catalog_json, "r") as f:
304+
collections = [
305+
pystac.Collection.from_dict(c) for c in json.loads(f.read())["collections"]
306+
]
307+
client.open.return_value.get_collections.return_value = collections
308+
309+
with open(item_json, "r") as f:
310+
get_assets.return_value = [json.loads(f.read())]
311+
312+
response = app.get(
313+
"/wmts",
314+
params={
315+
"SERVICE": "WMTS",
316+
"VERSION": "1.0.0",
317+
"REQUEST": "getTile",
318+
"LAYER": "MAXAR_BayofBengal_Cyclone_Mocha_May_23_visual",
319+
"STYLE": "default",
320+
"FORMAT": "image/png",
321+
"TILEMATRIXSET": "WebMercatorQuad",
322+
"TILEMATRIX": 14,
323+
"TILEROW": 7188,
324+
"TILECOL": 12375,
325+
"TIME": "2023-01-05",
326+
"expression": "(where(visual_invalid >= 0))",
327+
},
328+
)
329+
assert response.status_code == 500
330+
assert "Could not find any valid assets" in response.json()["detail"]
331+
332+
response = app.get(
333+
"/wmts",
334+
params={
335+
"SERVICE": "WMTS",
336+
"VERSION": "1.0.0",
337+
"REQUEST": "getTile",
338+
"LAYER": "MAXAR_BayofBengal_Cyclone_Mocha_May_23_color",
339+
"STYLE": "default",
340+
"FORMAT": "image/png",
341+
"TILEMATRIXSET": "WebMercatorQuad",
342+
"TILEMATRIX": 14,
343+
"TILEROW": 7188,
344+
"TILECOL": 12375,
345+
"TIME": "2023-01-05",
346+
"colormap": "{invalid}",
347+
},
348+
)
349+
assert response.status_code == 400
350+
assert "Could not parse the colormap value" in response.json()["detail"]
351+
352+
290353
@patch("rio_tiler.io.rasterio.rasterio")
291354
@patch("titiler.stacapi.factory.STACAPIBackend.get_assets")
292355
@patch("titiler.stacapi.factory.Client")

titiler/stacapi/factory.py

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import datetime as python_datetime
44
import json
55
import os
6+
from copy import copy
67
from dataclasses import dataclass, field
78
from enum import Enum
89
from typing import Any, Callable, Dict, List, Literal, Optional, Type
@@ -629,10 +630,20 @@ def get_layer_from_collections( # noqa: C901
629630
tms_id: None for tms_id in tilematrixsets
630631
}
631632

632-
# TODO: handle multiple intervals
633-
# Check datacube extension
634-
# https://github.com/stac-extensions/datacube?tab=readme-ov-file#temporal-dimension-object
635-
if intervals := temporal_extent.intervals:
633+
if (
634+
"cube:dimensions" in collection.extra_fields
635+
and "time" in collection.extra_fields["cube:dimensions"]
636+
):
637+
layer["time"] = [
638+
python_datetime.datetime.strptime(
639+
t,
640+
"%Y-%m-%dT%H:%M:%SZ",
641+
).strftime("%Y-%m-%d")
642+
for t in collection.extra_fields["cube:dimensions"]["time"][
643+
"values"
644+
]
645+
]
646+
elif intervals := temporal_extent.intervals:
636647
start_date = intervals[0][0]
637648
end_date = (
638649
intervals[0][1]
@@ -792,7 +803,12 @@ def get_tile( # noqa: C901
792803
"datetime"
793804
] = f"{start_datetime.strftime('%Y-%m-%dT%H:%M:%SZ')}/{end_datetime.strftime('%Y-%m-%dT%H:%M:%SZ')}"
794805

795-
query_params = layer.get("render") or {}
806+
query_params = copy(layer.get("render")) or {}
807+
if "color_formula" in req:
808+
query_params["color_formula"] = req["color_formula"]
809+
if "expression" in req:
810+
query_params["expression"] = req["expression"]
811+
796812
layer_params = get_dependency_params(
797813
dependency=self.layer_dependency,
798814
query_params=query_params,
@@ -959,6 +975,26 @@ def register_routes(self): # noqa: C901
959975
"name": "TileCol",
960976
"in": "query",
961977
},
978+
{
979+
"required": False,
980+
"schema": {
981+
"title": "Color Formula",
982+
"description": "rio-color formula (info: https://github.com/mapbox/rio-color)",
983+
"type": "string",
984+
},
985+
"name": "color_formula",
986+
"in": "query",
987+
},
988+
{
989+
"required": False,
990+
"schema": {
991+
"title": "Colormap name",
992+
"description": "JSON encoded custom Colormap",
993+
"type": "string",
994+
},
995+
"name": "colormap",
996+
"in": "query",
997+
},
962998
################
963999
# GetFeatureInfo
9641000
# InfoFormat
@@ -1126,7 +1162,9 @@ def web_map_tile_service( # noqa: C901
11261162

11271163
colormap = get_dependency_params(
11281164
dependency=self.colormap_dependency,
1129-
query_params=layer.get("render") or {},
1165+
query_params={"colormap": req["colormap"]}
1166+
if "colormap" in req
1167+
else layer.get("render") or {},
11301168
)
11311169

11321170
content, media_type = render_image(

titiler/stacapi/templates/wmts-getcapabilities_1.0.0.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@
114114
<TileMatrixSet>
115115
<ows:Identifier>{{ tms.id }}</ows:Identifier>
116116
{% if tms.crs.to_epsg() %}
117-
<ows:SupportedCRS>urn:ogc:def:crs:epsg::{{tms.crs.to_epsg()}}</ows:SupportedCRS>
117+
<ows:SupportedCRS>urn:ogc:def:crs:EPSG::{{tms.crs.to_epsg()}}</ows:SupportedCRS>
118118
{% else %}
119119
<ows:SupportedCRS>{{ tms.crs.srs.replace("http://www.opengis.net/def/", "urn:ogc:def:").replace("/", ":")}} </ows:SupportedCRS>
120120
{% endif %}

0 commit comments

Comments
 (0)