Skip to content

Commit

Permalink
Merge pull request #120 from stac-utils/LayerLinks
Browse files Browse the repository at this point in the history
add tilejson links for layers
  • Loading branch information
vincentsarago authored Aug 31, 2023
2 parents 89c3a3b + 8fec9c3 commit 4b9da63
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 32 deletions.
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Release Notes

## 0,5,2 (TDB)

* add `tilejson` URL links for `defaults layers` defined in mosaic's metadata in `/mosaic/register` and `/mosaic/{mosaic_id}/info` response

## 0.5.1 (2023-08-03)

* add `python-dotenv` requirement
Expand Down
58 changes: 50 additions & 8 deletions tests/test_mosaic.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ def test_register(app):
resp = response.json()
assert resp["searchid"] == search_no_bbox
assert resp["links"]
assert [link["rel"] for link in resp["links"]] == ["metadata", "tilejson"]
assert [link["rel"] for link in resp["links"]] == [
"metadata",
"tilejson",
"map",
"wmts",
]

query = {
"collections": ["noaa-emergency-response"],
Expand All @@ -34,7 +39,12 @@ def test_register(app):
resp = response.json()
assert resp["searchid"] == search_bbox
assert resp["links"]
assert [link["rel"] for link in resp["links"]] == ["metadata", "tilejson"]
assert [link["rel"] for link in resp["links"]] == [
"metadata",
"tilejson",
"map",
"wmts",
]


def test_info(app):
Expand Down Expand Up @@ -504,6 +514,41 @@ def test_query_with_metadata(app):
assert resp["minzoom"] == 1
assert resp["maxzoom"] == 2

# Check that `defaults` created `tilejson` URL in links
query = {
"filter": {
"op": "=",
"args": [{"property": "collection"}, "noaa-emergency-response"],
},
"metadata": {
"name": "mymosaic",
"minzoom": 1,
"maxzoom": 2,
"defaults": {
"one_band": {
"assets": "cog",
"asset_bidx": 1,
},
"three_bands": {
"assets": "cog",
"asset_bidx": [1, 2, 3],
},
},
},
}

response = app.post("/mosaic/register", json=query)
assert response.status_code == 200
resp = response.json()
assert resp["searchid"]
assert (
len(resp["links"]) == 6
) # info, tilejson, map, wmts tilejson for one_band, tilejson for three_bands
link = resp["links"][-2]
assert link["title"] == "TileJSON link for `one_band` layer."
assert "asset_bidx=1" in link["href"]
assert "assets=cog" in link["href"]


@patch("rio_tiler.io.rasterio.rasterio")
def test_statistics(rio, app):
Expand Down Expand Up @@ -572,23 +617,20 @@ def test_mosaic_list(app):
assert response.status_code == 200
resp = response.json()
assert ["searches", "links", "context"] == list(resp)
assert resp["context"] == {"returned": 5, "limit": 10, "matched": 5}
assert len(resp["searches"]) == 5
assert len(resp["searches"]) > 0
assert len(resp["links"]) == 1

response = app.get("/mosaic/list?limit=1")
assert response.status_code == 200
resp = response.json()
assert ["searches", "links", "context"] == list(resp)
assert resp["context"] == {"returned": 1, "limit": 1, "matched": 5}
assert len(resp["searches"]) == 1
assert len(resp["links"]) == 2

response = app.get("/mosaic/list?limit=1&offset=1")
assert response.status_code == 200
resp = response.json()
assert ["searches", "links", "context"] == list(resp)
assert resp["context"] == {"returned": 1, "limit": 1, "matched": 5}
assert len(resp["searches"]) == 1
assert len(resp["links"]) == 3

Expand Down Expand Up @@ -645,7 +687,7 @@ def test_mosaic_list(app):
response = app.get("/mosaic/list?sortby=lastused")
assert response.status_code == 200
resp = response.json()
assert resp["context"] == {"returned": 8, "limit": 10, "matched": 8}
assert resp["context"]
dates = [
datetime.strptime(s["search"]["lastused"][0:-6], "%Y-%m-%dT%H:%M:%S.%f")
for s in resp["searches"]
Expand All @@ -655,7 +697,7 @@ def test_mosaic_list(app):
response = app.get("/mosaic/list?sortby=-lastused")
assert response.status_code == 200
resp = response.json()
assert resp["context"] == {"returned": 8, "limit": 10, "matched": 8}
assert resp["context"]
dates = [
datetime.strptime(s["search"]["lastused"][0:-6], "%Y-%m-%dT%H:%M:%S.%f")
for s in resp["searches"]
Expand Down
123 changes: 100 additions & 23 deletions titiler/pgstac/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from starlette.datastructures import QueryParams
from starlette.requests import Request
from starlette.responses import HTMLResponse, Response
from starlette.routing import NoMatchFound

from titiler.core.dependencies import (
AssetsBidxExprParams,
Expand Down Expand Up @@ -652,7 +653,7 @@ def assets_for_point(
**pgstac_params,
)

def _search_routes(self) -> None:
def _search_routes(self) -> None: # noqa: C901
"""register search routes."""

@self.router.post(
Expand All @@ -678,21 +679,59 @@ def register_search(
)
search_info = cursor.fetchone()

return model.RegisterResponse(
searchid=search_info.id,
links=[
links: List[model.Link] = [
model.Link(
rel="metadata",
title="Mosaic metadata",
href=self.url_for(request, "info_search", searchid=search_info.id),
),
model.Link(
rel="tilejson",
title="Link for TileJSON",
href=self.url_for(request, "tilejson", searchid=search_info.id),
),
]

try:
links.append(
model.Link(
rel="metadata",
rel="map",
title="Link for Map viewer",
href=self.url_for(
request, "info_search", searchid=search_info.id
request, "map_viewer", searchid=search_info.id
),
),
)
)
except NoMatchFound:
pass

try:
links.append(
model.Link(
rel="tilejson",
href=self.url_for(request, "tilejson", searchid=search_info.id),
),
],
)
rel="wmts",
title="Link for WMTS",
href=self.url_for(request, "wmts", searchid=search_info.id),
)
)
except NoMatchFound:
pass

if search_info.metadata.defaults:
for name, values in search_info.metadata.defaults.items():
links.append(
model.Link(
title=f"TileJSON link for `{name}` layer.",
rel="tilejson",
href=self.url_for(
request,
"tilejson",
searchid=search_info.id,
)
+ f"?{urlencode(values, doseq=True)}",
)
)

return model.RegisterResponse(searchid=search_info.id, links=links)

@self.router.get(
"/{searchid}/info",
Expand All @@ -713,21 +752,59 @@ def info_search(request: Request, searchid=Depends(self.path_dependency)):
if not search_info:
raise MosaicNotFoundError(f"SearchId `{searchid}` not found")

return model.Info(
search=search_info,
links=[
links: List[model.Link] = [
model.Link(
rel="self",
title="Mosaic metadata",
href=self.url_for(request, "info_search", searchid=search_info.id),
),
model.Link(
title="Link for TileJSON",
rel="tilejson",
href=self.url_for(request, "tilejson", searchid=search_info.id),
),
]

try:
links.append(
model.Link(
rel="self",
rel="map",
title="Link for Map viewer",
href=self.url_for(
request, "info_search", searchid=search_info.id
request, "map_viewer", searchid=search_info.id
),
),
)
)
except NoMatchFound:
pass

try:
links.append(
model.Link(
rel="tilejson",
href=self.url_for(request, "tilejson", searchid=search_info.id),
),
],
)
rel="wmts",
title="Link for WMTS",
href=self.url_for(request, "wmts", searchid=search_info.id),
)
)
except NoMatchFound:
pass

if search_info.metadata.defaults:
for name, values in search_info.metadata.defaults.items():
links.append(
model.Link(
title=f"TileJSON link for `{name}` layer.",
rel="tilejson",
href=self.url_for(
request,
"tilejson",
searchid=search_info.id,
)
+ f"?{urlencode(values, doseq=True)}",
)
)

return model.Info(search=search_info, links=links)

def _search_list_routes(self) -> None:
"""Add mosaic listing route."""
Expand Down
2 changes: 1 addition & 1 deletion titiler/pgstac/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ async def lifespan(app: FastAPI):
CORSMiddleware,
allow_origins=settings.cors_origins,
allow_credentials=True,
allow_methods=["GET", "POST"],
allow_methods=["GET", "POST", "OPTIONS"],
allow_headers=["*"],
)

Expand Down

0 comments on commit 4b9da63

Please sign in to comment.