From bbd4961e6a5f51f2bfef250a6cc9bee37e6bd604 Mon Sep 17 00:00:00 2001 From: vincentsarago Date: Wed, 11 Oct 2023 09:29:06 +0200 Subject: [PATCH 1/4] rename searchid path parameter to search_id --- CHANGES.md | 2 + tests/test_mosaic.py | 2 +- titiler/pgstac/dependencies.py | 8 +- titiler/pgstac/factory.py | 135 +++++++++++++++++---------------- 4 files changed, 77 insertions(+), 70 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 37dea77..385692c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,8 @@ * add `pgstac_dependency` attribute in `MosaicTilerFactory` (defaults to `dependencies.PgSTACParams`) * add database's `pool` check in startup event +* rename `dependencies.PathParams` to `dependencies.SearchIdParams` **breaking change** +* rename `searchid` path parameter to `search_id` **breaking change** ## 0.8.0 (2023-10-06) diff --git a/tests/test_mosaic.py b/tests/test_mosaic.py index e0b166b..c02d76c 100644 --- a/tests/test_mosaic.py +++ b/tests/test_mosaic.py @@ -379,7 +379,7 @@ def test_cql2(rio, app): assert resp["minzoom"] == 0 assert resp["maxzoom"] == 24 assert round(resp["bounds"][0]) == -180 - # Make sure we return a tilejson with the `/{searchid}/tiles/{tms}` format + # Make sure we return a tilejson with the `/{search_id}/tiles/{tms}` format assert ( f"/mosaic/{cql2_id}/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}?assets=cog" in resp["tiles"][0] diff --git a/titiler/pgstac/dependencies.py b/titiler/pgstac/dependencies.py index f7242ae..c62cbc9 100644 --- a/titiler/pgstac/dependencies.py +++ b/titiler/pgstac/dependencies.py @@ -23,14 +23,14 @@ retry_config = RetrySettings() -def PathParams( - searchid: Annotated[ +def SearchIdParams( + search_id: Annotated[ str, Path(description="Search Id (pgSTAC Search Hash)"), ] ) -> str: - """SearchId""" - return searchid + """search_id""" + return search_id def SearchParams( diff --git a/titiler/pgstac/factory.py b/titiler/pgstac/factory.py index 815beec..b6fe0d9 100644 --- a/titiler/pgstac/factory.py +++ b/titiler/pgstac/factory.py @@ -57,8 +57,8 @@ from titiler.pgstac import model from titiler.pgstac.dependencies import ( BackendParams, - PathParams, PgSTACParams, + SearchIdParams, SearchParams, TileParams, ) @@ -92,7 +92,7 @@ class MosaicTilerFactory(BaseTilerFactory): """Custom MosaicTiler for PgSTAC Mosaic Backend.""" reader: Type[BaseBackend] = PGSTACBackend - path_dependency: Callable[..., str] = PathParams + path_dependency: Callable[..., str] = SearchIdParams layer_dependency: Type[DefaultDependency] = AssetsBidxExprParams # Statistics/Histogram Dependencies @@ -169,33 +169,33 @@ def register_routes(self) -> None: def _tiles_routes(self) -> None: """register tiles routes.""" - @self.router.get("/{searchid}/tiles/{z}/{x}/{y}", **img_endpoint_params) + @self.router.get("/{search_id}/tiles/{z}/{x}/{y}", **img_endpoint_params) @self.router.get( - "/{searchid}/tiles/{z}/{x}/{y}.{format}", **img_endpoint_params + "/{search_id}/tiles/{z}/{x}/{y}.{format}", **img_endpoint_params ) @self.router.get( - "/{searchid}/tiles/{z}/{x}/{y}@{scale}x", **img_endpoint_params + "/{search_id}/tiles/{z}/{x}/{y}@{scale}x", **img_endpoint_params ) @self.router.get( - "/{searchid}/tiles/{z}/{x}/{y}@{scale}x.{format}", **img_endpoint_params + "/{search_id}/tiles/{z}/{x}/{y}@{scale}x.{format}", **img_endpoint_params ) @self.router.get( - "/{searchid}/tiles/{tileMatrixSetId}/{z}/{x}/{y}", **img_endpoint_params + "/{search_id}/tiles/{tileMatrixSetId}/{z}/{x}/{y}", **img_endpoint_params ) @self.router.get( - "/{searchid}/tiles/{tileMatrixSetId}/{z}/{x}/{y}.{format}", + "/{search_id}/tiles/{tileMatrixSetId}/{z}/{x}/{y}.{format}", **img_endpoint_params, ) @self.router.get( - "/{searchid}/tiles/{tileMatrixSetId}/{z}/{x}/{y}@{scale}x", + "/{search_id}/tiles/{tileMatrixSetId}/{z}/{x}/{y}@{scale}x", **img_endpoint_params, ) @self.router.get( - "/{searchid}/tiles/{tileMatrixSetId}/{z}/{x}/{y}@{scale}x.{format}", + "/{search_id}/tiles/{tileMatrixSetId}/{z}/{x}/{y}@{scale}x.{format}", **img_endpoint_params, ) def tile( - searchid=Depends(self.path_dependency), + search_id=Depends(self.path_dependency), tile=Depends(TileParams), tileMatrixSetId: Annotated[ # type: ignore Literal[tuple(self.supported_tms.list())], @@ -229,7 +229,7 @@ def tile( tms = self.supported_tms.get(tileMatrixSetId) with rasterio.Env(**env): with self.reader( - searchid, + search_id, tms=tms, reader_options={**reader_params}, **backend_params, @@ -283,20 +283,20 @@ def _tilejson_routes(self) -> None: """register tiles routes.""" @self.router.get( - "/{searchid}/tilejson.json", + "/{search_id}/tilejson.json", response_model=TileJSON, responses={200: {"description": "Return a tilejson"}}, response_model_exclude_none=True, ) @self.router.get( - "/{searchid}/{tileMatrixSetId}/tilejson.json", + "/{search_id}/{tileMatrixSetId}/tilejson.json", response_model=TileJSON, responses={200: {"description": "Return a tilejson"}}, response_model_exclude_none=True, ) def tilejson( request: Request, - searchid=Depends(self.path_dependency), + search_id=Depends(self.path_dependency), tileMatrixSetId: Annotated[ # type: ignore Literal[tuple(self.supported_tms.list())], f"Identifier selecting one of the TileMatrixSetId supported (default: '{self.default_tms}')", @@ -334,19 +334,19 @@ def tilejson( backend_params=Depends(self.backend_dependency), reader_params=Depends(self.reader_dependency), ): - """Return TileJSON document for a SearchId.""" + """Return TileJSON document for a search_id.""" with request.app.state.dbpool.connection() as conn: with conn.cursor(row_factory=class_row(model.Search)) as cursor: cursor.execute( "SELECT * FROM searches WHERE hash=%s;", - (searchid,), + (search_id,), ) search_info = cursor.fetchone() if not search_info: - raise MosaicNotFoundError(f"SearchId `{searchid}` not found") + raise MosaicNotFoundError(f"SearchId `{search_id}` not found") route_params = { - "searchid": search_info.id, + "search_id": search_info.id, "z": "{z}", "x": "{x}", "y": "{y}", @@ -392,13 +392,13 @@ def tilejson( def _map_routes(self): # noqa: C901 """Register /map endpoint.""" - @self.router.get("/{searchid}/map", response_class=HTMLResponse) + @self.router.get("/{search_id}/map", response_class=HTMLResponse) @self.router.get( - "/{searchid}/{tileMatrixSetId}/map", response_class=HTMLResponse + "/{search_id}/{tileMatrixSetId}/map", response_class=HTMLResponse ) def map_viewer( request: Request, - searchid=Depends(self.path_dependency), + search_id=Depends(self.path_dependency), tileMatrixSetId: Annotated[ Literal[tuple(self.supported_tms.list())], f"Identifier selecting one of the TileMatrixSetId supported (default: '{self.default_tms}')", @@ -439,7 +439,10 @@ def map_viewer( ): """Return a simple map viewer.""" tilejson_url = self.url_for( - request, "tilejson", searchid=searchid, tileMatrixSetId=tileMatrixSetId + request, + "tilejson", + search_id=search_id, + tileMatrixSetId=tileMatrixSetId, ) if request.query_params._list: tilejson_url += f"?{urlencode(request.query_params._list)}" @@ -459,14 +462,16 @@ def map_viewer( def _wmts_routes(self): # noqa: C901 """Add wmts endpoint.""" - @self.router.get("/{searchid}/WMTSCapabilities.xml", response_class=XMLResponse) @self.router.get( - "/{searchid}/{tileMatrixSetId}/WMTSCapabilities.xml", + "/{search_id}/WMTSCapabilities.xml", response_class=XMLResponse + ) + @self.router.get( + "/{search_id}/{tileMatrixSetId}/WMTSCapabilities.xml", response_class=XMLResponse, ) def wmts( request: Request, - searchid=Depends(self.path_dependency), + search_id=Depends(self.path_dependency), tileMatrixSetId: Annotated[ Literal[tuple(self.supported_tms.list())], f"Identifier selecting one of the TileMatrixSetId supported (default: '{self.default_tms}')", @@ -495,14 +500,14 @@ def wmts( with conn.cursor(row_factory=class_row(model.Search)) as cursor: cursor.execute( "SELECT * FROM searches WHERE hash=%s;", - (searchid,), + (search_id,), ) search_info = cursor.fetchone() if not search_info: - raise MosaicNotFoundError(f"SearchId `{searchid}` not found") + raise MosaicNotFoundError(f"SearchId `{search_id}` not found") route_params = { - "searchid": searchid, + "search_id": search_id, "z": "{TileMatrix}", "x": "{TileCol}", "y": "{TileRow}", @@ -611,7 +616,7 @@ def wmts( "wmts.xml", { "request": request, - "title": search_info.metadata.name or searchid, + "title": search_info.metadata.name or search_id, "bounds": bounds, "tileMatrix": tileMatrix, "tms": tms, @@ -625,16 +630,16 @@ def _assets_routes(self): """Register assets routes.""" @self.router.get( - "/{searchid}/tiles/{z}/{x}/{y}/assets", + "/{search_id}/tiles/{z}/{x}/{y}/assets", responses={200: {"description": "Return list of assets"}}, ) @self.router.get( - "/{searchid}/tiles/{tileMatrixSetId}/{z}/{x}/{y}/assets", + "/{search_id}/tiles/{tileMatrixSetId}/{z}/{x}/{y}/assets", responses={200: {"description": "Return list of assets"}}, response_model=List[Dict], ) def assets_for_tile( - searchid=Depends(self.path_dependency), + search_id=Depends(self.path_dependency), tile=Depends(TileParams), tileMatrixSetId: Annotated[ Literal[tuple(self.supported_tms.list())], @@ -647,7 +652,7 @@ def assets_for_tile( """Return a list of assets which overlap a given tile""" tms = self.supported_tms.get(tileMatrixSetId) with self.reader( - searchid, + search_id, tms=tms, reader_options={**reader_params}, **backend_params, @@ -655,14 +660,14 @@ def assets_for_tile( return src_dst.assets_for_tile(tile.x, tile.y, tile.z, **pgstac_params) @self.router.get( - "/{searchid}/{lon},{lat}/assets", + "/{search_id}/{lon},{lat}/assets", responses={200: {"description": "Return list of assets"}}, response_model=List[Dict], ) def assets_for_point( lon: Annotated[float, Path(description="Longitude")], lat: Annotated[float, Path(description="Latitude")], - searchid=Depends(self.path_dependency), + search_id=Depends(self.path_dependency), coord_crs=Depends(CoordCRSParams), pgstac_params=Depends(self.pgstac_dependency), backend_params=Depends(self.backend_dependency), @@ -670,7 +675,7 @@ def assets_for_point( ): """Return a list of assets for a given point.""" with self.reader( - searchid, + search_id, reader_options={**reader_params}, **backend_params, ) as src_dst: @@ -711,12 +716,12 @@ def register_search( model.Link( rel="metadata", title="Mosaic metadata", - href=self.url_for(request, "info_search", searchid=search_info.id), + href=self.url_for(request, "info_search", search_id=search_info.id), ), model.Link( rel="tilejson", title="Link for TileJSON", - href=self.url_for(request, "tilejson", searchid=search_info.id), + href=self.url_for(request, "tilejson", search_id=search_info.id), ), ] @@ -726,7 +731,7 @@ def register_search( rel="map", title="Link for Map viewer", href=self.url_for( - request, "map_viewer", searchid=search_info.id + request, "map_viewer", search_id=search_info.id ), ) ) @@ -738,7 +743,7 @@ def register_search( model.Link( rel="wmts", title="Link for WMTS", - href=self.url_for(request, "wmts", searchid=search_info.id), + href=self.url_for(request, "wmts", search_id=search_info.id), ) ) except NoMatchFound: @@ -782,7 +787,7 @@ def register_search( href=self.url_for( request, "tilejson", - searchid=search_info.id, + search_id=search_info.id, ) + f"?{query_string}", ) @@ -791,34 +796,34 @@ def register_search( return model.RegisterResponse(searchid=search_info.id, links=links) @self.router.get( - "/{searchid}/info", + "/{search_id}/info", responses={200: {"description": "Get Search query metadata."}}, response_model=model.Info, response_model_exclude_none=True, ) - def info_search(request: Request, searchid=Depends(self.path_dependency)): + def info_search(request: Request, search_id=Depends(self.path_dependency)): """Get Search query metadata.""" with request.app.state.dbpool.connection() as conn: with conn.cursor(row_factory=class_row(model.Search)) as cursor: cursor.execute( "SELECT * FROM searches WHERE hash=%s;", - (searchid,), + (search_id,), ) search_info = cursor.fetchone() if not search_info: - raise MosaicNotFoundError(f"SearchId `{searchid}` not found") + raise MosaicNotFoundError(f"SearchId `{search_id}` not found") links: List[model.Link] = [ model.Link( rel="self", title="Mosaic metadata", - href=self.url_for(request, "info_search", searchid=search_info.id), + href=self.url_for(request, "info_search", search_id=search_info.id), ), model.Link( title="Link for TileJSON", rel="tilejson", - href=self.url_for(request, "tilejson", searchid=search_info.id), + href=self.url_for(request, "tilejson", search_id=search_info.id), ), ] @@ -828,7 +833,7 @@ def info_search(request: Request, searchid=Depends(self.path_dependency)): rel="map", title="Link for Map viewer", href=self.url_for( - request, "map_viewer", searchid=search_info.id + request, "map_viewer", search_id=search_info.id ), ) ) @@ -840,7 +845,7 @@ def info_search(request: Request, searchid=Depends(self.path_dependency)): model.Link( rel="wmts", title="Link for WMTS", - href=self.url_for(request, "wmts", searchid=search_info.id), + href=self.url_for(request, "wmts", search_id=search_info.id), ) ) except NoMatchFound: @@ -855,7 +860,7 @@ def info_search(request: Request, searchid=Depends(self.path_dependency)): href=self.url_for( request, "tilejson", - searchid=search_info.id, + search_id=search_info.id, ) + f"?{urlencode(values, doseq=True)}", ) @@ -1008,13 +1013,13 @@ def parse_sort_by(sortby: str) -> Generator[sql.Composable, None, None]: model.Link( rel="metadata", href=self.url_for( - request, "info_search", searchid=search.id + request, "info_search", search_id=search.id ), ), model.Link( rel="tilejson", href=self.url_for( - request, "tilejson", searchid=search.id + request, "tilejson", search_id=search.id ), ), ], @@ -1033,7 +1038,7 @@ def _statistics_routes(self): """Register /statistics endpoint.""" @self.router.post( - "/{searchid}/statistics", + "/{search_id}/statistics", response_model=MultiBaseStatisticsGeoJSON, response_model_exclude_none=True, response_class=GeoJSONResponse, @@ -1049,7 +1054,7 @@ def geojson_statistics( Union[FeatureCollection, Feature], Body(description="GeoJSON Feature or FeatureCollection."), ], - searchid=Depends(self.path_dependency), + search_id=Depends(self.path_dependency), coord_crs=Depends(CoordCRSParams), dst_crs=Depends(DstCRSParams), layer_params=Depends(self.layer_dependency), @@ -1070,7 +1075,7 @@ def geojson_statistics( with rasterio.Env(**env): with self.reader( - searchid, + search_id, reader_options={**reader_params}, **backend_params, ) as src_dst: @@ -1110,11 +1115,11 @@ def _part_routes(self): # noqa: C901 # GET endpoints @self.router.get( - "/{searchid}/bbox/{minx},{miny},{maxx},{maxy}.{format}", + "/{search_id}/bbox/{minx},{miny},{maxx},{maxy}.{format}", **img_endpoint_params, ) @self.router.get( - "/{searchid}/bbox/{minx},{miny},{maxx},{maxy}/{width}x{height}.{format}", + "/{search_id}/bbox/{minx},{miny},{maxx},{maxy}/{width}x{height}.{format}", **img_endpoint_params, ) def bbox_image( @@ -1122,7 +1127,7 @@ def bbox_image( miny: Annotated[float, Path(description="Bounding box min Y")], maxx: Annotated[float, Path(description="Bounding box max X")], maxy: Annotated[float, Path(description="Bounding box max Y")], - searchid=Depends(self.path_dependency), + search_id=Depends(self.path_dependency), format: Annotated[ ImageType, "Default will be automatically defined if the output image needs a mask (png) or not (jpeg).", @@ -1146,7 +1151,7 @@ def bbox_image( """Create image from a bbox.""" with rasterio.Env(**env): with self.reader( - searchid, + search_id, reader_options={**reader_params}, **backend_params, ) as src_dst: @@ -1184,20 +1189,20 @@ def bbox_image( return Response(content, media_type=media_type, headers=headers) @self.router.post( - "/{searchid}/feature", + "/{search_id}/feature", **img_endpoint_params, ) @self.router.post( - "/{searchid}/feature.{format}", + "/{search_id}/feature.{format}", **img_endpoint_params, ) @self.router.post( - "/{searchid}/feature/{width}x{height}.{format}", + "/{search_id}/feature/{width}x{height}.{format}", **img_endpoint_params, ) def feature_image( geojson: Annotated[Union[Feature], Body(description="GeoJSON Feature.")], - searchid=Depends(self.path_dependency), + search_id=Depends(self.path_dependency), format: Annotated[ ImageType, "Default will be automatically defined if the output image needs a mask (png) or not (jpeg).", @@ -1221,7 +1226,7 @@ def feature_image( """Create image from a geojson feature.""" with rasterio.Env(**env): with self.reader( - searchid, + search_id, reader_options={**reader_params}, **backend_params, ) as src_dst: From 0e5d3283411b4340d6467075f826146aa0fe71f9 Mon Sep 17 00:00:00 2001 From: vincentsarago Date: Thu, 12 Oct 2023 16:07:06 +0200 Subject: [PATCH 2/4] rename `searchid` to `id` in response model --- titiler/pgstac/factory.py | 2 +- titiler/pgstac/main.py | 4 ++-- titiler/pgstac/model.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/titiler/pgstac/factory.py b/titiler/pgstac/factory.py index b6fe0d9..d9add2f 100644 --- a/titiler/pgstac/factory.py +++ b/titiler/pgstac/factory.py @@ -793,7 +793,7 @@ def register_search( ) ) - return model.RegisterResponse(searchid=search_info.id, links=links) + return model.RegisterResponse(id=search_info.id, links=links) @self.router.get( "/{search_id}/info", diff --git a/titiler/pgstac/main.py b/titiler/pgstac/main.py index 05be1e3..5cfeabf 100644 --- a/titiler/pgstac/main.py +++ b/titiler/pgstac/main.py @@ -183,13 +183,13 @@ def landing(request: Request): }, { "title": "Mosaic Metadata (template URL)", - "href": mosaic.url_for(request, "info_search", searchid="{searchid}"), + "href": mosaic.url_for(request, "info_search", search_id="{search_id}"), "type": "application/json", "rel": "data", }, { "title": "Mosaic viewer (template URL)", - "href": mosaic.url_for(request, "map_viewer", searchid="{searchid}"), + "href": mosaic.url_for(request, "map_viewer", search_id="{search_id}"), "type": "text/html", "rel": "data", }, diff --git a/titiler/pgstac/model.py b/titiler/pgstac/model.py index f36e0de..50196c8 100644 --- a/titiler/pgstac/model.py +++ b/titiler/pgstac/model.py @@ -175,7 +175,7 @@ class Link(BaseModel): class RegisterResponse(BaseModel): """Response model for /register endpoint.""" - searchid: str + id: str links: Optional[List[Link]] = None From 7695e1da874ebad6ccf1b50f129bb3a0b68b6fb8 Mon Sep 17 00:00:00 2001 From: vincentsarago Date: Thu, 12 Oct 2023 16:07:34 +0200 Subject: [PATCH 3/4] update tests and docs --- CHANGES.md | 15 ++++++ benchmark/stac/items.json | 30 ++++++------ docs/src/intro.md | 88 +++++++++++++++++++++++++++++------ docs/src/mosaic_endpoints.md | 56 +++++++++++----------- docs/src/notebooks/demo.ipynb | 4 +- docs/src/tiler_factories.md | 20 ++++---- tests/test_mosaic.py | 20 ++++---- titiler/pgstac/reader.py | 2 +- 8 files changed, 155 insertions(+), 80 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 385692c..0e867e3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,10 +3,25 @@ ## Unrelease * add `pgstac_dependency` attribute in `MosaicTilerFactory` (defaults to `dependencies.PgSTACParams`) + * add database's `pool` check in startup event + * rename `dependencies.PathParams` to `dependencies.SearchIdParams` **breaking change** + * rename `searchid` path parameter to `search_id` **breaking change** +* in `model.RegisterResponse` (model used in `/register` endpoint) rename `searchid` by `id` **breaking change** + + ```python + # before + resp = httpx.post("/mosaic/register", body={"collections": ["my-collection"], "filter-lang": "cql-json"}) + assert resp.json()["searchid"] + + # now + resp = httpx.post("/mosaic/register", body={"collections": ["my-collection"], "filter-lang": "cql-json"}) + assert resp.json()["id"] + ``` + ## 0.8.0 (2023-10-06) * update titiler requirement to `>=0.14.0,<0.15` diff --git a/benchmark/stac/items.json b/benchmark/stac/items.json index da2ec30..4ba5886 100644 --- a/benchmark/stac/items.json +++ b/benchmark/stac/items.json @@ -1,15 +1,15 @@ -{"type":"Feature","stac_version":"1.0.0","id":"world_0_0.tif","properties":{"datetime":"2021-11-30T09:39:05.616134Z"},"geometry":{"type":"Polygon","coordinates":[[[-179.8333333333333,89.83333333333331],[-179.8333333333333,6.4999999999833165],[-96.49999999998329,6.4999999999833165],[-96.49999999998329,89.83333333333331],[-179.8333333333333,89.83333333333331]]]},"links":[{"rel":"collection","href":"world","type":"application/json"}],"assets":{"asset":{"href":"./benchmark/data/world_0_0.tif","roles":[]}},"bbox":[-179.8333333333333,6.4999999999833165,-96.49999999998329,89.83333333333331],"stac_extensions":[],"collection":"world"} -{"type":"Feature","stac_version":"1.0.0","id":"world_0_10000.tif","properties":{"datetime":"2021-11-30T09:39:06.699738Z"},"geometry":{"type":"Polygon","coordinates":[[[-179.8333333333333,-76.83333333336668],[-179.8333333333333,-89.83333333336928],[-96.49999999998329,-89.83333333336928],[-96.49999999998329,-76.83333333336668],[-179.8333333333333,-76.83333333336668]]]},"links":[{"rel":"collection","href":"world","type":"application/json"}],"assets":{"asset":{"href":"./benchmark/data/world_0_10000.tif","roles":[]}},"bbox":[-179.8333333333333,-89.83333333336928,-96.49999999998329,-76.83333333336668],"stac_extensions":[],"collection":"world"} -{"type":"Feature","stac_version":"1.0.0","id":"world_0_5000.tif","properties":{"datetime":"2021-11-30T09:39:07.730359Z"},"geometry":{"type":"Polygon","coordinates":[[[-179.8333333333333,6.4999999999833165],[-179.8333333333333,-76.83333333336668],[-96.49999999998329,-76.83333333336668],[-96.49999999998329,6.4999999999833165],[-179.8333333333333,6.4999999999833165]]]},"links":[{"rel":"collection","href":"world","type":"application/json"}],"assets":{"asset":{"href":"./benchmark/data/world_0_5000.tif","roles":[]}},"bbox":[-179.8333333333333,-76.83333333336668,-96.49999999998329,6.4999999999833165],"stac_extensions":[],"collection":"world"} -{"type":"Feature","stac_version":"1.0.0","id":"world_10000_0.tif","properties":{"datetime":"2021-11-30T09:39:08.794708Z"},"geometry":{"type":"Polygon","coordinates":[[[-13.16666666663329,89.83333333333331],[-13.16666666663329,6.4999999999833165],[70.16666666671671,6.4999999999833165],[70.16666666671671,89.83333333333331],[-13.16666666663329,89.83333333333331]]]},"links":[{"rel":"collection","href":"world","type":"application/json"}],"assets":{"asset":{"href":"./benchmark/data/world_10000_0.tif","roles":[]}},"bbox":[-13.16666666663329,6.4999999999833165,70.16666666671671,89.83333333333331],"stac_extensions":[],"collection":"world"} -{"type":"Feature","stac_version":"1.0.0","id":"world_10000_10000.tif","properties":{"datetime":"2021-11-30T09:39:09.846715Z"},"geometry":{"type":"Polygon","coordinates":[[[-13.16666666663329,-76.83333333336668],[-13.16666666663329,-89.83333333336928],[70.16666666671671,-89.83333333336928],[70.16666666671671,-76.83333333336668],[-13.16666666663329,-76.83333333336668]]]},"links":[{"rel":"collection","href":"world","type":"application/json"}],"assets":{"asset":{"href":"./benchmark/data/world_10000_10000.tif","roles":[]}},"bbox":[-13.16666666663329,-89.83333333336928,70.16666666671671,-76.83333333336668],"stac_extensions":[],"collection":"world"} -{"type":"Feature","stac_version":"1.0.0","id":"world_10000_5000.tif","properties":{"datetime":"2021-11-30T09:39:10.899435Z"},"geometry":{"type":"Polygon","coordinates":[[[-13.16666666663329,6.4999999999833165],[-13.16666666663329,-76.83333333336668],[70.16666666671671,-76.83333333336668],[70.16666666671671,6.4999999999833165],[-13.16666666663329,6.4999999999833165]]]},"links":[{"rel":"collection","href":"world","type":"application/json"}],"assets":{"asset":{"href":"./benchmark/data/world_10000_5000.tif","roles":[]}},"bbox":[-13.16666666663329,-76.83333333336668,70.16666666671671,6.4999999999833165],"stac_extensions":[],"collection":"world"} -{"type":"Feature","stac_version":"1.0.0","id":"world_15000_0.tif","properties":{"datetime":"2021-11-30T09:39:12.113870Z"},"geometry":{"type":"Polygon","coordinates":[[[70.16666666671674,89.83333333333331],[70.16666666671674,6.4999999999833165],[153.50000000006673,6.4999999999833165],[153.50000000006673,89.83333333333331],[70.16666666671674,89.83333333333331]]]},"links":[{"rel":"collection","href":"world","type":"application/json"}],"assets":{"asset":{"href":"./benchmark/data/world_15000_0.tif","roles":[]}},"bbox":[70.16666666671674,6.4999999999833165,153.50000000006673,89.83333333333331],"stac_extensions":[],"collection":"world"} -{"type":"Feature","stac_version":"1.0.0","id":"world_15000_10000.tif","properties":{"datetime":"2021-11-30T09:39:13.203191Z"},"geometry":{"type":"Polygon","coordinates":[[[70.16666666671674,-76.83333333336668],[70.16666666671674,-89.83333333336928],[153.50000000006673,-89.83333333336928],[153.50000000006673,-76.83333333336668],[70.16666666671674,-76.83333333336668]]]},"links":[{"rel":"collection","href":"world","type":"application/json"}],"assets":{"asset":{"href":"./benchmark/data/world_15000_10000.tif","roles":[]}},"bbox":[70.16666666671674,-89.83333333336928,153.50000000006673,-76.83333333336668],"stac_extensions":[],"collection":"world"} -{"type":"Feature","stac_version":"1.0.0","id":"world_15000_5000.tif","properties":{"datetime":"2021-11-30T09:39:14.283519Z"},"geometry":{"type":"Polygon","coordinates":[[[70.16666666671674,6.4999999999833165],[70.16666666671674,-76.83333333336668],[153.50000000006673,-76.83333333336668],[153.50000000006673,6.4999999999833165],[70.16666666671674,6.4999999999833165]]]},"links":[{"rel":"collection","href":"world","type":"application/json"}],"assets":{"asset":{"href":"./benchmark/data/world_15000_5000.tif","roles":[]}},"bbox":[70.16666666671674,-76.83333333336668,153.50000000006673,6.4999999999833165],"stac_extensions":[],"collection":"world"} -{"type":"Feature","stac_version":"1.0.0","id":"world_20000_0.tif","properties":{"datetime":"2021-11-30T09:39:15.356470Z"},"geometry":{"type":"Polygon","coordinates":[[[153.5000000000667,89.83333333333331],[153.5000000000667,6.4999999999833165],[179.8333333334053,6.4999999999833165],[179.8333333334053,89.83333333333331],[153.5000000000667,89.83333333333331]]]},"links":[{"rel":"collection","href":"world","type":"application/json"}],"assets":{"asset":{"href":"./benchmark/data/world_20000_0.tif","roles":[]}},"bbox":[153.5000000000667,6.4999999999833165,179.8333333334053,89.83333333333331],"stac_extensions":[],"collection":"world"} -{"type":"Feature","stac_version":"1.0.0","id":"world_20000_10000.tif","properties":{"datetime":"2021-11-30T09:39:16.406036Z"},"geometry":{"type":"Polygon","coordinates":[[[153.5000000000667,-76.83333333336668],[153.5000000000667,-89.83333333336928],[179.8333333334053,-89.83333333336928],[179.8333333334053,-76.83333333336668],[153.5000000000667,-76.83333333336668]]]},"links":[{"rel":"collection","href":"world","type":"application/json"}],"assets":{"asset":{"href":"./benchmark/data/world_20000_10000.tif","roles":[]}},"bbox":[153.5000000000667,-89.83333333336928,179.8333333334053,-76.83333333336668],"stac_extensions":[],"collection":"world"} -{"type":"Feature","stac_version":"1.0.0","id":"world_20000_5000.tif","properties":{"datetime":"2021-11-30T09:39:17.490533Z"},"geometry":{"type":"Polygon","coordinates":[[[153.5000000000667,6.4999999999833165],[153.5000000000667,-76.83333333336668],[179.8333333334053,-76.83333333336668],[179.8333333334053,6.4999999999833165],[153.5000000000667,6.4999999999833165]]]},"links":[{"rel":"collection","href":"world","type":"application/json"}],"assets":{"asset":{"href":"./benchmark/data/world_20000_5000.tif","roles":[]}},"bbox":[153.5000000000667,-76.83333333336668,179.8333333334053,6.4999999999833165],"stac_extensions":[],"collection":"world"} -{"type":"Feature","stac_version":"1.0.0","id":"world_5000_0.tif","properties":{"datetime":"2021-11-30T09:39:18.565540Z"},"geometry":{"type":"Polygon","coordinates":[[[-96.49999999998329,89.83333333333331],[-96.49999999998329,6.4999999999833165],[-13.16666666663329,6.4999999999833165],[-13.16666666663329,89.83333333333331],[-96.49999999998329,89.83333333333331]]]},"links":[{"rel":"collection","href":"world","type":"application/json"}],"assets":{"asset":{"href":"./benchmark/data/world_5000_0.tif","roles":[]}},"bbox":[-96.49999999998329,6.4999999999833165,-13.16666666663329,89.83333333333331],"stac_extensions":[],"collection":"world"} -{"type":"Feature","stac_version":"1.0.0","id":"world_5000_10000.tif","properties":{"datetime":"2021-11-30T09:39:19.700285Z"},"geometry":{"type":"Polygon","coordinates":[[[-96.49999999998329,-76.83333333336668],[-96.49999999998329,-89.83333333336928],[-13.16666666663329,-89.83333333336928],[-13.16666666663329,-76.83333333336668],[-96.49999999998329,-76.83333333336668]]]},"links":[{"rel":"collection","href":"world","type":"application/json"}],"assets":{"asset":{"href":"./benchmark/data/world_5000_10000.tif","roles":[]}},"bbox":[-96.49999999998329,-89.83333333336928,-13.16666666663329,-76.83333333336668],"stac_extensions":[],"collection":"world"} -{"type":"Feature","stac_version":"1.0.0","id":"world_5000_5000.tif","properties":{"datetime":"2021-11-30T09:39:20.923109Z"},"geometry":{"type":"Polygon","coordinates":[[[-96.49999999998329,6.4999999999833165],[-96.49999999998329,-76.83333333336668],[-13.16666666663329,-76.83333333336668],[-13.16666666663329,6.4999999999833165],[-96.49999999998329,6.4999999999833165]]]},"links":[{"rel":"collection","href":"world","type":"application/json"}],"assets":{"asset":{"href":"./benchmark/data/world_5000_5000.tif","roles":[]}},"bbox":[-96.49999999998329,-76.83333333336668,-13.16666666663329,6.4999999999833165],"stac_extensions":[],"collection":"world"} +{"type":"Feature","stac_version":"1.0.0","id":"world_0_0","properties":{"datetime":"2021-11-30T09:39:05.616134Z"},"geometry":{"type":"Polygon","coordinates":[[[-179.8333333333333,89.83333333333331],[-179.8333333333333,6.4999999999833165],[-96.49999999998329,6.4999999999833165],[-96.49999999998329,89.83333333333331],[-179.8333333333333,89.83333333333331]]]},"links":[{"rel":"collection","href":"world","type":"application/json"}],"assets":{"asset":{"href":"./benchmark/data/world_0_0.tif","roles":[]}},"bbox":[-179.8333333333333,6.4999999999833165,-96.49999999998329,89.83333333333331],"stac_extensions":[],"collection":"world"} +{"type":"Feature","stac_version":"1.0.0","id":"world_0_10000","properties":{"datetime":"2021-11-30T09:39:06.699738Z"},"geometry":{"type":"Polygon","coordinates":[[[-179.8333333333333,-76.83333333336668],[-179.8333333333333,-89.83333333336928],[-96.49999999998329,-89.83333333336928],[-96.49999999998329,-76.83333333336668],[-179.8333333333333,-76.83333333336668]]]},"links":[{"rel":"collection","href":"world","type":"application/json"}],"assets":{"asset":{"href":"./benchmark/data/world_0_10000.tif","roles":[]}},"bbox":[-179.8333333333333,-89.83333333336928,-96.49999999998329,-76.83333333336668],"stac_extensions":[],"collection":"world"} +{"type":"Feature","stac_version":"1.0.0","id":"world_0_5000","properties":{"datetime":"2021-11-30T09:39:07.730359Z"},"geometry":{"type":"Polygon","coordinates":[[[-179.8333333333333,6.4999999999833165],[-179.8333333333333,-76.83333333336668],[-96.49999999998329,-76.83333333336668],[-96.49999999998329,6.4999999999833165],[-179.8333333333333,6.4999999999833165]]]},"links":[{"rel":"collection","href":"world","type":"application/json"}],"assets":{"asset":{"href":"./benchmark/data/world_0_5000.tif","roles":[]}},"bbox":[-179.8333333333333,-76.83333333336668,-96.49999999998329,6.4999999999833165],"stac_extensions":[],"collection":"world"} +{"type":"Feature","stac_version":"1.0.0","id":"world_10000_0","properties":{"datetime":"2021-11-30T09:39:08.794708Z"},"geometry":{"type":"Polygon","coordinates":[[[-13.16666666663329,89.83333333333331],[-13.16666666663329,6.4999999999833165],[70.16666666671671,6.4999999999833165],[70.16666666671671,89.83333333333331],[-13.16666666663329,89.83333333333331]]]},"links":[{"rel":"collection","href":"world","type":"application/json"}],"assets":{"asset":{"href":"./benchmark/data/world_10000_0.tif","roles":[]}},"bbox":[-13.16666666663329,6.4999999999833165,70.16666666671671,89.83333333333331],"stac_extensions":[],"collection":"world"} +{"type":"Feature","stac_version":"1.0.0","id":"world_10000_10000","properties":{"datetime":"2021-11-30T09:39:09.846715Z"},"geometry":{"type":"Polygon","coordinates":[[[-13.16666666663329,-76.83333333336668],[-13.16666666663329,-89.83333333336928],[70.16666666671671,-89.83333333336928],[70.16666666671671,-76.83333333336668],[-13.16666666663329,-76.83333333336668]]]},"links":[{"rel":"collection","href":"world","type":"application/json"}],"assets":{"asset":{"href":"./benchmark/data/world_10000_10000.tif","roles":[]}},"bbox":[-13.16666666663329,-89.83333333336928,70.16666666671671,-76.83333333336668],"stac_extensions":[],"collection":"world"} +{"type":"Feature","stac_version":"1.0.0","id":"world_10000_5000","properties":{"datetime":"2021-11-30T09:39:10.899435Z"},"geometry":{"type":"Polygon","coordinates":[[[-13.16666666663329,6.4999999999833165],[-13.16666666663329,-76.83333333336668],[70.16666666671671,-76.83333333336668],[70.16666666671671,6.4999999999833165],[-13.16666666663329,6.4999999999833165]]]},"links":[{"rel":"collection","href":"world","type":"application/json"}],"assets":{"asset":{"href":"./benchmark/data/world_10000_5000.tif","roles":[]}},"bbox":[-13.16666666663329,-76.83333333336668,70.16666666671671,6.4999999999833165],"stac_extensions":[],"collection":"world"} +{"type":"Feature","stac_version":"1.0.0","id":"world_15000_0","properties":{"datetime":"2021-11-30T09:39:12.113870Z"},"geometry":{"type":"Polygon","coordinates":[[[70.16666666671674,89.83333333333331],[70.16666666671674,6.4999999999833165],[153.50000000006673,6.4999999999833165],[153.50000000006673,89.83333333333331],[70.16666666671674,89.83333333333331]]]},"links":[{"rel":"collection","href":"world","type":"application/json"}],"assets":{"asset":{"href":"./benchmark/data/world_15000_0.tif","roles":[]}},"bbox":[70.16666666671674,6.4999999999833165,153.50000000006673,89.83333333333331],"stac_extensions":[],"collection":"world"} +{"type":"Feature","stac_version":"1.0.0","id":"world_15000_10000","properties":{"datetime":"2021-11-30T09:39:13.203191Z"},"geometry":{"type":"Polygon","coordinates":[[[70.16666666671674,-76.83333333336668],[70.16666666671674,-89.83333333336928],[153.50000000006673,-89.83333333336928],[153.50000000006673,-76.83333333336668],[70.16666666671674,-76.83333333336668]]]},"links":[{"rel":"collection","href":"world","type":"application/json"}],"assets":{"asset":{"href":"./benchmark/data/world_15000_10000.tif","roles":[]}},"bbox":[70.16666666671674,-89.83333333336928,153.50000000006673,-76.83333333336668],"stac_extensions":[],"collection":"world"} +{"type":"Feature","stac_version":"1.0.0","id":"world_15000_5000","properties":{"datetime":"2021-11-30T09:39:14.283519Z"},"geometry":{"type":"Polygon","coordinates":[[[70.16666666671674,6.4999999999833165],[70.16666666671674,-76.83333333336668],[153.50000000006673,-76.83333333336668],[153.50000000006673,6.4999999999833165],[70.16666666671674,6.4999999999833165]]]},"links":[{"rel":"collection","href":"world","type":"application/json"}],"assets":{"asset":{"href":"./benchmark/data/world_15000_5000.tif","roles":[]}},"bbox":[70.16666666671674,-76.83333333336668,153.50000000006673,6.4999999999833165],"stac_extensions":[],"collection":"world"} +{"type":"Feature","stac_version":"1.0.0","id":"world_20000_0","properties":{"datetime":"2021-11-30T09:39:15.356470Z"},"geometry":{"type":"Polygon","coordinates":[[[153.5000000000667,89.83333333333331],[153.5000000000667,6.4999999999833165],[179.8333333334053,6.4999999999833165],[179.8333333334053,89.83333333333331],[153.5000000000667,89.83333333333331]]]},"links":[{"rel":"collection","href":"world","type":"application/json"}],"assets":{"asset":{"href":"./benchmark/data/world_20000_0.tif","roles":[]}},"bbox":[153.5000000000667,6.4999999999833165,179.8333333334053,89.83333333333331],"stac_extensions":[],"collection":"world"} +{"type":"Feature","stac_version":"1.0.0","id":"world_20000_10000","properties":{"datetime":"2021-11-30T09:39:16.406036Z"},"geometry":{"type":"Polygon","coordinates":[[[153.5000000000667,-76.83333333336668],[153.5000000000667,-89.83333333336928],[179.8333333334053,-89.83333333336928],[179.8333333334053,-76.83333333336668],[153.5000000000667,-76.83333333336668]]]},"links":[{"rel":"collection","href":"world","type":"application/json"}],"assets":{"asset":{"href":"./benchmark/data/world_20000_10000.tif","roles":[]}},"bbox":[153.5000000000667,-89.83333333336928,179.8333333334053,-76.83333333336668],"stac_extensions":[],"collection":"world"} +{"type":"Feature","stac_version":"1.0.0","id":"world_20000_5000","properties":{"datetime":"2021-11-30T09:39:17.490533Z"},"geometry":{"type":"Polygon","coordinates":[[[153.5000000000667,6.4999999999833165],[153.5000000000667,-76.83333333336668],[179.8333333334053,-76.83333333336668],[179.8333333334053,6.4999999999833165],[153.5000000000667,6.4999999999833165]]]},"links":[{"rel":"collection","href":"world","type":"application/json"}],"assets":{"asset":{"href":"./benchmark/data/world_20000_5000.tif","roles":[]}},"bbox":[153.5000000000667,-76.83333333336668,179.8333333334053,6.4999999999833165],"stac_extensions":[],"collection":"world"} +{"type":"Feature","stac_version":"1.0.0","id":"world_5000_0","properties":{"datetime":"2021-11-30T09:39:18.565540Z"},"geometry":{"type":"Polygon","coordinates":[[[-96.49999999998329,89.83333333333331],[-96.49999999998329,6.4999999999833165],[-13.16666666663329,6.4999999999833165],[-13.16666666663329,89.83333333333331],[-96.49999999998329,89.83333333333331]]]},"links":[{"rel":"collection","href":"world","type":"application/json"}],"assets":{"asset":{"href":"./benchmark/data/world_5000_0.tif","roles":[]}},"bbox":[-96.49999999998329,6.4999999999833165,-13.16666666663329,89.83333333333331],"stac_extensions":[],"collection":"world"} +{"type":"Feature","stac_version":"1.0.0","id":"world_5000_10000","properties":{"datetime":"2021-11-30T09:39:19.700285Z"},"geometry":{"type":"Polygon","coordinates":[[[-96.49999999998329,-76.83333333336668],[-96.49999999998329,-89.83333333336928],[-13.16666666663329,-89.83333333336928],[-13.16666666663329,-76.83333333336668],[-96.49999999998329,-76.83333333336668]]]},"links":[{"rel":"collection","href":"world","type":"application/json"}],"assets":{"asset":{"href":"./benchmark/data/world_5000_10000.tif","roles":[]}},"bbox":[-96.49999999998329,-89.83333333336928,-13.16666666663329,-76.83333333336668],"stac_extensions":[],"collection":"world"} +{"type":"Feature","stac_version":"1.0.0","id":"world_5000_5000","properties":{"datetime":"2021-11-30T09:39:20.923109Z"},"geometry":{"type":"Polygon","coordinates":[[[-96.49999999998329,6.4999999999833165],[-96.49999999998329,-76.83333333336668],[-13.16666666663329,-76.83333333336668],[-13.16666666663329,6.4999999999833165],[-96.49999999998329,6.4999999999833165]]]},"links":[{"rel":"collection","href":"world","type":"application/json"}],"assets":{"asset":{"href":"./benchmark/data/world_5000_5000.tif","roles":[]}},"bbox":[-96.49999999998329,-76.83333333336668,-13.16666666663329,6.4999999999833165],"stac_extensions":[],"collection":"world"} diff --git a/docs/src/intro.md b/docs/src/intro.md index 63792a7..a0ab00f 100644 --- a/docs/src/intro.md +++ b/docs/src/intro.md @@ -8,22 +8,24 @@ By default the main application (`titiler.pgstac.main.app`) provides two sets of - `/collections/{collection_id}/items/{item_id}`: Dynamic tiler for single STAC item (stored in PgSTAC) -## Mosaic +## Virtual Mosaic The goal of the `mosaic` endpoints is to use any [`search`](https://github.com/radiantearth/stac-api-spec/tree/master/item-search) query to create tiles. `titiler-pgstac` provides a set of endpoint to `register` and `list` the `search` queries. -### Register a `Search` request +### Register a PgSTAC `Search` request ![](https://user-images.githubusercontent.com/10407788/132193537-0560016f-09bc-4a25-8a2a-eac9b50bc28a.png) !!! Important - In `TiTiler.PgSTAC` a STAC [`Search Query`](https://github.com/radiantearth/stac-api-spec/tree/master/item-search) is equivalent to a Mosaic. + In `TiTiler.PgSTAC` a STAC [`Search Query`](https://github.com/radiantearth/stac-api-spec/tree/master/item-search) is equivalent to a *Virtual Mosaic*. Before being able to create Map Tiles, the user needs to register a `Search Query` within the PgSTAC database (in the `searches` table). By default, `TiTiler.PgSTAC` has a `/mosaic/register (POST)` endpoint which will: - validate the search query (based on the STAC API specification [`item-search`]((https://github.com/radiantearth/stac-api-spec/tree/master/item-search))) + - send the search query to the postgres database using the [`search_query`](https://github.com/stac-utils/pgstac/blob/76512ab50e1373e3f77c65843cf328cbe6dd0dec/sql/004_search.sql#L1000) PgSTAC function - - return a `searchid` (might be also called `mosaicid`). + + - return a pgstac search identifier (`id`) (might be also called `mosaicid`). **Example** @@ -34,7 +36,7 @@ curl -X 'POST' 'http://127.0.0.1:8081/mosaic/register' \ -d '{"collections":["landsat-c2l2-sr"], "bbox":[-123.75,34.30714385628804,-118.125,38.82259097617712], "filter-lang": "cql-json"}' | jq >> { - "searchid": "5a1b82d38d53a5d200273cbada886bd7", + "id": "5a1b82d38d53a5d200273cbada886bd7", "links": [ { "rel": "metadata", @@ -56,7 +58,7 @@ curl -X 'POST' 'http://127.0.0.1:8081/mosaic/register' \ -d '{"filter": {"op": "and", "args": [{"op": "=", "args": [{"property": "collection"}, "landsat-c2l2-sr"]}, {"op": "s_intersects", "args": [{"property": "geometry"}, {"coordinates": [[[-123.75, 34.30714385628804], [-123.75, 38.82259097617712], [-118.125, 38.82259097617712], [-118.125, 34.30714385628804], [-123.75, 34.30714385628804]]], "type": "Polygon"}]}]}}' | jq >> { - "searchid": "5063721f06957d6b2320326d82e90d1e", + "id": "5063721f06957d6b2320326d82e90d1e", "links": [ { "rel": "metadata", @@ -79,7 +81,7 @@ curl http://127.0.0.1:8081/mosaic/5063721f06957d6b2320326d82e90d1e/info | jq >> { "search": { - "hash": "5063721f06957d6b2320326d82e90d1e", # <-- this is the search/mosaic ID + "hash": "5063721f06957d6b2320326d82e90d1e", # <-- this is the search/mosaic identifier "search": { # <-- Summary of the search request "filter": { # <-- this is CQL2 filter associated with the search "op": "and", @@ -163,7 +165,7 @@ curl -X 'POST' 'http://127.0.0.1:8081/mosaic/register' \ -d '{"collections":["landsat-c2l2-sr"], "bbox":[-123.75,34.30714385628804,-118.125,38.82259097617712], "filter-lang": "cql-json", "metadata": {"minzoom": 8, "maxzoom": 13, "assets": ["B04", "B03", "B02"], "defaults": {"true_color": {"assets": ["B04", "B03", "B02"], "color_formula": "Gamma RGB 3.5 Saturation 1.7 Sigmoidal RGB 15 0.35"}}}}' | jq >> { - "searchid": "f31d7de8a5ddfa3a80b9a9dd06378db1", + "id": "f31d7de8a5ddfa3a80b9a9dd06378db1", "links": [ { "rel": "metadata", @@ -203,16 +205,16 @@ curl http://127.0.0.1:8081/mosaic/f31d7de8a5ddfa3a80b9a9dd06378db1/info | jq '.s ### Fetch mosaic `Tiles` -When we have a `searchid` we can now call the dynamic tiler and ask for Map Tiles. +When we have an **id** we can call the dynamic tiler and ask for Map Tiles. ![](https://user-images.githubusercontent.com/10407788/132197899-e79b3118-313b-45e7-a431-5d3034984459.png) **How it works** -On each `Tile` request, the tiler api is going to call the PgSTAC [`geometrysearch`](https://github.com/stac-utils/pgstac/blob/76512ab50e1373e3f77c65843cf328cbe6dd0dec/sql/006_tilesearch.sql#L4) function with the `searchid` and the Tile geometry to get the list of STAC items ([code](https://github.com/stac-utils/titiler-pgstac/blob/0f2b5b4ba50bb3458237ab21cf9a154d7b811851/titiler/pgstac/mosaic.py#L238-L247)). Then based on the `assets` parameter, the tiler will construct the tile image ([code](https://github.com/stac-utils/titiler-pgstac/blob/0f2b5b4ba50bb3458237ab21cf9a154d7b811851/titiler/pgstac/mosaic.py#L257-L263)). +On each `Tile` request, the tiler api is going to call the PgSTAC [`geometrysearch`](https://github.com/stac-utils/pgstac/blob/76512ab50e1373e3f77c65843cf328cbe6dd0dec/sql/006_tilesearch.sql#L4) function with the `id` and the `Tile geometry` to get the list of **STAC Items** ([code](https://github.com/stac-utils/titiler-pgstac/blob/0f2b5b4ba50bb3458237ab21cf9a154d7b811851/titiler/pgstac/mosaic.py#L238-L247)). Then based on the `assets` parameter, the tiler will construct the tile image ([code](https://github.com/stac-utils/titiler-pgstac/blob/0f2b5b4ba50bb3458237ab21cf9a154d7b811851/titiler/pgstac/mosaic.py#L257-L263)). !!! important - Because `Tiles` will be created from STAC items we HAVE TO pass **`assets={stac asset}`** option to the tile endpoint. + Because `Tiles` will be created from **STAC Items** we HAVE TO pass **`assets={stac asset}`** option to the tile endpoint to tell the tiler which **STAC assets** has to be used. See full list of [options](../mosaic_endpoints/#tiles) @@ -222,14 +224,72 @@ On each `Tile` request, the tiler api is going to call the PgSTAC [`geometrysear curl 'http://127.0.0.1:8081/mosaic/f1ed59f0a6ad91ed80ae79b7b52bc707/tiles/8/40/102.png?assets=B01&rescale=0,16000 > 8-40-102.png ``` -## Items +## Individual Item -Set of endpoints created using TiTiler's [`MultiBaseTilerFactory`]() but with `item` and `collection` path parameter (instead of the `url=` query parameter). +`titiler-pgstac` can also be used to access individual item stored in the PgSTAC database. By default the `titiler-pgstac` application will have a set of `/collections/{collection_id}/items/{item_id}/...` endpoints. The endpoints are created using [titiler.core.factory.MultiBaseTilerFactory](https://developmentseed.org/titiler/advanced/tiler_factories/#titilercorefactorymultibasetilerfactory) but using a custom `path_dependency` with `collection_id` and `item_id` path parameters instead of the STAC url as query parameter. **example** ```bash -curl http://127.0.0.1:8081/collections/landsat-c2l2-sr/items/LC08_L1TP_028004_20171002_20171018_01_A1 +curl http://127.0.0.1:8081/collections/world/items/world_20000_5000/info | jq +{ + "asset": { + "bounds": [ + 153.5000000000667, + -76.83333333336668, + 179.8333333334053, + 6.4999999999833165 + ], + "minzoom": 3, + "maxzoom": 6, + "band_metadata": [ + [ + "b1", + {} + ], + [ + "b2", + {} + ], + [ + "b3", + {} + ] + ], + "band_descriptions": [ + [ + "b1", + "" + ], + [ + "b2", + "" + ], + [ + "b3", + "" + ] + ], + "dtype": "uint8", + "nodata_type": "None", + "colorinterp": [ + "red", + "green", + "blue" + ], + "driver": "GTiff", + "count": 3, + "width": 1580, + "height": 5000, + "overviews": [ + 2, + 4, + 8, + 16 + ] + } +} + ``` See full list of [endpoints](../item_endpoints) diff --git a/docs/src/mosaic_endpoints.md b/docs/src/mosaic_endpoints.md index 0ae26ac..434cd03 100644 --- a/docs/src/mosaic_endpoints.md +++ b/docs/src/mosaic_endpoints.md @@ -3,17 +3,17 @@ The `titiler.pgstac` package comes with a full FastAPI application with Mosaic a | Method | URL | Output | Description | ------ | ---------------------------------------------------------------------------------|-----------------------------------------|-------------- | `POST` | `/mosaic/register` | JSON ([Register][register_model]) | Register **Search** query -| `GET` | `/mosaic/{searchid}/info` | JSON ([Info][info_model]) | Return **Search** query infos +| `GET` | `/mosaic/{search_id}/info` | JSON ([Info][info_model]) | Return **Search** query infos | `GET` | `/mosaic/list` | JSON ([Infos][infos_model]) | Return list of **Search** entries with `Mosaic` type -| `GET` | `/mosaic/{searchid}/{lon},{lat}/assets` | JSON | Return a list of assets which overlap a given point -| `GET` | `/mosaic/{searchid}/tiles[/{TileMatrixSetId}]/{z}/{x}/{Y}/assets` | JSON | Return a list of assets which overlap a given tile -| `GET` | `/mosaic/{searchid}/tiles[/{TileMatrixSetId}]/{z}/{x}/{y}[@{scale}x][.{format}]` | image/bin | Create a web map tile image for a search query and a tile index -| `GET` | `/mosaic/{searchid}[/{TileMatrixSetId}]/tilejson.json` | JSON ([TileJSON][tilejson_model]) | Return a Mapbox TileJSON document -| `GET` | `/mosaic/{searchid}[/{TileMatrixSetId}]/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities -| `GET` | `/mosaic/{searchid}[/{TileMatrixSetId}]/map` | HTML | simple map viewer -| `POST` | `/mosaic/{searchid}/statistics` | GeoJSON ([Statistics][statitics_model]) | Return statistics for geojson features -| `GET` | `/mosaic/{searchid}/bbox/{minx},{miny},{maxx},{maxy}[/{width}x{height}].{format}`| image/bin | Create an image from part of a dataset -| `POST` | `/mosaic/{searchid}/feature[/{width}x{height}][.{format}]` | image/bin | Create an image from a GeoJSON feature +| `GET` | `/mosaic/{search_id}/{lon},{lat}/assets` | JSON | Return a list of assets which overlap a given point +| `GET` | `/mosaic/{search_id}/tiles[/{TileMatrixSetId}]/{z}/{x}/{Y}/assets` | JSON | Return a list of assets which overlap a given tile +| `GET` | `/mosaic/{search_id}/tiles[/{TileMatrixSetId}]/{z}/{x}/{y}[@{scale}x][.{format}]` | image/bin | Create a web map tile image for a search query and a tile index +| `GET` | `/mosaic/{search_id}[/{TileMatrixSetId}]/tilejson.json` | JSON ([TileJSON][tilejson_model]) | Return a Mapbox TileJSON document +| `GET` | `/mosaic/{search_id}[/{TileMatrixSetId}]/WMTSCapabilities.xml` | XML | return OGC WMTS Get Capabilities +| `GET` | `/mosaic/{search_id}[/{TileMatrixSetId}]/map` | HTML | simple map viewer +| `POST` | `/mosaic/{search_id}/statistics` | GeoJSON ([Statistics][statitics_model]) | Return statistics for geojson features +| `GET` | `/mosaic/{search_id}/bbox/{minx},{miny},{maxx},{maxy}[/{width}x{height}].{format}`| image/bin | Create an image from part of a dataset +| `POST` | `/mosaic/{search_id}/feature[/{width}x{height}][.{format}]` | image/bin | Create an image from a GeoJSON feature ### Register a Search Request @@ -86,7 +86,7 @@ Example: ```bash curl -X 'POST' 'http://127.0.0.1:8081/mosaic/register' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{"collections":["landsat-c2l2-sr"], "bbox":[-123.75,34.30714385628804,-118.125,38.82259097617712], "filter-lang": "cql-json"}' | jq >> { - "searchid": "5a1b82d38d53a5d200273cbada886bd7", + "id": "5a1b82d38d53a5d200273cbada886bd7", "links": [ { "rel": "metadata", @@ -110,10 +110,10 @@ curl -X 'POST' 'http://127.0.0.1:8081/mosaic/register' -H 'accept: application/j ### Search infos -`:endpoint:/mosaic/{searchid}/info - [GET]` +`:endpoint:/mosaic/{search_id}/info - [GET]` - PathParams: - - **searchid**: search query hashkey. + - **search_id**: search query hashkey. Example: @@ -183,10 +183,10 @@ Example: ### Tiles -`:endpoint:/mosaic/{searchid}/tiles[/{TileMatrixSetId}]/{z}/{x}/{y}[@{scale}x][.{format}]` +`:endpoint:/mosaic/{search_id}/tiles[/{TileMatrixSetId}]/{z}/{x}/{y}[@{scale}x][.{format}]` - PathParams: - - **searchid**: search query hashkey. + - **search_id**: search query hashkey. - **TileMatrixSetId**: TileMatrixSet name, default is `WebMercatorQuad`. OPTIONAL - **z**: Tile's zoom level. - **x**: Tile's column. @@ -229,10 +229,10 @@ Example: ### TilesJSON -`:endpoint:/mosaic/{searchid}[/{TileMatrixSetId}]/tilejson.json` +`:endpoint:/mosaic/{search_id}[/{TileMatrixSetId}]/tilejson.json` - PathParams: - - **searchid**: search query hashkey. + - **search_id**: search query hashkey. - **TileMatrixSetId**: TileMatrixSet name, default is `WebMercatorQuad`. OPTIONAL - QueryParams: @@ -273,10 +273,10 @@ Example: ### WMTS -`:endpoint:/mosaic/{searchid}[/{TileMatrixSetId}]/WMTSCapabilities.xml` +`:endpoint:/mosaic/{search_id}[/{TileMatrixSetId}]/WMTSCapabilities.xml` - PathParams: - - **searchid**: search query hashkey. + - **search_id**: search query hashkey. - **TileMatrixSetId**: TileMatrixSet name, default is `WebMercatorQuad`. OPTIONAL - QueryParams: @@ -298,10 +298,10 @@ Example: ### Assets -`:endpoint:/mosaic/{searchid}/tiles/[{TileMatrixSetId}]/{z}/{x}/{y}/assets` +`:endpoint:/mosaic/{search_id}/tiles/[{TileMatrixSetId}]/{z}/{x}/{y}/assets` - PathParams: - - **searchid**: search query hashkey. + - **search_id**: search query hashkey. - **TileMatrixSetId**: TileMatrixSet name, default is `WebMercatorQuad`. OPTIONAL - **z**: Tile's zoom level. - **x**: Tile's column. @@ -318,10 +318,10 @@ Example: - `https://myendpoint/mosaic/f1ed59f0a6ad91ed80ae79b7b52bc707/tiles/0/0/0/assets` -`:endpoint:/mosaic/{searchid}/{lon},{lat}/assets` +`:endpoint:/mosaic/{search_id}/{lon},{lat}/assets` - PathParams: - - **searchid**: search query hashkey. + - **search_id**: search query hashkey. - **lon**: Longitude (in WGS84 CRS). - **lat**: Latitude (in WGS84 CRS). @@ -338,7 +338,7 @@ Example: ### Statistics -`:endpoint:/mosaic/{searchid}/statistics - [POST]` +`:endpoint:/mosaic/{search_id}/statistics - [POST]` - Body: - **feature** (JSON): A valid GeoJSON feature or FeatureCollection @@ -379,12 +379,12 @@ Example: ### BBOX/Feature -`:endpoint:/mosaic/{searchid}/bbox/{minx},{miny},{maxx},{maxy}.{format}` +`:endpoint:/mosaic/{search_id}/bbox/{minx},{miny},{maxx},{maxy}.{format}` -`:endpoint:/mosaic/{searchid}/bbox/{minx},{miny},{maxx},{maxy}/{width}x{height}.{format}` +`:endpoint:/mosaic/{search_id}/bbox/{minx},{miny},{maxx},{maxy}/{width}x{height}.{format}` - PathParams: - - **searchid**: search query hashkey. + - **search_id**: search query hashkey. - **minx,miny,maxx,maxy** (str): Comma (',') delimited bounding box in WGS84. - **format** (str): Output image format. - **height** (int): Force output image height. @@ -424,7 +424,7 @@ Example: - `https://myendpoint/mosaic/f1ed59f0a6ad91ed80ae79b7b52bc707/bbox/0,0,10,10/400x300.png?assets=B01` -`:endpoint:/mosaic/{searchid}/feature[/{width}x{height}][].{format}] - [POST]` +`:endpoint:/mosaic/{search_id}/feature[/{width}x{height}][].{format}] - [POST]` - Body: - **feature** (JSON): A valid GeoJSON feature (Polygon or MultiPolygon) diff --git a/docs/src/notebooks/demo.ipynb b/docs/src/notebooks/demo.ipynb index bf475af..a860392 100644 --- a/docs/src/notebooks/demo.ipynb +++ b/docs/src/notebooks/demo.ipynb @@ -218,7 +218,7 @@ ").json()\n", "print(response)\n", "\n", - "searchid = response[\"searchid\"]" + "searchid = response[\"id\"]" ] }, { @@ -495,7 +495,7 @@ ").json()\n", "print(response)\n", "\n", - "searchid = response[\"searchid\"]" + "searchid = response[\"id\"]" ] }, { diff --git a/docs/src/tiler_factories.md b/docs/src/tiler_factories.md index 1133182..7bc3a02 100644 --- a/docs/src/tiler_factories.md +++ b/docs/src/tiler_factories.md @@ -30,16 +30,16 @@ app.include_router(mosaic.router) | ------ | --------------------------------------------------------------------------|---------------------------------------- |-------------- | `POST` | `/register` | JSON ([Register][register_model]) | Register **Search** query **OPTIONAL** | `GET` | `/list` | JSON ([Info][info_model]) | Return **Search** query infos **OPTIONAL** -| `GET` | `/{searchid}/info` | JSON ([Infos][infos_model]) | Return list of **Search** entries with `Mosaic` type **OPTIONAL** -| `GET` | `/{searchid}/{lon},{lat}/assets` | JSON | Return a list of assets which overlap a given point -| `GET` | `/{searchid}/tiles[/{TileMatrixSetId}]/{z}/{x}/{Y}/assets` | JSON | Return a list of assets which overlap a given tile -| `GET` | `/{searchid}/tiles[/{TileMatrixSetId}]/{z}/{x}/{y}[@{scale}x][.{format}]` | image/bin | Create a web map tile image for a search query and a tile index -| `GET` | `/{searchid}[/{TileMatrixSetId}]/tilejson.json` | JSON ([TileJSON][tilejson_model]) | Return a Mapbox TileJSON document -| `GET` | `/{searchid}[/{TileMatrixSetId}]/WMTSCapabilities.xml` | XML | Return OGC WMTS Get Capabilities -| `GET` | `/{searchid}[/{TileMatrixSetId}]/map` | HTML | Simple map viewer **OPTIONAL** -| `POST` | `/{searchid}/statistics` | GeoJSON ([Statistics][statitics_model]) | Return statistics for geojson features **OPTIONAL** -| `GET` | `/{searchid}/bbox/{minx},{miny},{maxx},{maxy}[/{width}x{height}].{format}`| image/bin | Create an image from part of a dataset **OPTIONAL** -| `POST` | `/{searchid}/feature[/{width}x{height}][.{format}]` | image/bin | Create an image from a GeoJSON feature **OPTIONAL** +| `GET` | `/{search_id}/info` | JSON ([Infos][infos_model]) | Return list of **Search** entries with `Mosaic` type **OPTIONAL** +| `GET` | `/{search_id}/{lon},{lat}/assets` | JSON | Return a list of assets which overlap a given point +| `GET` | `/{search_id}/tiles[/{TileMatrixSetId}]/{z}/{x}/{Y}/assets` | JSON | Return a list of assets which overlap a given tile +| `GET` | `/{search_id}/tiles[/{TileMatrixSetId}]/{z}/{x}/{y}[@{scale}x][.{format}]` | image/bin | Create a web map tile image for a search query and a tile index +| `GET` | `/{search_id}[/{TileMatrixSetId}]/tilejson.json` | JSON ([TileJSON][tilejson_model]) | Return a Mapbox TileJSON document +| `GET` | `/{search_id}[/{TileMatrixSetId}]/WMTSCapabilities.xml` | XML | Return OGC WMTS Get Capabilities +| `GET` | `/{search_id}[/{TileMatrixSetId}]/map` | HTML | Simple map viewer **OPTIONAL** +| `POST` | `/{search_id}/statistics` | GeoJSON ([Statistics][statitics_model]) | Return statistics for geojson features **OPTIONAL** +| `GET` | `/{search_id}/bbox/{minx},{miny},{maxx},{maxy}[/{width}x{height}].{format}`| image/bin | Create an image from part of a dataset **OPTIONAL** +| `POST` | `/{search_id}/feature[/{width}x{height}][.{format}]` | image/bin | Create an image from a GeoJSON feature **OPTIONAL** ## Item diff --git a/tests/test_mosaic.py b/tests/test_mosaic.py index c02d76c..d1c7e5c 100644 --- a/tests/test_mosaic.py +++ b/tests/test_mosaic.py @@ -25,7 +25,7 @@ def search_no_bbox(app): "map", "wmts", ] - return resp["searchid"] + return resp["id"] @pytest.fixture @@ -47,7 +47,7 @@ def search_bbox(app): "map", "wmts", ] - return resp["searchid"] + return resp["id"] def test_info(app, search_no_bbox): @@ -338,10 +338,10 @@ def test_cql2(rio, app): response = app.post("/mosaic/register", json=query) assert response.status_code == 200 resp = response.json() - assert resp["searchid"] + assert resp["id"] assert resp["links"] - cql2_id = resp["searchid"] + cql2_id = resp["id"] response = app.get(f"/mosaic/{cql2_id}/info") assert response.status_code == 200 @@ -431,10 +431,10 @@ def test_cql2_with_geometry(rio, app): response = app.post("/mosaic/register", json=query) assert response.status_code == 200 resp = response.json() - assert resp["searchid"] + assert resp["id"] assert resp["links"] - cql2_id = resp["searchid"] + cql2_id = resp["id"] response = app.get(f"/mosaic/{cql2_id}/info") assert response.status_code == 200 @@ -487,10 +487,10 @@ def test_query_with_metadata(app): response = app.post("/mosaic/register", json=query) assert response.status_code == 200 resp = response.json() - assert resp["searchid"] + assert resp["id"] assert resp["links"] - mosaic_id = resp["searchid"] + mosaic_id = resp["id"] response = app.get(f"/mosaic/{mosaic_id}/info") assert response.status_code == 200 @@ -548,13 +548,13 @@ def test_query_with_metadata(app): response = app.post("/mosaic/register", json=query) assert response.status_code == 200 resp = response.json() - assert resp["searchid"] + assert resp["id"] assert ( len(resp["links"]) == 6 ) # info, tilejson, map, wmts tilejson for one_band, tilejson for three_bands link = resp["links"][-2] - mosaic_id_metadata = resp["searchid"] + mosaic_id_metadata = resp["id"] assert link["title"] == "TileJSON link for `one_band` layer." assert "asset_bidx=cog%7C1" in link["href"] diff --git a/titiler/pgstac/reader.py b/titiler/pgstac/reader.py index a7d1cc1..513b61a 100644 --- a/titiler/pgstac/reader.py +++ b/titiler/pgstac/reader.py @@ -77,7 +77,7 @@ def _get_asset_info(self, asset: str) -> AssetInfo: extras = asset_info.extra_fields info = AssetInfo( - url=asset_info.get_absolute_href(), + url=asset_info.get_absolute_href() or asset_info.href, metadata=extras, ) From b2ea9ce816d85dc1a59002157fc7b511e403f66b Mon Sep 17 00:00:00 2001 From: vincentsarago Date: Thu, 12 Oct 2023 16:15:37 +0200 Subject: [PATCH 4/4] fix benchmark --- .github/workflows/tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests/conftest.py b/.github/workflows/tests/conftest.py index 32ab56d..7e15889 100644 --- a/.github/workflows/tests/conftest.py +++ b/.github/workflows/tests/conftest.py @@ -17,4 +17,4 @@ def mosaic_id(): assert response.status_code == 200 resp = response.json() - return resp["searchid"] + return resp["id"]