Skip to content

Commit 6bc1429

Browse files
add OGC tileset list and tileset metadata endpoints (#1017)
* add OGC tileset list and tileset metadata endpoints * fix
1 parent dc56ddc commit 6bc1429

File tree

10 files changed

+1281
-97
lines changed

10 files changed

+1281
-97
lines changed

CHANGES.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
* Use `@attrs.define` instead of dataclass for factories **breaking change**
2424
* Use `@attrs.define` instead of dataclass for factory extensions **breaking change**
2525
* Handle `numpy` types in JSON/GeoJSON response
26-
* In the `map.html` template, use the tilejson's `minzoom` and `maxzoom` to populate `minNativeZoom` and `maxNativeZoom` parameters in leaflet `tileLayer` instead of `minZoom` and `maxZoom`
26+
* In the `map.html` template, use the tilejson's `minzoom` and `maxzoom` to populate `minNativeZoom` and `maxNativeZoom` parameters in leaflet `tileLayer` instead of `minZoom` and `maxZoom`
2727
2828
### titiler.core
2929
@@ -84,6 +84,8 @@
8484
8585
* avoid `lat/lon` overflow in `map` viewer
8686
87+
* add OGC Tiles `/tiles` and `/tiles/{tileMatrixSet}` endpoints
88+
8789
### titiler.mosaic
8890
8991
* Rename `reader` attribute to `backend` in `MosaicTilerFactory` **breaking change**
@@ -96,6 +98,8 @@
9698
9799
* re-order endpoints parameters
98100
101+
* add OGC Tiles `/tiles` and `/tiles/{tileMatrixSet}` endpoints
102+
99103
### titiler.extensions
100104
101105
* Encode URL for cog_viewer and stac_viewer (author @guillemc23, https://github.com/developmentseed/titiler/pull/961)

docs/src/advanced/endpoints_factories.md

Lines changed: 61 additions & 45 deletions
Large diffs are not rendered by default.

docs/src/endpoints/cog.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,16 @@ The `/cog` routes are based on `titiler.core.factory.TilerFactory` but with `cog
1414
| `GET` | `/cog/info.geojson` | GeoJSON | return dataset's basic info as a GeoJSON feature
1515
| `GET` | `/cog/statistics` | JSON | return dataset's statistics
1616
| `POST` | `/cog/statistics` | GeoJSON | return dataset's statistics for a GeoJSON
17+
| `GET` | `/cog/tiles` | JSON | List of OGC Tilesets available
18+
| `GET` | `/cog/tiles/{tileMatrixSetId}` | JSON | OGC Tileset metadata
1719
| `GET` | `/cog/tiles/{tileMatrixSetId}/{z}/{x}/{y}[@{scale}x][.{format}]` | image/bin | create a web map tile image from a dataset
20+
| `GET` | `/cog/{tileMatrixSetId}/map` | HTML | simple map viewer
1821
| `GET` | `/cog/{tileMatrixSetId}/tilejson.json` | JSON | return a Mapbox TileJSON document
1922
| `GET` | `/cog/{tileMatrixSetId}/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities
2023
| `GET` | `/cog/point/{lon},{lat}` | JSON | return pixel values from a dataset
2124
| `GET` | `/cog/preview[.{format}]` | image/bin | create a preview image from a dataset
2225
| `GET` | `/cog/bbox/{minx},{miny},{maxx},{maxy}[/{width}x{height}].{format}` | image/bin | create an image from part of a dataset
2326
| `POST` | `/cog/feature[/{width}x{height}][].{format}]` | image/bin | create an image from a GeoJSON feature
24-
| `GET` | `/cog/{tileMatrixSetId}/map` | HTML | simple map viewer
2527
| `GET` | `/cog/validate` | JSON | validate a COG and return dataset info (from `titiler.extensions.cogValidateExtension`)
2628
| `GET` | `/cog/viewer` | HTML | demo webpage (from `titiler.extensions.cogViewerExtension`)
2729
| `GET` | `/cog/stac` | GeoJSON | create STAC Items from a dataset (from `titiler.extensions.stacExtension`)

docs/src/endpoints/mosaic.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,16 @@ Read Mosaic Info/Metadata and create Web map Tiles from a multiple COG. The `mos
1313
| `GET` | `/mosaicjson/bounds` | JSON | return mosaic's bounds
1414
| `GET` | `/mosaicjson/info` | JSON | return mosaic's basic info
1515
| `GET` | `/mosaicjson/info.geojson` | GeoJSON | return mosaic's basic info as a GeoJSON feature
16-
| `GET` | `/mosaicjson/tiles/{tileMatrixSetId}/{z}/{x}/{y}[@{scale}x][.{format}]` | image/bin | create a web map tile image from mosaic assets
17-
| `GET` | `/mosaicjson/{tileMatrixSetId}/tilejson.json` | JSON | return a Mapbox TileJSON document
16+
| `GET` | `/mosaicjson/tiles` | JSON | List of OGC Tilesets available
17+
| `GET` | `/mosaicjson/tiles/{tileMatrixSetId}` | JSON | OGC Tileset metadata
18+
| `GET` | `/mosaicjson/tiles/{tileMatrixSetId}/{z}/{x}/{y}[@{scale}x][.{format}]` | image/bin | create a web map tile image from mosaic assets
19+
| `GET` | `/mosaicjson/{tileMatrixSetId}/map` | HTML | simple map viewer
20+
| `GET` | `/mosaicjson/{tileMatrixSetId}/tilejson.json` | JSON | return a Mapbox TileJSON document
1821
| `GET` | `/mosaicjson/{tileMatrixSetId}/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities
1922
| `GET` | `/mosaicjson/point/{lon},{lat}` | JSON | return pixel value from a mosaic assets
2023
| `GET` | `/mosaicjson/{z}/{x}/{y}/assets` | JSON | return list of assets intersecting a XYZ tile
2124
| `GET` | `/mosaicjson/{lon},{lat}/assets` | JSON | return list of assets intersecting a point
2225
| `GET` | `/mosaicjson/{minx},{miny},{maxx},{maxy}/assets` | JSON | return list of assets intersecting a bounding box
23-
| `GET` | `/mosaicjson/{tileMatrixSetId}/map` | HTML | simple map viewer
2426

2527
## Description
2628

docs/src/endpoints/stac.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,16 @@ The `/stac` routes are based on `titiler.core.factory.MultiBaseTilerFactory` but
1616
| `GET` | `/stac/asset_statistics` | JSON | return per asset statistics
1717
| `GET` | `/stac/statistics` | JSON | return asset's statistics
1818
| `POST` | `/stac/statistics` | GeoJSON | return asset's statistics for a GeoJSON
19-
| `GET` | `/stac/tiles/{tileMatrixSetId}/{z}/{x}/{y}[@{scale}x][.{format}]` | image/bin | create a web map tile image from assets
20-
| `GET` | `/stac/{tileMatrixSetId}/tilejson.json` | JSON | return a Mapbox TileJSON document
19+
| `GET` | `/stac/tiles` | JSON | List of OGC Tilesets available
20+
| `GET` | `/stac/tiles/{tileMatrixSetId}` | JSON | OGC Tileset metadata
21+
| `GET` | `/stac/tiles/{tileMatrixSetId}/{z}/{x}/{y}[@{scale}x][.{format}]` | image/bin | create a web map tile image from assets
22+
| `GET` | `/stac/{tileMatrixSetId}/map` | HTML | simple map viewer
23+
| `GET` | `/stac/{tileMatrixSetId}/tilejson.json` | JSON | return a Mapbox TileJSON document
2124
| `GET` | `/stac/{tileMatrixSetId}/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities
2225
| `GET` | `/stac/point/{lon},{lat}` | JSON | return pixel value from assets
2326
| `GET` | `/stac/preview[.{format}]` | image/bin | create a preview image from assets
2427
| `GET` | `/stac/bbox/{minx},{miny},{maxx},{maxy}[/{width}x{height}].{format}` | image/bin | create an image from part of assets
25-
| `POST` | `/stac/feature[/{width}x{height}][].{format}]` | image/bin | create an image from a geojson covering the assets
26-
| `GET` | `/stac/{tileMatrixSetId}/map` | HTML | simple map viewer
28+
| `POST` | `/stac/feature[/{width}x{height}][].{format}]` | image/bin | create an image from a geojson covering the assets
2729
| `GET` | `/stac/viewer` | HTML | demo webpage (from `titiler.extensions.stacViewerExtension`)
2830

2931
## Description

src/titiler/core/tests/test_factories.py

Lines changed: 77 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,16 @@
77
import xml.etree.ElementTree as ET
88
from enum import Enum
99
from io import BytesIO
10-
from typing import Dict, Optional, Type
10+
from typing import Dict, Optional, Sequence, Type
1111
from unittest.mock import patch
1212
from urllib.parse import urlencode
1313

14+
import attr
1415
import httpx
1516
import morecantile
1617
import numpy
1718
import pytest
18-
from attrs import define, field
19+
from attrs import define
1920
from fastapi import Depends, FastAPI, HTTPException, Path, Query, security, status
2021
from morecantile.defaults import TileMatrixSets
2122
from rasterio.crs import CRS
@@ -48,7 +49,7 @@
4849
def test_TilerFactory():
4950
"""Test TilerFactory class."""
5051
cog = TilerFactory()
51-
assert len(cog.router.routes) == 20
52+
assert len(cog.router.routes) == 22
5253
assert len(cog.supported_tms.list()) == NB_DEFAULT_TMS
5354

5455
cog = TilerFactory(router_prefix="something", supported_tms=WEB_TMS)
@@ -75,7 +76,7 @@ def test_TilerFactory():
7576
assert response.status_code == 422
7677

7778
cog = TilerFactory(add_preview=False, add_part=False, add_viewer=False)
78-
assert len(cog.router.routes) == 12
79+
assert len(cog.router.routes) == 14
7980

8081
app = FastAPI()
8182
cog = TilerFactory()
@@ -725,14 +726,33 @@ def test_TilerFactory():
725726
assert meta["dtype"] == "uint8"
726727
assert meta["count"] == 3
727728

729+
# OGC Tileset
730+
response = client.get(f"/tiles?url={DATA_DIR}/cog.tif")
731+
assert response.status_code == 200
732+
assert response.headers["content-type"] == "application/json"
733+
resp = response.json()
734+
assert len(resp["tilesets"]) == NB_DEFAULT_TMS
735+
736+
first_tms = resp["tilesets"][0]
737+
first_id = DEFAULT_TMS.list()[0]
738+
assert first_id in first_tms["title"]
739+
assert len(first_tms["links"]) == 2 # no link to the tms definition
740+
741+
response = client.get(f"/tiles/WebMercatorQuad?url={DATA_DIR}/cog.tif")
742+
assert response.status_code == 200
743+
assert response.headers["content-type"] == "application/json"
744+
resp = response.json()
745+
# covers only 5 zoom levels
746+
assert len(resp["tileMatrixSetLimits"]) == 5
747+
728748

729749
@patch("rio_tiler.io.rasterio.rasterio")
730750
def test_MultiBaseTilerFactory(rio):
731751
"""test MultiBaseTilerFactory."""
732752
rio.open = mock_rasterio_open
733753

734754
stac = MultiBaseTilerFactory(reader=STACReader)
735-
assert len(stac.router.routes) == 22
755+
assert len(stac.router.routes) == 24
736756

737757
app = FastAPI()
738758
app.include_router(stac.router)
@@ -1054,29 +1074,43 @@ def test_MultiBaseTilerFactory(rio):
10541074
assert len(props) == 1
10551075
assert "(B09 - B01) / (B09 + B01)" in props
10561076

1077+
# OGC Tileset
1078+
response = client.get(f"/tiles?url={DATA_DIR}/item.json")
1079+
assert response.status_code == 200
1080+
assert response.headers["content-type"] == "application/json"
1081+
resp = response.json()
1082+
assert len(resp["tilesets"]) == NB_DEFAULT_TMS
1083+
1084+
first_tms = resp["tilesets"][0]
1085+
first_id = DEFAULT_TMS.list()[0]
1086+
assert first_id in first_tms["title"]
1087+
assert len(first_tms["links"]) == 2 # no link to the tms definition
1088+
1089+
response = client.get(f"/tiles/WebMercatorQuad?url={DATA_DIR}/item.json")
1090+
assert response.status_code == 200
1091+
assert response.headers["content-type"] == "application/json"
1092+
resp = response.json()
1093+
# default minzoom/maxzoom are 0->24
1094+
assert len(resp["tileMatrixSetLimits"]) == 25
1095+
10571096

1058-
@define
1097+
@attr.s
10591098
class BandFileReader(MultiBandReader):
10601099
"""Test MultiBand"""
10611100

1062-
input: str = field()
1063-
tms: morecantile.TileMatrixSet = field(
1101+
input: str = attr.ib()
1102+
tms: morecantile.TileMatrixSet = attr.ib(
10641103
default=morecantile.tms.get("WebMercatorQuad")
10651104
)
1066-
reader_options: Dict = field(factory=dict)
10671105

1068-
reader: Type[BaseReader] = field(default=Reader)
1106+
reader: Type[BaseReader] = attr.ib(default=Reader)
1107+
reader_options: Dict = attr.ib(factory=dict)
10691108

1070-
minzoom: int = field()
1071-
maxzoom: int = field()
1109+
bands: Sequence[str] = attr.ib(init=False)
1110+
default_bands: Optional[Sequence[str]] = attr.ib(init=False, default=None)
10721111

1073-
@minzoom.default
1074-
def _minzoom(self):
1075-
return self.tms.minzoom
1076-
1077-
@maxzoom.default
1078-
def _maxzoom(self):
1079-
return self.tms.maxzoom
1112+
minzoom: int = attr.ib(init=False)
1113+
maxzoom: int = attr.ib(init=False)
10801114

10811115
def __attrs_post_init__(self):
10821116
"""Parse Sceneid and get grid bounds."""
@@ -1086,6 +1120,9 @@ def __attrs_post_init__(self):
10861120
self.crs = cog.crs
10871121
self.minzoom = cog.minzoom
10881122
self.maxzoom = cog.maxzoom
1123+
self.width = cog.width
1124+
self.height = cog.height
1125+
self.transform = cog.transform
10891126

10901127
def _get_band_url(self, band: str) -> str:
10911128
"""Validate band's name and return band's url."""
@@ -1103,7 +1140,7 @@ def test_MultiBandTilerFactory():
11031140
bands = MultiBandTilerFactory(
11041141
reader=BandFileReader, path_dependency=CustomPathParams
11051142
)
1106-
assert len(bands.router.routes) == 21
1143+
assert len(bands.router.routes) == 23
11071144

11081145
app = FastAPI()
11091146
app.include_router(bands.router)
@@ -1397,6 +1434,25 @@ def test_MultiBandTilerFactory():
13971434
assert props["B01"]
13981435
assert props["B09"]
13991436

1437+
# OGC Tileset
1438+
response = client.get(f"/tiles?directory={DATA_DIR}")
1439+
assert response.status_code == 200
1440+
assert response.headers["content-type"] == "application/json"
1441+
resp = response.json()
1442+
assert len(resp["tilesets"]) == NB_DEFAULT_TMS
1443+
1444+
first_tms = resp["tilesets"][0]
1445+
first_id = DEFAULT_TMS.list()[0]
1446+
assert first_id in first_tms["title"]
1447+
assert len(first_tms["links"]) == 2 # no link to the tms definition
1448+
1449+
response = client.get(f"/tiles/WebMercatorQuad?directory={DATA_DIR}")
1450+
assert response.status_code == 200
1451+
assert response.headers["content-type"] == "application/json"
1452+
resp = response.json()
1453+
# 1 Zoom level (8)
1454+
assert len(resp["tileMatrixSetLimits"]) == 1
1455+
14001456

14011457
def test_TMSFactory():
14021458
"""test TMSFactory."""
@@ -1475,7 +1531,7 @@ def must_be_bob(credentials: security.HTTPBasicCredentials = Depends(http_basic)
14751531
],
14761532
router_prefix="something",
14771533
)
1478-
assert len(cog.router.routes) == 20
1534+
assert len(cog.router.routes) == 22
14791535

14801536
app = FastAPI()
14811537
app.include_router(cog.router, prefix="/something")

0 commit comments

Comments
 (0)