Skip to content

Commit ad6f7f6

Browse files
committed
move shared bbox code to helpers
1 parent 849d1cb commit ad6f7f6

File tree

4 files changed

+85
-73
lines changed

4 files changed

+85
-73
lines changed

stac_fastapi/elasticsearch/stac_fastapi/elasticsearch/database_logic.py

Lines changed: 4 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
)
3030
from stac_fastapi.sfeos_helpers import filter as filter_module
3131
from stac_fastapi.sfeos_helpers.database import (
32+
apply_collections_bbox_filter_shared,
3233
apply_free_text_filter_shared,
3334
apply_intersects_filter_shared,
3435
create_index_templates_shared,
@@ -317,42 +318,9 @@ async def get_all_collections(
317318
raise
318319

319320
# Apply bbox filter if provided
320-
if bbox:
321-
# Parse bbox if it's a string (from GET requests)
322-
if isinstance(bbox, str):
323-
try:
324-
bbox = [float(x.strip()) for x in bbox.split(",")]
325-
except (ValueError, AttributeError) as e:
326-
logger.error(f"Invalid bbox format: {bbox}, error: {e}")
327-
bbox = None
328-
329-
if bbox and len(bbox) >= 4:
330-
# Extract 2D coordinates (bbox can be 2D [minx, miny, maxx, maxy] or 3D [minx, miny, minz, maxx, maxy, maxz])
331-
# For geospatial queries, we discard altitude (z) values
332-
minx, miny = bbox[0], bbox[1]
333-
if len(bbox) == 4:
334-
# 2D bbox
335-
maxx, maxy = bbox[2], bbox[3]
336-
else:
337-
# 3D bbox - extract indices 3,4 for maxx,maxy, discarding altitude at indices 2 (minz) and 5 (maxz)
338-
maxx, maxy = bbox[3], bbox[4]
339-
340-
# Convert bbox to a polygon for geo_shape query
341-
bbox_polygon = {
342-
"type": "Polygon",
343-
"coordinates": bbox2polygon(minx, miny, maxx, maxy),
344-
}
345-
# Add geo_shape query to filter collections by bbox_shape field
346-
query_parts.append(
347-
{
348-
"geo_shape": {
349-
"bbox_shape": {
350-
"shape": bbox_polygon,
351-
"relation": "intersects",
352-
}
353-
}
354-
}
355-
)
321+
bbox_filter = apply_collections_bbox_filter_shared(bbox)
322+
if bbox_filter:
323+
query_parts.append(bbox_filter)
356324

357325
# Combine all query parts with AND logic if there are multiple
358326
datetime_filter = None

stac_fastapi/opensearch/stac_fastapi/opensearch/database_logic.py

Lines changed: 4 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from stac_fastapi.opensearch.config import OpensearchSettings as SyncSearchSettings
3030
from stac_fastapi.sfeos_helpers import filter as filter_module
3131
from stac_fastapi.sfeos_helpers.database import (
32+
apply_collections_bbox_filter_shared,
3233
apply_free_text_filter_shared,
3334
apply_intersects_filter_shared,
3435
create_index_templates_shared,
@@ -301,42 +302,9 @@ async def get_all_collections(
301302
raise
302303

303304
# Apply bbox filter if provided
304-
if bbox:
305-
# Parse bbox if it's a string (from GET requests)
306-
if isinstance(bbox, str):
307-
try:
308-
bbox = [float(x.strip()) for x in bbox.split(",")]
309-
except (ValueError, AttributeError) as e:
310-
logger.error(f"Invalid bbox format: {bbox}, error: {e}")
311-
bbox = None
312-
313-
if bbox and len(bbox) >= 4:
314-
# Extract 2D coordinates (bbox can be 2D [minx, miny, maxx, maxy] or 3D [minx, miny, minz, maxx, maxy, maxz])
315-
# For geospatial queries, we discard altitude (z) values
316-
minx, miny = bbox[0], bbox[1]
317-
if len(bbox) == 4:
318-
# 2D bbox
319-
maxx, maxy = bbox[2], bbox[3]
320-
else:
321-
# 3D bbox - extract indices 3,4 for maxx,maxy, discarding altitude at indices 2 (minz) and 5 (maxz)
322-
maxx, maxy = bbox[3], bbox[4]
323-
324-
# Convert bbox to a polygon for geo_shape query
325-
bbox_polygon = {
326-
"type": "Polygon",
327-
"coordinates": bbox2polygon(minx, miny, maxx, maxy),
328-
}
329-
# Add geo_shape query to filter collections by bbox_shape field
330-
query_parts.append(
331-
{
332-
"geo_shape": {
333-
"bbox_shape": {
334-
"shape": bbox_polygon,
335-
"relation": "intersects",
336-
}
337-
}
338-
}
339-
)
305+
bbox_filter = apply_collections_bbox_filter_shared(bbox)
306+
if bbox_filter:
307+
query_parts.append(bbox_filter)
340308

341309
# Combine all query parts with AND logic if there are multiple
342310
datetime_filter = None

stac_fastapi/sfeos_helpers/stac_fastapi/sfeos_helpers/database/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
)
4343
from .mapping import get_queryables_mapping_shared
4444
from .query import (
45+
apply_collections_bbox_filter_shared,
4546
apply_free_text_filter_shared,
4647
apply_intersects_filter_shared,
4748
populate_sort_shared,
@@ -59,6 +60,7 @@
5960
# Query operations
6061
"apply_free_text_filter_shared",
6162
"apply_intersects_filter_shared",
63+
"apply_collections_bbox_filter_shared",
6264
"populate_sort_shared",
6365
# Mapping operations
6466
"get_queryables_mapping_shared",

stac_fastapi/sfeos_helpers/stac_fastapi/sfeos_helpers/database/query.py

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
This module provides functions for building and manipulating Elasticsearch/OpenSearch queries.
44
"""
55

6-
from typing import Any, Dict, List, Optional
6+
import logging
7+
from typing import Any, Dict, List, Optional, Union
78

9+
from stac_fastapi.core.utilities import bbox2polygon
810
from stac_fastapi.sfeos_helpers.mappings import Geometry
911

1012
ES_MAX_URL_LENGTH = 4096
@@ -66,6 +68,78 @@ def apply_intersects_filter_shared(
6668
}
6769

6870

71+
def apply_collections_bbox_filter_shared(
72+
bbox: Union[str, List[float], None]
73+
) -> Optional[Dict[str, Dict]]:
74+
"""Create a geo_shape filter for collections bbox search.
75+
76+
This function handles bbox parsing from both GET requests (string format) and POST requests
77+
(list format), and constructs a geo_shape query for filtering collections by their bbox_shape field.
78+
79+
Args:
80+
bbox: The bounding box parameter. Can be:
81+
- A string of comma-separated coordinates (from GET requests)
82+
- A list of floats [minx, miny, maxx, maxy] for 2D bbox
83+
- None if no bbox filter is provided
84+
85+
Returns:
86+
Optional[Dict[str, Dict]]: A dictionary containing the geo_shape filter configuration
87+
that can be used with Elasticsearch/OpenSearch queries, or None if bbox is invalid.
88+
Example return value:
89+
{
90+
"geo_shape": {
91+
"bbox_shape": {
92+
"shape": {
93+
"type": "Polygon",
94+
"coordinates": [[[minx, miny], [maxx, miny], [maxx, maxy], [minx, maxy], [minx, miny]]]
95+
},
96+
"relation": "intersects"
97+
}
98+
}
99+
}
100+
101+
Notes:
102+
- This function is specifically for filtering collections by their spatial extent
103+
- It queries the bbox_shape field (not the geometry field used for items)
104+
- The bbox is expected to be 2D (4 values) after any 3D to 2D conversion in the API layer
105+
"""
106+
logger = logging.getLogger(__name__)
107+
108+
if not bbox:
109+
return None
110+
111+
# Parse bbox if it's a string (from GET requests)
112+
if isinstance(bbox, str):
113+
try:
114+
bbox = [float(x.strip()) for x in bbox.split(",")]
115+
except (ValueError, AttributeError) as e:
116+
logger.error(f"Invalid bbox format: {bbox}, error: {e}")
117+
return None
118+
119+
if not bbox or len(bbox) != 4:
120+
if bbox:
121+
logger.warning(
122+
f"bbox has incorrect number of coordinates (length={len(bbox)}), expected 4 (2D bbox)"
123+
)
124+
return None
125+
126+
# Convert bbox to a polygon for geo_shape query
127+
bbox_polygon = {
128+
"type": "Polygon",
129+
"coordinates": bbox2polygon(bbox[0], bbox[1], bbox[2], bbox[3]),
130+
}
131+
132+
# Return geo_shape query for bbox_shape field
133+
return {
134+
"geo_shape": {
135+
"bbox_shape": {
136+
"shape": bbox_polygon,
137+
"relation": "intersects",
138+
}
139+
}
140+
}
141+
142+
69143
def populate_sort_shared(sortby: List) -> Optional[Dict[str, Dict[str, str]]]:
70144
"""Create a sort configuration for Elasticsearch/OpenSearch queries.
71145

0 commit comments

Comments
 (0)