Skip to content

Commit

Permalink
fix: remove use of JSON LD from CQL JSON parsing (#316)
Browse files Browse the repository at this point in the history
* fix: remove use of JSON LD from CQL JSON parsing
* Change all filtering to use FILTER so as to avoid equality issues for numeric and string comparisons. e.g. FILTER(?var = 2000) will match on 2000, "2000"^^xsd:decimal etc. whereas VALUES ?var { 2000 } will only match on the exact same value. Use a triple pattern match for "=" filtering where the RHS operand is a URI. This should be more performant.
* Make enums optional in SHACL defined CQL Queryables. Black code.
* Add geo:hasGeometry geo:asWKT sequence path to OGCFeaturesAllProps profile in order to support geometries that are specified on named node sequence paths (rather than the common specification of geometries using blank nodes).
  • Loading branch information
recalcitrantsupplant authored Jan 8, 2025
1 parent 165ee61 commit 59ba6a8
Show file tree
Hide file tree
Showing 117 changed files with 382 additions and 374 deletions.
2 changes: 1 addition & 1 deletion dev/dev-setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,4 @@ def setup():
print(f"Successfully loaded {file_name}")


setup()
setup()
10 changes: 8 additions & 2 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,11 @@

port = int(environ.get("PREZ_DEV_SERVER_PORT", 8000))

uvicorn.run("prez.app:assemble_app", factory=True, port=port, reload=True,
proxy_headers=True, forwarded_allow_ips='*')
uvicorn.run(
"prez.app:assemble_app",
factory=True,
port=port,
reload=True,
proxy_headers=True,
forwarded_allow_ips="*",
)
26 changes: 10 additions & 16 deletions prez/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@
SearchMethod,
SPARQLQueryMediaType,
)
from prez.exceptions.model_exceptions import NoEndpointNodeshapeException, URINotFoundException
from prez.exceptions.model_exceptions import (
NoEndpointNodeshapeException,
URINotFoundException,
)
from prez.enums import SearchMethod
from prez.models.query_params import QueryParams
from prez.reference_data.prez_ns import ALTREXT, EP, OGCE, OGCFEAT, ONT
Expand Down Expand Up @@ -150,12 +153,7 @@ async def cql_post_parser_dependency(
) -> CQLParser:
try:
body = await request.json()
context = json.load(
(Path(__file__).parent / "reference_data/cql/default_context.json").open()
)
cql_parser = CQLParser(
cql=body, context=context, queryable_props=queryable_props
)
cql_parser = CQLParser(cql=body, queryable_props=queryable_props)
cql_parser.generate_jsonld()
cql_parser.parse()
return cql_parser
Expand All @@ -175,16 +173,12 @@ async def cql_get_parser_dependency(
try:
crs = query_params.filter_crs
query = json.loads(query_params.filter)
context = json.load(
(
Path(__file__).parent / "reference_data/cql/default_context.json"
).open()
)
cql_parser = CQLParser(
cql=query, context=context, crs=crs, queryable_props=queryable_props
)
cql_parser = CQLParser(cql=query, crs=crs, queryable_props=queryable_props)
cql_parser.generate_jsonld()
cql_parser.parse()
try:
cql_parser.parse()
except Exception as e:
raise e
return cql_parser
except json.JSONDecodeError:
raise HTTPException(status_code=400, detail="Invalid JSON format.")
Expand Down
4 changes: 2 additions & 2 deletions prez/examples/cql/geo_contains_inverse.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@
"op": "=",
"args": [
{
"property": "^http://www.w3.org/2000/01/rdf-schema#member"
"property": "http://www.w3.org/2000/01/rdf-schema#member"
},
{ "@id": "http://example.com/datasets/sandgate/facilities" }
"http://example.com/datasets/sandgate/facilities"
]
}
]
Expand Down
38 changes: 10 additions & 28 deletions prez/examples/cql/geo_crosses.json
Original file line number Diff line number Diff line change
@@ -1,35 +1,17 @@
{
"op": "s_crosses",
"args": [
{
"property": "geometry"
},
{ "property": "geometry" },
{
"type": "LineString",
"coordinates": [
[
[
153.06307,
-27.3151243
],
[
153.069877,
-27.3151243
],
[
153.069877,
-27.2859541
],
[
153.06307,
-27.2859541
],
[
153.06307,
-27.3151243
]
]
]
"coordinates": [ [ 172.03086, 1.5 ],
[ 1.1, -90.0 ],
[ -159.757695, 0.99999 ],
[ -180.0, 0.5 ],
[ -12.111235, 81.336403 ],
[ -0.5, 64.43958 ],
[ 0.0, 81.991815 ],
[ -155.93831, 90.0 ] ]
}
]
}
}
9 changes: 8 additions & 1 deletion prez/reference_data/profiles/ogc_features.ttl
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,14 @@ prez:OGCFeaturesAllProps a
"application/rdf+xml",
"text/anot+turtle",
"text/turtle" ;
sh:property [ sh:path shext:allPredicateValues ] ;
sh:property [
sh:path (
sh:union (
shext:allPredicateValues
( geo:hasGeometry geo:asWKT )
)
)
] ;
shext:bnode-depth 2 ;
.

Expand Down
37 changes: 23 additions & 14 deletions prez/services/listings.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,10 @@ async def ogc_features_listing_function(
)
if queryables: # from shacl definitions
content = io.BytesIO(
queryables.model_dump_json(exclude_none=True, by_alias=True).encode(
"utf-8"
)
queryables.model_dump_json(
exclude_none=True,
by_alias=True,
).encode("utf-8")
)
else:
queryable_var = Var(value="queryable")
Expand Down Expand Up @@ -264,7 +265,9 @@ async def ogc_features_listing_function(
# queries.append(feature_collection_query)
# run the feature_collection_query by itself with caching as it will be the same for all paginated sets of features

fc_item_graph = await _cached_feature_collection_query(collection_uri, data_repo, feature_collection_query)
fc_item_graph = await _cached_feature_collection_query(
collection_uri, data_repo, feature_collection_query
)

link_headers = None
if selected_mediatype == "application/sparql-query":
Expand Down Expand Up @@ -484,14 +487,16 @@ async def generate_queryables_from_shacl_definition(
CONSTRUCT {
?queryable cql:id ?id ;
cql:name ?title ;
cql:description ?description ;
cql:datatype ?type ;
cql:enum ?enums .
}
WHERE {?queryable a cql:Queryable ;
dcterms:identifier ?id ;
sh:name ?title ;
sh:datatype ?type ;
sh:in/rdf:rest*/rdf:first ?enums ;
sh:description ?description ;
sh:datatype ?type .
OPTIONAL { ?queryable sh:in/rdf:rest*/rdf:first ?enums }
}
"""
g, _ = await system_repo.send_queries([query], [])
Expand All @@ -511,11 +516,15 @@ async def generate_queryables_from_shacl_definition(
].split("#")[
-1
], # hack
"enum": [
enum_item["@id"]
for enum_item in item["http://www.opengis.net/doc/IS/cql2/1.0/enum"]
],
"description": item["http://www.opengis.net/doc/IS/cql2/1.0/description"][
0
]["@value"],
}
enum = item.get(
"http://www.opengis.net/doc/IS/cql2/1.0/enum"
) # enums are optional.
if enum:
queryable_props[id_value]["enum"] = [enum_item["@id"] for enum_item in enum]
if endpoint_uri == OGCFEAT["queryables-global"]:
title = "Global Queryables"
description = (
Expand All @@ -536,9 +545,9 @@ async def generate_queryables_from_shacl_definition(


@cached(ttl=600, key=lambda collection_uri: collection_uri)
async def _cached_feature_collection_query(collection_uri, data_repo, feature_collection_query):
async def _cached_feature_collection_query(
collection_uri, data_repo, feature_collection_query
):
"""cache the feature collection information for 10 minutes as it is an expensive query at present"""
fc_item_graph, _ = await data_repo.send_queries(
[feature_collection_query], []
)
fc_item_graph, _ = await data_repo.send_queries([feature_collection_query], [])
return fc_item_graph
Loading

0 comments on commit 59ba6a8

Please sign in to comment.