Skip to content

Commit

Permalink
IN and BETWEEN operators
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesfisher-geo committed Dec 19, 2023
1 parent 8ed4d35 commit 73f6dc4
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 6 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

### Added

- LIKE search operator to Filter extension [#178](https://github.com/stac-utils/stac-fastapi-elasticsearch/pull/178)
- Advanced comparison (LIKE, IN, BETWEEN) operators to the Filter extension [#178](https://github.com/stac-utils/stac-fastapi-elasticsearch/pull/178)

### Changed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
Basic CQL2 (AND, OR, NOT), comparison operators (=, <>, <, <=, >, >=), and IS NULL.
The comparison operators are allowed against string, numeric, boolean, date, and datetime types.
Advanced CQL2 LIKE comparison operator (http://www.opengis.net/spec/cql2/1.0/req/advanced-comparison-operators).
The LIKE comparison operator is allowed against string types.
Advanced comparison operators (http://www.opengis.net/spec/cql2/1.0/req/advanced-comparison-operators)
defines the LIKE, IN, and BETWEEN operators.
Basic Spatial Operators (http://www.opengis.net/spec/cql2/1.0/conf/basic-spatial-operators)
defines the intersects operator (S_INTERSECTS).
Expand Down Expand Up @@ -84,10 +84,12 @@ def to_es(self):
class AdvancedComparisonOp(str, Enum):
"""Advanced Comparison operator.
CQL2 advanced comparison operator like (~).
CQL2 advanced comparison operators like (~), between, and in.
"""

like = "like"
between = "between"
_in = "in"


class SpatialIntersectsOp(str, Enum):
Expand Down Expand Up @@ -165,7 +167,7 @@ class Clause(BaseModel):
"""Filter extension clause."""

op: Union[LogicalOp, ComparisonOp, AdvancedComparisonOp, SpatialIntersectsOp]
args: List[Arg]
args: List[Union[Arg, List[Arg]]]

def to_es(self):
"""Generate an Elasticsearch expression for this Clause."""
Expand All @@ -188,11 +190,27 @@ def to_es(self):
"wildcard": {
to_es(self.args[0]): {
"value": cql2_like_to_es(str(to_es(self.args[1]))),
"boost": 1.0,
"case_insensitive": "true",
}
}
}
elif self.op == AdvancedComparisonOp.between:
if not isinstance(self.args[1], List):
raise RuntimeError(f"Arg {self.args[1]} is not a list")
return {
"range": {
to_es(self.args[0]): {
"gte": to_es(self.args[1][0]),
"lte": to_es(self.args[1][1]),
}
}
}
elif self.op == AdvancedComparisonOp._in:
if not isinstance(self.args[1], List):
raise RuntimeError(f"Arg {self.args[1]} is not a list")
return {
"terms": {to_es(self.args[0]): [to_es(arg) for arg in self.args[1]]}
}
elif (
self.op == ComparisonOp.lt
or self.op == ComparisonOp.lte
Expand Down
35 changes: 35 additions & 0 deletions stac_fastapi/elasticsearch/tests/extensions/cql2/example21.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"op": "and",
"args": [
{
"op": "between",
"args": [
{
"property": "cloud_cover"
},
[
0.1,
0.2
]
]
},
{
"op": "=",
"args": [
{
"property": "landsat:wrs_row"
},
28
]
},
{
"op": "=",
"args": [
{
"property": "landsat:wrs_path"
},
203
]
}
]
}
13 changes: 13 additions & 0 deletions stac_fastapi/elasticsearch/tests/extensions/cql2/example22.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"op": "and",
"args": [
{
"op": "in",
"args": [
{"property": "id"},
["LC08_L1TP_060247_20180905_20180912_01_T1_L1TP"]
]
},
{"op": "=", "args": [{"property": "collection"}, "landsat8_l1tp"]}
]
}
107 changes: 107 additions & 0 deletions stac_fastapi/elasticsearch/tests/extensions/test_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,3 +309,110 @@ async def test_search_filter_extension_escape_chars(app_client, ctx):

assert resp.status_code == 200
assert len(resp.json()["features"]) == 1


@pytest.mark.asyncio
async def test_search_filter_extension_in(app_client, ctx):
product_id = ctx.item["properties"]["landsat:product_id"]

params = {
"filter": {
"op": "and",
"args": [
{"op": "=", "args": [{"property": "id"}, ctx.item["id"]]},
{
"op": "in",
"args": [
{"property": "properties.landsat:product_id"},
[product_id],
],
},
],
}
}

resp = await app_client.post("/search", json=params)

assert resp.status_code == 200
assert len(resp.json()["features"]) == 1


@pytest.mark.asyncio
async def test_search_filter_extension_in_no_list(app_client, ctx):
product_id = ctx.item["properties"]["landsat:product_id"]

params = {
"filter": {
"op": "and",
"args": [
{"op": "=", "args": [{"property": "id"}, ctx.item["id"]]},
{
"op": "in",
"args": [
{"property": "properties.landsat:product_id"},
product_id,
],
},
],
}
}

resp = await app_client.post("/search", json=params)

assert resp.status_code == 400
assert resp.json() == {
"detail": f"Error with cql2_json filter: Arg {product_id} is not a list"
}


@pytest.mark.asyncio
async def test_search_filter_extension_between(app_client, ctx):
sun_elevation = ctx.item["properties"]["view:sun_elevation"]

params = {
"filter": {
"op": "and",
"args": [
{"op": "=", "args": [{"property": "id"}, ctx.item["id"]]},
{
"op": "between",
"args": [
{"property": "properties.view:sun_elevation"},
[sun_elevation - 0.01, sun_elevation + 0.01],
],
},
],
}
}
resp = await app_client.post("/search", json=params)

assert resp.status_code == 200
assert len(resp.json()["features"]) == 1


@pytest.mark.asyncio
async def test_search_filter_extension_between_no_list(app_client, ctx):
sun_elevation = ctx.item["properties"]["view:sun_elevation"]

params = {
"filter": {
"op": "and",
"args": [
{"op": "=", "args": [{"property": "id"}, ctx.item["id"]]},
{
"op": "between",
"args": [
{"property": "properties.view:sun_elevation"},
sun_elevation - 0.01,
sun_elevation + 0.01,
],
},
],
}
}
resp = await app_client.post("/search", json=params)

assert resp.status_code == 400
assert resp.json() == {
"detail": f"Error with cql2_json filter: Arg {sun_elevation - 0.01} is not a list"
}

0 comments on commit 73f6dc4

Please sign in to comment.