Skip to content

Commit

Permalink
Merge pull request #173 from developmentseed/feature/new-multi-tms-vi…
Browse files Browse the repository at this point in the history
…ewer

(feature): new tile viewer supporting multiple TMS
  • Loading branch information
vincentsarago authored Mar 29, 2024
2 parents a6c0038 + f93d777 commit 5ed605c
Show file tree
Hide file tree
Showing 5 changed files with 265 additions and 212 deletions.
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

0 comments on commit 5ed605c

Please sign in to comment.