stac-fastapi-core 4.1.0__py3-none-any.whl → 5.0.0a0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- stac_fastapi/core/core.py +21 -260
- stac_fastapi/core/datetime_utils.py +33 -1
- stac_fastapi/core/extensions/aggregation.py +2 -530
- stac_fastapi/core/extensions/filter.py +50 -153
- stac_fastapi/core/utilities.py +20 -5
- stac_fastapi/core/version.py +1 -1
- stac_fastapi_core-5.0.0a0.dist-info/METADATA +570 -0
- {stac_fastapi_core-4.1.0.dist-info → stac_fastapi_core-5.0.0a0.dist-info}/RECORD +10 -11
- stac_fastapi/core/database_logic.py +0 -232
- stac_fastapi_core-4.1.0.dist-info/METADATA +0 -374
- {stac_fastapi_core-4.1.0.dist-info → stac_fastapi_core-5.0.0a0.dist-info}/WHEEL +0 -0
- {stac_fastapi_core-4.1.0.dist-info → stac_fastapi_core-5.0.0a0.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Filter extension logic for
|
|
1
|
+
"""Filter extension logic for conversion."""
|
|
2
2
|
|
|
3
3
|
# """
|
|
4
4
|
# Implements Filter Extension.
|
|
@@ -10,50 +10,58 @@
|
|
|
10
10
|
# defines the LIKE, IN, and BETWEEN operators.
|
|
11
11
|
|
|
12
12
|
# Basic Spatial Operators (http://www.opengis.net/spec/cql2/1.0/conf/basic-spatial-operators)
|
|
13
|
-
# defines
|
|
13
|
+
# defines spatial operators (S_INTERSECTS, S_CONTAINS, S_WITHIN, S_DISJOINT).
|
|
14
14
|
# """
|
|
15
15
|
|
|
16
|
-
import re
|
|
17
16
|
from enum import Enum
|
|
18
17
|
from typing import Any, Dict
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"
|
|
26
|
-
|
|
19
|
+
DEFAULT_QUERYABLES: Dict[str, Dict[str, Any]] = {
|
|
20
|
+
"id": {
|
|
21
|
+
"description": "ID",
|
|
22
|
+
"$ref": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/definitions/core/allOf/2/properties/id",
|
|
23
|
+
},
|
|
24
|
+
"collection": {
|
|
25
|
+
"description": "Collection",
|
|
26
|
+
"$ref": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/definitions/core/allOf/2/then/properties/collection",
|
|
27
|
+
},
|
|
28
|
+
"geometry": {
|
|
29
|
+
"description": "Geometry",
|
|
30
|
+
"$ref": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/definitions/core/allOf/1/oneOf/0/properties/geometry",
|
|
31
|
+
},
|
|
32
|
+
"datetime": {
|
|
33
|
+
"description": "Acquisition Timestamp",
|
|
34
|
+
"$ref": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/datetime.json#/properties/datetime",
|
|
35
|
+
},
|
|
36
|
+
"created": {
|
|
37
|
+
"description": "Creation Timestamp",
|
|
38
|
+
"$ref": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/datetime.json#/properties/created",
|
|
39
|
+
},
|
|
40
|
+
"updated": {
|
|
41
|
+
"description": "Creation Timestamp",
|
|
42
|
+
"$ref": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/datetime.json#/properties/updated",
|
|
43
|
+
},
|
|
44
|
+
"cloud_cover": {
|
|
45
|
+
"description": "Cloud Cover",
|
|
46
|
+
"$ref": "https://stac-extensions.github.io/eo/v1.0.0/schema.json#/definitions/fields/properties/eo:cloud_cover",
|
|
47
|
+
},
|
|
48
|
+
"cloud_shadow_percentage": {
|
|
49
|
+
"title": "Cloud Shadow Percentage",
|
|
50
|
+
"description": "Cloud Shadow Percentage",
|
|
51
|
+
"type": "number",
|
|
52
|
+
"minimum": 0,
|
|
53
|
+
"maximum": 100,
|
|
54
|
+
},
|
|
55
|
+
"nodata_pixel_percentage": {
|
|
56
|
+
"title": "No Data Pixel Percentage",
|
|
57
|
+
"description": "No Data Pixel Percentage",
|
|
58
|
+
"type": "number",
|
|
59
|
+
"minimum": 0,
|
|
60
|
+
"maximum": 100,
|
|
61
|
+
},
|
|
27
62
|
}
|
|
28
63
|
|
|
29
64
|
|
|
30
|
-
def _replace_like_patterns(match: re.Match) -> str:
|
|
31
|
-
pattern = match.group()
|
|
32
|
-
try:
|
|
33
|
-
return _valid_like_substitutions[pattern]
|
|
34
|
-
except KeyError:
|
|
35
|
-
raise ValueError(f"'{pattern}' is not a valid escape sequence")
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def cql2_like_to_es(string: str) -> str:
|
|
39
|
-
"""
|
|
40
|
-
Convert CQL2 "LIKE" characters to Elasticsearch "wildcard" characters.
|
|
41
|
-
|
|
42
|
-
Args:
|
|
43
|
-
string (str): The string containing CQL2 wildcard characters.
|
|
44
|
-
|
|
45
|
-
Returns:
|
|
46
|
-
str: The converted string with Elasticsearch compatible wildcards.
|
|
47
|
-
|
|
48
|
-
Raises:
|
|
49
|
-
ValueError: If an invalid escape sequence is encountered.
|
|
50
|
-
"""
|
|
51
|
-
return _cql2_like_patterns.sub(
|
|
52
|
-
repl=_replace_like_patterns,
|
|
53
|
-
string=string,
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
|
|
57
65
|
class LogicalOp(str, Enum):
|
|
58
66
|
"""Enumeration for logical operators used in constructing Elasticsearch queries."""
|
|
59
67
|
|
|
@@ -82,121 +90,10 @@ class AdvancedComparisonOp(str, Enum):
|
|
|
82
90
|
IN = "in"
|
|
83
91
|
|
|
84
92
|
|
|
85
|
-
class
|
|
86
|
-
"""Enumeration for spatial
|
|
93
|
+
class SpatialOp(str, Enum):
|
|
94
|
+
"""Enumeration for spatial operators as per CQL2 standards."""
|
|
87
95
|
|
|
88
96
|
S_INTERSECTS = "s_intersects"
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
"id": "id",
|
|
93
|
-
"collection": "collection",
|
|
94
|
-
"geometry": "geometry",
|
|
95
|
-
"datetime": "properties.datetime",
|
|
96
|
-
"created": "properties.created",
|
|
97
|
-
"updated": "properties.updated",
|
|
98
|
-
"cloud_cover": "properties.eo:cloud_cover",
|
|
99
|
-
"cloud_shadow_percentage": "properties.s2:cloud_shadow_percentage",
|
|
100
|
-
"nodata_pixel_percentage": "properties.s2:nodata_pixel_percentage",
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
def to_es_field(field: str) -> str:
|
|
105
|
-
"""
|
|
106
|
-
Map a given field to its corresponding Elasticsearch field according to a predefined mapping.
|
|
107
|
-
|
|
108
|
-
Args:
|
|
109
|
-
field (str): The field name from a user query or filter.
|
|
110
|
-
|
|
111
|
-
Returns:
|
|
112
|
-
str: The mapped field name suitable for Elasticsearch queries.
|
|
113
|
-
"""
|
|
114
|
-
return queryables_mapping.get(field, field)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
def to_es(query: Dict[str, Any]) -> Dict[str, Any]:
|
|
118
|
-
"""
|
|
119
|
-
Transform a simplified CQL2 query structure to an Elasticsearch compatible query DSL.
|
|
120
|
-
|
|
121
|
-
Args:
|
|
122
|
-
query (Dict[str, Any]): The query dictionary containing 'op' and 'args'.
|
|
123
|
-
|
|
124
|
-
Returns:
|
|
125
|
-
Dict[str, Any]: The corresponding Elasticsearch query in the form of a dictionary.
|
|
126
|
-
"""
|
|
127
|
-
if query["op"] in [LogicalOp.AND, LogicalOp.OR, LogicalOp.NOT]:
|
|
128
|
-
bool_type = {
|
|
129
|
-
LogicalOp.AND: "must",
|
|
130
|
-
LogicalOp.OR: "should",
|
|
131
|
-
LogicalOp.NOT: "must_not",
|
|
132
|
-
}[query["op"]]
|
|
133
|
-
return {"bool": {bool_type: [to_es(sub_query) for sub_query in query["args"]]}}
|
|
134
|
-
|
|
135
|
-
elif query["op"] in [
|
|
136
|
-
ComparisonOp.EQ,
|
|
137
|
-
ComparisonOp.NEQ,
|
|
138
|
-
ComparisonOp.LT,
|
|
139
|
-
ComparisonOp.LTE,
|
|
140
|
-
ComparisonOp.GT,
|
|
141
|
-
ComparisonOp.GTE,
|
|
142
|
-
]:
|
|
143
|
-
range_op = {
|
|
144
|
-
ComparisonOp.LT: "lt",
|
|
145
|
-
ComparisonOp.LTE: "lte",
|
|
146
|
-
ComparisonOp.GT: "gt",
|
|
147
|
-
ComparisonOp.GTE: "gte",
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
field = to_es_field(query["args"][0]["property"])
|
|
151
|
-
value = query["args"][1]
|
|
152
|
-
if isinstance(value, dict) and "timestamp" in value:
|
|
153
|
-
value = value["timestamp"]
|
|
154
|
-
if query["op"] == ComparisonOp.EQ:
|
|
155
|
-
return {"range": {field: {"gte": value, "lte": value}}}
|
|
156
|
-
elif query["op"] == ComparisonOp.NEQ:
|
|
157
|
-
return {
|
|
158
|
-
"bool": {
|
|
159
|
-
"must_not": [{"range": {field: {"gte": value, "lte": value}}}]
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
else:
|
|
163
|
-
return {"range": {field: {range_op[query["op"]]: value}}}
|
|
164
|
-
else:
|
|
165
|
-
if query["op"] == ComparisonOp.EQ:
|
|
166
|
-
return {"term": {field: value}}
|
|
167
|
-
elif query["op"] == ComparisonOp.NEQ:
|
|
168
|
-
return {"bool": {"must_not": [{"term": {field: value}}]}}
|
|
169
|
-
else:
|
|
170
|
-
return {"range": {field: {range_op[query["op"]]: value}}}
|
|
171
|
-
|
|
172
|
-
elif query["op"] == ComparisonOp.IS_NULL:
|
|
173
|
-
field = to_es_field(query["args"][0]["property"])
|
|
174
|
-
return {"bool": {"must_not": {"exists": {"field": field}}}}
|
|
175
|
-
|
|
176
|
-
elif query["op"] == AdvancedComparisonOp.BETWEEN:
|
|
177
|
-
field = to_es_field(query["args"][0]["property"])
|
|
178
|
-
gte, lte = query["args"][1], query["args"][2]
|
|
179
|
-
if isinstance(gte, dict) and "timestamp" in gte:
|
|
180
|
-
gte = gte["timestamp"]
|
|
181
|
-
if isinstance(lte, dict) and "timestamp" in lte:
|
|
182
|
-
lte = lte["timestamp"]
|
|
183
|
-
return {"range": {field: {"gte": gte, "lte": lte}}}
|
|
184
|
-
|
|
185
|
-
elif query["op"] == AdvancedComparisonOp.IN:
|
|
186
|
-
field = to_es_field(query["args"][0]["property"])
|
|
187
|
-
values = query["args"][1]
|
|
188
|
-
if not isinstance(values, list):
|
|
189
|
-
raise ValueError(f"Arg {values} is not a list")
|
|
190
|
-
return {"terms": {field: values}}
|
|
191
|
-
|
|
192
|
-
elif query["op"] == AdvancedComparisonOp.LIKE:
|
|
193
|
-
field = to_es_field(query["args"][0]["property"])
|
|
194
|
-
pattern = cql2_like_to_es(query["args"][1])
|
|
195
|
-
return {"wildcard": {field: {"value": pattern, "case_insensitive": True}}}
|
|
196
|
-
|
|
197
|
-
elif query["op"] == SpatialIntersectsOp.S_INTERSECTS:
|
|
198
|
-
field = to_es_field(query["args"][0]["property"])
|
|
199
|
-
geometry = query["args"][1]
|
|
200
|
-
return {"geo_shape": {field: {"shape": geometry, "relation": "intersects"}}}
|
|
201
|
-
|
|
202
|
-
return {}
|
|
97
|
+
S_CONTAINS = "s_contains"
|
|
98
|
+
S_WITHIN = "s_within"
|
|
99
|
+
S_DISJOINT = "s_disjoint"
|
stac_fastapi/core/utilities.py
CHANGED
|
@@ -12,20 +12,35 @@ from stac_fastapi.types.stac import Item
|
|
|
12
12
|
MAX_LIMIT = 10000
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
def get_bool_env(name: str, default: bool = False) -> bool:
|
|
15
|
+
def get_bool_env(name: str, default: Union[bool, str] = False) -> bool:
|
|
16
16
|
"""
|
|
17
17
|
Retrieve a boolean value from an environment variable.
|
|
18
18
|
|
|
19
19
|
Args:
|
|
20
20
|
name (str): The name of the environment variable.
|
|
21
|
-
default (bool, optional): The default value to use if the variable is not set or unrecognized. Defaults to False.
|
|
21
|
+
default (Union[bool, str], optional): The default value to use if the variable is not set or unrecognized. Defaults to False.
|
|
22
22
|
|
|
23
23
|
Returns:
|
|
24
24
|
bool: The boolean value parsed from the environment variable.
|
|
25
25
|
"""
|
|
26
|
-
value = os.getenv(name, str(default).lower())
|
|
27
26
|
true_values = ("true", "1", "yes", "y")
|
|
28
27
|
false_values = ("false", "0", "no", "n")
|
|
28
|
+
|
|
29
|
+
# Normalize the default value
|
|
30
|
+
if isinstance(default, bool):
|
|
31
|
+
default_str = "true" if default else "false"
|
|
32
|
+
elif isinstance(default, str):
|
|
33
|
+
default_str = default.lower()
|
|
34
|
+
else:
|
|
35
|
+
logger = logging.getLogger(__name__)
|
|
36
|
+
logger.warning(
|
|
37
|
+
f"The `default` parameter must be a boolean or string, got {type(default).__name__}. "
|
|
38
|
+
f"Falling back to `False`."
|
|
39
|
+
)
|
|
40
|
+
default_str = "false"
|
|
41
|
+
|
|
42
|
+
# Retrieve and normalize the environment variable value
|
|
43
|
+
value = os.getenv(name, default_str)
|
|
29
44
|
if value.lower() in true_values:
|
|
30
45
|
return True
|
|
31
46
|
elif value.lower() in false_values:
|
|
@@ -34,9 +49,9 @@ def get_bool_env(name: str, default: bool = False) -> bool:
|
|
|
34
49
|
logger = logging.getLogger(__name__)
|
|
35
50
|
logger.warning(
|
|
36
51
|
f"Environment variable '{name}' has unrecognized value '{value}'. "
|
|
37
|
-
f"Expected one of {true_values + false_values}. Using default: {
|
|
52
|
+
f"Expected one of {true_values + false_values}. Using default: {default_str}"
|
|
38
53
|
)
|
|
39
|
-
return
|
|
54
|
+
return default_str in true_values
|
|
40
55
|
|
|
41
56
|
|
|
42
57
|
def bbox2polygon(b0: float, b1: float, b2: float, b3: float) -> List[List[List[float]]]:
|
stac_fastapi/core/version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""library version."""
|
|
2
|
-
__version__ = "
|
|
2
|
+
__version__ = "5.0.0a0"
|