Skip to content

Commit

Permalink
Merge pull request #209 from developmentseed/feature/infer-ogc-tiles-…
Browse files Browse the repository at this point in the history
…links-in-ogc-feature-responses

infer OGC Tiles links /collections and /collections/{collectionId} response
  • Loading branch information
vincentsarago authored Feb 25, 2025
2 parents 5d48cd7 + 25c82e6 commit 2053459
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 57 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Note: Minor version `0.X.0` update might break the API, It's recommended to pin
* renamed `tipg.collections.get_collection_index` to `tipg.collections.pg_get_collection_index` and change the function to use `DatabaseSettings` instance directly instead of keyword option
* update `tipg.collections.pg_get_collection_index` to return a list of PgCollection instead of a Catalog
* update `tipg.collections.register_collection_catalog` to pass `db_settings` to `pg_get_collection_index` function
* add `tilesets` and `viewer` links in `/collections` and `/collections/{collectionId}` response links

## [0.11.0] - TBD

Expand Down
13 changes: 12 additions & 1 deletion tests/routes/test_collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ def test_collections(app):
assert body["numberMatched"] >= collection_number
assert body["numberReturned"] >= collection_number

col = body["collections"][0]
link_titles = [link.get("title", "") for link in col["links"]]
assert "Collection TileSets" in link_titles
assert "Collection TileSet (Template URL)" in link_titles
assert "Collection Map viewer (Template URL)" in link_titles

ids = [x["id"] for x in body["collections"]]
assert "public.landsat_wrs" in ids
assert "public.my_data" in ids
Expand Down Expand Up @@ -158,7 +164,7 @@ def test_collections_landsat(app):
assert response.headers["content-type"] == "application/json"
body = response.json()
assert body["id"] == "public.landsat_wrs"
assert ["id", "title", "links", "extent", "itemType", "crs"] == list(body)
assert sorted(["id", "links", "extent", "itemType", "crs"]) == sorted(body)
assert body["crs"] == ["http://www.opengis.net/def/crs/OGC/1.3/CRS84"]
assert ["bbox", "crs"] == list(body["extent"]["spatial"])
assert (
Expand All @@ -167,6 +173,11 @@ def test_collections_landsat(app):
)
assert not body["extent"].get("temporal")

link_titles = [link.get("title", "") for link in body["links"]]
assert "Collection TileSets" in link_titles
assert "Collection TileSet (Template URL)" in link_titles
assert "Collection Map viewer (Template URL)" in link_titles

response = app.get("/collections/public.landsat_wrs?f=html")
assert response.status_code == 200
assert "text/html" in response.headers["content-type"]
Expand Down
2 changes: 1 addition & 1 deletion tests/routes/test_non_geo.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ def test_non_geo(app):
body = response.json()
assert body["id"] == "public.nongeo_data"
# No Extent for non-geo table
assert ["id", "title", "links", "extent", "itemType", "crs"] == list(body)
assert sorted(["id", "links", "extent", "itemType", "crs"]) == sorted(body)
assert body["extent"]["temporal"]
assert body["extent"]["temporal"]["interval"][0] == [
"2004-10-19T09:23:54+00:00",
Expand Down
177 changes: 122 additions & 55 deletions tipg/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
from starlette.datastructures import QueryParams
from starlette.requests import Request
from starlette.responses import HTMLResponse, Response, StreamingResponse
from starlette.routing import compile_path, replace_params
from starlette.routing import NoMatchFound, compile_path, replace_params
from starlette.templating import Jinja2Templates, _TemplateResponse

tms_settings = TMSSettings()
Expand Down Expand Up @@ -416,6 +416,65 @@ def links(self, request: Request) -> List[model.Link]:
),
]

def _additional_collection_tiles_links(
self, request: Request, collection: Collection
) -> List[model.Link]:
links = []
base_url = str(request.base_url)
try:
links.append(
model.Link(
rel="data",
title="Collection TileSets",
type=MediaType.json,
href=str(
request.app.url_path_for(
"collection_tileset_list",
collectionId=collection.id,
).make_absolute_url(base_url=base_url)
),
),
)
links.append(
model.Link(
rel="data",
title="Collection TileSet (Template URL)",
type=MediaType.json,
templated=True,
href=str(
request.app.url_path_for(
"collection_tileset",
collectionId=collection.id,
tileMatrixSetId="{tileMatrixSetId}",
).make_absolute_url(base_url=base_url)
),
),
)
except NoMatchFound:
pass

try:
links.append(
model.Link(
title="Collection Map viewer (Template URL)",
href=str(
request.app.url_path_for(
"viewer_endpoint",
collectionId=collection.id,
tileMatrixSetId="{tileMatrixSetId}",
).make_absolute_url(base_url=base_url)
),
type=MediaType.html,
rel="data",
templated=True,
)
)

except NoMatchFound:
pass

return links

def register_routes(self):
"""Register OGC Features endpoints."""
self._collections_route()
Expand Down Expand Up @@ -530,6 +589,9 @@ def collections(
rel="queryables",
type=MediaType.schemajson,
),
*self._additional_collection_tiles_links(
request, collection
),
],
)
for collection in collection_list["collections"]
Expand Down Expand Up @@ -568,60 +630,65 @@ def collection(
output_type: Annotated[Optional[MediaType], Depends(OutputType)] = None,
):
"""Metadata for a feature collection."""

data = model.Collection.model_validate(
{
**collection.model_dump(),
"title": collection.id,
"extent": collection.extent,
"links": [
{
"href": self.url_for(
request,
"collection",
collectionId=collection.id,
),
"rel": "self",
"type": "application/json",
},
{
"title": "Items",
"href": self.url_for(
request, "items", collectionId=collection.id
),
"rel": "items",
"type": "application/geo+json",
},
{
"title": "Items (CSV)",
"href": self.url_for(
request, "items", collectionId=collection.id
)
+ "?f=csv",
"rel": "alternate",
"type": "text/csv",
},
{
"title": "Items (GeoJSONSeq)",
"href": self.url_for(
request, "items", collectionId=collection.id
)
+ "?f=geojsonseq",
"rel": "alternate",
"type": "application/geo+json-seq",
},
{
"title": "Queryables",
"href": self.url_for(
request,
"queryables",
collectionId=collection.id,
),
"rel": "queryables",
"type": "application/schema+json",
},
],
}
data = model.Collection(
id=collection.id,
title=collection.title,
description=collection.description,
extent=collection.extent,
links=[
model.Link(
title="Collection",
href=self.url_for(
request,
"collection",
collectionId=collection.id,
),
rel="self",
type=MediaType.json,
),
model.Link(
title="Items",
href=self.url_for(
request,
"items",
collectionId=collection.id,
),
rel="items",
type=MediaType.geojson,
),
model.Link(
title="Items (CSV)",
href=self.url_for(
request,
"items",
collectionId=collection.id,
)
+ "?f=csv",
rel="alternate",
type=MediaType.csv,
),
model.Link(
title="Items (GeoJSONSeq)",
href=self.url_for(
request,
"items",
collectionId=collection.id,
)
+ "?f=geojsonseq",
rel="alternate",
type=MediaType.geojsonseq,
),
model.Link(
href=self.url_for(
request,
"queryables",
collectionId=collection.id,
),
rel="queryables",
type=MediaType.schemajson,
),
*self._additional_collection_tiles_links(request, collection),
],
)

if output_type == MediaType.html:
Expand Down

1 comment on commit 2053459

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'TiPg Benchmarks'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.30.

Benchmark suite Current: 2053459 Previous: 5d48cd7 Ratio
tests/benchmarks.py::test_benchmark_collections[json-10] 205.88738644408718 iter/sec (stddev: 0.00048693342708740884) 320.61241374098546 iter/sec (stddev: 0.00011543969174090084) 1.56
tests/benchmarks.py::test_benchmark_collections[html-10] 196.86125922616094 iter/sec (stddev: 0.00010557246603651671) 284.9603990269925 iter/sec (stddev: 0.0002760240622788884) 1.45

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.