Skip to content

Commit f657908

Browse files
refactor wmts (#1282)
* refactor wmts * fix tests * more fixes
1 parent 30046a9 commit f657908

File tree

27 files changed

+787
-414
lines changed

27 files changed

+787
-414
lines changed

CHANGES.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
* update rio-tiler requirement to `>=8.0,<9.0`
88
* return `UINT8` datatype JPEG/PNG when no output format is specified **breaking change**
9+
* remove `/{tileMatrixSetId}/WMTSCapabilities.xml` endpoints from factories **breaking change**
910

1011
### titiler.core
1112

@@ -14,6 +15,8 @@
1415
### titiler.extensions
1516

1617
* update rio-cogeo requirement to `7.0,<8.0`
18+
* add `wmtsExtension` which adds `/WMTSCapabilities.xml` to factories
19+
* `WMTSCapabilities.xml` response now support all TileMatrixSets as separate layers **breaking change**
1720

1821
### titiler.mosaic
1922

@@ -36,9 +39,12 @@
3639
coordinates: list[float]
3740
assets: list[AssetPoint]
3841
```
42+
3943
* add `/feature`, `/bbox` and `/statistics` optional endpoints
4044
* make `cogeo-mosaic` an optional dependency **breaking change**
4145
* remove default for `MosaicTilerFactory.backend` attribute **breaking change**
46+
* add `titiler.mosaic.extensions.mosaicjson.MosaicJSONExtension` which adds MosaicJSON specific `/` and `/validate` endpoints
47+
* add `titiler.mosaic.extension.wmts.wmtsExtension` which adds `/WMTSCapabilities.xml` endpoint
4248

4349
## 0.26.0 (2025-11-25)
4450

docs/src/advanced/Extensions.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ class FactoryExtension(metaclass=abc.ABCMeta):
5555

5656
- Goal: adds a `/wms` endpoint to support OGC WMS specification (`GetCapabilities` and `GetMap`)
5757

58+
#### titiler.extensions.wmtsExtension
59+
60+
- Goal: adds a `/WMTSCapabilities.xml` endpoint to support OGC WMTS RESTFULL specification (`GetCapabilities` and `GetTile`)
61+
5862
#### titiler.extensions.stacRenderExtenstion
5963

6064
- Goal: adds `/render` and `/render/{render_id}` endpoints which return the contents of [STAC render extension](https://github.com/stac-extensions/render) and links to tileset.json and WMTS service
@@ -63,7 +67,11 @@ class FactoryExtension(metaclass=abc.ABCMeta):
6367

6468
- Goal: adds `/dataset/`, `/dataset/keys` and `/datasets/dict` endpoints which return metadata about a multidimensional Dataset (not a DataArray)
6569

66-
#### titiler.mosaic.extensions.MosaicJSONExtension
70+
#### titiler.mosaic.extensions.wmts.wmtsExtension
71+
72+
- Goal: adds `/WMTSCapabilities.xml` to support OGC WMTS RESTFULL specification (`GetCapabilities` and `GetTile`)
73+
74+
#### titiler.mosaic.extensions.mosaicjson.MosaicJSONExtension
6775

6876
- Goal: adds `/` and `/validate` endpoints to return MosaicJSON content and validate external mosaics.
6977

docs/src/advanced/endpoints_factories.md

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ app.include_router(cog.router)
9494
| `GET` | `/tiles/{tileMatrixSetId}/{z}/{x}/{y}[@{scale}x][.{format}]` | image/bin | create a web map tile image from a dataset
9595
| `GET` | `/{tileMatrixSetId}/map.html` | HTML | return a simple map viewer **Optional**
9696
| `GET` | `/{tileMatrixSetId}/tilejson.json` | JSON ([TileJSON][tilejson_model]) | return a Mapbox TileJSON document
97-
| `GET` | `/{tileMatrixSetId}/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities
9897
| `GET` | `/point/{lon},{lat}` | JSON ([Point][point_model]) | return pixel values from a dataset
9998
| `GET` | `/bbox/{minx},{miny},{maxx},{maxy}[/{width}x{height}].{format}` | image/bin | create an image from part of a dataset **Optional**
10099
| `POST` | `/feature[/{width}x{height}][.{format}]` | image/bin | create an image from a GeoJSON feature **Optional**
@@ -147,7 +146,6 @@ app.include_router(stac.router)
147146
| `GET` | `/tiles/{tileMatrixSetId}/{z}/{x}/{y}[@{scale}x][.{format}]` | image/bin | create a web map tile image from assets
148147
| `GET` | `/{tileMatrixSetId}/map.html` | HTML | return a simple map viewer **Optional**
149148
| `GET` | `/{tileMatrixSetId}/tilejson.json` | JSON ([TileJSON][tilejson_model]) | return a Mapbox TileJSON document
150-
| `GET` | `/{tileMatrixSetId}/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities
151149
| `GET` | `/point/{lon},{lat}` | JSON ([Point][multipoint_model]) | return pixel values from assets
152150
| `GET` | `/bbox/{minx},{miny},{maxx},{maxy}[/{width}x{height}].{format}` | image/bin | create an image from part of assets **Optional**
153151
| `POST` | `/feature[/{width}x{height}][.{format}]` | image/bin | create an image from a geojson feature intersecting assets **Optional**
@@ -203,7 +201,6 @@ app.include_router(landsat.router)
203201
| `GET` | `/tiles/{tileMatrixSetId}/{z}/{x}/{y}[@{scale}x][.{format}]` | image/bin | create a web map tile image from a dataset
204202
| `GET` | `/{tileMatrixSetId}/map.html` | HTML | return a simple map viewer **Optional**
205203
| `GET` | `/{tileMatrixSetId}/tilejson.json` | JSON ([TileJSON][tilejson_model]) | return a Mapbox TileJSON document
206-
| `GET` | `/{tileMatrixSetId}/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities
207204
| `GET` | `/point/{lon},{lat}` | JSON ([Point][point_model]) | return pixel value from a dataset
208205
| `GET` | `/bbox/{minx},{miny},{maxx},{maxy}[/{width}x{height}].{format}` | image/bin | create an image from part of a dataset **Optional**
209206
| `POST` | `/feature[/{width}x{height}][.{format}]` | image/bin | create an image from a geojson feature **Optional**
@@ -342,7 +339,6 @@ Endpoints factory for mosaics.
342339
| `GET` | `/tiles/{tileMatrixSetId}/{z}/{x}/{y}/assets` | JSON | return list of assets intersecting a XYZ tile
343340
| `GET` | `/{tileMatrixSetId}/map.html` | HTML | return a simple map viewer **Optional**
344341
| `GET` | `/{tileMatrixSetId}/tilejson.json` | JSON ([TileJSON][tilejson_model]) | return a Mapbox TileJSON document
345-
| `GET` | `/{tileMatrixSetId}/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities
346342
| `GET` | `/point/{lon},{lat}` | JSON ([Point][mosaic_point]) | return pixel value from a MosaicJSON dataset
347343
| `GET` | `/point/{lon},{lat}/assets` | JSON | return list of assets intersecting a point
348344
| `GET` | `/bbox/{minx},{miny},{maxx},{maxy}/assets` | JSON | return list of assets intersecting a bounding box
@@ -430,7 +426,6 @@ app.include_router(md.router)
430426
| `GET` | `/tiles/{tileMatrixSetId}/{z}/{x}/{y}[@{scale}x][.{format}]` | image/bin | create a web map tile image from a dataset
431427
| `GET` | `/{tileMatrixSetId}/map.html` | HTML | return a simple map viewer **Optional**
432428
| `GET` | `/{tileMatrixSetId}/tilejson.json` | JSON ([TileJSON][tilejson_model]) | return a Mapbox TileJSON document
433-
| `GET` | `/{tileMatrixSetId}/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities
434429
| `GET` | `/point/{lon},{lat}` | JSON ([Point][point_model]) | return pixel values from a dataset
435430
| `GET` | `/bbox/{minx},{miny},{maxx},{maxy}[/{width}x{height}].{format}` | image/bin | create an image from part of a dataset **Optional**
436431
| `POST` | `/feature[/{width}x{height}][.{format}]` | image/bin | create an image from a GeoJSON feature **Optional**

docs/src/endpoints/cog.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ The `/cog` routes are based on `titiler.core.factory.TilerFactory` but with `cog
1818
| `GET` | `/cog/tiles/{tileMatrixSetId}/{z}/{x}/{y}[@{scale}x][.{format}]` | image/bin | create a web map tile image from a dataset
1919
| `GET` | `/cog/{tileMatrixSetId}/map.html` | HTML | simple map viewer
2020
| `GET` | `/cog/{tileMatrixSetId}/tilejson.json` | JSON | return a Mapbox TileJSON document
21-
| `GET` | `/cog/{tileMatrixSetId}/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities
2221
| `GET` | `/cog/point/{lon},{lat}` | JSON | return pixel values from a dataset
2322
| `GET` | `/cog/bbox/{minx},{miny},{maxx},{maxy}[/{width}x{height}].{format}` | image/bin | create an image from part of a dataset
2423
| `POST` | `/cog/feature[/{width}x{height}][.{format}]` | image/bin | create an image from a GeoJSON feature
@@ -27,6 +26,7 @@ The `/cog` routes are based on `titiler.core.factory.TilerFactory` but with `cog
2726
| `GET` | `/cog/validate` | JSON | validate a COG and return dataset info (from `titiler.extensions.cogValidateExtension`)
2827
| `GET` | `/cog/viewer` | HTML | demo webpage (from `titiler.extensions.cogViewerExtension`)
2928
| `GET` | `/cog/stac` | GeoJSON | create STAC Items from a dataset (from `titiler.extensions.stacExtension`)
29+
| `GET` | `/cog/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities (from `titiler.extensions.wmts.wmtsExtension`)
3030

3131

3232
## Description

docs/src/endpoints/mosaic.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,20 @@ Read Mosaic Info/Metadata and create Web map Tiles from a multiple COG. The `mos
99

1010
| Method | URL | Output | Description
1111
| ------ | -------------------------------------------------------------------------- |---------- |--------------
12-
| `GET` | `/mosaicjson/` | JSON | return a MosaicJSON document
1312
| `GET` | `/mosaicjson/info` | JSON | return mosaic's basic info
1413
| `GET` | `/mosaicjson/info.geojson` | GeoJSON | return mosaic's basic info as a GeoJSON feature
1514
| `GET` | `/mosaicjson/tiles` | JSON | List of OGC Tilesets available
1615
| `GET` | `/mosaicjson/tiles/{tileMatrixSetId}` | JSON | OGC Tileset metadata
1716
| `GET` | `/mosaicjson/tiles/{tileMatrixSetId}/{z}/{x}/{y}[@{scale}x][.{format}]` | image/bin | create a web map tile image from mosaic assets
1817
| `GET` | `/mosaicjson/{tileMatrixSetId}/map.html` | HTML | simple map viewer
1918
| `GET` | `/mosaicjson/{tileMatrixSetId}/tilejson.json` | JSON | return a Mapbox TileJSON document
20-
| `GET` | `/mosaicjson/{tileMatrixSetId}/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities
2119
| `GET` | `/mosaicjson/point/{lon},{lat}` | JSON | return pixel value from a mosaic assets
2220
| `GET` | `/mosaicjson/tiles/{tileMatrixSetId}/{z}/{x}/{y}/assets` | JSON | return list of assets intersecting a XYZ tile
2321
| `GET` | `/mosaicjson/point/{lon},{lat}/assets` | JSON | return list of assets intersecting a point
2422
| `GET` | `/mosaicjson/bbox/{minx},{miny},{maxx},{maxy}/assets` | JSON | return list of assets intersecting a bounding box
23+
| `GET` | `/mosaicjson/` | JSON | return a MosaicJSON document (from titiler.mosaic.extensions.mosaicjson.MosaicJSONExtension)
24+
| `GET` | `/mosaicjson/validate` | JSON | validate a MosaicJSON document (from titiler.mosaic.extensions.mosaicjson.MosaicJSONExtension)
25+
| `GET` | `/mosaicjson/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities (from titiler.mosaic.extensions.wmts.wmtsExtension)
2526

2627
## Description
2728

docs/src/endpoints/stac.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ The `/stac` routes are based on `titiler.core.factory.MultiBaseTilerFactory` but
2020
| `GET` | `/stac/tiles/{tileMatrixSetId}/{z}/{x}/{y}[@{scale}x][.{format}]` | image/bin | create a web map tile image from assets
2121
| `GET` | `/stac/{tileMatrixSetId}/map.html` | HTML | simple map viewer
2222
| `GET` | `/stac/{tileMatrixSetId}/tilejson.json` | JSON | return a Mapbox TileJSON document
23-
| `GET` | `/stac/{tileMatrixSetId}/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities
2423
| `GET` | `/stac/point/{lon},{lat}` | JSON | return pixel value from assets
2524
| `GET` | `/stac/bbox/{minx},{miny},{maxx},{maxy}[/{width}x{height}].{format}` | image/bin | create an image from part of assets
2625
| `POST` | `/stac/feature[/{width}x{height}][.{format}]` | image/bin | create an image from a geojson covering the assets
2726
| `GET` | `/stac/preview[/{width}x{height}][.{format}]` | image/bin | create a preview image from assets
2827
| `GET` | `/stac/viewer` | HTML | demo webpage (from `titiler.extensions.stacViewerExtension`)
28+
| `GET` | `/stac/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities (from `titiler.extensions.wmts.wmtsExtension`)
2929

3030
## Description
3131

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ dev = [
5252
"pystac[validation]>=1.0.0,<2.0.0",
5353
"brotlipy",
5454
"boto3",
55+
"owslib",
5556
"pre-commit",
5657
"bump-my-version",
5758
]

src/titiler/application/tests/routes/test_cog.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,14 @@ def test_wmts(rio, app):
4242
"""test wmts endpoints."""
4343
rio.open = mock_rasterio_open
4444

45-
response = app.get(
46-
"/cog/WebMercatorQuad/WMTSCapabilities.xml?url=https://myurl.com/cog.tif"
47-
)
45+
response = app.get("/cog/WMTSCapabilities.xml?url=https://myurl.com/cog.tif")
4846
assert response.status_code == 200
4947
assert response.headers["content-type"] == "application/xml"
5048
assert response.headers["Cache-Control"] == "private, max-age=3600"
5149
assert (
52-
"http://testserver/cog/WebMercatorQuad/WMTSCapabilities.xml?url=https"
50+
"http://testserver/cog/WMTSCapabilities.xml?url=https"
5351
in response.content.decode()
5452
)
55-
assert "<ows:Identifier>default</ows:Identifier>" in response.content.decode()
5653
assert (
5754
"http://testserver/cog/tiles/WebMercatorQuad/{TileMatrix}/{TileCol}/{TileRow}@1x.png?url=https"
5855
in response.content.decode()
@@ -63,7 +60,7 @@ def test_wmts(rio, app):
6360
)
6461

6562
response = app.get(
66-
"/cog/WebMercatorQuad/WMTSCapabilities.xml?url=https://myurl.com/cog.tif&tile_scale=2&tile_format=jpg"
63+
"/cog/WMTSCapabilities.xml?url=https://myurl.com/cog.tif&tile_scale=2&tile_format=jpg"
6764
)
6865
assert response.status_code == 200
6966
assert response.headers["content-type"] == "application/xml"
@@ -73,7 +70,7 @@ def test_wmts(rio, app):
7370
)
7471

7572
response = app.get(
76-
"/cog/WebMercatorQuad/WMTSCapabilities.xml?url=https://myurl.com/cog.tif&use_epsg=true"
73+
"/cog/WMTSCapabilities.xml?url=https://myurl.com/cog.tif&use_epsg=true"
7774
)
7875
assert response.status_code == 200
7976
assert response.headers["content-type"] == "application/xml"

src/titiler/application/tests/routes/test_mosaic.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ def test_wmts(app):
180180
"""test GET /mosaicjson/WebMercatorQuad/WMTSCapabilities.xml endpoint"""
181181
with patch.object(FileBackend, "_read", mosaic_read_factory(MOSAICJSON_FILE)):
182182
response = app.get(
183-
"/mosaicjson/WebMercatorQuad/WMTSCapabilities.xml",
183+
"/mosaicjson/WMTSCapabilities.xml",
184184
params={"url": MOSAICJSON_FILE},
185185
)
186186
assert response.status_code == 200
@@ -191,7 +191,7 @@ def test_wmts(app):
191191
)
192192

193193
response = app.get(
194-
"/mosaicjson/WebMercatorQuad/WMTSCapabilities.xml",
194+
"/mosaicjson/WMTSCapabilities.xml",
195195
params={"url": MOSAICJSON_FILE, "tile_scale": 2},
196196
)
197197
assert response.status_code == 200

src/titiler/application/titiler/application/main.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,11 @@
4343
stacExtension,
4444
stacRenderExtension,
4545
stacViewerExtension,
46+
wmtsExtension,
4647
)
4748
from titiler.mosaic.errors import MOSAIC_STATUS_CODES
48-
from titiler.mosaic.extensions import MosaicJSONExtension
49+
from titiler.mosaic.extensions.mosaicjson import MosaicJSONExtension
50+
from titiler.mosaic.extensions.wmts import wmtsExtension as mosaic_wmtsExtension
4951
from titiler.mosaic.factory import MosaicTilerFactory
5052

5153
logging.getLogger("botocore.credentials").disabled = True
@@ -70,7 +72,7 @@
7072
)
7173

7274
jinja2_env = jinja2.Environment(
73-
autoescape=jinja2.select_autoescape(["html", "xml"]),
75+
autoescape=jinja2.select_autoescape(["html"]),
7476
loader=jinja2.ChoiceLoader(templates_location),
7577
)
7678
titiler_templates = Jinja2Templates(env=jinja2_env)
@@ -133,6 +135,7 @@ def validate_access_token(access_token: str = Security(api_key_query)):
133135
cogValidateExtension(),
134136
cogViewerExtension(),
135137
stacExtension(),
138+
wmtsExtension(),
136139
],
137140
enable_telemetry=api_settings.telemetry_enabled,
138141
templates=titiler_templates,
@@ -153,10 +156,7 @@ def validate_access_token(access_token: str = Security(api_key_query)):
153156
reader=STACReader,
154157
router_prefix="/stac",
155158
add_ogc_maps=True,
156-
extensions=[
157-
stacViewerExtension(),
158-
stacRenderExtension(),
159-
],
159+
extensions=[stacViewerExtension(), stacRenderExtension(), wmtsExtension()],
160160
enable_telemetry=api_settings.telemetry_enabled,
161161
templates=titiler_templates,
162162
)
@@ -177,6 +177,7 @@ def validate_access_token(access_token: str = Security(api_key_query)):
177177
router_prefix="/mosaicjson",
178178
extensions=[
179179
MosaicJSONExtension(),
180+
mosaic_wmtsExtension(),
180181
],
181182
enable_telemetry=api_settings.telemetry_enabled,
182183
templates=titiler_templates,

0 commit comments

Comments
 (0)