Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(feature): new tile viewer supporting multiple TMS #173

Merged
merged 4 commits into from
Mar 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions tests/test_factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def test_tiles_factory():
endpoints = OGCTilesFactory()
assert endpoints.with_common
assert endpoints.title == "OGC API"
assert len(endpoints.router.routes) == 14
assert len(endpoints.router.routes) == 15
assert len(endpoints.conforms_to) == 5

app = FastAPI()
Expand All @@ -127,7 +127,7 @@ def test_tiles_factory():
assert response.headers["content-type"] == "application/json"
assert response.json()["title"] == "OGC API"
links = response.json()["links"]
assert len(links) == 9 # 5 from tiles + 4 from common
assert len(links) == 10 # 6 from tiles + 4 from common
landing_link = [link for link in links if link["title"] == "Landing Page"][0]
assert landing_link["href"] == "http://testserver/"
tms_link = [link for link in links if link["title"] == "TileMatrixSets"][0]
Expand All @@ -150,7 +150,7 @@ def test_tiles_factory():
assert endpoints.router_prefix == "/map"
assert endpoints.with_common
assert endpoints.title == "OGC Tiles API"
assert len(endpoints.router.routes) == 14
assert len(endpoints.router.routes) == 15

app = FastAPI()
app.include_router(endpoints.router, prefix="/map")
Expand All @@ -160,7 +160,7 @@ def test_tiles_factory():
assert response.headers["content-type"] == "application/json"
assert response.json()["title"] == "OGC Tiles API"
links = response.json()["links"]
assert len(links) == 9
assert len(links) == 10
landing_link = [link for link in links if link["title"] == "Landing Page"][0]
assert landing_link["href"] == "http://testserver/map/"
tms_link = [link for link in links if link["title"] == "TileMatrixSets"][0]
Expand All @@ -180,7 +180,7 @@ def test_tiles_factory():
endpoints = OGCTilesFactory(title="OGC Tiles API", with_common=False)
assert not endpoints.with_common
assert endpoints.title == "OGC Tiles API"
assert len(endpoints.router.routes) == 12
assert len(endpoints.router.routes) == 13
assert len(endpoints.conforms_to) == 5

app = FastAPI()
Expand All @@ -203,7 +203,7 @@ def test_endpoints_factory():
endpoints = Endpoints()
assert endpoints.with_common
assert endpoints.title == "OGC API"
assert len(endpoints.router.routes) == 19
assert len(endpoints.router.routes) == 20
assert len(endpoints.conforms_to) == 11 # 5 from tiles + 6 from features

app = FastAPI()
Expand All @@ -214,7 +214,7 @@ def test_endpoints_factory():
assert response.headers["content-type"] == "application/json"
assert response.json()["title"] == "OGC API"
links = response.json()["links"]
assert len(links) == 14 # 5 from tiles + 5 from features + 4 from common
assert len(links) == 15 # 6 from tiles + 5 from features + 4 from common
landing_link = [link for link in links if link["title"] == "Landing Page"][0]
assert landing_link["href"] == "http://testserver/"
queryables_link = [
Expand All @@ -237,14 +237,14 @@ def test_endpoints_factory():
assert response.status_code == 200
assert response.headers["content-type"] == "application/json"
body = response.json()["conformsTo"]
assert len(body) > 10 # 4 from tiles + 6 from features
assert len(body) > 9 # 3 from tiles + 6 from features
assert "http://www.opengis.net/spec/ogcapi-common-1/1.0/conf/core" in body

endpoints = Endpoints(router_prefix="/ogc", title="OGC Full API", with_common=True)
assert endpoints.router_prefix == "/ogc"
assert endpoints.with_common
assert endpoints.title == "OGC Full API"
assert len(endpoints.router.routes) == 19
assert len(endpoints.router.routes) == 20
assert not endpoints.ogc_features.with_common
assert endpoints.ogc_features.router_prefix == "/ogc"
assert not endpoints.ogc_tiles.with_common
Expand All @@ -258,7 +258,7 @@ def test_endpoints_factory():
assert response.headers["content-type"] == "application/json"
assert response.json()["title"] == "OGC Full API"
links = response.json()["links"]
assert len(links) == 14
assert len(links) == 15
landing_link = [link for link in links if link["title"] == "Landing Page"][0]
assert landing_link["href"] == "http://testserver/ogc/"
queryables_link = [
Expand Down Expand Up @@ -288,7 +288,7 @@ def test_endpoints_factory():
endpoints = Endpoints(title="Tiles and Features API", with_common=False)
assert not endpoints.with_common
assert endpoints.title == "Tiles and Features API"
assert len(endpoints.router.routes) == 17 # 10 from tiles + 5 from features
assert len(endpoints.router.routes) == 18 # 11 from tiles + 5 from features
assert len(endpoints.conforms_to) == 11 # 4 from tiles + 6 from features

app = FastAPI()
Expand Down
2 changes: 0 additions & 2 deletions tipg/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,8 +318,6 @@ def TileParams(
z: Annotated[
int,
Path(
ge=0,
le=30,
description="Identifier (Z) selecting one of the scales defined in the TileMatrixSet and representing the scaleDenominator the tile.",
),
],
Expand Down
151 changes: 103 additions & 48 deletions tipg/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,10 +187,10 @@ class EndpointsFactory(metaclass=abc.ABCMeta):

def __post_init__(self):
"""Post Init: register route and configure specific options."""
self.register_routes()
if self.with_common:
self._conformance_route()
self._landing_route()
self._conformance_route()
self.register_routes()

def url_for(self, request: Request, name: str, **path_params: Any) -> str:
"""Return full url (with prefix) for a specific handler."""
Expand Down Expand Up @@ -1111,7 +1111,7 @@ def conforms_to(self) -> List[str]:

def links(self, request: Request) -> List[model.Link]:
"""OGC Tiles API links."""
return [
links = [
model.Link(
title="Collection Vector Tiles (Template URL)",
href=self.url_for(
Expand Down Expand Up @@ -1149,6 +1149,25 @@ def links(self, request: Request) -> List[model.Link]:
rel="data",
templated=True,
),
]

if self.with_viewer:
links.append(
model.Link(
title="Collection Map viewer (Template URL)",
href=self.url_for(
request,
"viewer_endpoint",
collectionId="{collectionId}",
tileMatrixSetId="{tileMatrixSetId}",
),
type=MediaType.html,
rel="data",
templated=True,
)
)

links += [
model.Link(
title="TileMatrixSets",
href=self.url_for(
Expand All @@ -1171,6 +1190,8 @@ def links(self, request: Request) -> List[model.Link]:
),
]

return links

def register_routes(self): # noqa: C901
"""Register OGC Tiles endpoints."""
self._tilematrixsets_routes()
Expand Down Expand Up @@ -1428,49 +1449,67 @@ async def collection_tileset(
for matrix in tms
]

links = [
{
"href": self.url_for(
request,
"collection_tileset",
collectionId=collection.id,
tileMatrixSetId=tileMatrixSetId,
),
"rel": "self",
"type": "application/json",
"title": f"'{collection.id}' tileset tiled using {tileMatrixSetId} TileMatrixSet",
},
{
"href": self.url_for(
request,
"tilematrixset",
tileMatrixSetId=tileMatrixSetId,
),
"rel": "http://www.opengis.net/def/rel/ogc/1.0/tiling-schemes",
"type": "application/json",
"title": f"Definition of '{tileMatrixSetId}' tileMatrixSet",
},
{
"href": self.url_for(
request,
"collection_get_tile",
tileMatrixSetId=tileMatrixSetId,
collectionId=collection.id,
z="{z}",
x="{x}",
y="{y}",
),
"rel": "tile",
"type": "application/vnd.mapbox-vector-tile",
"title": "Templated link for retrieving Vector tiles",
"templated": True,
},
]

if self.with_viewer:
links.append(
{
"href": self.url_for(
request,
"viewer_endpoint",
tileMatrixSetId=tileMatrixSetId,
collectionId=collection.id,
),
"type": "text/html",
"rel": "data",
"title": f"Map viewer for '{tileMatrixSetId}' tileMatrixSet",
}
)

data = model.TileSet.model_validate(
{
"title": f"'{collection.id}' tileset tiled using {tileMatrixSetId} TileMatrixSet",
"dataType": "vector",
"crs": tms.crs,
"boundingBox": collection_bbox,
"links": [
{
"href": self.url_for(
request,
"collection_tileset",
collectionId=collection.id,
tileMatrixSetId=tileMatrixSetId,
),
"rel": "self",
"type": "application/json",
"title": f"'{collection.id}' tileset tiled using {tileMatrixSetId} TileMatrixSet",
},
{
"href": self.url_for(
request,
"tilematrixset",
tileMatrixSetId=tileMatrixSetId,
),
"rel": "http://www.opengis.net/def/rel/ogc/1.0/tiling-schemes",
"type": "application/json",
"title": f"Definition of '{tileMatrixSetId}' tileMatrixSet",
},
{
"href": self.url_for(
request,
"collection_get_tile",
tileMatrixSetId=tileMatrixSetId,
collectionId=collection.id,
z="{z}",
x="{x}",
y="{y}",
),
"rel": "tile",
"type": "application/vnd.mapbox-vector-tile",
"title": "Templated link for retrieving Vector tiles",
},
],
"links": links,
"tileMatrixSetLimits": tilematrix_limit,
}
)
Expand Down Expand Up @@ -1499,6 +1538,7 @@ def _tile_routes(self):
responses={200: {"content": {MediaType.mvt.value: {}}}},
operation_id=".collection.vector.getTile",
tags=["OGC Tiles API"],
deprecated=True,
)
async def collection_get_tile(
request: Request,
Expand Down Expand Up @@ -1582,6 +1622,7 @@ def _tilejson_routes(self):
response_class=ORJSONResponse,
operation_id=".collection.vector.getTileJSON",
tags=["OGC Tiles API"],
deprecated=True,
)
async def collection_tilejson(
request: Request,
Expand Down Expand Up @@ -1683,6 +1724,7 @@ def _stylejson_routes(self):
response_class=ORJSONResponse,
operation_id=".collection.vector.getStyleJSON",
tags=["OGC Tiles API"],
deprecated=True,
)
async def collection_stylejson(
request: Request,
Expand Down Expand Up @@ -1809,19 +1851,29 @@ async def collection_stylejson(
"/collections/{collectionId}/{tileMatrixSetId}/viewer",
response_class=HTMLResponse,
operation_id=".collection.vector.viewerTms",
deprecated=True,
tags=["Map Viewer"],
)
@self.router.get(
"/collections/{collectionId}/viewer",
response_class=HTMLResponse,
operation_id=".collection.vector.viewer",
deprecated=True,
tags=["Map Viewer"],
)
@self.router.get(
"/collections/{collectionId}/tiles/{tileMatrixSetId}/viewer",
response_class=HTMLResponse,
operation_id=".collection.vector.map",
tags=["Map Viewer"],
)
def viewer_endpoint(
request: Request,
collection: Annotated[Collection, Depends(self.collection_dependency)],
tileMatrixSetId: Annotated[
Literal["WebMercatorQuad"],
"Identifier selecting one of the TileMatrixSetId supported (default: 'WebMercatorQuad')",
] = "WebMercatorQuad",
Literal[tuple(self.supported_tms.list())],
f"Identifier selecting one of the TileMatrixSetId supported (default: '{tms_settings.default_tms}')",
] = tms_settings.default_tms,
minzoom: Annotated[
Optional[int],
Query(description="Overwrite default minzoom."),
Expand All @@ -1839,7 +1891,7 @@ def viewer_endpoint(
] = None,
):
"""Return Simple HTML Viewer for a collection."""
self.supported_tms.get(tileMatrixSetId)
tms = self.supported_tms.get(tileMatrixSetId)

tilejson_url = self.url_for(
request,
Expand All @@ -1850,13 +1902,16 @@ def viewer_endpoint(
if request.query_params._list:
tilejson_url += f"?{urlencode(request.query_params._list)}"

return self.templates.TemplateResponse(
return self._create_html_response(
request,
name="map.html",
context={
{
"title": collection.id,
"tilejson_endpoint": tilejson_url,
"tms": tms,
"resolutions": [matrix.cellSize for matrix in tms],
},
media_type="text/html",
template_name="map",
title=f"{collection.id} viewer",
)


Expand Down
4 changes: 2 additions & 2 deletions tipg/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,8 +307,8 @@ class TileJSON(BaseModel):
vector_layers: Optional[List[LayerJSON]] = None
grids: Optional[List[str]] = None
data: Optional[List[str]] = None
minzoom: int = Field(0, ge=0, le=30)
maxzoom: int = Field(30, ge=0, le=30)
minzoom: int = Field(0)
maxzoom: int = Field(30)
fillzoom: Optional[int] = None
bounds: List[float] = [180, -85.05112877980659, 180, 85.0511287798066]
center: Optional[Tuple[float, float, int]] = None
Expand Down
Loading
Loading