Skip to content

Commit 34ac6bf

Browse files
committed
feat: add temporal_resolution parameter to OpenMeteoSource
Allows fetching 3-hourly ("hourly_3"), 6-hourly ("hourly_6"), or model-native resolution ("native") data in addition to the default 1 h. When temporal_resolution is not None the parameter is forwarded to the Open-Meteo API. For "native" the points-per-day value is inferred from the delta between the first two returned timestamps instead of being hardcoded to 24, so forecast index offsets stay correct regardless of model step size. The value is included in the cache config key so different resolutions cache independently.
1 parent 376d998 commit 34ac6bf

3 files changed

Lines changed: 65 additions & 2 deletions

File tree

docs/src/content/docs/data-sources/openmeteo.mdx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,35 @@ df = source.fetch(
4242
| `model` | str | No | `"jma_seamless"` | Weather model |
4343
| `columns` | List[str] | No | DEFAULT_COLUMNS | Weather variables |
4444
| `prefix` | str | No | `""` | Prefix for column names |
45+
| `temporal_resolution` | str \| None | No | `None` | Time step of returned data — see below |
46+
47+
### Temporal Resolution
48+
49+
| Value | Time step | Notes |
50+
|-------|-----------|-------|
51+
| `None` *(default)* | 1 h | Standard hourly data; no extra API parameter sent |
52+
| `"hourly_3"` | 3 h | 8 data points per day |
53+
| `"hourly_6"` | 6 h | 4 data points per day |
54+
| `"native"` | model-dependent | Uses the weather model's native resolution; step is inferred automatically from the response |
55+
56+
```python
57+
# 3-hourly data — useful for coarser feature sets or faster pipelines
58+
source = OpenMeteoSource(
59+
latitude=52.2297,
60+
longitude=21.0122,
61+
horizon=7,
62+
prefix="warsaw",
63+
temporal_resolution="hourly_3",
64+
)
65+
66+
# Model-native resolution
67+
source = OpenMeteoSource(
68+
latitude=52.2297,
69+
longitude=21.0122,
70+
model="icon_seamless",
71+
temporal_resolution="native",
72+
)
73+
```
4574

4675
## Rate Limiting
4776

epftoolbox2/data/sources/open_meteo.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ class OpenMeteoSource(DataSource):
5656
"wind_direction_10m",
5757
]
5858

59+
VALID_TEMPORAL_RESOLUTIONS = {None, "native", "hourly_3", "hourly_6"}
60+
_POINTS_PER_DAY = {None: 24, "hourly_3": 8, "hourly_6": 4}
61+
5962
def __init__(
6063
self,
6164
latitude: float,
@@ -65,6 +68,7 @@ def __init__(
6568
columns: List[str] = None,
6669
prefix: str = "",
6770
proxy_url: str = None,
71+
temporal_resolution: str = None,
6872
):
6973
"""
7074
Initialize Open-Meteo data source
@@ -77,6 +81,10 @@ def __init__(
7781
columns: List of weather variables to fetch (default: DEFAULT_COLUMNS)
7882
prefix: Prefix to add to column names (default: "")
7983
proxy_url: Proxy URL (e.g., 'http://proxy.example.com:8080')
84+
temporal_resolution: Temporal resolution of the data. One of:
85+
None (default) — hourly (1 h), no parameter sent to API;
86+
"hourly_3" — 3-hourly; "hourly_6" — 6-hourly;
87+
"native" — model-native resolution (inferred from response).
8088
"""
8189
self.latitude = latitude
8290
self.longitude = longitude
@@ -85,6 +93,7 @@ def __init__(
8593
self.columns = columns if columns else self.DEFAULT_COLUMNS
8694
self.prefix = prefix
8795
self.proxy_url = proxy_url
96+
self.temporal_resolution = temporal_resolution
8897

8998
self.session = requests.Session()
9099
self._configure_proxy()
@@ -113,6 +122,12 @@ def _validate_config(self) -> bool:
113122
if not self.columns:
114123
raise ValueError("At least one weather column must be specified")
115124

125+
if self.temporal_resolution not in self.VALID_TEMPORAL_RESOLUTIONS:
126+
raise ValueError(
127+
f"temporal_resolution must be one of {self.VALID_TEMPORAL_RESOLUTIONS}, "
128+
f"got {self.temporal_resolution!r}"
129+
)
130+
116131
return True
117132

118133
def _configure_proxy(self):
@@ -208,6 +223,8 @@ def _fetch_chunk(self, start: pd.Timestamp, end: pd.Timestamp) -> pd.DataFrame:
208223
"start_date": start.strftime("%Y-%m-%d"),
209224
"end_date": end.strftime("%Y-%m-%d"),
210225
}
226+
if self.temporal_resolution is not None:
227+
params["temporal_resolution"] = self.temporal_resolution
211228

212229
max_retries = 5
213230
retry_count = 0
@@ -252,13 +269,22 @@ def _fetch_chunk(self, start: pd.Timestamp, end: pd.Timestamp) -> pd.DataFrame:
252269

253270
raise RuntimeError(f"Failed to fetch data after {max_retries} retries")
254271

272+
def _points_per_day(self, times: list) -> int:
273+
if self.temporal_resolution == "native":
274+
if len(times) >= 2:
275+
delta_hours = (pd.Timestamp(times[1]) - pd.Timestamp(times[0])).total_seconds() / 3600
276+
return max(1, int(round(24 / delta_hours)))
277+
return 24
278+
return self._POINTS_PER_DAY.get(self.temporal_resolution, 24)
279+
255280
def _parse_weather_data(self, response_data: dict) -> pd.DataFrame:
256281
if "hourly" not in response_data or "time" not in response_data["hourly"]:
257282
return pd.DataFrame()
258283

259284
weather = {}
260285
hourly_data = response_data["hourly"]
261286
times = hourly_data["time"]
287+
points_per_day = self._points_per_day(times)
262288

263289
for x, timestamp_str in enumerate(times):
264290
try:
@@ -267,7 +293,7 @@ def _parse_weather_data(self, response_data: dict) -> pd.DataFrame:
267293
for column in self.columns:
268294
column_key = f"{column}_previous_day{i}"
269295
if column_key in hourly_data:
270-
forecast_idx = x + (i * 24)
296+
forecast_idx = x + (i * points_per_day)
271297
if forecast_idx < len(hourly_data[column_key]):
272298
value = hourly_data[column_key][forecast_idx]
273299
tmp[f"{column}_d+{i}"] = value
@@ -302,6 +328,7 @@ def get_cache_config(self) -> dict:
302328
"model": self.model,
303329
"columns": sorted(self.columns),
304330
"prefix": self.prefix,
331+
"temporal_resolution": self.temporal_resolution,
305332
}
306333

307334
return config

llms.txt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,15 +110,22 @@ source = OpenMeteoSource(
110110
model="jma_seamless", # Optional. Weather model (default: "jma_seamless")
111111
columns=None, # Optional. Weather variables (default: DEFAULT_COLUMNS)
112112
prefix="warsaw", # Optional. Prefix for column names (default: "")
113+
temporal_resolution=None, # Optional. None|"hourly_3"|"hourly_6"|"native" (default: None = 1 h)
113114
)
114115
```
115116

116117
**Default columns:** `temperature_2m`, `rain`, `showers`, `snowfall`, `relative_humidity_2m`, `dew_point_2m`, `apparent_temperature`, `precipitation`, `weather_code`, `surface_pressure`, `pressure_msl`, `cloud_cover`, `wind_speed_10m`, `wind_direction_10m`
117118

119+
**temporal_resolution values:**
120+
- `None` (default) — 1-hourly; no extra API parameter sent
121+
- `"hourly_3"` — 3-hourly (8 points/day)
122+
- `"hourly_6"` — 6-hourly (4 points/day)
123+
- `"native"` — model-native resolution; time step inferred from the API response
124+
118125
**Output column naming:** `{prefix}_{variable}_d+{horizon}`
119126
- Example: `warsaw_temperature_2m_d+1`, `warsaw_temperature_2m_d+2`, ..., `warsaw_temperature_2m_d+7`
120127

121-
**Caching:** Returns cache config based on location, horizon, model, columns.
128+
**Caching:** Returns cache config based on location, horizon, model, columns, temporal_resolution.
122129

123130
---
124131

0 commit comments

Comments
 (0)