44from typing import TYPE_CHECKING , Any , Literal
55
66from django .contrib .gis .geos import Polygon
7- from django .db .models import Case , IntegerField , Q , QuerySet , Value , When
7+ from django .db .models import Q , QuerySet
88from ninja import Query , Router , Schema
99from ninja .errors import HttpError
1010
1515 Recording ,
1616 RecordingAnnotation ,
1717)
18+ from bats_ai .core .utils .grts_utils import normalize_sample_frame_id
1819
1920if TYPE_CHECKING :
2021 from django .http import HttpRequest
@@ -125,40 +126,43 @@ def _get_recording_location_coords(recording: Recording) -> list[float] | None:
125126 return [float (point .x ), float (point .y )]
126127
127128
129+ def _recording_grts_lookup_pair (recording : Recording ) -> tuple [int , int ] | None :
130+ """Return ``(grts_cell_id, sample_frame_id)`` for GRTS DB lookup, if applicable."""
131+ if recording .grts_cell_id is None :
132+ return None
133+ frame_id = (
134+ recording .sample_frame_id
135+ if recording .sample_frame_id is not None
136+ else DEFAULT_SAMPLE_FRAME_ID
137+ )
138+ normalized = normalize_sample_frame_id (frame_id )
139+ if normalized is None :
140+ normalized = DEFAULT_SAMPLE_FRAME_ID
141+ return (recording .grts_cell_id , normalized )
142+
143+
128144def _precompute_grts_cell_centroids (
129- cell_ids : set [int ],
130- ) -> dict [int , list [float ]]:
131- """Precompute centroid coordinates for each `grts_cell_id`.
132-
133- Choose the same "best" cell as `core/views/grts_cells.py` does,
134- then compute `[lon, lat]` from its centroid.
135- """
136- if not cell_ids :
145+ pairs : set [tuple [int , int ]],
146+ ) -> dict [tuple [int , int ], list [float ]]:
147+ """Map each ``(grts_cell_id, sample_frame_id)`` to ``[lon, lat]`` centroid."""
148+ if not pairs :
137149 return {}
138150
139- # Default to Continental US
140- # (sample_frame_id=DEFAULT_SAMPLE_FRAME_ID). We currently only import
141- # CONUS GRTS, so this keeps centroid selection aligned with loaded data.
142- frame_rank = Case (
143- When (sample_frame_id = DEFAULT_SAMPLE_FRAME_ID , then = Value (0 )),
144- default = Value (1 ),
145- output_field = IntegerField (),
146- )
151+ pair_filter = Q ()
152+ for grts_cell_id , sample_frame_id in pairs :
153+ pair_filter |= Q (grts_cell_id = grts_cell_id , sample_frame_id = sample_frame_id )
147154
148- rows = (
149- GRTSCells .objects .filter (
150- grts_cell_id__in = cell_ids ,
151- centroid_4326__isnull = False ,
152- )
153- .annotate (frame_rank = frame_rank )
154- .order_by ("grts_cell_id" , "frame_rank" )
155- .distinct ("grts_cell_id" )
156- .values_list ("grts_cell_id" , "centroid_4326" )
157- )
155+ rows = GRTSCells .objects .filter (
156+ pair_filter ,
157+ centroid_4326__isnull = False ,
158+ ).values_list ("grts_cell_id" , "sample_frame_id" , "centroid_4326" )
158159
159160 return {
160- int (cell_id ): [float (centroid .x ), float (centroid .y )]
161- for cell_id , centroid in rows
161+ (int (grts_cell_id ), int (sample_frame_id )): [
162+ float (centroid .x ),
163+ float (centroid .y ),
164+ ]
165+ for grts_cell_id , sample_frame_id , centroid in rows
162166 if centroid is not None
163167 }
164168
@@ -210,20 +214,34 @@ def get_recording_locations(
210214 grts_cell_ids = grts_cell_ids ,
211215 )
212216
213- my_list = list (my_qs .only ("id" , "audio_file" , "recording_location" , "grts_cell_id" , "created" ))
217+ my_list = list (
218+ my_qs .only (
219+ "id" ,
220+ "audio_file" ,
221+ "recording_location" ,
222+ "grts_cell_id" ,
223+ "sample_frame_id" ,
224+ "created" ,
225+ )
226+ )
214227 shared_list = list (
215228 shared_qs .only (
216229 "id" ,
217230 "audio_file" ,
218231 "recording_location" ,
219232 "grts_cell_id" ,
233+ "sample_frame_id" ,
220234 "created" ,
221235 )
222236 )
223237 recordings = my_list + shared_list
224238
225- required_cell_ids = {r .grts_cell_id for r in recordings if r .grts_cell_id is not None }
226- centroids_by_cell_id = _precompute_grts_cell_centroids (required_cell_ids )
239+ grts_pairs = set ()
240+ for r in recordings :
241+ pair = _recording_grts_lookup_pair (r )
242+ if pair is not None :
243+ grts_pairs .add (pair )
244+ centroids_by_pair = _precompute_grts_cell_centroids (grts_pairs )
227245
228246 features : list [dict [str , Any ]] = []
229247 for rec in recordings :
@@ -233,14 +251,18 @@ def get_recording_locations(
233251 # When vetting is enabled, we only show the centroid of the
234252 # GRTS cell and not the direct recording location.
235253 if rec .grts_cell_id is not None :
236- coords = centroids_by_cell_id .get (rec .grts_cell_id )
254+ pair = _recording_grts_lookup_pair (rec )
255+ if pair is not None :
256+ coords = centroids_by_pair .get (pair )
237257 # If we can't resolve a centroid, fall back to recording_location.
238258 if coords is None :
239259 coords = _get_recording_location_coords (rec )
240260 else :
241261 coords = _get_recording_location_coords (rec )
242262 if coords is None and rec .grts_cell_id is not None :
243- coords = centroids_by_cell_id .get (rec .grts_cell_id )
263+ pair = _recording_grts_lookup_pair (rec )
264+ if pair is not None :
265+ coords = centroids_by_pair .get (pair )
244266
245267 if coords is None :
246268 continue
0 commit comments