Skip to content

Commit 50e057a

Browse files
committed
ENH: add new models and keep old ones as legacy
1 parent c612e43 commit 50e057a

1 file changed

Lines changed: 163 additions & 6 deletions

File tree

rocketpy/environment/weather_model_mapping.py

Lines changed: 163 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,42 @@
11
class WeatherModelMapping:
2-
"""Class to map the weather model variables to the variables used in the
3-
Environment class.
2+
"""Map provider-specific variable names to RocketPy weather fields.
3+
4+
RocketPy reads forecast/reanalysis/ensemble datasets using canonical keys
5+
such as ``time``, ``latitude``, ``longitude``, ``level``, ``temperature``,
6+
``geopotential_height``, ``geopotential``, ``u_wind`` and ``v_wind``.
7+
Each dictionary in this class maps those canonical keys to the actual
8+
variable names in a specific data provider format.
9+
10+
Mapping families
11+
----------------
12+
- Base names (for example ``GFS``, ``NAM``, ``RAP``) represent the current
13+
default mappings used by the latest-model shortcuts and THREDDS-style
14+
datasets.
15+
- ``*_LEGACY`` names represent older NOMADS-style variable naming
16+
conventions (for example ``lev``, ``tmpprs``, ``ugrdprs`` and
17+
``vgrdprs``) and are intended for archived or previously downloaded files.
18+
19+
Notes
20+
-----
21+
- Mappings can also include optional keys such as ``projection`` for
22+
projected grids and ``ensemble`` for member dimensions.
23+
- The :meth:`get` method is case-insensitive, so ``"gfs_legacy"`` and
24+
``"GFS_LEGACY"`` are equivalent.
425
"""
526

627
GFS = {
28+
"time": "time",
29+
"latitude": "lat",
30+
"longitude": "lon",
31+
"level": "isobaric",
32+
"temperature": "Temperature_isobaric",
33+
"surface_geopotential_height": "Geopotential_height_surface",
34+
"geopotential_height": "Geopotential_height_isobaric",
35+
"geopotential": None,
36+
"u_wind": "u-component_of_wind_isobaric",
37+
"v_wind": "v-component_of_wind_isobaric",
38+
}
39+
GFS_LEGACY = {
740
"time": "time",
841
"latitude": "lat",
942
"longitude": "lon",
@@ -16,6 +49,19 @@ class WeatherModelMapping:
1649
"v_wind": "vgrdprs",
1750
}
1851
NAM = {
52+
"time": "time",
53+
"latitude": "y",
54+
"longitude": "x",
55+
"projection": "LambertConformal_Projection",
56+
"level": "isobaric",
57+
"temperature": "Temperature_isobaric",
58+
"surface_geopotential_height": None,
59+
"geopotential_height": "Geopotential_height_isobaric",
60+
"geopotential": None,
61+
"u_wind": "u-component_of_wind_isobaric",
62+
"v_wind": "v-component_of_wind_isobaric",
63+
}
64+
NAM_LEGACY = {
1965
"time": "time",
2066
"latitude": "lat",
2167
"longitude": "lon",
@@ -54,6 +100,18 @@ class WeatherModelMapping:
54100
"v_wind": "v",
55101
}
56102
NOAA = {
103+
"time": "time",
104+
"latitude": "lat",
105+
"longitude": "lon",
106+
"level": "isobaric",
107+
"temperature": "Temperature_isobaric",
108+
"surface_geopotential_height": "Geopotential_height_surface",
109+
"geopotential_height": "Geopotential_height_isobaric",
110+
"geopotential": None,
111+
"u_wind": "u-component_of_wind_isobaric",
112+
"v_wind": "v-component_of_wind_isobaric",
113+
}
114+
NOAA_LEGACY = {
57115
"time": "time",
58116
"latitude": "lat",
59117
"longitude": "lon",
@@ -66,6 +124,19 @@ class WeatherModelMapping:
66124
"v_wind": "vgrdprs",
67125
}
68126
RAP = {
127+
"time": "time",
128+
"latitude": "y",
129+
"longitude": "x",
130+
"projection": "LambertConformal_Projection",
131+
"level": "isobaric",
132+
"temperature": "Temperature_isobaric",
133+
"surface_geopotential_height": None,
134+
"geopotential_height": "Geopotential_height_isobaric",
135+
"geopotential": None,
136+
"u_wind": "u-component_of_wind_isobaric",
137+
"v_wind": "v-component_of_wind_isobaric",
138+
}
139+
RAP_LEGACY = {
69140
"time": "time",
70141
"latitude": "lat",
71142
"longitude": "lon",
@@ -90,6 +161,19 @@ class WeatherModelMapping:
90161
"u_wind": "ugrdprs",
91162
"v_wind": "vgrdprs",
92163
}
164+
CMC_LEGACY = {
165+
"time": "time",
166+
"latitude": "lat",
167+
"longitude": "lon",
168+
"level": "lev",
169+
"ensemble": "ens",
170+
"temperature": "tmpprs",
171+
"surface_geopotential_height": None,
172+
"geopotential_height": "hgtprs",
173+
"geopotential": None,
174+
"u_wind": "ugrdprs",
175+
"v_wind": "vgrdprs",
176+
}
93177
GEFS = {
94178
"time": "time",
95179
"latitude": "lat",
@@ -103,6 +187,19 @@ class WeatherModelMapping:
103187
"u_wind": "ugrdprs",
104188
"v_wind": "vgrdprs",
105189
}
190+
GEFS_LEGACY = {
191+
"time": "time",
192+
"latitude": "lat",
193+
"longitude": "lon",
194+
"level": "lev",
195+
"ensemble": "ens",
196+
"temperature": "tmpprs",
197+
"surface_geopotential_height": None,
198+
"geopotential_height": "hgtprs",
199+
"geopotential": None,
200+
"u_wind": "ugrdprs",
201+
"v_wind": "vgrdprs",
202+
}
106203
HIRESW = {
107204
"time": "time",
108205
"latitude": "lat",
@@ -114,6 +211,17 @@ class WeatherModelMapping:
114211
"u_wind": "ugrdprs",
115212
"v_wind": "vgrdprs",
116213
}
214+
HIRESW_LEGACY = {
215+
"time": "time",
216+
"latitude": "lat",
217+
"longitude": "lon",
218+
"level": "lev",
219+
"temperature": "tmpprs",
220+
"surface_geopotential_height": "hgtsfc",
221+
"geopotential_height": "hgtprs",
222+
"u_wind": "ugrdprs",
223+
"v_wind": "vgrdprs",
224+
}
117225
MERRA2 = {
118226
"time": "time",
119227
"latitude": "lat",
@@ -127,29 +235,78 @@ class WeatherModelMapping:
127235
"u_wind": "U",
128236
"v_wind": "V",
129237
}
238+
MERRA2_LEGACY = {
239+
"time": "time",
240+
"latitude": "lat",
241+
"longitude": "lon",
242+
"level": "lev",
243+
"temperature": "T",
244+
"surface_geopotential_height": None,
245+
"surface_geopotential": "PHIS",
246+
"geopotential_height": "H",
247+
"geopotential": None,
248+
"u_wind": "U",
249+
"v_wind": "V",
250+
}
130251

131252
def __init__(self):
132-
"""Initialize the class, creates a dictionary with all the weather models
133-
available and their respective dictionaries with the variables."""
253+
"""Build the lookup table with default and legacy mapping aliases."""
134254

135255
self.all_dictionaries = {
136256
"GFS": self.GFS,
257+
"GFS_LEGACY": self.GFS_LEGACY,
137258
"NAM": self.NAM,
259+
"NAM_LEGACY": self.NAM_LEGACY,
138260
"ECMWF_v0": self.ECMWF_v0,
139261
"ECMWF": self.ECMWF,
140262
"NOAA": self.NOAA,
263+
"NOAA_LEGACY": self.NOAA_LEGACY,
141264
"RAP": self.RAP,
265+
"RAP_LEGACY": self.RAP_LEGACY,
142266
"CMC": self.CMC,
267+
"CMC_LEGACY": self.CMC_LEGACY,
143268
"GEFS": self.GEFS,
269+
"GEFS_LEGACY": self.GEFS_LEGACY,
144270
"HIRESW": self.HIRESW,
271+
"HIRESW_LEGACY": self.HIRESW_LEGACY,
145272
"MERRA2": self.MERRA2,
273+
"MERRA2_LEGACY": self.MERRA2_LEGACY,
146274
}
147275

148276
def get(self, model):
277+
"""Return a mapping dictionary by model alias (case-insensitive).
278+
279+
Parameters
280+
----------
281+
model : str
282+
Mapping alias name, such as ``"GFS"`` or ``"GFS_LEGACY"``.
283+
284+
Returns
285+
-------
286+
dict
287+
Dictionary mapping RocketPy canonical weather keys to dataset
288+
variable names.
289+
290+
Raises
291+
------
292+
KeyError
293+
If ``model`` is unknown or not a string.
294+
"""
295+
if not isinstance(model, str):
296+
raise KeyError(
297+
f"Model {model} not found in the WeatherModelMapping. "
298+
f"The available models are: {self.all_dictionaries.keys()}"
299+
)
300+
149301
try:
150302
return self.all_dictionaries[model]
151-
except KeyError as e:
303+
except KeyError as exc:
304+
model_casefold = model.casefold()
305+
for key, value in self.all_dictionaries.items():
306+
if key.casefold() == model_casefold:
307+
return value
308+
152309
raise KeyError(
153310
f"Model {model} not found in the WeatherModelMapping. "
154311
f"The available models are: {self.all_dictionaries.keys()}"
155-
) from e
312+
) from exc

0 commit comments

Comments
 (0)