Skip to content

Commit c6ff359

Browse files
Merge pull request #155 from developmentseed/feat/update-tipg-and-titiler-pgstac
update raster and vector services
2 parents f300973 + f5cd23d commit c6ff359

File tree

6 files changed

+164
-70
lines changed

6 files changed

+164
-70
lines changed

.github/workflows/tests/test_raster.py

Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,29 +16,61 @@ def test_raster_api():
1616
def test_mosaic_api():
1717
"""test mosaic."""
1818
query = {"collections": ["noaa-emergency-response"], "filter-lang": "cql-json"}
19-
resp = httpx.post(f"{raster_endpoint}/mosaic/register", json=query)
19+
resp = httpx.post(f"{raster_endpoint}/searches/register", json=query)
2020
assert resp.headers["content-type"] == "application/json"
2121
assert resp.status_code == 200
22-
assert resp.json()["searchid"]
22+
assert resp.json()["id"]
2323
assert resp.json()["links"]
2424

25-
searchid = resp.json()["searchid"]
25+
searchid = resp.json()["id"]
2626

27-
resp = httpx.get(f"{raster_endpoint}/mosaic/{searchid}/-85.6358,36.1624/assets")
27+
resp = httpx.get(f"{raster_endpoint}/searches/{searchid}/-85.6358,36.1624/assets")
2828
assert resp.status_code == 200
2929
assert len(resp.json()) == 1
3030
assert list(resp.json()[0]) == ["id", "bbox", "assets", "collection"]
3131
assert resp.json()[0]["id"] == "20200307aC0853900w361030"
3232

33-
resp = httpx.get(f"{raster_endpoint}/mosaic/{searchid}/tiles/15/8589/12849/assets")
33+
resp = httpx.get(
34+
f"{raster_endpoint}/searches/{searchid}/tiles/15/8589/12849/assets"
35+
)
3436
assert resp.status_code == 200
3537
assert len(resp.json()) == 1
3638
assert list(resp.json()[0]) == ["id", "bbox", "assets", "collection"]
3739
assert resp.json()[0]["id"] == "20200307aC0853900w361030"
3840

3941
z, x, y = 15, 8589, 12849
4042
resp = httpx.get(
41-
f"{raster_endpoint}/mosaic/{searchid}/tiles/{z}/{x}/{y}",
43+
f"{raster_endpoint}/searches/{searchid}/tiles/{z}/{x}/{y}",
44+
params={"assets": "cog"},
45+
headers={"Accept-Encoding": "br, gzip"},
46+
timeout=10.0,
47+
)
48+
assert resp.status_code == 200
49+
assert resp.headers["content-type"] == "image/jpeg"
50+
assert "content-encoding" not in resp.headers
51+
52+
53+
def test_mosaic_collection_api():
54+
"""test mosaic collection."""
55+
resp = httpx.get(
56+
f"{raster_endpoint}/collections/noaa-emergency-response/-85.6358,36.1624/assets"
57+
)
58+
assert resp.status_code == 200
59+
assert len(resp.json()) == 1
60+
assert list(resp.json()[0]) == ["id", "bbox", "assets", "collection"]
61+
assert resp.json()[0]["id"] == "20200307aC0853900w361030"
62+
63+
resp = httpx.get(
64+
f"{raster_endpoint}/collections/noaa-emergency-response/tiles/15/8589/12849/assets"
65+
)
66+
assert resp.status_code == 200
67+
assert len(resp.json()) == 1
68+
assert list(resp.json()[0]) == ["id", "bbox", "assets", "collection"]
69+
assert resp.json()[0]["id"] == "20200307aC0853900w361030"
70+
71+
z, x, y = 15, 8589, 12849
72+
resp = httpx.get(
73+
f"{raster_endpoint}/collections/noaa-emergency-response/tiles/{z}/{x}/{y}",
4274
params={"assets": "cog"},
4375
headers={"Accept-Encoding": "br, gzip"},
4476
timeout=10.0,
@@ -102,11 +134,11 @@ def test_mosaic_search():
102134
},
103135
]
104136
for search in searches:
105-
resp = httpx.post(f"{raster_endpoint}/mosaic/register", json=search)
137+
resp = httpx.post(f"{raster_endpoint}/searches/register", json=search)
106138
assert resp.status_code == 200
107-
assert resp.json()["searchid"]
139+
assert resp.json()["id"]
108140

109-
resp = httpx.get(f"{raster_endpoint}/mosaic/list")
141+
resp = httpx.get(f"{raster_endpoint}/searches/list")
110142
assert resp.headers["content-type"] == "application/json"
111143
assert resp.status_code == 200
112144
assert (
@@ -122,9 +154,11 @@ def test_mosaic_search():
122154
assert len(links) == 2
123155
assert links[0]["rel"] == "self"
124156
assert links[1]["rel"] == "next"
125-
assert links[1]["href"] == f"{raster_endpoint}/mosaic/list?limit=10&offset=10"
157+
assert links[1]["href"] == f"{raster_endpoint}/searches/list?limit=10&offset=10"
126158

127-
resp = httpx.get(f"{raster_endpoint}/mosaic/list", params={"limit": 1, "offset": 1})
159+
resp = httpx.get(
160+
f"{raster_endpoint}/searches/list", params={"limit": 1, "offset": 1}
161+
)
128162
assert resp.status_code == 200
129163
assert resp.json()["context"]["matched"] > 10
130164
assert resp.json()["context"]["limit"] == 1
@@ -133,33 +167,33 @@ def test_mosaic_search():
133167
links = resp.json()["links"]
134168
assert len(links) == 3
135169
assert links[0]["rel"] == "self"
136-
assert links[0]["href"] == f"{raster_endpoint}/mosaic/list?limit=1&offset=1"
170+
assert links[0]["href"] == f"{raster_endpoint}/searches/list?limit=1&offset=1"
137171
assert links[1]["rel"] == "next"
138-
assert links[1]["href"] == f"{raster_endpoint}/mosaic/list?limit=1&offset=2"
172+
assert links[1]["href"] == f"{raster_endpoint}/searches/list?limit=1&offset=2"
139173
assert links[2]["rel"] == "prev"
140-
assert links[2]["href"] == f"{raster_endpoint}/mosaic/list?limit=1&offset=0"
174+
assert links[2]["href"] == f"{raster_endpoint}/searches/list?limit=1&offset=0"
141175

142176
# Filter on mosaic metadata
143-
resp = httpx.get(f"{raster_endpoint}/mosaic/list", params={"owner": "vincent"})
177+
resp = httpx.get(f"{raster_endpoint}/searches/list", params={"owner": "vincent"})
144178
assert resp.status_code == 200
145179
assert resp.json()["context"]["matched"] == 7
146180
assert resp.json()["context"]["limit"] == 10
147181
assert resp.json()["context"]["returned"] == 7
148182

149183
# sortBy
150-
resp = httpx.get(f"{raster_endpoint}/mosaic/list", params={"sortby": "lastused"})
184+
resp = httpx.get(f"{raster_endpoint}/searches/list", params={"sortby": "lastused"})
151185
assert resp.status_code == 200
152186

153-
resp = httpx.get(f"{raster_endpoint}/mosaic/list", params={"sortby": "usecount"})
187+
resp = httpx.get(f"{raster_endpoint}/searches/list", params={"sortby": "usecount"})
154188
assert resp.status_code == 200
155189

156-
resp = httpx.get(f"{raster_endpoint}/mosaic/list", params={"sortby": "-owner"})
190+
resp = httpx.get(f"{raster_endpoint}/searches/list", params={"sortby": "-owner"})
157191
assert resp.status_code == 200
158192
assert (
159193
"owner" not in resp.json()["searches"][0]["search"]["metadata"]
160194
) # some mosaic don't have owners
161195

162-
resp = httpx.get(f"{raster_endpoint}/mosaic/list", params={"sortby": "owner"})
196+
resp = httpx.get(f"{raster_endpoint}/searches/list", params={"sortby": "owner"})
163197
assert resp.status_code == 200
164198
assert "owner" in resp.json()["searches"][0]["search"]["metadata"]
165199

docker-compose.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ services:
4343
# At the time of writing, rasterio and psycopg wheels are not available for arm64 arch
4444
# so we force the image to be built with linux/amd64
4545
platform: linux/amd64
46-
image: ghcr.io/stac-utils/titiler-pgstac:0.8.0
46+
image: ghcr.io/stac-utils/titiler-pgstac:1.0.0a3
4747
ports:
4848
- "${MY_DOCKER_IP:-127.0.0.1}:8082:8082"
4949
environment:
@@ -89,7 +89,7 @@ services:
8989
- ./dockerfiles/scripts:/tmp/scripts
9090

9191
tipg:
92-
image: ghcr.io/developmentseed/tipg:0.4.4
92+
image: ghcr.io/developmentseed/tipg:0.5.0
9393
ports:
9494
- "${MY_DOCKER_IP:-127.0.0.1}:8083:8083"
9595
environment:

runtime/eoapi/raster/eoapi/raster/app.py

Lines changed: 106 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,13 @@
2121
from titiler.core.middleware import CacheControlMiddleware
2222
from titiler.mosaic.errors import MOSAIC_STATUS_CODES
2323
from titiler.pgstac.db import close_db_connection, connect_to_db
24-
from titiler.pgstac.dependencies import ItemPathParams
25-
from titiler.pgstac.factory import MosaicTilerFactory
24+
from titiler.pgstac.dependencies import CollectionIdParams, ItemIdParams, SearchIdParams
25+
from titiler.pgstac.extensions import searchInfoExtension
26+
from titiler.pgstac.factory import (
27+
MosaicTilerFactory,
28+
add_search_list_route,
29+
add_search_register_route,
30+
)
2631
from titiler.pgstac.reader import PgSTACReader
2732

2833
try:
@@ -86,56 +91,100 @@ async def lifespan(app: FastAPI):
8691
)
8792

8893
###############################################################################
89-
# MOSAIC Endpoints
90-
mosaic = MosaicTilerFactory(
91-
router_prefix="/mosaic",
92-
# add /statistics [POST]
94+
# `Secret` endpoint for mosaic builder. Do not need to be public (in the OpenAPI docs)
95+
@app.get("/collections", include_in_schema=False)
96+
async def list_collection(request: Request):
97+
"""list collections."""
98+
with request.app.state.dbpool.connection() as conn:
99+
with conn.cursor(row_factory=dict_row) as cursor:
100+
cursor.execute("SELECT * FROM pgstac.all_collections();")
101+
r = cursor.fetchone()
102+
return r.get("all_collections", [])
103+
104+
105+
###############################################################################
106+
# STAC Search Endpoints
107+
searches = MosaicTilerFactory(
108+
path_dependency=SearchIdParams,
109+
router_prefix="/searches/{search_id}",
93110
add_statistics=True,
94-
# add /map viewer
95111
add_viewer=True,
96-
# add /mosaic/list endpoint
97-
add_mosaic_list=True,
98-
# add `/bbox` and `/feature [POST]` endpoint
99-
add_part=False,
112+
add_part=True,
113+
extensions=[
114+
searchInfoExtension(),
115+
],
116+
)
117+
app.include_router(
118+
searches.router, tags=["STAC Search"], prefix="/searches/{search_id}"
119+
)
120+
121+
add_search_register_route(
122+
app,
123+
prefix="/searches",
124+
tile_dependencies=[
125+
searches.layer_dependency,
126+
searches.dataset_dependency,
127+
searches.pixel_selection_dependency,
128+
searches.tile_dependency,
129+
searches.process_dependency,
130+
searches.rescale_dependency,
131+
searches.colormap_dependency,
132+
searches.render_dependency,
133+
searches.pgstac_dependency,
134+
searches.reader_dependency,
135+
searches.backend_dependency,
136+
],
137+
tags=["STAC Search"],
100138
)
139+
add_search_list_route(app, prefix="/searches", tags=["STAC Search"])
101140

102141

103-
@mosaic.router.get("/builder", response_class=HTMLResponse)
104-
async def mosaic_builder(request: Request):
142+
@app.get("/searches/builder", response_class=HTMLResponse, tags=["STAC Search"])
143+
async def virtual_mosaic_builder(request: Request):
105144
"""Mosaic Builder Viewer."""
145+
base_url = str(request.base_url)
106146
return templates.TemplateResponse(
107147
name="mosaic-builder.html",
108148
context={
109149
"request": request,
110-
"register_endpoint": mosaic.url_for(request, "register_search"),
111-
"collections_endpoint": str(request.url_for("list_collection")),
150+
"register_endpoint": str(
151+
app.url_path_for("register_search").make_absolute_url(base_url=base_url)
152+
),
153+
"collections_endpoint": str(
154+
app.url_path_for("list_collection").make_absolute_url(base_url=base_url)
155+
),
112156
},
113157
media_type="text/html",
114158
)
115159

116160

117-
# `Secret` endpoint for mosaic builder. Do not need to be public (in the OpenAPI docs)
118-
@app.get("/collections", include_in_schema=False)
119-
async def list_collection(request: Request):
120-
"""list collections."""
121-
with request.app.state.dbpool.connection() as conn:
122-
with conn.cursor(row_factory=dict_row) as cursor:
123-
cursor.execute("SELECT * FROM pgstac.all_collections();")
124-
r = cursor.fetchone()
125-
return r.get("all_collections", [])
126-
161+
###############################################################################
162+
# STAC COLLECTION Endpoints
163+
collection = MosaicTilerFactory(
164+
path_dependency=CollectionIdParams,
165+
router_prefix="/collections/{collection_id}",
166+
add_statistics=True,
167+
add_viewer=True,
168+
add_part=True,
169+
)
170+
app.include_router(
171+
collection.router, tags=["STAC Collection"], prefix="/collections/{collection_id}"
172+
)
127173

128-
app.include_router(mosaic.router, tags=["Mosaic"], prefix="/mosaic")
129174

130175
###############################################################################
131176
# STAC Item Endpoints
132177
stac = MultiBaseTilerFactory(
133178
reader=PgSTACReader,
134-
path_dependency=ItemPathParams,
179+
path_dependency=ItemIdParams,
135180
router_prefix="/collections/{collection_id}/items/{item_id}",
136-
# add /map viewer
137181
add_viewer=True,
138182
)
183+
app.include_router(
184+
stac.router,
185+
tags=["STAC Item"],
186+
prefix="/collections/{collection_id}/items/{item_id}",
187+
)
139188

140189

141190
@stac.router.get("/viewer", response_class=HTMLResponse)
@@ -155,9 +204,12 @@ def viewer(request: Request, item: pystac.Item = Depends(stac.path_dependency)):
155204

156205

157206
app.include_router(
158-
stac.router, tags=["Item"], prefix="/collections/{collection_id}/items/{item_id}"
207+
stac.router,
208+
tags=["STAC Item"],
209+
prefix="/collections/{collection_id}/items/{item_id}",
159210
)
160211

212+
161213
###############################################################################
162214
# Tiling Schemes Endpoints
163215
tms = TMSFactory()
@@ -216,44 +268,52 @@ def landing(request: Request):
216268
"rel": "service-doc",
217269
},
218270
{
219-
"title": "STAC Item Asset's Info (template URL)",
220-
"href": stac.url_for(request, "info"),
271+
"title": "PgSTAC Virtual Mosaic list (JSON)",
272+
"href": str(app.url_path_for("list_searches")),
221273
"type": "application/json",
222274
"rel": "data",
223275
},
224276
{
225-
"title": "STAC Item Viewer (template URL)",
226-
"href": stac.url_for(request, "viewer"),
277+
"title": "PgSTAC Virtual Mosaic builder",
278+
"href": str(app.url_path_for("virtual_mosaic_builder")),
227279
"type": "text/html",
228280
"rel": "data",
229281
},
230282
{
231-
"title": "STAC Mosaic List (JSON)",
232-
"href": mosaic.url_for(request, "list_mosaic"),
233-
"type": "application/json",
283+
"title": "PgSTAC Virtual Mosaic viewer (template URL)",
284+
"href": str(app.url_path_for("map_viewer", search_id="{search_id}")),
285+
"type": "text/html",
234286
"rel": "data",
235287
},
236288
{
237-
"title": "STAC Mosaic Builder",
238-
"href": mosaic.url_for(request, "mosaic_builder"),
289+
"title": "PgSTAC Collection viewer (template URL)",
290+
"href": str(
291+
app.url_path_for("map_viewer", collection_id="{collection_id}")
292+
),
239293
"type": "text/html",
240294
"rel": "data",
241295
},
242296
{
243-
"title": "STAC Mosaic Metadata (template URL)",
244-
"href": mosaic.url_for(request, "info_search", searchid="{searchid}"),
245-
"type": "application/json",
297+
"title": "PgSTAC Item viewer (template URL)",
298+
"href": str(
299+
app.url_path_for(
300+
"map_viewer",
301+
collection_id="{collection_id}",
302+
item_id="{item_id}",
303+
)
304+
),
305+
"type": "text/html",
246306
"rel": "data",
247307
},
248308
{
249-
"title": "STAC Mosaic viewer (template URL)",
250-
"href": mosaic.url_for(request, "map_viewer", searchid="{searchid}"),
309+
"title": "TiTiler-PgSTAC Documentation (external link)",
310+
"href": "https://stac-utils.github.io/titiler-pgstac/",
251311
"type": "text/html",
252-
"rel": "data",
312+
"rel": "doc",
253313
},
254314
{
255-
"title": "TiTiler-pgSTAC Documentation (external link)",
256-
"href": "https://stac-utils.github.io/titiler-pgstac/",
315+
"title": "TiTiler-PgSTAC source code (external link)",
316+
"href": "https://github.com/stac-utils/titiler-pgstac",
257317
"type": "text/html",
258318
"rel": "doc",
259319
},

runtime/eoapi/raster/eoapi/raster/templates/mosaic-builder.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -596,7 +596,7 @@
596596
var info_link = data.links.filter((link) => link.rel == 'metadata');
597597
var tilejson_link = data.links.filter((link) => link.rel == 'tilejson');
598598
var map_link = data.links.filter((link) => link.rel == 'map');
599-
var mosaic_info_str = `<p class="mt6">MosaicID: <strong>${data.searchid}</strong></p><p>Links:</p>`
599+
var mosaic_info_str = `<p class="mt6">MosaicID: <strong>${data.id}</strong></p><p>Links:</p>`
600600
if (info_link) {
601601
mosaic_info_str += `<p class="mt6">- <a href="${info_link[0].href}" target="_blank">Mosaic Info Link</a></p>`
602602
}

0 commit comments

Comments
 (0)