Skip to content

Commit badd7e2

Browse files
committed
MNT: Refactor longitude and latitude index functions
1 parent e89046b commit badd7e2

1 file changed

Lines changed: 127 additions & 70 deletions

File tree

rocketpy/environment/tools.py

Lines changed: 127 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -225,72 +225,148 @@ def mask_and_clean_dataset(*args):
225225
return data_array
226226

227227

228-
def find_longitude_index(longitude, lon_list): # pylint: disable=too-many-statements
229-
"""Finds the index of the given longitude in a list of longitudes.
228+
def _normalize_longitude_value(longitude, lon_start, lon_end):
229+
"""Normalize longitude based on grid format [-180, 180] or [0, 360].
230230
231231
Parameters
232232
----------
233233
longitude : float
234-
The longitude to find in the list.
235-
lon_list : list of float
236-
The list of longitudes.
234+
The longitude to normalize.
235+
lon_start : float
236+
The first longitude value in the grid.
237+
lon_end : float
238+
The last longitude value in the grid.
237239
238240
Returns
239241
-------
240-
tuple
241-
A tuple containing the adjusted longitude and its index in the list.
242-
243-
Raises
244-
------
245-
ValueError
246-
If the longitude is not within the range covered by the list.
242+
float
243+
The normalized longitude value.
247244
"""
248-
249-
def _coord_value(source, index):
250-
return float(source[index])
251-
252-
lon_len = len(lon_list)
253-
lon_start = _coord_value(lon_list, 0)
254-
lon_end = _coord_value(lon_list, lon_len - 1)
255-
256245
# Determine if file uses geographic longitudes in [-180, 180] or [0, 360].
257246
# Do not remap projected x coordinates.
258247
is_geographic_longitude = abs(lon_start) <= 360 and abs(lon_end) <= 360
259248
if is_geographic_longitude:
260249
if lon_start < 0 or lon_end < 0:
261-
lon = longitude if longitude < 180 else -180 + longitude % 180
262-
else:
263-
lon = longitude % 360
264-
else:
265-
lon = longitude
250+
return longitude if longitude < 180 else -180 + longitude % 180
251+
return longitude % 360
252+
return longitude
266253

267-
is_ascending = lon_start < lon_end
268254

269-
# Binary search to find the insertion index such that index-1 and index
270-
# bracket the requested longitude.
255+
def _binary_search_coordinate_index(target_value, coord_list, is_ascending):
256+
"""Find insertion index for target value using binary search.
257+
258+
Parameters
259+
----------
260+
target_value : float
261+
The coordinate value to locate.
262+
coord_list : list of float
263+
The list of coordinate values.
264+
is_ascending : bool
265+
Whether the coordinate list is in ascending order.
266+
267+
Returns
268+
-------
269+
int
270+
The insertion index such that coord_list[index-1] and coord_list[index]
271+
bracket the target value.
272+
"""
271273
low = 0
272-
high = lon_len
274+
high = len(coord_list)
273275
while low < high:
274276
mid = (low + high) // 2
275-
mid_value = _coord_value(lon_list, mid)
276-
if (mid_value < lon) if is_ascending else (mid_value > lon):
277+
mid_value = float(coord_list[mid])
278+
if (mid_value < target_value) if is_ascending else (mid_value > target_value):
277279
low = mid + 1
278280
else:
279281
high = mid
280-
lon_index = low
281-
282-
# Take care of longitude value equal to minimum/maximum longitude in the grid
283-
if lon_index == 0 and math.isclose(_coord_value(lon_list, 0), lon):
284-
lon_index = 1
285-
if lon_index == lon_len and _coord_value(lon_list, lon_index - 1) == lon:
286-
lon_index -= 1
287-
# Check if longitude value is inside the grid
288-
if lon_index in (0, lon_len):
282+
return low
283+
284+
285+
def _adjust_boundary_coordinate_index(index, coord_list, coord_value):
286+
"""Adjust index for exact matches at grid boundaries.
287+
288+
Parameters
289+
----------
290+
index : int
291+
The current index from binary search.
292+
coord_list : list of float
293+
The list of coordinate values.
294+
coord_value : float
295+
The coordinate value being matched.
296+
297+
Returns
298+
-------
299+
int
300+
The adjusted index after boundary handling.
301+
"""
302+
coord_len = len(coord_list)
303+
if index == 0 and math.isclose(float(coord_list[0]), coord_value):
304+
return 1
305+
if index == coord_len and float(coord_list[coord_len - 1]) == coord_value:
306+
return index - 1
307+
return index
308+
309+
310+
def _validate_coordinate_index_in_range(index, coord_len, coord_start, coord_end, coord_name):
311+
"""Validate that coordinate index is within valid interpolation range.
312+
313+
Parameters
314+
----------
315+
index : int
316+
The coordinate index to validate.
317+
coord_len : int
318+
The length of the coordinate list.
319+
coord_start : float
320+
The first coordinate value in the grid.
321+
coord_end : float
322+
The last coordinate value in the grid.
323+
coord_name : str
324+
The name of the coordinate (e.g., "Longitude", "Latitude").
325+
326+
Raises
327+
------
328+
ValueError
329+
If the index is out of valid range (0 or coord_len).
330+
"""
331+
if index in (0, coord_len):
289332
raise ValueError(
290-
f"Longitude {lon} not inside region covered by file, which is "
291-
f"from {lon_start} to {lon_end}."
333+
f"{coord_name} not inside region covered by file, which is "
334+
f"from {coord_start} to {coord_end}."
292335
)
293336

337+
338+
def find_longitude_index(longitude, lon_list):
339+
"""Finds the index of the given longitude in a list of longitudes.
340+
341+
Parameters
342+
----------
343+
longitude : float
344+
The longitude to find in the list.
345+
lon_list : list of float
346+
The list of longitudes.
347+
348+
Returns
349+
-------
350+
tuple
351+
A tuple containing the adjusted longitude and its index in the list.
352+
353+
Raises
354+
------
355+
ValueError
356+
If the longitude is not within the range covered by the list.
357+
"""
358+
lon_len = len(lon_list)
359+
lon_start = float(lon_list[0])
360+
lon_end = float(lon_list[lon_len - 1])
361+
362+
lon = _normalize_longitude_value(longitude, lon_start, lon_end)
363+
is_ascending = lon_start < lon_end
364+
365+
lon_index = _binary_search_coordinate_index(lon, lon_list, is_ascending)
366+
lon_index = _adjust_boundary_coordinate_index(lon_index, lon_list, lon)
367+
368+
_validate_coordinate_index_in_range(lon_index, lon_len, lon_start, lon_end, "Longitude")
369+
294370
return lon, lon_index
295371

296372

@@ -314,37 +390,18 @@ def find_latitude_index(latitude, lat_list):
314390
ValueError
315391
If the latitude is not within the range covered by the list.
316392
"""
317-
318-
def _coord_value(source, index):
319-
return float(source[index])
320-
321393
lat_len = len(lat_list)
322-
lat_start = _coord_value(lat_list, 0)
323-
lat_end = _coord_value(lat_list, lat_len - 1)
394+
lat_start = float(lat_list[0])
395+
lat_end = float(lat_list[lat_len - 1])
324396
is_ascending = lat_start < lat_end
325397

326-
low = 0
327-
high = lat_len
328-
while low < high:
329-
mid = (low + high) // 2
330-
mid_value = _coord_value(lat_list, mid)
331-
if (mid_value < latitude) if is_ascending else (mid_value > latitude):
332-
low = mid + 1
333-
else:
334-
high = mid
335-
lat_index = low
336-
337-
# Take care of latitude value equal to minimum/maximum latitude in the grid
338-
if lat_index == 0 and math.isclose(_coord_value(lat_list, 0), latitude):
339-
lat_index = 1
340-
if lat_index == lat_len and _coord_value(lat_list, lat_index - 1) == latitude:
341-
lat_index -= 1
342-
# Check if latitude value is inside the grid
343-
if lat_index in (0, lat_len):
344-
raise ValueError(
345-
f"Latitude {latitude} not inside region covered by file, "
346-
f"which is from {lat_start} to {lat_end}."
347-
)
398+
lat_index = _binary_search_coordinate_index(latitude, lat_list, is_ascending)
399+
lat_index = _adjust_boundary_coordinate_index(lat_index, lat_list, latitude)
400+
401+
_validate_coordinate_index_in_range(
402+
lat_index, lat_len, lat_start, lat_end, "Latitude"
403+
)
404+
348405
return latitude, lat_index
349406

350407

0 commit comments

Comments
 (0)