Skip to content

Commit

Permalink
STAC API refactor into distinct endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
tomkralidis authored and kalxas committed Sep 30, 2023
1 parent 67be03c commit 0e19d07
Show file tree
Hide file tree
Showing 22 changed files with 980 additions and 284 deletions.
15 changes: 8 additions & 7 deletions docker/entrypoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
# =================================================================
#
# Authors: Ricardo Garcia Silva <ricardo.garcia.silva@gmail.com>
# Tom Kralidis <tomkralidis@gmail.com>
#
# Copyright (c) 2017 Ricardo Garcia Silva
# Copyright (c) 2023 Tom Kralidis
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
Expand Down Expand Up @@ -82,7 +84,7 @@ def launch_pycsw(pycsw_config, workers=2, reload=False):
"postgresql": handle_postgresql_db,
}.get(db)
logger.debug("Setting up pycsw's data repository...")
logger.debug("Repository URL: {}".format(db_url))
logger.debug(f"Repository URL: {db_url}")
db_handler(
db_url,
pycsw_config.get("repository", "table"),
Expand All @@ -105,12 +107,12 @@ def launch_pycsw(pycsw_config, workers=2, reload=False):
"--bind=0.0.0.0:8000",
"--access-logfile=-",
"--error-logfile=-",
"--workers={}".format(workers),
"--timeout={}".format(timeout),
f"--workers={workers}",
f"--timeout={timeout}",
"pycsw.wsgi_flask:APP",

]
logger.debug("Launching pycsw with {} ...".format(" ".join(execution_args)))
logger.debug(f"Launching pycsw with {' '.join(execution_args)} ...")
os.execlp(
"gunicorn",
*execution_args
Expand Down Expand Up @@ -139,7 +141,7 @@ def handle_postgresql_db(database_url, table_name, pycsw_home):


def _wait_for_postgresql_db(database_url, max_tries=10, wait_seconds=3):
logger.debug("Waiting for {!r}...".format(database_url))
logger.debug(f"Waiting for {database_url}...")
engine = create_engine(database_url)
current_try = 0
while current_try < max_tries:
Expand All @@ -153,8 +155,7 @@ def _wait_for_postgresql_db(database_url, max_tries=10, wait_seconds=3):
sleep(wait_seconds)
else:
raise RuntimeError(
"Database not responding at {} after {} tries. "
"Giving up".format(database_url, max_tries)
f"Database not responding at {database_url} after {max_tries} tries. "
)


Expand Down
4 changes: 2 additions & 2 deletions docs/locale/zh/LC_MESSAGES/stac.po
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ msgstr "版本"

#: ../../stac.rst:9
msgid ""
"pycsw supports `SpatioTemporal Asset Catalog API version 1.0.0-beta2`_ "
"pycsw supports `SpatioTemporal Asset Catalog API version v1.0.0`_ "
"by default."
msgstr ""
"pycsw 默认支持 `SpatioTemporal Asset Catalog API version 1.0.0-beta2`_。"
"pycsw 默认支持 `SpatioTemporal Asset Catalog API version v1.0.0`_。"

#: ../../stac.rst:11
msgid "pycsw implements provides STAC support in the following manner:"
Expand Down
93 changes: 57 additions & 36 deletions docs/stac.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,64 +15,85 @@ pycsw implements provides STAC support in the following manner:

The STAC specification is designed with the same principles as OGC API - Records.


Implementation
--------------

The following design patterns are put forth for STAC support:

Collections
^^^^^^^^^^^

* any pycsw record that is ingested as a STAC Collection will appear on
``/stac/collections`` as a collection

Search
^^^^^^

* In addition to OGC API - Records ``/collections/metadata:main/items`` search,
STAC API specific searches are realized via ``/stac/search``

Links and Assets
^^^^^^^^^^^^^^^^

STAC support will render links as follows:

* links that are enclosures will be encoded as STAC assets (in ``assets``)
* all other links remain as record links (in ``links``)

Request Examples
----------------

As the OGC successor to CSW, OARec is a change in paradigm rooted in lowering
the barrier to entry, being webby/of the web, and focusing on developer experience/adoption.
JSON and HTML output formats are both supported via the ``f`` parameter.

.. code-block:: bash
# landing page
http://localhost:8000/
# landing page explictly as JSON
http://localhost:8000/?f=json
# landing page as HTML
http://localhost:8000/?f=html
http://localhost:8000/stac
# collections
http://localhost:8000/stac/collections
# collection queries
# query parameters can be combined (exclusive/AND)
# landing page
http://localhost:8000/stac
# OpenAPI
http://localhost:8000/stac/openapi
# collections
http://localhost:8000/stac/collections
# single collection
http://localhost:8000/stac/collections/metadata:main
# collection queryables, all records
http://localhost:8000/stac/queryables
# collection query, all records
http://localhost:8000/search
http://localhost:8000/stac/search
# collection query, full text search
http://localhost:8000/search?q=lorem
http://localhost:8000/stac/search?q=lorem
# collection query, spatial query
http://localhost:8000/search?bbox=-142,42,-52,84
http://localhost:8000/stac/search?bbox=-142,42,-52,84
# collection query, temporal query
http://localhost:8000/search?datetime=2001-10-30/2007-10-30
http://localhost:8000/stac/search?datetime=2001-10-30/2007-10-30
# collection query, temporal query, before
http://localhost:8000/search?datetime=../2007-10-30
http://localhost:8000/stac/search?datetime=../2007-10-30
# collection query, temporal query, after
http://localhost:8000/search?datetime=2007-10-30/..
http://localhost:8000/stac/search?datetime=2007-10-30/..
# collection query, property query
http://localhost:8000/search?title=Lorem%20ipsum
http://localhost:8000/stac/search?title=Lorem%20ipsum
# collection query, CQL filter
http://localhost:8000/search?filter=title like "%lorem%"
http://localhost:8000/stac/search?filter=title like "%lorem%"
# collection query, limiting results
http://localhost:8000/search?limit=1
http://localhost:8000/stac/search?limit=1
# collection filter query, limiting results
http://localhost:8000/stac/search?limit=1&collections=landsat
# collection ids filter query, limiting results
http://localhost:8000/stac/search?limit=1&ids=id1,id2
# collection query, paging
http://localhost:8000/search?limit=10&offset=10
http://localhost:8000/stac/search?limit=10&offset=10
# collection query, paging and sorting (default ascending)
http://localhost:8000/search?limit=10&offset=10&sortby=title
http://localhost:8000/stac/search?limit=10&offset=10&sortby=title
# collection query, paging and sorting (descending)
http://localhost:8000/search?limit=10&offset=10&sortby=-title
# collection query as JSON (HTTP POST), as curl request
curl http://localhost:8000/search --request POST -H "Content-Type: application/json" --data '{"bbox": [-180, -90, 180, 90], "datetime": "2006-03-26"}'
# collection query as JSON (HTTP POST), as curl request, with sorting (default ascending)
curl http://localhost:8000/search --request POST -H "Content-Type: application/json" --data '{"bbox": [-180, -90, 180, 90], "datetime": "2006-03-26", "sortby": [{"field": "title", "direction": "ascending"}]}'
# collection query as JSON (HTTP POST), as curl request, with sorting (descending)
curl http://localhost:8000/search --request POST -H "Content-Type: application/json" --data '{"bbox": [-180, -90, 180, 90], "datetime": "2006-03-26", "sortby": [{"field": "title", "direction": "descending"}]}'
# collection query as CQL JSON (HTTP POST), limiting results, as curl request
curl http://localhost:8000/search --request POST -H "Content-Type: application/json" --data '{"limit": 1, "bbox": [-180, -90, 180, 90], "datetime": "2006-03-26"}'
http://localhost:8000/stac/search?limit=10&offset=10&sortby=-title
# collection item as GeoJSON
http://localhost:8000/collections/metadata:main/items/{itemId}
# collection item as HTML
http://localhost:8000/collections/metadata:main/items/{itemId}?f=html
# collection item as XML
http://localhost:8000/collections/metadata:main/items/{itemId}?f=xml
http://localhost:8000/stac/collections/metadata:main/items/{itemId}
.. _`SpatioTemporal Asset Catalog API version v1.0.0`: https://github.com/radiantearth/stac-api-spec
4 changes: 2 additions & 2 deletions pycsw/core/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# Authors: Tom Kralidis <tomkralidis@gmail.com>
# Ricardo Garcia Silva <ricardo.garcia.silva@gmail.com>
#
# Copyright (c) 2022 Tom Kralidis
# Copyright (c) 2023 Tom Kralidis
# Copyright (c) 2016 James F. Dickens
# Copyright (c) 2017 Ricardo Garcia Silva
#
Expand Down Expand Up @@ -1960,7 +1960,7 @@ def bbox_from_polygons(bboxs):
multi_pol = MultiPolygon(
[loads(bbox) for bbox in bboxs]
)
bstr = ",".join(["{0:.2f}".format(b) for b in multi_pol.bounds])
bstr = ",".join([f"{b:.2f}" for b in multi_pol.bounds])
return util.bbox2wktpolygon(bstr)
except Exception as err:
raise RuntimeError('Cannot aggregate polygons: %s' % str(err)) from err
4 changes: 2 additions & 2 deletions pycsw/core/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# Angelos Tzotsos <tzotsos@gmail.com>
# Ricardo Garcia Silva <ricardo.garcia.silva@gmail.com>
#
# Copyright (c) 2022 Tom Kralidis
# Copyright (c) 2023 Tom Kralidis
# Copyright (c) 2015 Angelos Tzotsos
# Copyright (c) 2017 Ricardo Garcia Silva
#
Expand Down Expand Up @@ -167,7 +167,7 @@ def nspath_eval(xpath, nsmap):
elif len(chunks) == 1:
out.append(node)
else:
raise RuntimeError("Invalid XPath expression: {0}".format(xpath))
raise RuntimeError(f"Invalid XPath expression: {xpath}")
return '/'.join(out)


Expand Down
51 changes: 45 additions & 6 deletions pycsw/ogc/api/oapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
LOGGER = logging.getLogger(__name__)


def gen_oapi(config, oapi_filepath):
def gen_oapi(config, oapi_filepath, mode='ogcapi-records'):
"""
Genrate OpenAPI document
Expand Down Expand Up @@ -156,7 +156,7 @@ def gen_oapi(config, oapi_filepath):
'description': 'The optional filter parameter specifies a CQL2 expression to be used for enhanced filtering', # noqa
'required': False,
'schema': {
'type': 'object',
'type': 'object'
},
'style': 'form',
'explode': False
Expand All @@ -170,7 +170,7 @@ def gen_oapi(config, oapi_filepath):
'type': 'string',
'enum': [
'cql2-json',
'cql2-text',
'cql2-text'
],
'default': 'cql2-text'
},
Expand All @@ -187,6 +187,38 @@ def gen_oapi(config, oapi_filepath):
},
'style': 'form'
}
# TODO: remove local definition of ids once implemented
# in OGC API - Records
oapi['components']['parameters']['ids'] = {
'name': 'ids',
'in': 'query',
'description': 'Comma-separated list of identifiers',
'required': False,
'schema': {
'type': 'array',
'items': {
'type': 'string'
}
},
'style': 'form',
'explode': False
}

if mode == 'stac-api':
oapi['components']['parameters']['collections'] = {
'name': 'collections',
'in': 'query',
'description': 'Comma-separated list of collection identifiers',
'required': False,
'schema': {
'type': 'array',
'items': {
'type': 'string'
}
},
'style': 'form',
'explode': False
}

LOGGER.debug('Adding server info')
oapi['info'] = {
Expand Down Expand Up @@ -342,6 +374,7 @@ def gen_oapi(config, oapi_filepath):
'parameters': [
{'$ref': '#/components/parameters/collectionId'},
{'$ref': '#/components/parameters/bbox'},
{'$ref': '#/components/parameters/ids'},
{'$ref': '#/components/parameters/datetime'},
{'$ref': '#/components/parameters/limit'},
{'$ref': '#/components/parameters/q'},
Expand Down Expand Up @@ -400,9 +433,15 @@ def gen_oapi(config, oapi_filepath):

oapi['paths']['/collections/{collectionId}/items'] = path

path2 = deepcopy(path)
path2['get']['operationId'] = 'searchRecords'
oapi['paths']['/search'] = path2
if mode == 'stac-api':
LOGGER.debug('Adding /stac/search')
path2 = deepcopy(path)
path2['get']['operationId'] = 'searchRecords'
oapi['paths']['/search'] = path2

oapi['paths']['/search']['get']['parameters'].append({
'$ref': '#/components/parameters/collections'
})

f = deepcopy(oapi['components']['parameters']['f'])
f['schema']['enum'].append('xml')
Expand Down
Loading

0 comments on commit 0e19d07

Please sign in to comment.