MARS serves rendered raster tiles over both WMTS 1.0.0 and OGC API - Tiles 1.0 (Map Tiles conformance class). Both surfaces share the same render pipeline; only the wire shape differs. This document covers the OGC API - Tiles surface: how to enable it, what it advertises, how it caches, and how it errors.
WMTS and OGC API - Tiles coexist indefinitely. Operators can expose either or both.
Add an interfaces.ogc_tiles block to the service config. Fields and defaults
match the schema in mars-config (crates/support/mars-config/src/model/interfaces.rs,
OgcTilesConfig).
interfaces:
ogc_tiles:
enabled: true
# tile matrix sets to expose. names must resolve in cfg.tile_matrix_sets.
tile_matrix_sets:
- WebMercatorQuad
- WorldCRS84Quad
# MIME formats offered. order is not significant; clients select via `f=`.
formats:
- image/png
- image/jpeg
- image/webp
# format used when a request omits `f=`. must also appear in `formats`.
default_format: image/png
# when true and `f=` is omitted, negotiate image/webp if the Accept header
# advertises it and webp is in `formats`. honours `default_format` as the
# fallback.
prefer_webp_when_accepted: true
# Cache-Control max-age on tile responses. seconds.
tile_cache_max_age_seconds: 3600
# Cache-Control max-age on metadata responses. seconds.
metadata_cache_max_age_seconds: 300The block is optional. Omit it (or set enabled: false) and the /ogc/tiles/
routes return 404.
All paths are JSON unless noted. The URL prefix /ogc/ namespaces future OGC
API surfaces (Features, Maps, Styles, Coverages); only Tiles is implemented
today.
| Method | Path | Returns |
|---|---|---|
| GET | /ogc/tiles |
308 redirect to /ogc/tiles/ |
| GET | /ogc/tiles/ |
Landing page with link relations |
| GET | /ogc/tiles/conformance |
Conformance declaration |
| GET | /ogc/tiles/api |
OpenAPI 3.0 document (application/vnd.oai.openapi+json;version=3.0) |
| GET | /ogc/tiles/collections |
Collections list (one per permitted layer) |
| GET | /ogc/tiles/collections/{collectionId} |
Collection metadata |
| GET | /ogc/tiles/collections/{collectionId}/map/tiles |
Tilesets list for the collection |
| GET | /ogc/tiles/collections/{collectionId}/map/tiles/{tileMatrixSetId} |
Tileset metadata |
| GET | /ogc/tiles/collections/{collectionId}/map/tiles/{tileMatrixSetId}/{tileMatrix}/{tileRow}/{tileCol} |
Rendered tile (PNG/JPEG/WebP) |
| GET | /ogc/tiles/tileMatrixSets |
TMS catalog |
| GET | /ogc/tiles/tileMatrixSets/{tileMatrixSetId} |
TMS document (TMS 2.0 JSON) |
The tile URL template {tileMatrix}/{tileRow}/{tileCol} is fixed by the OGC
API - Tiles 1.0 spec. There is no XYZ alias.
Tile format selection: append ?f=<ext> where <ext> is one of png, jpeg,
webp (subject to the configured formats list). When f= is omitted, the
edge selects WebP if prefer_webp_when_accepted is true and the Accept
header advertises it, otherwise default_format, otherwise the first entry in
formats.
GET /ogc/tiles/conformance advertises the URIs MARS satisfies. The
authoritative list lives in crates/interfaces/mars-ogc-tiles/src/dto/conformance.rs
(CONFORMANCE_URIS). At the time of writing:
http://www.opengis.net/spec/ogcapi-common-1/1.0/conf/corehttp://www.opengis.net/spec/ogcapi-common-1/1.0/conf/landing-pagehttp://www.opengis.net/spec/ogcapi-common-1/1.0/conf/jsonhttp://www.opengis.net/spec/ogcapi-common-1/1.0/conf/oas30http://www.opengis.net/spec/ogcapi-common-2/1.0/conf/collectionshttp://www.opengis.net/spec/ogcapi-tiles-1/1.0/conf/corehttp://www.opengis.net/spec/ogcapi-tiles-1/1.0/conf/tilesethttp://www.opengis.net/spec/ogcapi-tiles-1/1.0/conf/tilesets-listhttp://www.opengis.net/spec/ogcapi-tiles-1/1.0/conf/geodata-tilesetshttp://www.opengis.net/spec/tms/2.0/conf/tilematrixsethttp://www.opengis.net/spec/tms/2.0/conf/json-tilematrixset
Every tile and metadata response carries ETag and Cache-Control headers,
and the routes honour If-None-Match. WMTS does not currently emit either;
that asymmetry is a known follow-up.
Deterministic, no body hashing. Formula in
crates/interfaces/mars-http/src/ogc_tiles_cache.rs (tile_etag):
ETag = blake3(
config_etag_seed
|| current_manifest_version
|| collection || tms || z || row || col || format_mime
)[..16]
Invalidation triggers, in operator terms:
-
Manifest swap: every artifact recompile bumps the manifest version; every tile ETag changes atomically with the swap. Clients refetch on next request.
-
Config change:
config_etag_seedis computed once at router construction from theinterfaces.ogc_tilesblock plus the TMS catalog plus per-layer policies. Any of the following invalidates every prior tile validator without needing a manifest swap:- toggling
enabled,prefer_webp_when_accepted - changing
tile_cache_max_age_seconds,metadata_cache_max_age_seconds - adding, removing, or renaming a TMS in
tile_matrix_sets - changing the configured
formatsordefault_format - changing a layer's
ServiceOp::OgcTilesGetTile/OgcTilesGetCollectionpolicy
See
compute_etag_seedin the same file for the exact inputs folded into the seed. - toggling
The seed is computed once per process start. Hot-reloading config requires a restart (same constraint as WMTS).
Strong validator over the rendered JSON body (metadata_etag in the same
file). Builders are deterministic, so identical inputs produce identical
bytes and identical validators across replicas.
| Surface | Header | Tunable |
|---|---|---|
| Tile | Cache-Control: public, max-age=<tile_cache_max_age_seconds> |
interfaces.ogc_tiles.tile_cache_max_age_seconds (default 3600) |
| Metadata | Cache-Control: public, max-age=<metadata_cache_max_age_seconds> |
interfaces.ogc_tiles.metadata_cache_max_age_seconds (default 300) |
If-None-Match: <etag> with an exact byte-equal match returns
304 Not Modified with the same ETag and Cache-Control headers and an
empty body. If-None-Match: * always returns 304 when a representation
exists. Per RFC 9110 §13.1.2.
Error responses use RFC 7807 application/problem+json. The shape is defined
in crates/interfaces/mars-http/src/errors_ogc_tiles.rs (ProblemDetails).
Unknown collection (also returned when a layer denies OgcTilesGetCollection,
see below):
{
"type": "about:blank",
"title": "Collection not found",
"status": 404,
"detail": "collection not found: foo"
}Permission denied on a known collection (OgcTilesGetTile policy false):
{
"type": "about:blank",
"title": "Forbidden",
"status": 403,
"detail": "layer `foo` does not permit operation ogc_tiles_get_tile"
}Status mapping:
| Condition | Status | Title |
|---|---|---|
| Missing or invalid query/path parameter | 400 | Missing parameter / Invalid parameter |
| Collection unknown or hidden by gating | 404 | Collection not found |
| Tile matrix set unknown or hidden | 404 | Tile matrix set not found |
Format not in formats |
406 | Not acceptable |
OgcTilesGetTile denied on a visible collection |
403 | Forbidden |
| Runtime not ready | 503 | Service unavailable |
| Unimplemented OGC feature | 501 | Not implemented |
Runtime-side failures (manifest unavailable, render error, etc.) share the
classification table with the WMS/WMTS responders via
classify_runtime_error in crates/interfaces/mars-http/src/errors.rs, so a
given runtime condition maps to the same HTTP status on every surface.
Two ServiceOp values gate access:
ServiceOp::OgcTilesGetCollectioncontrols whether a layer is visible at all. Default-allow; set to false to hide.ServiceOp::OgcTilesGetTilecontrols whether tile rendering is permitted on an otherwise-visible layer. Default-allow.
The gating contract for OgcTilesGetCollection is existence-hiding: a
layer with the policy denied disappears from /ogc/tiles/collections AND
/ogc/tiles/collections/{collectionId} returns 404 Collection not found
rather than 403. Unknown collections return the same 404. This keeps the API
from confirming the existence of a layer the caller is not entitled to see.
OgcTilesGetTile is conventional: a denied tile request on a visible
collection returns 403.
Configure per-layer in the service definition:
layers:
- id: parcels
service_ops:
ogc_tiles_get_collection: true
ogc_tiles_get_tile: true
- id: internal_overlay
service_ops:
# hide entirely from /collections and /collections/{id}
ogc_tiles_get_collection: falseGET /ogc/tiles/api returns the OpenAPI 3.0 document for the surface,
generated at compile time from the handler annotations and DTO schemas
(utoipa derive in mars-ogc-tiles + handler-side #[utoipa::path(...)] in
mars-http). Content type:
application/vnd.oai.openapi+json;version=3.0.