diff --git a/pyproject.toml b/pyproject.toml index 7e04952..47c2db1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,7 @@ test = [ "pytest-cov", "pytest-asyncio", "httpx", + "owslib", ] [project.urls] diff --git a/tests/conftest.py b/tests/conftest.py index 29569ec..75922a6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,6 +13,8 @@ def app(monkeypatch): """App fixture.""" monkeypatch.setenv("TITILER_STACAPI_STAC_API_URL", "http://something.stac") + monkeypatch.setenv("TITILER_STACAPI_API_DEBUG", "TRUE") + monkeypatch.setenv("TITILER_STACAPI_CACHE_DISABLE", "TRUE") from titiler.stacapi.main import app diff --git a/tests/fixtures/1040010082988200-visual.tif b/tests/fixtures/1040010082988200-visual.tif new file mode 100644 index 0000000..163bdf9 Binary files /dev/null and b/tests/fixtures/1040010082988200-visual.tif differ diff --git a/tests/fixtures/46_033111301201_1040010082988200.json b/tests/fixtures/46_033111301201_1040010082988200.json new file mode 100644 index 0000000..6dc5abb --- /dev/null +++ b/tests/fixtures/46_033111301201_1040010082988200.json @@ -0,0 +1,307 @@ +{ + "id": "46_033111301201_1040010082988200", + "bbox": [ + 91.88812130564935, + 21.518532895453674, + 91.93942487240946, + 21.520759340266974 + ], + "type": "Feature", + "links": [ + { + "rel": "collection", + "type": "application/json", + "href": "https://stac.eoapi.dev/collections/MAXAR_BayofBengal_Cyclone_Mocha_May_23" + }, + { + "rel": "parent", + "type": "application/json", + "href": "https://stac.eoapi.dev/collections/MAXAR_BayofBengal_Cyclone_Mocha_May_23" + }, + { + "rel": "root", + "type": "application/json", + "href": "https://stac.eoapi.dev/" + }, + { + "rel": "self", + "type": "application/geo+json", + "href": "https://stac.eoapi.dev/collections/MAXAR_BayofBengal_Cyclone_Mocha_May_23/items/46_033111301201_1040010082988200" + } + ], + "assets": { + "visual": { + "href": "s3://maxar-opendata/events/BayofBengal-Cyclone-Mocha-May-23/ard/46/033111301201/2023-03-14/1040010082988200-visual.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "visual" + ], + "title": "Visual Image", + "eo:bands": [ + { + "name": "BAND_R", + "common_name": "red", + "description": "Red" + }, + { + "name": "BAND_G", + "common_name": "green", + "description": "Green" + }, + { + "name": "BAND_B", + "common_name": "blue", + "description": "Blue" + } + ], + "alternate": { + "public": { + "href": "https://maxar-opendata.s3.amazonaws.com/events/BayofBengal-Cyclone-Mocha-May-23/ard/46/033111301201/2023-03-14/1040010082988200-visual.tif", + "title": "Public Access" + } + }, + "proj:bbox": [ + 384843.75, + 2374843.75, + 390156.25, + 2380156.25 + ], + "proj:shape": [ + 17408, + 17408 + ], + "proj:transform": [ + 0.30517578125, + 0, + 384843.75, + 0, + -0.30517578125, + 2380156.25, + 0, + 0, + 1 + ] + }, + "data-mask": { + "href": "s3://maxar-opendata/events/BayofBengal-Cyclone-Mocha-May-23/ard/46/033111301201/2023-03-14/1040010082988200-data-mask.gpkg", + "type": "application/geopackage+sqlite3", + "roles": [ + "data-mask" + ], + "title": "Data Mask", + "alternate": { + "public": { + "href": "https://maxar-opendata.s3.amazonaws.com/events/BayofBengal-Cyclone-Mocha-May-23/ard/46/033111301201/2023-03-14/1040010082988200-data-mask.gpkg", + "title": "Public Access" + } + } + }, + "ms_analytic": { + "href": "s3://maxar-opendata/events/BayofBengal-Cyclone-Mocha-May-23/ard/46/033111301201/2023-03-14/1040010082988200-ms.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "title": "Multispectral Image", + "eo:bands": [ + { + "name": "BAND_C", + "common_name": "coastal", + "description": "Coastal Blue" + }, + { + "name": "BAND_B", + "common_name": "blue", + "description": "Blue" + }, + { + "name": "BAND_G", + "common_name": "green", + "description": "Green" + }, + { + "name": "BAND_Y", + "common_name": "yellow", + "description": "Yellow" + }, + { + "name": "BAND_R", + "common_name": "red", + "description": "Red" + }, + { + "name": "BAND_RE", + "common_name": "rededge", + "description": "Red Edge 1" + }, + { + "name": "BAND_N", + "common_name": "nir08", + "description": "Near Infrared 1" + }, + { + "name": "BAND_N2", + "common_name": "nir09", + "description": "Near Infrared 2" + } + ], + "alternate": { + "public": { + "href": "https://maxar-opendata.s3.amazonaws.com/events/BayofBengal-Cyclone-Mocha-May-23/ard/46/033111301201/2023-03-14/1040010082988200-ms.tif", + "title": "Public Access" + } + }, + "proj:bbox": [ + 384843.75, + 2374843.75, + 390156.25, + 2380156.25 + ], + "proj:shape": [ + 3993, + 3993 + ], + "proj:transform": [ + 1.3304532932632107, + 0, + 384843.75, + 0, + -1.3304532932632107, + 2380156.25, + 0, + 0, + 1 + ] + }, + "pan_analytic": { + "href": "s3://maxar-opendata/events/BayofBengal-Cyclone-Mocha-May-23/ard/46/033111301201/2023-03-14/1040010082988200-pan.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "title": "Panchromatic Image", + "eo:bands": [ + { + "name": "BAND_P", + "description": "Pan" + } + ], + "alternate": { + "public": { + "href": "https://maxar-opendata.s3.amazonaws.com/events/BayofBengal-Cyclone-Mocha-May-23/ard/46/033111301201/2023-03-14/1040010082988200-pan.tif", + "title": "Public Access" + } + }, + "proj:bbox": [ + 384843.75, + 2374843.75, + 390156.25, + 2380156.25 + ], + "proj:shape": [ + 15972, + 15972 + ], + "proj:transform": [ + 0.3326133233158027, + 0, + 384843.75, + 0, + -0.3326133233158027, + 2380156.25, + 0, + 0, + 1 + ] + } + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 91.88812130564935, + 21.520425546284702 + ], + [ + 91.939408735678, + 21.520759340266974 + ], + [ + 91.93942487240946, + 21.518532895453674 + ], + [ + 91.88813292057566, + 21.518896903912832 + ], + [ + 91.88812130564935, + 21.520425546284702 + ] + ] + ] + }, + "collection": "MAXAR_BayofBengal_Cyclone_Mocha_May_23", + "properties": { + "gsd": 0.34, + "quadkey": "033111301201", + "datetime": "2023-03-14T04:30:25Z", + "platform": "WV03", + "utm_zone": 46, + "grid:code": "MXRA-Z46-033111301201", + "proj:bbox": [ + 384843.75, + 2379910.40011545, + 390156.25, + 2380156.25 + ], + "proj:epsg": 32646, + "catalog_id": "1040010082988200", + "view:azimuth": 161.9, + "proj:geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 384843.75, + 2380156.25 + ], + [ + 390156.25, + 2380156.25 + ], + [ + 390156.25, + 2379910.40011545 + ], + [ + 384843.75, + 2379988.3126768884 + ], + [ + 384843.75, + 2380156.25 + ] + ] + ] + }, + "tile:data_area": 1, + "view:off_nadir": 19.5, + "tile:clouds_area": 0, + "view:sun_azimuth": 134.3, + "view:sun_elevation": 57.2, + "tile:clouds_percent": 0, + "ard_metadata_version": "0.0.1", + "view:incidence_angle": 68.5 + }, + "stac_version": "1.0.0", + "stac_extensions": [ + "https://stac-extensions.github.io/view/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json", + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/raster/v1.1.0/schema.json", + "https://stac-extensions.github.io/grid/v1.0.0/schema.json", + "https://stac-extensions.github.io/alternate-assets/v1.1.0/schema.json" + ] + } diff --git a/tests/test_app.py b/tests/test_app.py index b0711bd..3b1d1fb 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -70,3 +70,12 @@ def test_docs(app): response = app.get("/api.html") assert response.status_code == 200 assert "text/html" in response.headers["content-type"] + + +def test_debug(app): + """Test / endpoint.""" + response = app.get("/debug") + assert response.status_code == 200 + assert response.headers["content-type"] == "application/json" + body = response.json() + assert body["url"] == "http://something.stac" diff --git a/tests/test_wmts.py b/tests/test_wmts.py new file mode 100644 index 0000000..ffb3220 --- /dev/null +++ b/tests/test_wmts.py @@ -0,0 +1,501 @@ +"""Test titiler.stacapi Item endpoints.""" + +import json +import os +from unittest.mock import patch +from urllib.parse import parse_qs + +import pystac +import rasterio +from owslib.wmts import WebMapTileService + +item_json = os.path.join( + os.path.dirname(__file__), "fixtures", "46_033111301201_1040010082988200.json" +) +catalog_json = os.path.join(os.path.dirname(__file__), "fixtures", "catalog.json") + +DATA_DIR = os.path.join(os.path.dirname(__file__), "fixtures") + + +def mock_rasterio_open(asset): + """Mock rasterio Open.""" + asset = asset.replace( + "s3://maxar-opendata/events/BayofBengal-Cyclone-Mocha-May-23/ard/46/033111301201/2023-03-14", + DATA_DIR, + ) + return rasterio.open(asset) + + +@patch("titiler.stacapi.factory.Client") +def test_wmts_getcapabilities(client, app): + """test STAC items endpoints.""" + with open(catalog_json, "r") as f: + collections = [ + pystac.Collection.from_dict(c) for c in json.loads(f.read())["collections"] + ] + client.open.return_value.get_collections.return_value = collections + + # Missing Service + response = app.get("/wmts") + assert response.status_code == 400 + + # Invalid Service + response = app.get("/wmts", params={"service": "WMS"}) + assert response.status_code == 400 + + # Missing Version + response = app.get("/wmts", params={"service": "WMTS"}) + assert response.status_code == 400 + + # Invalid Version + response = app.get( + "/wmts", + params={ + "service": "WMTS", + "version": "2.0.0", + }, + ) + assert response.status_code == 400 + + # Missing Request + response = app.get( + "/wmts", + params={ + "service": "WMTS", + "version": "1.0.0", + }, + ) + assert response.status_code == 400 + + response = app.get( + "/wmts", + params={ + "service": "WMTS", + "version": "1.0.0", + "request": "getSomething", + }, + ) + assert response.status_code == 400 + + response = app.get( + "/wmts", + params={ + "service": "WMTS", + "version": "1.0.0", + "request": "getcapabilities", + }, + ) + assert response.status_code == 200 + wmts = WebMapTileService(url="/wmts", xml=response.text.encode()) + layers = list(wmts.contents) + assert len(layers) == 3 + assert "MAXAR_BayofBengal_Cyclone_Mocha_May_23_visual" in layers + assert "MAXAR_BayofBengal_Cyclone_Mocha_May_23_color" in layers + assert "MAXAR_BayofBengal_Cyclone_Mocha_May_23_visualr" in layers + + layer = wmts["MAXAR_BayofBengal_Cyclone_Mocha_May_23_visual"] + assert "WebMercatorQuad" in layer.tilematrixsetlinks + assert "TIME" in layer.dimensions + assert ["default"] == list(layer.styles.keys()) + assert ["image/png"] == layer.formats + + params = layer.resourceURLs[0]["template"].split("?")[1] + query = parse_qs(params) + assert query["assets"] == ["visual"] + assert query["asset_bidx"] == ["visual|1,2,3"] + + +@patch("rio_tiler.io.rasterio.rasterio") +@patch("titiler.stacapi.factory.STACAPIBackend.get_assets") +@patch("titiler.stacapi.factory.Client") +def test_wmts_gettile(client, get_assets, rio, app): + """test STAC items endpoints.""" + rio.open = mock_rasterio_open + + with open(catalog_json, "r") as f: + collections = [ + pystac.Collection.from_dict(c) for c in json.loads(f.read())["collections"] + ] + client.open.return_value.get_collections.return_value = collections + + with open(item_json, "r") as f: + get_assets.return_value = [json.loads(f.read())] + + # missing keys + response = app.get( + "/wmts", + params={ + "service": "WMTS", + "version": "1.0.0", + "request": "gettile", + }, + ) + assert response.status_code == 400 + + # invalid format + response = app.get( + "/wmts", + params={ + "service": "WMTS", + "version": "1.0.0", + "request": "gettile", + "layer": "MAXAR_BayofBengal_Cyclone_Mocha_May_23_color", + "style": "default", + "format": "image/yo", + "tilematrixset": "WebMercatorQuad", + "tilematrix": 0, + "tilerow": 0, + "tilecol": 0, + }, + ) + assert response.status_code == 400 + + # invalid layer + response = app.get( + "/wmts", + params={ + "service": "WMTS", + "version": "1.0.0", + "request": "gettile", + "layer": "MAXAR_BayofBengal_Cyclone_Mocha_May_23_colorrrrrrrrr", + "style": "", + "format": "image/png", + "tilematrixset": "WebMercatorQuad", + "tilematrix": 0, + "tilerow": 0, + "tilecol": 0, + }, + ) + assert response.status_code == 400 + assert ( + "Invalid 'LAYER' parameter: MAXAR_BayofBengal_Cyclone_Mocha_May_23_colorrrrrrrrr" + in response.json()["detail"] + ) + + # invalid style + response = app.get( + "/wmts", + params={ + "service": "WMTS", + "version": "1.0.0", + "request": "gettile", + "layer": "MAXAR_BayofBengal_Cyclone_Mocha_May_23_color", + "style": "something", + "format": "image/png", + "tilematrixset": "WebMercatorQuad", + "tilematrix": 0, + "tilerow": 0, + "tilecol": 0, + }, + ) + assert response.status_code == 400 + assert "Invalid STYLE parameters something" in response.json()["detail"] + + # Missing Time + response = app.get( + "/wmts", + params={ + "service": "WMTS", + "version": "1.0.0", + "request": "gettile", + "layer": "MAXAR_BayofBengal_Cyclone_Mocha_May_23_color", + "style": "default", + "format": "image/png", + "tilematrixset": "WebMercatorQuad", + "tilematrix": 15, + "tilerow": 12849, + "tilecol": 8589, + }, + ) + assert response.status_code == 400 + assert "Missing 'TIME' parameter" in response.json()["detail"] + + # Invalid Time + response = app.get( + "/wmts", + params={ + "service": "WMTS", + "version": "1.0.0", + "request": "gettile", + "layer": "MAXAR_BayofBengal_Cyclone_Mocha_May_23_color", + "style": "default", + "format": "image/png", + "tilematrixset": "WebMercatorQuad", + "tilematrix": 15, + "tilerow": 12849, + "tilecol": 8589, + "TIME": "2000-01-01", + }, + ) + assert response.status_code == 400 + assert "Invalid 'TIME' parameter:" in response.json()["detail"] + + # Invalid TMS + response = app.get( + "/wmts", + params={ + "service": "WMTS", + "version": "1.0.0", + "request": "gettile", + "layer": "MAXAR_BayofBengal_Cyclone_Mocha_May_23_color", + "style": "default", + "format": "image/png", + "tilematrixset": "WebMercatorQua", + "tilematrix": 15, + "tilerow": 12849, + "tilecol": 8589, + "TIME": "2023-01-05", + }, + ) + assert response.status_code == 400 + assert "Invalid 'TILEMATRIXSET' parameter" in response.json()["detail"] + + response = app.get( + "/wmts", + params={ + "service": "WMTS", + "version": "1.0.0", + "request": "gettile", + "layer": "MAXAR_BayofBengal_Cyclone_Mocha_May_23_visual", + "style": "default", + "format": "image/png", + "tilematrixset": "WebMercatorQuad", + "tilematrix": 14, + "tilerow": 7188, + "tilecol": 12375, + "TIME": "2023-01-05", + }, + ) + assert response.status_code == 200 + + response = app.get( + "/wmts", + params={ + "SERVICE": "WMTS", + "VERSION": "1.0.0", + "REQUEST": "getTile", + "LAYER": "MAXAR_BayofBengal_Cyclone_Mocha_May_23_visual", + "STYLE": "default", + "FORMAT": "image/png", + "TILEMATRIXSET": "WebMercatorQuad", + "TILEMATRIX": 14, + "TILEROW": 7188, + "TILECOL": 12375, + "TIME": "2023-01-05", + }, + ) + assert response.status_code == 200 + + +@patch("rio_tiler.io.rasterio.rasterio") +@patch("titiler.stacapi.factory.STACAPIBackend.get_assets") +@patch("titiler.stacapi.factory.Client") +def test_wmts_getfeatureinfo(client, get_assets, rio, app): + """test STAC items endpoints.""" + rio.open = mock_rasterio_open + + with open(catalog_json, "r") as f: + collections = [ + pystac.Collection.from_dict(c) for c in json.loads(f.read())["collections"] + ] + client.open.return_value.get_collections.return_value = collections + + with open(item_json, "r") as f: + get_assets.return_value = [json.loads(f.read())] + + # missing keys + response = app.get( + "/wmts", + params={ + "service": "WMTS", + "version": "1.0.0", + "request": "getfeatureinfo", + }, + ) + assert response.status_code == 400 + + # invalid infoformat + response = app.get( + "/wmts", + params={ + "service": "WMTS", + "version": "1.0.0", + "request": "getfeatureinfo", + "layer": "MAXAR_BayofBengal_Cyclone_Mocha_May_23_visual", + "style": "", + "format": "image/png", + "tilematrixset": "WebMercatorQuad", + "tilematrix": 0, + "tilerow": 0, + "tilecol": 0, + "TIME": "2023-01-05", + "infoformat": "application/xml", + "i": 0, + "j": 0, + }, + ) + assert response.status_code == 400 + assert "Invalid 'InfoFormat' parameter:" in response.json()["detail"] + + # invalid layer + response = app.get( + "/wmts", + params={ + "service": "WMTS", + "version": "1.0.0", + "request": "getfeatureinfo", + "layer": "MAXAR_BayofBengal_Cyclone_Mocha_May_23_colorrrrrrrrr", + "style": "", + "format": "image/png", + "tilematrixset": "WebMercatorQuad", + "tilematrix": 0, + "tilerow": 0, + "tilecol": 0, + "infoformat": "application/geo+json", + "i": 0, + "j": 0, + }, + ) + assert response.status_code == 400 + assert ( + "Invalid 'LAYER' parameter: MAXAR_BayofBengal_Cyclone_Mocha_May_23_colorrrrrrrrr" + in response.json()["detail"] + ) + + # invalid style + response = app.get( + "/wmts", + params={ + "service": "WMTS", + "version": "1.0.0", + "request": "getfeatureinfo", + "layer": "MAXAR_BayofBengal_Cyclone_Mocha_May_23_color", + "style": "something", + "format": "image/png", + "tilematrixset": "WebMercatorQuad", + "tilematrix": 0, + "tilerow": 0, + "tilecol": 0, + "infoformat": "application/geo+json", + "i": 0, + "j": 0, + }, + ) + assert response.status_code == 400 + assert "Invalid STYLE parameters something" in response.json()["detail"] + + # Missing Time + response = app.get( + "/wmts", + params={ + "service": "WMTS", + "version": "1.0.0", + "request": "getfeatureinfo", + "layer": "MAXAR_BayofBengal_Cyclone_Mocha_May_23_color", + "style": "default", + "format": "image/png", + "tilematrixset": "WebMercatorQuad", + "tilematrix": 15, + "tilerow": 12849, + "tilecol": 8589, + "infoformat": "application/geo+json", + "i": 0, + "j": 0, + }, + ) + assert response.status_code == 400 + assert "Missing 'TIME' parameter" in response.json()["detail"] + + # Invalid Time + response = app.get( + "/wmts", + params={ + "service": "WMTS", + "version": "1.0.0", + "request": "getfeatureinfo", + "layer": "MAXAR_BayofBengal_Cyclone_Mocha_May_23_color", + "style": "default", + "format": "image/png", + "tilematrixset": "WebMercatorQuad", + "tilematrix": 15, + "tilerow": 12849, + "tilecol": 8589, + "TIME": "2000-01-01", + "infoformat": "application/geo+json", + "i": 0, + "j": 0, + }, + ) + assert response.status_code == 400 + assert "Invalid 'TIME' parameter:" in response.json()["detail"] + + # Invalid TMS + response = app.get( + "/wmts", + params={ + "service": "WMTS", + "version": "1.0.0", + "request": "getfeatureinfo", + "layer": "MAXAR_BayofBengal_Cyclone_Mocha_May_23_color", + "style": "default", + "format": "image/png", + "tilematrixset": "WebMercatorQua", + "tilematrix": 15, + "tilerow": 12849, + "tilecol": 8589, + "TIME": "2023-01-05", + "infoformat": "application/geo+json", + "i": 0, + "j": 0, + }, + ) + assert response.status_code == 400 + assert "Invalid 'TILEMATRIXSET' parameter" in response.json()["detail"] + + response = app.get( + "/wmts", + params={ + "service": "WMTS", + "version": "1.0.0", + "request": "getfeatureinfo", + "layer": "MAXAR_BayofBengal_Cyclone_Mocha_May_23_visual", + "style": "default", + "format": "image/png", + "tilematrixset": "WebMercatorQuad", + "tilematrix": 14, + "tilerow": 7188, + "tilecol": 12375, + "TIME": "2023-01-05", + "infoformat": "application/geo+json", + "i": 0, + "j": 0, + }, + ) + assert response.status_code == 200 + + +@patch("rio_tiler.io.rasterio.rasterio") +@patch("titiler.stacapi.factory.STACAPIBackend.get_assets") +@patch("titiler.stacapi.factory.Client") +def test_wmts_gettile_REST(client, get_assets, rio, app): + """test STAC items endpoints.""" + rio.open = mock_rasterio_open + + with open(catalog_json, "r") as f: + collections = [ + pystac.Collection.from_dict(c) for c in json.loads(f.read())["collections"] + ] + client.open.return_value.get_collections.return_value = collections + + with open(item_json, "r") as f: + get_assets.return_value = [json.loads(f.read())] + + # missing keys + response = app.get( + "/layers/MAXAR_BayofBengal_Cyclone_Mocha_May_23_visual/default/2023-01-05/WebMercatorQuad/14/12375/7188.png", + params={ + "assets": ["visual"], + "asset_bidx": ["visual|1,2,3"], + }, + ) + assert response.headers["content-type"] == "image/png" diff --git a/titiler/stacapi/factory.py b/titiler/stacapi/factory.py index b4823f3..f3767fd 100644 --- a/titiler/stacapi/factory.py +++ b/titiler/stacapi/factory.py @@ -742,7 +742,7 @@ def get_tile( # noqa: C901 if layer_time and "time" not in req: raise HTTPException( status_code=400, - detail=f"Missing TIME parameters for layer {layer['id']}", + detail=f"Missing 'TIME' parameters for layer {layer['id']}", ) if layer_time and req_time not in layer_time: @@ -1142,7 +1142,8 @@ def web_map_tile_service( # noqa: C901 # GetFeatureInfo Request elif request_type.lower() == "getfeatureinfo": req_keys = { - "service" "request", # wmts + "service", + "request", "version", "layer", "style", @@ -1163,10 +1164,10 @@ def web_map_tile_service( # noqa: C901 detail=f"Missing '{request_type}' parameters: {missing_keys}", ) - if req["infoformat"] != "application/xml": + if req["infoformat"] != "application/geo+json": raise HTTPException( status_code=400, - detail=f"Invalid 'InfoFormat' parameter: {req['infoformat']}. Should be 'application/xml'.", + detail=f"Invalid 'InfoFormat' parameter: {req['infoformat']}. Should be 'application/geo+json'.", ) if req["layer"] not in layers: @@ -1215,7 +1216,7 @@ def web_map_tile_service( # noqa: C901 "coordinates": (xs_wgs84[0], ys_wgs84[0]), }, "properties": { - "values": image.data[:, j, i], + "values": image.data[:, j, i].tolist(), "I": i, "J": j, "style": req_style, @@ -1226,7 +1227,6 @@ def web_map_tile_service( # noqa: C901 "tileCol": req["tilecol"], }, } - return GeoJSONResponse(geojson) else: