|
5 | 5 | from base64 import urlsafe_b64decode, urlsafe_b64encode |
6 | 6 | from collections.abc import Iterable |
7 | 7 | from copy import deepcopy |
| 8 | +from dataclasses import dataclass |
8 | 9 | from typing import Any, Dict, List, Optional, Tuple, Type |
9 | 10 |
|
10 | 11 | import attr |
|
74 | 75 | logger = logging.getLogger(__name__) |
75 | 76 |
|
76 | 77 |
|
| 78 | +# AST-style node classes for bbox filtering |
| 79 | +@dataclass |
| 80 | +class GeoShapeNode: |
| 81 | + """Represents a geo_shape query node in the AST.""" |
| 82 | + |
| 83 | + geometry: "GeometryNode" |
| 84 | + |
| 85 | + |
| 86 | +@dataclass |
| 87 | +class GeometryNode: |
| 88 | + """Represents a geometry node with shape and relation.""" |
| 89 | + |
| 90 | + shape: "ShapeNode" |
| 91 | + relation: str |
| 92 | + |
| 93 | + |
| 94 | +@dataclass |
| 95 | +class ShapeNode: |
| 96 | + """Represents a shape node with type and coordinates.""" |
| 97 | + |
| 98 | + type: str |
| 99 | + coordinates: List[List[List[float]]] |
| 100 | + |
| 101 | + |
| 102 | +def analyze_bbox_query(node) -> dict: |
| 103 | + """Analyze and build bbox query from node tree. |
| 104 | + Args: |
| 105 | + node: The AST node to analyze. |
| 106 | +
|
| 107 | + Returns: |
| 108 | + The constructed geo_shape query. |
| 109 | + """ |
| 110 | + if isinstance(node, GeoShapeNode): |
| 111 | + return analyze_bbox_query(node.geometry) |
| 112 | + elif isinstance(node, GeometryNode): |
| 113 | + shape_filter = analyze_bbox_query(node.shape) |
| 114 | + return {"shape": shape_filter, "relation": node.relation} |
| 115 | + elif isinstance(node, ShapeNode): |
| 116 | + if node.type == "polygon": |
| 117 | + return {"type": node.type, "coordinates": node.coordinates} |
| 118 | + return {} |
| 119 | + |
| 120 | + |
77 | 121 | async def create_index_templates() -> None: |
78 | 122 | """ |
79 | 123 | Create index templates for the Collection and Item indices. |
@@ -595,34 +639,15 @@ def apply_datetime_filter( |
595 | 639 |
|
596 | 640 | @staticmethod |
597 | 641 | def apply_bbox_filter(search: Search, bbox: List): |
598 | | - """Filter search results based on bounding box. |
599 | | -
|
600 | | - Args: |
601 | | - search (Search): The search object to apply the filter to. |
602 | | - bbox (List): The bounding box coordinates, represented as a list of four values [minx, miny, maxx, maxy]. |
| 642 | + """Filter search results based on bounding box using AST analysis pattern.""" |
| 643 | + polygon_coordinates = bbox2polygon(*bbox) |
603 | 644 |
|
604 | | - Returns: |
605 | | - search (Search): The search object with the bounding box filter applied. |
| 645 | + shape_node = ShapeNode(type="polygon", coordinates=polygon_coordinates) |
| 646 | + geometry_node = GeometryNode(shape=shape_node, relation="intersects") |
| 647 | + geo_shape_node = GeoShapeNode(geometry=geometry_node) |
606 | 648 |
|
607 | | - Notes: |
608 | | - The bounding box is transformed into a polygon using the `bbox2polygon` function and |
609 | | - a geo_shape filter is added to the search object, set to intersect with the specified polygon. |
610 | | - """ |
611 | | - return search.filter( |
612 | | - Q( |
613 | | - { |
614 | | - "geo_shape": { |
615 | | - "geometry": { |
616 | | - "shape": { |
617 | | - "type": "polygon", |
618 | | - "coordinates": bbox2polygon(*bbox), |
619 | | - }, |
620 | | - "relation": "intersects", |
621 | | - } |
622 | | - } |
623 | | - } |
624 | | - ) |
625 | | - ) |
| 649 | + bbox_query_dict = analyze_bbox_query(geo_shape_node) |
| 650 | + return search.filter(Q("geo_shape", geometry=bbox_query_dict)) |
626 | 651 |
|
627 | 652 | @staticmethod |
628 | 653 | def apply_intersects_filter( |
|
0 commit comments