44from typing import TYPE_CHECKING , Any , Literal
55
66from django .contrib .gis .geos import Polygon
7- from django .db .models import Q , QuerySet
7+ from django .db .models import Exists , OuterRef , Q , QuerySet
88from ninja import Query , Router , Schema
99from ninja .errors import HttpError
1010
1414 Recording ,
1515 RecordingAnnotation ,
1616)
17- from bats_ai .core .utils .grts_utils import normalize_sample_frame_id
17+ from bats_ai .core .utils .grts_utils import (
18+ normalize_sample_frame_id ,
19+ recording_effective_sample_frame_id_case ,
20+ )
1821
1922if TYPE_CHECKING :
2023 from django .http import HttpRequest
@@ -60,14 +63,33 @@ def _split_tags(tags: str | None) -> list[str]:
6063 return [t .strip () for t in tags .split ("," ) if t .strip ()]
6164
6265
63- def _apply_recording_filters_and_sort ( # noqa: PLR0913
66+ def filter_recordings_by_map_bbox (
67+ qs : QuerySet [Recording ],
68+ bbox_poly : Polygon ,
69+ ) -> QuerySet [Recording ]:
70+ """Keep recordings whose point lies in the bbox or whose GRTS cell centroid matches.
71+
72+ Cell matching uses ``(grts_cell_id, sample_frame_id)`` on ``GRTSCells``, with the
73+ same effective sample frame rules as ``normalize_sample_frame_id``.
74+ """
75+ cell_centroid_in_bbox = GRTSCells .objects .filter (
76+ centroid_4326__intersects = bbox_poly ,
77+ grts_cell_id = OuterRef ("grts_cell_id" ),
78+ sample_frame_id = OuterRef ("_map_bbox_effective_sf" ),
79+ )
80+ return qs .annotate (_map_bbox_effective_sf = recording_effective_sample_frame_id_case ()).filter (
81+ Q (recording_location__intersects = bbox_poly )
82+ | (Q (grts_cell_id__isnull = False ) & Exists (cell_centroid_in_bbox ))
83+ )
84+
85+
86+ def _apply_recording_filters_and_sort (
6487 * ,
6588 qs : QuerySet [Recording ],
6689 exclude_submitted : bool ,
6790 submitted_by_user : QuerySet [int ] | None ,
6891 tags : str | None ,
6992 bbox_poly : Polygon | None ,
70- grts_cell_ids : QuerySet [int ] | None ,
7193) -> QuerySet [Recording ]:
7294 if exclude_submitted and submitted_by_user is not None :
7395 qs = qs .exclude (pk__in = submitted_by_user )
@@ -78,10 +100,8 @@ def _apply_recording_filters_and_sort( # noqa: PLR0913
78100 qs = qs .filter (tags__text = tag )
79101 qs = qs .distinct ()
80102
81- if bbox_poly is not None and grts_cell_ids is not None :
82- qs = qs .filter (
83- Q (recording_location__intersects = bbox_poly ) | Q (grts_cell_id__in = grts_cell_ids )
84- )
103+ if bbox_poly is not None :
104+ qs = filter_recordings_by_map_bbox (qs , bbox_poly )
85105
86106 # Keep deterministic ordering even though we don't expose sorting params.
87107 return qs .order_by ("-created" )
@@ -177,28 +197,22 @@ def get_recording_locations(
177197
178198 bbox = _parse_bbox (q .bbox )
179199 bbox_poly : Polygon | None = None
180- grts_cell_ids : QuerySet [int ] | None = None
181200 if bbox is not None :
182201 bbox_poly = Polygon .from_bbox ((bbox [0 ], bbox [1 ], bbox [2 ], bbox [3 ]))
183- grts_cell_ids = GRTSCells .objects .filter (centroid_4326__intersects = bbox_poly ).values_list (
184- "grts_cell_id" , flat = True
185- )
186202
187203 my_qs = _apply_recording_filters_and_sort (
188204 qs = my_qs ,
189205 exclude_submitted = exclude_submitted ,
190206 submitted_by_user = submitted_by_user ,
191207 tags = q .tags ,
192208 bbox_poly = bbox_poly ,
193- grts_cell_ids = grts_cell_ids ,
194209 )
195210 shared_qs = _apply_recording_filters_and_sort (
196211 qs = shared_qs ,
197212 exclude_submitted = exclude_submitted ,
198213 submitted_by_user = submitted_by_user ,
199214 tags = q .tags ,
200215 bbox_poly = bbox_poly ,
201- grts_cell_ids = grts_cell_ids ,
202216 )
203217
204218 my_list = list (
@@ -226,7 +240,7 @@ def get_recording_locations(
226240 sample_frame_cell_id_pairs = {
227241 (normalize_sample_frame_id (r .sample_frame_id ), r .grts_cell_id )
228242 for r in recordings
229- if r .sample_frame_id is not None and r . grts_cell_id is not None
243+ if r .grts_cell_id is not None
230244 }
231245 cell_centroids_by_sample_frame_id = _precompute_grts_cell_centroids (sample_frame_cell_id_pairs )
232246
0 commit comments