Skip to content

Commit

Permalink
add ids, bbox and datetime parameters to the collections endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
vincentsarago committed Sep 5, 2024
1 parent 484d066 commit 372f6e1
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* add `PgstacSettings` such that the user can provide their own default settings for PgSTAC search
* add check for pgstac `read-only` mode and raise `ReadOnlyPgSTACError` error when trying to write to the pgstac instance
* add `/pgstac` endpoint in the application (when `TITILER_PGSTAC_API_DEBUG=TRUE`)
* add `ids`, `bbox` and `datetime` options to the `/collections/{collection_id}` endpoints

## 1.3.1 (2024-08-01)

Expand Down
21 changes: 21 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,27 @@ services:
depends_on:
- database

stac-fastapi:
image: ghcr.io/stac-utils/stac-fastapi-pgstac:3.0.0
ports:
- "${MY_DOCKER_IP:-127.0.0.1}:8082:8082"
environment:
# Postgres connection
- POSTGRES_USER=username
- POSTGRES_PASS=password
- POSTGRES_DBNAME=postgis
- POSTGRES_HOST_READER=database
- POSTGRES_HOST_WRITER=database
- POSTGRES_PORT=5432
- DB_MIN_CONN_SIZE=1
- DB_MAX_CONN_SIZE=1
depends_on:
- database
command:
bash -c "uvicorn stac_fastapi.pgstac.app:app --host 0.0.0.0 --port 8082"
volumes:
- ./dockerfiles/scripts:/tmp/scripts

database:
container_name: stac-db
image: ghcr.io/stac-utils/pgstac:v${PGSTAC_VERSION-0.9.1}
Expand Down
30 changes: 30 additions & 0 deletions docs/src/endpoints/collections_endpoints.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@
- **time_limit** (int): Return after N seconds to avoid long requests, Default is 5sec in PgSTAC.
- **exitwhenfull** (bool): Return as soon as the geometry is fully covered, Default is `True` in PgSTAC.
- **skipcovered** (bool): Skip any items that would show up completely under the previous items, Default is `True` in PgSTAC.
- **ids** (str): Array of Item ids to show.
- **bbox** (str): Filters items intersecting this bounding box.
- **datetime** (str):Filters items that have a temporal property that intersects this value. Either a date-time or an interval, open or closed.

!!! important
**assets** OR **expression** is required
Expand Down Expand Up @@ -99,6 +102,9 @@ Example:
- **time_limit** (int): Return after N seconds to avoid long requests, Default is 5sec in PgSTAC.
- **exitwhenfull** (bool): Return as soon as the geometry is fully covered, Default is `True` in PgSTAC.
- **skipcovered** (bool): Skip any items that would show up completely under the previous items, Default is `True` in PgSTAC.
- **ids** (str): Array of Item ids to show.
- **bbox** (str): Filters items intersecting this bounding box.
- **datetime** (str):Filters items that have a temporal property that intersects this value. Either a date-time or an interval, open or closed.

!!! important
**assets** OR **expression** is required
Expand All @@ -123,6 +129,10 @@ Example:
- **tile_scale**: Tile size scale, default is set to 1 (256x256). OPTIONAL
- **minzoom**: Overwrite default minzoom. OPTIONAL
- **maxzoom**: Overwrite default maxzoom. OPTIONAL
- **ids** (str): Array of Item ids to show.
- **bbox** (str): Filters items intersecting this bounding box.
- **datetime** (str):Filters items that have a temporal property that intersects this value. Either a date-time or an interval, open or closed.



!!! important
Expand Down Expand Up @@ -152,6 +162,10 @@ Example:
- **time_limit** (int): Return after N seconds to avoid long requests, Default is 5sec in PgSTAC.
- **exitwhenfull** (bool): Return as soon as the geometry is fully covered, Default is `True` in PgSTAC.
- **skipcovered** (bool): Skip any items that would show up completely under the previous items, Default is `True` in PgSTAC.
- **ids** (str): Array of Item ids to show.
- **bbox** (str): Filters items intersecting this bounding box.
- **datetime** (str):Filters items that have a temporal property that intersects this value. Either a date-time or an interval, open or closed.


Example:

Expand All @@ -170,6 +184,10 @@ Example:
- **time_limit** (int): Return after N seconds to avoid long requests, Default is 5sec in PgSTAC.
- **exitwhenfull** (bool): Return as soon as the geometry is fully covered, Default is `True` in PgSTAC.
- **skipcovered** (bool): Skip any items that would show up completely under the previous items, Default is `True` in PgSTAC.
- **ids** (str): Array of Item ids to show.
- **bbox** (str): Filters items intersecting this bounding box.
- **datetime** (str):Filters items that have a temporal property that intersects this value. Either a date-time or an interval, open or closed.


Example:

Expand Down Expand Up @@ -212,6 +230,9 @@ Example:
- **time_limit** (int): Return after N seconds to avoid long requests, Default is 5sec in PgSTAC.
- **exitwhenfull** (bool): Return as soon as the geometry is fully covered, Default is `True` in PgSTAC.
- **skipcovered** (bool): Skip any items that would show up completely under the previous items, Default is `True` in PgSTAC.
- **ids** (str): Array of Item ids to show.
- **bbox** (str): Filters items intersecting this bounding box.
- **datetime** (str):Filters items that have a temporal property that intersects this value. Either a date-time or an interval, open or closed.

!!! important
if **height** and **width** are provided **max_size** will be ignored.
Expand Down Expand Up @@ -258,6 +279,9 @@ Example:
- **time_limit** (int): Return after N seconds to avoid long requests, Default is 5sec in PgSTAC.
- **exitwhenfull** (bool): Return as soon as the geometry is fully covered, Default is `True` in PgSTAC.
- **skipcovered** (bool): Skip any items that would show up completely under the previous items, Default is `True` in PgSTAC.
- **ids** (str): Array of Item ids to show.
- **bbox** (str): Filters items intersecting this bounding box.
- **datetime** (str):Filters items that have a temporal property that intersects this value. Either a date-time or an interval, open or closed.

!!! important
if **height** and **width** are provided **max_size** will be ignored.
Expand Down Expand Up @@ -304,6 +328,9 @@ Example:
- **time_limit** (int): Return after N seconds to avoid long requests, Default is 5sec in PgSTAC.
- **exitwhenfull** (bool): Return as soon as the geometry is fully covered, Default is `True` in PgSTAC.
- **skipcovered** (bool): Skip any items that would show up completely under the previous items, Default is `True` in PgSTAC.
- **ids** (str): Array of Item ids to show.
- **bbox** (str): Filters items intersecting this bounding box.
- **datetime** (str):Filters items that have a temporal property that intersects this value. Either a date-time or an interval, open or closed.

!!! important
if **height** and **width** are provided **max_size** will be ignored.
Expand Down Expand Up @@ -339,6 +366,9 @@ Example:
- **time_limit** (int): Return after N seconds to avoid long requests, Default is 5sec in PgSTAC.
- **exitwhenfull** (bool): Return as soon as the geometry is fully covered, Default is `True` in PgSTAC.
- **skipcovered** (bool): Skip any items that would show up completely under the previous items, Default is `True` in PgSTAC.
- **ids** (str): Array of Item ids to show.
- **bbox** (str): Filters items intersecting this bounding box.
- **datetime** (str):Filters items that have a temporal property that intersects this value. Either a date-time or an interval, open or closed.

!!! important
**assets** OR **expression** is required
Expand Down
51 changes: 51 additions & 0 deletions tests/test_collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -547,3 +547,54 @@ def test_collections_render(app, tmp_path):
response = app.get("/collections/MAXAR_BayofBengal_Cyclone_Mocha_May_23/info")
assert response.status_code == 200
assert len(response.json()["links"]) == 10 # self, tilejson (4), map (4), wmts (1)


def test_collections_additional_parameters(app):
"""Check that additional parameter work."""
# bbox
response = app.get(
"/collections/noaa-emergency-response/info",
params={"bbox": "-87.0251,36.1749,-86.9999,36.2001"},
)
assert response.status_code == 200
resp = response.json()
assert resp["search"]["search"]["bbox"] == [-87.0251, 36.1749, -86.9999, 36.2001]
assert resp["search"]["metadata"]["bbox"] == [-87.0251, 36.1749, -86.9999, 36.2001]

# ids
response = app.get(
"/collections/noaa-emergency-response/info",
params={"ids": "20200307aC0853130w361030"},
)
assert response.status_code == 200
resp = response.json()
assert resp["search"]["search"]["ids"] == ["20200307aC0853130w361030"]

response = app.get(
"/collections/noaa-emergency-response/-85.5,36.1624/assets",
params={"ids": "20200307aC0853130w361030"},
)
assert response.status_code == 200
resp = response.json()
assert len(resp) == 1
assert resp[0]["id"] == "20200307aC0853000w361030"

# datetime
response = app.get(
"/collections/noaa-emergency-response/info",
params={"datetime": "2020-03-07T00:00:00Z"},
)
assert response.status_code == 200
resp = response.json()
assert resp["search"]["search"]["datetime"] == "2020-03-07T00:00:00Z"
assert "datetime <= '2020-03-07 00:00:00+00'" in resp["search"]["_where"]
assert "end_datetime >= '2020-03-07 00:00:00+00''" in resp["search"]["_where"]

response = app.get(
"/collections/noaa-emergency-response/info",
params={"datetime": "../2020-03-07T00:00:00Z"},
)
assert response.status_code == 200
resp = response.json()
assert resp["search"]["search"]["datetime"] == "../2020-03-07T00:00:00Z"
assert "end_datetime >= '-infinity'" in resp["search"]["_where"]
74 changes: 66 additions & 8 deletions titiler/pgstac/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

import warnings
from dataclasses import dataclass, field
from typing import Optional, Tuple
from typing import List, Optional, Tuple

import morecantile
import pystac
from cachetools import TTLCache, cached
from cachetools.keys import hashkey
from cogeo_mosaic.errors import MosaicNotFoundError
from fastapi import HTTPException, Path, Query
from geojson_pydantic.types import BBox
from psycopg import errors as pgErrors
from psycopg.rows import class_row, dict_row
from psycopg_pool import ConnectionPool
Expand Down Expand Up @@ -38,7 +39,12 @@ def SearchIdParams(

@cached( # type: ignore
TTLCache(maxsize=cache_config.maxsize, ttl=cache_config.ttl),
key=lambda pool, collection_id: hashkey(collection_id),
key=lambda pool, collection_id, ids, bbox, datetime: hashkey(
collection_id,
str(ids),
str(bbox),
str(datetime),
),
)
@retry(
tries=retry_config.retry,
Expand All @@ -48,9 +54,20 @@ def SearchIdParams(
pgErrors.InterfaceError,
),
)
def get_collection_id(pool: ConnectionPool, collection_id: str) -> str: # noqa: C901
def get_collection_id(
pool: ConnectionPool,
collection_id: str,
ids: Optional[List[str]] = None,
bbox: Optional[BBox] = None,
datetime: Optional[str] = None,
) -> str: # noqa: C901
"""Get Search Id for a Collection."""
search = model.PgSTACSearch(collections=[collection_id])
search = model.PgSTACSearch(
collections=[collection_id],
ids=ids,
bbox=bbox,
datetime=datetime,
)

with pool.connection() as conn:
with conn.cursor(row_factory=dict_row) as cursor:
Expand All @@ -62,10 +79,12 @@ def get_collection_id(pool: ConnectionPool, collection_id: str) -> str: # noqa:
if not collection:
raise MosaicNotFoundError(f"CollectionId `{collection_id}` not found")

bbox = collection["extent"]["spatial"].get("bbox", [[-180, -90, 180, 90]])
collection_bbox = collection["extent"]["spatial"].get(
"bbox", [[-180, -90, 180, 90]]
)
metadata = model.Metadata(
name=f"Mosaic for '{collection_id}' Collection",
bounds=bbox[0],
bounds=bbox or collection_bbox[0],
)

# item-assets https://github.com/stac-extensions/item-assets
Expand Down Expand Up @@ -134,9 +153,48 @@ def CollectionIdParams(
str,
Path(description="STAC Collection Identifier"),
],
ids: Annotated[
Optional[str],
Query(
description="Array of Item ids",
json_schema_extra={
"example": "item1,item2",
},
),
] = None,
bbox: Annotated[
Optional[str],
Query(
description="Filters items intersecting this bounding box",
json_schema_extra={
"example": "-175.05,-85.05,175.05,85.05",
},
),
] = None,
datetime: Annotated[
Optional[str],
Query(
description="""Filters items that have a temporal property that intersects this value.\n
Either a date-time or an interval, open or closed. Date and time expressions adhere to RFC 3339. Open intervals are expressed using double-dots.""",
openapi_examples={
"datetime": {"value": "2018-02-12T23:20:50Z"},
"closed-interval": {
"value": "2018-02-12T00:00:00Z/2018-03-18T12:31:12Z"
},
"open-interval-from": {"value": "2018-02-12T00:00:00Z/.."},
"open-interval-to": {"value": "../2018-03-18T12:31:12Z"},
},
),
] = None,
) -> str:
"""collection_id Path Parameter"""
return get_collection_id(request.app.state.dbpool, collection_id=collection_id)
"""Collection endpoints Parameters"""
return get_collection_id(
request.app.state.dbpool,
collection_id=collection_id,
ids=ids.split(",") if ids else None,
bbox=list(map(float, bbox.split(","))) if bbox else None,
datetime=datetime,
)


def SearchParams(
Expand Down

0 comments on commit 372f6e1

Please sign in to comment.