Skip to content

Commit aba06c7

Browse files
authored
Merge pull request #719 from jlmaurer/fix_hrrr
Use known CRS for HRRR-AK instead of reading CRS from file
2 parents e9b9a47 + fb52a65 commit aba06c7

File tree

11 files changed

+167
-33
lines changed

11 files changed

+167
-33
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ and uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
1515
### Fixed
1616
* [721](https://github.com/dbekaert/RAiDER/pull/721) - Fixed bug mixing station_delay_mean and station_delay_median
1717
* [715](https://github.com/dbekaert/RAiDER/pull/715) - Fixed the coverage test Github action and a timing issue with raiderCombine
18+
* [719](https://github.com/dbekaert/RAiDER/pull/719) - Fixed the automatic switch to HRRR-AK based on bounding box when generic HRRR is specified as the weather model
1819
* [720](https://github.com/dbekaert/RAiDER/pull/720) - Bug-fix to properly pass expected `user_title` argument through raiderStats function.
1920

2021
## [0.5.4]
Binary file not shown.
Binary file not shown.

test/test_GUNW.py

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@
2222
from RAiDER.aria.prepFromGUNW import (
2323
check_hrrr_dataset_availablity_for_s1_azimuth_time_interpolation,
2424
check_weather_model_availability,_get_acq_time_from_gunw_id,
25-
get_slc_ids_from_gunw,get_acq_time_from_slc_id
25+
get_slc_ids_from_gunw,get_acq_time_from_slc_id,identify_which_hrrr
2626
)
2727
from RAiDER.cli.raider import calcDelaysGUNW
28+
from RAiDER.models.hrrr import HRRR, HRRRAK
2829
from RAiDER.models.customExceptions import (
2930
NoWeatherModelData, WrongNumberOfFiles,
3031
)
@@ -632,3 +633,72 @@ def test_get_acq_time_invalid_slc_id():
632633
invalid_slc_id = "test/gunw_azimuth_test_data/S1B_OPER_AUX_POEORB_OPOD_20210731T111940_V20210710T225942_20210712T005942.EOF"
633634
with pytest.raises(ValueError):
634635
get_acq_time_from_slc_id(invalid_slc_id)
636+
637+
638+
def test_identify_which_hrrr_1():
639+
"""Tests if function identifies the correct HRRR file"""
640+
gunw_id = Path("test/gunw_azimuth_test_data/S1-GUNW-A-R-064-tops-20210723_20210711-015000-00119W_00033N-PP-6267-v2_0_6.nc")
641+
result = identify_which_hrrr(gunw_id)
642+
assert result == "HRRR"
643+
644+
645+
def test_identify_which_hrrr_2():
646+
"""Tests if function identifies the correct HRRR file"""
647+
gunw_id = Path("test/gunw_test_data/S1-GUNW-D-R-059-tops-20230320_20220418-180300-00179W_00051N-PP-c92e-v2_0_6.nc")
648+
result = identify_which_hrrr(gunw_id)
649+
assert result == "HRRRAK"
650+
651+
652+
def test_cast_to_hrrrak_1():
653+
"""Tests if function casts the HRRR file to HRRRAK"""
654+
ak_bounds = [51.0, 71.0, -175., -130.0]
655+
conus_bounds = [34.0,35.0, -91, -90.0]
656+
model = HRRR()
657+
model.checkValidBounds(conus_bounds)
658+
model.checkValidBounds(ak_bounds)
659+
assert model._Name == "HRRR-AK"
660+
661+
662+
def test_cast_to_hrrrak_2():
663+
"""Tests if function casts the HRRR file to HRRRAK"""
664+
ak_bounds = [51.0, 71.0, -175., -130.0]
665+
model = HRRRAK()
666+
model.checkValidBounds(ak_bounds)
667+
assert model._Name == "HRRR-AK"
668+
669+
670+
def test_cast_to_hrrrak_2b():
671+
"""Tests if function casts the HRRR file to HRRRAK"""
672+
ak_bounds = [60.0, 65.0, -150., -120.0]
673+
model = HRRRAK()
674+
model.checkValidBounds(ak_bounds)
675+
assert model._Name == "HRRR-AK"
676+
677+
678+
def test_cast_to_hrrrak_3():
679+
"""Tests if function casts the HRRR file to HRRRAK"""
680+
conus_bounds = [34.0,35.0, -91, -90.0]
681+
model = HRRR()
682+
model.checkValidBounds(conus_bounds)
683+
assert model._Name == "HRRR"
684+
685+
686+
def test_cast_to_hrrrak_4():
687+
"""Tests if function casts the HRRR file to HRRRAK"""
688+
europe_bounds = [-1, 1, -1, 1]
689+
model = HRRR()
690+
with pytest.raises(ValueError):
691+
model.checkValidBounds(europe_bounds)
692+
693+
694+
def test_identify_which_hrrr_invalid():
695+
"""Tests if function raises error for an invalid gunw_id format"""
696+
invalid_gunw_id = "dummy.nc"
697+
with pytest.raises(NoWeatherModelData):
698+
identify_which_hrrr(invalid_gunw_id)
699+
700+
701+
def test_check_hrrr_dataset_availablity_for_s1_azimuth_time_interpolation_again():
702+
"""Tests if function raises error for an invalid gunw_id format"""
703+
gunw_id = "S1-GUNW-D-R-044-tops-20240418_20240406-171649-00163W_00069N-PP-af6b-v3_0_1.nc"
704+
assert check_hrrr_dataset_availablity_for_s1_azimuth_time_interpolation(gunw_id, 'hrrrak') is True

test/test_weather_model.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323
from RAiDER.models.gmao import GMAO
2424
from RAiDER.models.merra2 import MERRA2
2525
from RAiDER.models.ncmr import NCMR
26-
from RAiDER.models.customExceptions import DatetimeOutsideRange
26+
from RAiDER.models.customExceptions import (
27+
DatetimeOutsideRange, NoWeatherModelData,
28+
)
2729

2830

2931
_LON0 = 0
@@ -435,7 +437,7 @@ def test_get_bounds_indices_3() -> None:
435437
l = np.arange(-20, 20)
436438
l2 = (((np.arange(160, 200) + 180) % 360) - 180)
437439
lats, lons = np.meshgrid(l, l2)
438-
with pytest.raises(RuntimeError):
440+
with pytest.raises(NoWeatherModelData):
439441
get_bounds_indices(snwe, lats, lons)
440442

441443

tools/RAiDER/aria/prepFromGUNW.py

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
from RAiDER.logger import logger
2222
from RAiDER.models import credentials
23+
from RAiDER.models.customExceptions import NoWeatherModelData
2324
from RAiDER.models.hrrr import AK_GEO, HRRR_CONUS_COVERAGE_POLYGON, check_hrrr_dataset_availability
2425
from RAiDER.s1_azimuth_timing import get_times_for_azimuth_interpolation
2526
from RAiDER.s1_orbits import get_orbits_from_slc_ids
@@ -50,7 +51,7 @@ def _get_acq_time_from_gunw_id(gunw_id: str, reference_or_secondary: str) -> dt.
5051
return cen_acq_time
5152

5253

53-
def check_hrrr_dataset_availablity_for_s1_azimuth_time_interpolation(gunw_id: str) -> bool:
54+
def check_hrrr_dataset_availablity_for_s1_azimuth_time_interpolation(gunw_id: str, weather_model_name: str='hrrr') -> bool:
5455
"""
5556
Determine if all the times for azimuth interpolation are available using
5657
Herbie. Note that not all 1 hour times are available within the said date
@@ -71,11 +72,11 @@ def check_hrrr_dataset_availablity_for_s1_azimuth_time_interpolation(gunw_id: st
7172
ref_acq_time = _get_acq_time_from_gunw_id(gunw_id, 'reference')
7273
sec_acq_time = _get_acq_time_from_gunw_id(gunw_id, 'secondary')
7374

74-
model_step_hours = 1
75+
model_step_hours = [1 if weather_model_name == 'hrrr' else 3][0]
7576
ref_times_for_interp = get_times_for_azimuth_interpolation(ref_acq_time, model_step_hours)
7677
sec_times_for_interp = get_times_for_azimuth_interpolation(sec_acq_time, model_step_hours)
77-
ref_dataset_availability = list(map(check_hrrr_dataset_availability, ref_times_for_interp))
78-
sec_dataset_availability = list(map(check_hrrr_dataset_availability, sec_times_for_interp))
78+
ref_dataset_availability = list(map(check_hrrr_dataset_availability, ref_times_for_interp, [weather_model_name]*len(ref_times_for_interp)))
79+
sec_dataset_availability = list(map(check_hrrr_dataset_availability, sec_times_for_interp, [weather_model_name]*len(sec_times_for_interp)))
7980

8081
return all(ref_dataset_availability) and all(sec_dataset_availability)
8182

@@ -125,14 +126,9 @@ def check_weather_model_availability(gunw_path: Path, weather_model_name: str) -
125126
sec_ts = get_acq_time_from_slc_id(sec_slc_ids[0]).replace(tzinfo=dt.timezone(offset=dt.timedelta()))
126127

127128
if weather_model_name == 'HRRR':
128-
group = '/science/grids/data/'
129-
with xr.open_dataset(gunw_path, group=f'{group}') as ds:
130-
gunw_poly = box(*ds.rio.bounds())
131-
if HRRR_CONUS_COVERAGE_POLYGON.intersects(gunw_poly):
132-
pass
133-
elif AK_GEO.intersects(gunw_poly):
134-
weather_model_name = 'HRRRAK'
135-
else:
129+
try:
130+
weather_model_name = identify_which_hrrr(gunw_path)
131+
except NoWeatherModelData:
136132
return False
137133

138134
# source: https://stackoverflow.com/a/7668273
@@ -387,3 +383,26 @@ def main(args: CalcDelaysArgs) -> tuple[Path, float]:
387383
path_cfg = Path(f'GUNW_{GUNWObj.name}.yaml')
388384
write_yaml(raider_cfg, path_cfg)
389385
return path_cfg, GUNWObj.wavelength
386+
387+
388+
def identify_which_hrrr(gunw_path: Path) -> str:
389+
group = '/science/grids/data/'
390+
try:
391+
with xr.open_dataset(gunw_path, group=f'{group}') as ds:
392+
gunw_poly = box(*ds.rio.bounds())
393+
if HRRR_CONUS_COVERAGE_POLYGON.intersects(gunw_poly):
394+
weather_model_name = 'HRRR'
395+
elif AK_GEO.intersects(gunw_poly):
396+
weather_model_name = 'HRRRAK'
397+
else:
398+
raise NoWeatherModelData(
399+
f'GUNW {gunw_path} does not intersect with any HRRR coverage area. '
400+
'Please use a different weather model.'
401+
)
402+
except FileNotFoundError:
403+
raise NoWeatherModelData(
404+
f'''GUNW {gunw_path} does not exist or is not a valid HRRR file.
405+
Please check the file path.
406+
'''
407+
)
408+
return weather_model_name

tools/RAiDER/cli/raider.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
RuntimeGroup,
3131
TimeGroup,
3232
)
33+
from RAiDER.aria.prepFromGUNW import identify_which_hrrr
3334
from RAiDER.cli.validators import DateListAction, date_type
3435
from RAiDER.gnss.types import RAiDERCombineArgs
3536
from RAiDER.logger import logger, logging
@@ -334,7 +335,7 @@ def calcDelays(iargs: Optional[Sequence[str]]=None) -> list[Path]:
334335

335336
if len(wfiles) == 0:
336337
logger.error('No weather model data was successfully processed.')
337-
raise NoWeatherModelData()
338+
raise NoWeatherModelData('Weather model processing failed for all times')
338339

339340
# Get the weather model file
340341
weather_model_file = getWeatherFile(wfiles, times, t, model._Name, interp_method)
@@ -604,7 +605,8 @@ def calcDelaysGUNW(iargs: Optional[list[str]] = None) -> Optional[xr.Dataset]:
604605
args.interpolate_time == 'azimuth_time_grid'
605606
):
606607
gunw_id = args.file.name.replace('.nc', '')
607-
if not RAiDER.aria.prepFromGUNW.check_hrrr_dataset_availablity_for_s1_azimuth_time_interpolation(gunw_id):
608+
weather_model_name = identify_which_hrrr(args.file)
609+
if not RAiDER.aria.prepFromGUNW.check_hrrr_dataset_availablity_for_s1_azimuth_time_interpolation(gunw_id, weather_model_name):
608610
raise NoWeatherModelData('The required HRRR data for time-grid interpolation is not available')
609611

610612
if args.file is None:

tools/RAiDER/models/hrrr.py

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
from herbie import Herbie
99
from pyproj import CRS, Transformer
1010
from shapely.geometry import Polygon, box
11+
from typing import Optional, Union, List, Tuple
1112

1213
from RAiDER.logger import logger
14+
from RAiDER.models.customExceptions import NoWeatherModelData
1315
from RAiDER.models.model_levels import LEVELS_50_HEIGHTS
1416
from RAiDER.models.weatherModel import TIME_RES, WeatherModel
1517
from RAiDER.utilFcns import round_date
@@ -25,11 +27,11 @@
2527
AK_GEO = gpd.read_file(Path(__file__).parent / 'data' / 'alaska.geojson.zip').geometry.unary_union
2628

2729

28-
def check_hrrr_dataset_availability(datetime: dt.datetime) -> bool:
30+
def check_hrrr_dataset_availability(datetime: dt.datetime, model='hrrr') -> bool:
2931
"""Note a file could still be missing within the models valid range."""
3032
herbie = Herbie(
3133
datetime,
32-
model='hrrr',
34+
model=model,
3335
product='nat',
3436
fxx=0,
3537
)
@@ -87,11 +89,17 @@ def download_hrrr_file(ll_bounds, DATE, out, model='hrrr', product='nat', fxx=0,
8789
raise RuntimeError('Herbie did not obtain an HRRR dataset with the expected layers and coordinates')
8890

8991
# subset the full file by AOI
90-
x_min, x_max, y_min, y_max = get_bounds_indices(
91-
ll_bounds,
92-
ds_out.latitude.to_numpy(),
93-
ds_out.longitude.to_numpy(),
94-
)
92+
try:
93+
x_min, x_max, y_min, y_max = get_bounds_indices(
94+
ll_bounds,
95+
ds_out.latitude.to_numpy(),
96+
ds_out.longitude.to_numpy(),
97+
)
98+
except NoWeatherModelData as e:
99+
logger.error(e)
100+
logger.error('lat/lon bounds: %s', ll_bounds)
101+
logger.error('Weather model is {}'.format(model))
102+
raise
95103

96104
# bookkeepping
97105
ds_out = ds_out.rename({'gh': 'z', coord: 'levels'})
@@ -104,8 +112,15 @@ def download_hrrr_file(ll_bounds, DATE, out, model='hrrr', product='nat', fxx=0,
104112
ds_out[var].attrs['grid_mapping'] = 'proj'
105113

106114
# pull the grid information
107-
proj = CRS.from_cf(ds_out['proj'].attrs)
115+
if model=='hrrr':
116+
proj = CRS.from_cf(ds_out['proj'].attrs)
117+
elif model=='hrrrak':
118+
proj = HRRR_AK_PROJ
119+
else:
120+
raise ValueError('Model not recognized. Please use either hrrr or hrrrak')
121+
108122
t = Transformer.from_crs(4326, proj, always_xy=True)
123+
109124
xl, yl = t.transform(ds_out['longitude'].values, ds_out['latitude'].values)
110125
W, E, S, N = np.nanmin(xl), np.nanmax(xl), np.nanmin(yl), np.nanmax(yl)
111126

@@ -139,7 +154,7 @@ def get_bounds_indices(SNWE, lats, lons):
139154
W, E = np.mod([W, E], 360)
140155
m1 = (S <= lats) & (N >= lats) & (W <= lons) & (E >= lons)
141156
if np.sum(m1) == 0:
142-
raise RuntimeError('Area of Interest has no overlap with the HRRR model available extent')
157+
raise NoWeatherModelData('Area of Interest has no overlap with the HRRR model available extent')
143158

144159
# Y extent
145160
shp = lats.shape
@@ -266,6 +281,23 @@ def _fetch(self, out) -> None:
266281

267282
download_hrrr_file(bounds, corrected_DT, out, 'hrrr', self._model_level_type)
268283

284+
def _cast_to_hrrrak(self) -> None:
285+
"""
286+
Update the weather model to HRRR-AK if the bounding box is in Alaska.
287+
"""
288+
self.__class__ = HRRRAK
289+
self._dataset = 'hrrrak'
290+
self._valid_bounds = HRRR_AK_COVERAGE_POLYGON
291+
self._proj = HRRR_AK_PROJ
292+
self._Name = 'HRRR-AK'
293+
self._time_res = TIME_RES['HRRR-AK']
294+
self._valid_range = (
295+
dt.datetime(2018, 7, 13).replace(tzinfo=dt.timezone(offset=dt.timedelta())),
296+
dt.datetime.now(dt.timezone.utc),
297+
)
298+
self.setLevelType('nat')
299+
300+
269301
def load_weather(self, f=None, *args, **kwargs) -> None:
270302
"""
271303
Load a weather model into a python weatherModel object, from self.files if no
@@ -294,7 +326,7 @@ def checkValidBounds(self, ll_bounds: np.ndarray) -> None:
294326
(i.e., intersects with the model domain at all).
295327
296328
Args:
297-
ll_bounds : np.ndarray
329+
ll_bounds : np.ndarray SNWE bounding box
298330
"""
299331
S, N, W, E = ll_bounds
300332
aoi = box(W, S, E, N)
@@ -306,13 +338,18 @@ def checkValidBounds(self, ll_bounds: np.ndarray) -> None:
306338
logger.critical('The HRRR weather model extent does not completely cover your AOI!')
307339

308340
else:
341+
logger.info('The HRRR weather model extent does not include your AOI!')
342+
logger.info('Checking the HRRR-AK model.')
309343
Mod = HRRRAK()
310344
# valid bounds are in 0->360 to account for dateline crossing
311345
W, E = np.mod([W, E], 360)
312346
aoi = box(W, S, E, N)
313347
if Mod._valid_bounds.contains(aoi):
314-
pass
348+
self._cast_to_hrrrak()
349+
logger.info('Casting self to the HRRR-AK weather model.')
315350
elif aoi.intersects(Mod._valid_bounds):
351+
self._cast_to_hrrrak()
352+
logger.info('Casting self to the HRRR-AK weather model.')
316353
logger.critical('The HRRR-AK weather model extent does not completely cover your AOI!')
317354

318355
else:
@@ -391,3 +428,4 @@ def load_weather(self, f=None, *args, **kwargs) -> None:
391428
self._lats = _lats
392429
self._lons = _lons
393430
self._proj = proj
431+

tools/RAiDER/models/weatherModel.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,9 @@ def checkValidBounds(
465465
"""
466466
S, N, W, E = ll_bounds
467467
if not box(W, S, E, N).intersects(self._valid_bounds):
468-
raise ValueError(f'The requested location is unavailable for {self._Name}')
468+
#check the longitude wrapping
469+
if not box(360+W, S, 360+E, N).intersects(self._valid_bounds):
470+
raise ValueError(f'The requested location is unavailable for {self._Name}')
469471

470472
def checkContainment(self, ll_bounds: Union[List, Tuple,np.ndarray], buffer_deg: float = 1e-5) -> bool:
471473
"""

tools/RAiDER/processWM.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def prepareWeatherModel(
8989
logger.warning('The processed weather model file already exists, so I will use that.')
9090

9191
containment = weather_model.checkContainment(ll_bounds)
92-
if not containment and weather_model.Model() not in 'HRRR'.split():
92+
if not containment and weather_model.Model() not in 'HRRR HRRRAK'.split():
9393
raise ExistingWeatherModelTooSmall
9494

9595
return f

0 commit comments

Comments
 (0)