Skip to content

Commit 76cf710

Browse files
committed
Add radar geoquery utilities
1 parent ef25b7d commit 76cf710

6 files changed

Lines changed: 381 additions & 0 deletions

File tree

ci/environment.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ dependencies:
1212
- pandas
1313
- matplotlib
1414
- netcdf4
15+
- pyproj
1516
- pytest
1617
- pytest-cov
1718
- pytest-mock

ci/environment_latest.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ dependencies:
1212
- xradar
1313
- arm_pyart
1414
- matplotlib
15+
- pyproj
1516
- netcdf4
1617
- pytest
1718
- pytest-cov

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ dependencies = [
4343
"trollsift",
4444
"numpy",
4545
"pandas",
46+
"pyproj",
4647
"tqdm",
4748
]
4849
requires-python = ">=3.11"

radar_api/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,21 @@
4848
open_pyart,
4949
)
5050
from radar_api.search import find_files
51+
from radar_api.utilities import (
52+
available_radars_around_point,
53+
available_radars_within_extent,
54+
read_database,
55+
)
5156

5257
_root_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
5358

5459

5560
__all__ = [
5661
"available_networks",
5762
"available_products",
63+
"available_radars_around_point",
5864
"available_radars",
65+
"available_radars_within_extent",
5966
"config",
6067
"define_configs",
6168
"download_files",
@@ -65,6 +72,7 @@
6572
"open_datatree",
6673
"open_pyart",
6774
"read_configs",
75+
"read_database",
6876
]
6977

7078
# Get version

radar_api/tests/test_utilities.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# -----------------------------------------------------------------------------.
2+
# MIT License
3+
4+
# Copyright (c) 2025 RADAR-API developers
5+
#
6+
# This file is part of RADAR-API.
7+
8+
# Permission is hereby granted, free of charge, to any person obtaining a copy
9+
# of this software and associated documentation files (the "Software"), to deal
10+
# in the Software without restriction, including without limitation the rights
11+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
# copies of the Software, and to permit persons to whom the Software is
13+
# furnished to do so, subject to the following conditions:
14+
#
15+
# The above copyright notice and this permission notice shall be included in all
16+
# copies or substantial portions of the Software.
17+
#
18+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24+
# SOFTWARE.
25+
26+
# -----------------------------------------------------------------------------.
27+
"""Tests for geospatial radar utility helpers."""
28+
29+
import pytest
30+
31+
from radar_api.utilities import (
32+
available_radars_around_point,
33+
available_radars_within_extent,
34+
read_database,
35+
)
36+
37+
38+
def test_read_database():
39+
"""Test read_database returns a radar metadata DataFrame."""
40+
db = read_database(network="NEXRAD")
41+
assert len(db) > 0
42+
assert {"network", "radar", "longitude", "latitude"}.issubset(db.columns)
43+
row = db.loc[db["radar"] == "KABR"].iloc[0]
44+
assert row["network"] == "NEXRAD"
45+
assert row["longitude"] == -98.413333
46+
assert row["latitude"] == 45.455833
47+
48+
49+
def test_available_radars_around_point():
50+
"""Test search around a point using a tight search radius."""
51+
point = (-98.413333, 45.455833)
52+
radars = available_radars_around_point(point=point, distance=1, network="NEXRAD")
53+
assert ("NEXRAD", "KABR") in radars
54+
assert all(network == "NEXRAD" for network, _ in radars)
55+
56+
57+
def test_available_radars_around_point_return_distance():
58+
"""Test search around a point optionally returns the distance."""
59+
point = (-98.413333, 45.455833)
60+
radars = available_radars_around_point(
61+
point=point,
62+
distance=1,
63+
network="NEXRAD",
64+
return_distance=True,
65+
)
66+
assert ("NEXRAD", "KABR", 0.0) in radars
67+
assert all(network == "NEXRAD" for network, _, _ in radars)
68+
69+
70+
def test_available_radars_around_point_return_radar_location():
71+
"""Test search around a point optionally returns the radar location."""
72+
point = (-98.413333, 45.455833)
73+
radars = available_radars_around_point(
74+
point=point,
75+
distance=1,
76+
network="NEXRAD",
77+
return_radar_location=True,
78+
)
79+
assert ("NEXRAD", "KABR", (-98.413333, 45.455833)) in radars
80+
assert all(network == "NEXRAD" for network, _, _ in radars)
81+
82+
83+
def test_available_radars_around_point_return_distance_and_radar_location():
84+
"""Test search around a point optionally returns distance and radar location."""
85+
point = (-98.413333, 45.455833)
86+
radars = available_radars_around_point(
87+
point=point,
88+
distance=1,
89+
network="NEXRAD",
90+
return_distance=True,
91+
return_radar_location=True,
92+
)
93+
assert ("NEXRAD", "KABR", 0.0, (-98.413333, 45.455833)) in radars
94+
assert all(network == "NEXRAD" for network, _, _, _ in radars)
95+
96+
97+
def test_available_radars_around_point_no_match():
98+
"""Test search around a point far from any NEXRAD radar."""
99+
radars = available_radars_around_point(point=(0, 0), distance=1000, network="NEXRAD")
100+
assert radars == []
101+
102+
103+
def test_available_radars_within_extent():
104+
"""Test search within a small extent around KABR."""
105+
extent = (-98.42, -98.40, 45.45, 45.46)
106+
radars = available_radars_within_extent(extent=extent, network="NEXRAD")
107+
assert ("NEXRAD", "KABR") in radars
108+
assert all(network == "NEXRAD" for network, _ in radars)
109+
110+
111+
def test_available_radars_around_point_negative_distance():
112+
"""Test negative distance is rejected."""
113+
with pytest.raises(ValueError, match="greater than or equal to 0"):
114+
available_radars_around_point(point=(0, 0), distance=-1)
115+
116+
117+
def test_available_radars_within_extent_invalid_extent():
118+
"""Test extent with invalid latitude ordering is rejected."""
119+
with pytest.raises(ValueError, match="lat_min <= lat_max"):
120+
available_radars_within_extent(extent=(-10, 10, 5, -5))

0 commit comments

Comments
 (0)