diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c13087545..c17169819 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -102,7 +102,6 @@ jobs: python3 setup.py install pip3 install --global-option=build_ext --global-option="-I/usr/include/gdal" GDAL==`gdal-config --version` #pip3 install --upgrade rasterio==1.1.8 - pip3 install https://github.com/geopython/pygeofilter/archive/main.zip - name: setup test data ⚙️ run: | python3 tests/load_es_data.py tests/data/ne_110m_populated_places_simple.geojson geonameid @@ -125,7 +124,7 @@ jobs: pytest tests/test_csv__provider.py pytest tests/test_django.py pytest tests/test_elasticsearch__provider.py - # pytest tests/test_opensearch__provider.py + pytest tests/test_opensearch__provider.py pytest tests/test_esri_provider.py pytest tests/test_filesystem_provider.py pytest tests/test_geojson_provider.py diff --git a/docs/source/cql.rst b/docs/source/cql.rst index 4ba1c6c14..66a6d6364 100644 --- a/docs/source/cql.rst +++ b/docs/source/cql.rst @@ -3,15 +3,18 @@ CQL support =========== +OGC Common Query Language (`CQL2`_) is a generic language designed to provide enhanced query and subset/filtering to (primarily) feature and record data. + Providers --------- -As of now the available providers supported for CQL filtering are limited to :ref:`Elasticsearch ` and :ref:`PostgreSQL `. - +CQL2 support is implemented in various pygeoapi feature and record providers. See the :ref:`feature ` and :ref:`metadata ` provider sections +for current provider support. + Limitations ----------- -Support of CQL is limited to `Simple CQL filter `_ and thus it allows to query with the +Support of CQL is limited to `Basic CQL2 `_ and thus it allows to query with the following predicates: * comparison predicates @@ -21,20 +24,20 @@ following predicates: Formats ------- -At the moment Elasticsearch supports only the CQL dialect with the JSON encoding `CQL-JSON `_. +Supported providers leverage the CQL2 dialect with the JSON encoding `CQL-JSON `_. -PostgreSQL supports both CQL-JSON and CQL-text dialects, `CQL-JSON `_ and `CQL-TEXT `_ +PostgreSQL supports both `CQL2 JSON `_ and `CQL text `_ dialects. Queries ^^^^^^^ The PostgreSQL provider uses `pygeofilter `_ allowing a range of filter expressions, see examples for: -* `Comparison predicates `_ -* `Spatial predicates `_ -* `Temporal predicates `_ +* `Comparison predicates (`Advanced `_, `Case-insensitive `_) +* `Spatial predicates `_ +* `Temporal predicates `_ -Using Elasticsearch the following type of queries are supported right now: +Using Elasticsearch the following type of queries are supported currently: * ``between`` predicate query * Logical ``and`` query with ``between`` and ``eq`` expression @@ -59,11 +62,11 @@ A ``BETWEEN`` example for a specific property through an HTTP POST request: curl --location --request POST 'http://localhost:5000/collections/nhsl_hazard_threat_all_indicators_s_bc/items?f=json&limit=50&filter-lang=cql-json' \ --header 'Content-Type: application/query-cql-json' \ --data-raw '{ - "between": { - "value": { "property": "properties.MHn_Intensity" }, - "lower": 0.59, - "upper": 0.60 - } + "op": "between", + "args": [ + {"property": "properties.MHn_Intensity"}, + [0.59, 0.60] + ] }' Or @@ -73,11 +76,11 @@ Or curl --location --request POST 'http://localhost:5000/collections/recentearthquakes/items?f=json&limit=10&filter-lang=cql-json' --header 'Content-Type: application/query-cql-json' --data-raw '{ - "between":{ - "value":{"property": "ml"}, - "lower":4, - "upper":4.5 - } + "op": "between", + "args": [ + {"property": "ml"}, + [4, 4.5] + ] }' The same ``BETWEEN`` query using HTTP GET request formatted as CQL text and URL encoded as below: @@ -93,7 +96,11 @@ An ``EQUALS`` example for a specific property: curl --location --request POST 'http://localhost:5000/collections/recentearthquakes/items?f=json&limit=10&filter-lang=cql-json' --header 'Content-Type: application/query-cql-json' --data-raw '{ - "eq":[{"property": "user_entered"},"APBE"] + "op": "=", + "args": [ + {"property": "user_entered"}, + "APBE" + ] }' A ``CROSSES`` example via an HTTP GET request. The CQL text is passed via the ``filter`` parameter. @@ -115,7 +122,6 @@ The same example, but this time providing a geometry in EWKT format: curl "http://localhost:5000/collections/beni/items?filter=DWITHIN(geometry,SRID=3857;POINT(1392921%205145517),100,meters)" - - - Note that the CQL text has been URL encoded. This is required in curl commands but when entering in a browser, plain text can be used e.g. ``CROSSES(foo_geom, LINESTRING(28 -2, 30 -4))``. + +.. _`CQL2`: https://docs.ogc.org/is/21-065r2/21-065r2.html diff --git a/pygeoapi/django_/views.py b/pygeoapi/django_/views.py index fe0b7386b..682ef51ce 100644 --- a/pygeoapi/django_/views.py +++ b/pygeoapi/django_/views.py @@ -180,7 +180,7 @@ def collection_items(request: HttpRequest, collection_id: str) -> HttpResponse: 'create', collection_id, skip_valid_check=True) else: response_ = execute_from_django( - itemtypes_api.post_collection_items, + itemtypes_api.get_collection_items, request, collection_id, skip_valid_check=True,) elif request.method == 'OPTIONS': response_ = execute_from_django(itemtypes_api.manage_collection_item, diff --git a/pygeoapi/starlette_app.py b/pygeoapi/starlette_app.py index 9c44a9b12..984e62302 100644 --- a/pygeoapi/starlette_app.py +++ b/pygeoapi/starlette_app.py @@ -326,7 +326,7 @@ async def collection_items(request: Request, collection_id=None, item_id=None): 'create', collection_id, skip_valid_check=True) else: return await execute_from_starlette( - itemtypes_api.post_collection_items, + itemtypes_api.get_collection_items, request, collection_id, skip_valid_check=True, diff --git a/tests/test_postgresql_provider.py b/tests/test_postgresql_provider.py index 6751cf6a5..a011f3e64 100644 --- a/tests/test_postgresql_provider.py +++ b/tests/test_postgresql_provider.py @@ -8,7 +8,7 @@ # Bernhard Mallinger # # Copyright (c) 2019 Just van den Broecke -# Copyright (c) 2024 Tom Kralidis +# Copyright (c) 2025 Tom Kralidis # Copyright (c) 2022 John A Stevenson and Colin Blackburn # Copyright (c) 2023 Francesco Bartoli # Copyright (c) 2024 Bernhard Mallinger @@ -580,7 +580,7 @@ def test_get_collection_items_postgresql_cql_bad_cql(pg_api_, bad_cql): assert error_response['description'] == 'Bad CQL text' -def test_post_collection_items_postgresql_cql(pg_api_): +def test_get_collection_items_postgresql_cql_json(pg_api_): """ Test for PostgreSQL CQL - requires local PostgreSQL with appropriate data. See pygeoapi/provider/postgresql.py for details. @@ -625,7 +625,7 @@ def test_post_collection_items_postgresql_cql(pg_api_): assert ids == expected_ids -def test_post_collection_items_postgresql_cql_invalid_filter_language(pg_api_): +def test_get_collection_items_postgresql_cql_json_invalid_filter_language(pg_api_): # noqa """ Test for PostgreSQL CQL - requires local PostgreSQL with appropriate data. See pygeoapi/provider/postgresql.py for details. @@ -657,7 +657,7 @@ def test_post_collection_items_postgresql_cql_invalid_filter_language(pg_api_): # At some point this may return UnexpectedEOF '{"in": {"value": {"property": "id"}, "list": [1, 2}}' ]) -def test_post_collection_items_postgresql_cql_bad_cql(pg_api_, bad_cql): +def test_get_collection_items_postgresql_cql_json_bad_cql(pg_api_, bad_cql): """ Test for PostgreSQL CQL - requires local PostgreSQL with appropriate data. See pygeoapi/provider/postgresql.py for details.