meteostat 2.0.1__py3-none-any.whl → 2.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
meteostat/__init__.py CHANGED
@@ -12,7 +12,7 @@ The code is licensed under the MIT license.
12
12
  """
13
13
 
14
14
  __appname__ = "meteostat"
15
- __version__ = "2.0.1"
15
+ __version__ = "2.1.0"
16
16
 
17
17
  from meteostat.api.daily import daily
18
18
  from meteostat.api.hourly import hourly
meteostat/api/config.py CHANGED
@@ -154,5 +154,8 @@ class Config(ConfigService):
154
154
  )
155
155
  metno_user_agent: Optional[str] = None
156
156
 
157
+ # [Provider] GSA settings
158
+ gsa_api_base_url: str = "https://dataset.api.hub.geosphere.at/v1"
159
+
157
160
 
158
161
  config = Config("MS")
meteostat/enumerations.py CHANGED
@@ -102,6 +102,10 @@ class Provider(StrEnum):
102
102
  ECCC_DAILY = "eccc_daily"
103
103
  ECCC_MONTHLY = "eccc_monthly"
104
104
  METNO_FORECAST = "metno_forecast"
105
+ GSA_HOURLY = "gsa_hourly"
106
+ GSA_SYNOP = "gsa_synop"
107
+ GSA_DAILY = "gsa_daily"
108
+ GSA_MONTHLY = "gsa_monthly"
105
109
 
106
110
  HOURLY = "hourly"
107
111
  DAILY = "daily"
@@ -102,7 +102,7 @@ def get_df(parameter: str, mode: str, station_code: str) -> Optional[pd.DataFram
102
102
 
103
103
  buffer.seek(0)
104
104
  df = pd.read_csv(buffer, sep=";").rename(columns=lambda col: col.strip().lower())
105
- df.rename(columns=param_config["stubnames"], inplace=True)
105
+ df = df.rename(columns=param_config["stubnames"])
106
106
 
107
107
  # Convert wide to long format
108
108
  df = pd.wide_to_long(
@@ -23,7 +23,6 @@ from meteostat.providers.dwd.shared import get_ftp_connection
23
23
 
24
24
  BASE_DIR = "/climate_environment/CDC/observations_germany/climate/monthly/kl/"
25
25
  USECOLS = [1, 4, 5, 6, 7, 9, 10, 11, 12, 14] # CSV cols which should be read
26
- PARSE_DATES = {"time": [0]} # Which columns should be parsed as dates?
27
26
  NAMES = {
28
27
  "MO_N": Parameter.CLDC,
29
28
  "MO_TT": Parameter.TEMP,
@@ -83,15 +82,19 @@ def get_df(station: str, mode: str) -> Optional[pd.DataFrame]:
83
82
  df: pd.DataFrame = pd.read_csv( # type: ignore
84
83
  raw,
85
84
  sep=r"\s*;\s*",
86
- date_format="%Y%m%d",
87
85
  na_values=["-999", -999],
88
86
  usecols=USECOLS,
89
- parse_dates=PARSE_DATES,
90
87
  engine="python",
91
88
  )
92
89
 
93
90
  # Rename columns
94
91
  df = df.rename(columns=lambda x: x.strip())
92
+
93
+ # Parse date column
94
+ df["MESS_DATUM_BEGINN"] = pd.to_datetime(
95
+ df["MESS_DATUM_BEGINN"].astype(str), format="%Y%m%d"
96
+ )
97
+ df = df.rename(columns={"MESS_DATUM_BEGINN": "time"})
95
98
  df = df.rename(columns=NAMES)
96
99
 
97
100
  # Convert data
@@ -0,0 +1,3 @@
1
+ """
2
+ GeoSphere Austria Data Hub Provider
3
+ """
@@ -0,0 +1,194 @@
1
+ """
2
+ GeoSphere Austria Data Hub daily data import routine
3
+
4
+ Get daily climate data for weather stations in Austria.
5
+
6
+ License: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
7
+ """
8
+
9
+ from datetime import datetime
10
+ from typing import Dict, Optional
11
+
12
+ import pandas as pd
13
+
14
+ from meteostat.enumerations import TTL, Parameter
15
+ from meteostat.core.logger import logger
16
+ from meteostat.typing import ProviderRequest
17
+ from meteostat.core.cache import cache_service
18
+ from meteostat.api.config import config
19
+ from meteostat.core.network import network_service
20
+ from meteostat.utils.conversions import (
21
+ hours_to_minutes,
22
+ ms_to_kmh,
23
+ percentage_to_okta,
24
+ pres_to_msl,
25
+ )
26
+
27
+
28
+ RESOURCE_ID = "klima-v2-1d"
29
+
30
+ # Mapping from GeoSphere Austria parameter names to Meteostat parameters
31
+ # See: https://dataset.api.hub.geosphere.at/v1/station/historical/klima-v2-1d/metadata
32
+ PARAMETER_MAPPING: Dict[str, Parameter] = {
33
+ "tl_mittel": Parameter.TEMP, # Mean air temperature (°C) - Lufttemperatur 2m Mittelwert
34
+ "tlmax": Parameter.TMAX, # Maximum air temperature (°C) - Lufttemperatur 2m Maximalwert
35
+ "tlmin": Parameter.TMIN, # Minimum air temperature (°C) - Lufttemperatur 2m Minimalwert
36
+ "rr": Parameter.PRCP, # Precipitation 24h sum (mm) - Niederschlag 24h Summe
37
+ "sh": Parameter.SNWD, # Snow depth (cm) - Schneehöhe
38
+ "vv_mittel": Parameter.WSPD, # Mean wind speed (km/h) - Windgeschwindigkeit Mittelwert
39
+ "ffx": Parameter.WPGT, # Maximum wind gust (m/s) - Windböe Maximum
40
+ "p_mittel": Parameter.PRES, # Mean air pressure (hPa) - Luftdruck Mittelwert
41
+ "rf_mittel": Parameter.RHUM, # Mean relative humidity (%) - Relative Feuchte Mittelwert
42
+ "so_h": Parameter.TSUN, # Sunshine duration (h) - Sonnenscheindauer
43
+ "bewm_mittel": Parameter.CLDC, # Mean cloud cover (%) - Bedeckung Mittelwert
44
+ }
45
+
46
+ # Inverse mapping
47
+ METEOSTAT_TO_GSA = {v: k for k, v in PARAMETER_MAPPING.items()}
48
+
49
+
50
+ @cache_service.cache(TTL.WEEK, "pickle")
51
+ def get_data(
52
+ station_id: str,
53
+ elevation: int | None,
54
+ parameters: list[str],
55
+ start: datetime,
56
+ end: datetime,
57
+ ) -> Optional[pd.DataFrame]:
58
+ """
59
+ Fetch data from GeoSphere Austria Data Hub API
60
+ """
61
+ logger.debug(
62
+ f"Fetching daily data for station '{station_id}' from {start} to {end}"
63
+ )
64
+
65
+ # Format dates as ISO 8601 (date only for daily data)
66
+ start_str = start.strftime("%Y-%m-%d")
67
+ end_str = end.strftime("%Y-%m-%d")
68
+
69
+ # Build URL
70
+ url = f"{config.gsa_api_base_url}/station/historical/{RESOURCE_ID}"
71
+
72
+ # Make request
73
+ response = network_service.get(
74
+ url,
75
+ params={
76
+ "parameters": ",".join(parameters),
77
+ "station_ids": station_id,
78
+ "start": start_str,
79
+ "end": end_str,
80
+ "output_format": "geojson",
81
+ },
82
+ )
83
+
84
+ if response.status_code != 200:
85
+ logger.warning(
86
+ f"Failed to fetch daily data for station {station_id} (status: {response.status_code})"
87
+ )
88
+ return None
89
+
90
+ try:
91
+ data = response.json()
92
+
93
+ if not data.get("features"):
94
+ logger.info(f"No daily data returned for station {station_id}")
95
+ return None
96
+
97
+ # Get timestamps array
98
+ timestamps = data.get("timestamps")
99
+ if not timestamps:
100
+ logger.warning("No timestamps in daily response")
101
+ return None
102
+
103
+ # Extract time series data from GeoJSON response
104
+ # New API format has timestamps at top level and data as arrays
105
+ feature = data["features"][0]
106
+ props = feature.get("properties", {})
107
+ params_data = props.get("parameters", {})
108
+
109
+ if not params_data:
110
+ logger.info(f"No parameter data returned for station {station_id}")
111
+ return None
112
+
113
+ # Build DataFrame from timestamps and parameter arrays
114
+ df_dict = {}
115
+ for param in parameters:
116
+ if param in params_data:
117
+ param_info = params_data[param]
118
+ if "data" in param_info:
119
+ df_dict[param] = param_info["data"]
120
+
121
+ if not df_dict:
122
+ return None
123
+
124
+ # Create DataFrame with timestamps as index
125
+ df = pd.DataFrame(df_dict)
126
+ dt_index = pd.DatetimeIndex(pd.to_datetime(timestamps))
127
+ df.index = dt_index.tz_localize(None)
128
+ df.index.name = "time"
129
+
130
+ # Sort by time
131
+ df = df.sort_index()
132
+
133
+ # Rename columns to Meteostat parameter names
134
+ rename_map = {}
135
+ for gsadh_param, meteostat_param in PARAMETER_MAPPING.items():
136
+ if gsadh_param in df.columns:
137
+ rename_map[gsadh_param] = meteostat_param
138
+
139
+ df = df.rename(columns=rename_map)
140
+
141
+ # Convert units where necessary
142
+ if Parameter.WSPD in df.columns:
143
+ df[Parameter.WSPD] = df[Parameter.WSPD].apply(ms_to_kmh)
144
+
145
+ if Parameter.WPGT in df.columns:
146
+ df[Parameter.WPGT] = df[Parameter.WPGT].apply(ms_to_kmh)
147
+
148
+ if Parameter.TSUN in df.columns:
149
+ df[Parameter.TSUN] = df[Parameter.TSUN].apply(hours_to_minutes)
150
+
151
+ if Parameter.CLDC in df.columns:
152
+ df[Parameter.CLDC] = df[Parameter.CLDC].apply(percentage_to_okta)
153
+
154
+ if Parameter.PRES in df.columns:
155
+ df[Parameter.PRES] = df.apply(
156
+ lambda row: pres_to_msl(row, elevation), axis=1
157
+ )
158
+
159
+ # Round values
160
+ df = df.round(1)
161
+
162
+ return df
163
+
164
+ except Exception as error:
165
+ logger.warning(f"Error parsing daily response: {error}", exc_info=True)
166
+ return None
167
+
168
+
169
+ def fetch(req: ProviderRequest) -> Optional[pd.DataFrame]:
170
+ """
171
+ Fetch daily data from GeoSphere Austria Data Hub
172
+ """
173
+ if "national" not in req.station.identifiers:
174
+ return None
175
+
176
+ station_id = req.station.identifiers["national"]
177
+
178
+ # Map Meteostat parameters to GeoSphere Austria parameters
179
+ gsa_params = []
180
+ for param in req.parameters:
181
+ if param in METEOSTAT_TO_GSA:
182
+ gsa_params.append(METEOSTAT_TO_GSA[param])
183
+
184
+ if not gsa_params:
185
+ logger.info("No mappable parameters for GeoSphere Austria daily data")
186
+ return None
187
+
188
+ # Fetch data
189
+ df = get_data(station_id, req.station.elevation, gsa_params, req.start, req.end)
190
+
191
+ if df is None or df.empty:
192
+ return None
193
+
194
+ return df
@@ -0,0 +1,175 @@
1
+ """
2
+ GeoSphere Austria Data Hub hourly data import routine
3
+
4
+ Get hourly climate data for weather stations in Austria.
5
+
6
+ License: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
7
+ """
8
+
9
+ from datetime import datetime
10
+ from typing import Dict, Optional
11
+
12
+ import pandas as pd
13
+
14
+ from meteostat.enumerations import TTL, Parameter
15
+ from meteostat.core.logger import logger
16
+ from meteostat.typing import ProviderRequest
17
+ from meteostat.core.cache import cache_service
18
+ from meteostat.api.config import config
19
+ from meteostat.core.network import network_service
20
+ from meteostat.utils.conversions import hours_to_minutes, ms_to_kmh
21
+
22
+
23
+ RESOURCE_ID = "klima-v2-1h"
24
+
25
+ # Mapping from GeoSphere Austria parameter names to Meteostat parameters
26
+ # See: https://dataset.api.hub.geosphere.at/v1/station/historical/klima-v2-1h/metadata
27
+ PARAMETER_MAPPING: Dict[str, Parameter] = {
28
+ "tl": Parameter.TEMP, # Air temperature (°C)
29
+ "rr": Parameter.PRCP, # Precipitation (mm)
30
+ "pred": Parameter.PRES, # Air pressure (hPa)
31
+ "ff": Parameter.WSPD, # Wind speed (m/s)
32
+ "ffx": Parameter.WPGT, # Wind gust (m/s)
33
+ "dd": Parameter.WDIR, # Wind direction (°)
34
+ "rf": Parameter.RHUM, # Relative humidity (%)
35
+ "so_h": Parameter.TSUN, # Sunshine duration (h)
36
+ "sh": Parameter.SNWD, # Snow depth (cm)
37
+ }
38
+
39
+ # Inverse mapping
40
+ METEOSTAT_TO_GSA = {v: k for k, v in PARAMETER_MAPPING.items()}
41
+
42
+
43
+ @cache_service.cache(TTL.DAY, "pickle")
44
+ def get_data(
45
+ station_id: str, parameters: list[str], start: datetime, end: datetime
46
+ ) -> Optional[pd.DataFrame]:
47
+ """
48
+ Fetch data from GeoSphere Austria Data Hub API
49
+ """
50
+ logger.debug(
51
+ f"Fetching hourly data for station '{station_id}' from {start} to {end}"
52
+ )
53
+
54
+ # Format dates as ISO 8601
55
+ start_str = start.strftime("%Y-%m-%dT%H:%M")
56
+ end_str = end.strftime("%Y-%m-%dT%H:%M")
57
+
58
+ # Build URL
59
+ url = f"{config.gsa_api_base_url}/station/historical/{RESOURCE_ID}"
60
+
61
+ # Make request
62
+ response = network_service.get(
63
+ url,
64
+ params={
65
+ "parameters": ",".join(parameters),
66
+ "station_ids": station_id,
67
+ "start": start_str,
68
+ "end": end_str,
69
+ "output_format": "geojson",
70
+ },
71
+ )
72
+
73
+ if response.status_code != 200:
74
+ logger.warning(
75
+ f"Failed to fetch data for station {station_id} (status: {response.status_code})"
76
+ )
77
+ return None
78
+
79
+ try:
80
+ data = response.json()
81
+
82
+ if not data.get("features"):
83
+ logger.info(f"No data returned for station {station_id}")
84
+ return None
85
+
86
+ # Get timestamps array
87
+ timestamps = data.get("timestamps")
88
+ if not timestamps:
89
+ logger.warning("No timestamps in hourly response")
90
+ return None
91
+
92
+ # Extract time series data from GeoJSON response
93
+ # New API format has timestamps at top level and data as arrays
94
+ feature = data["features"][0]
95
+ props = feature.get("properties", {})
96
+ params_data = props.get("parameters", {})
97
+
98
+ if not params_data:
99
+ logger.info(f"No parameter data returned for station {station_id}")
100
+ return None
101
+
102
+ # Build DataFrame from timestamps and parameter arrays
103
+ df_dict = {}
104
+ for param in parameters:
105
+ if param in params_data:
106
+ param_info = params_data[param]
107
+ if "data" in param_info:
108
+ df_dict[param] = param_info["data"]
109
+
110
+ if not df_dict:
111
+ return None
112
+
113
+ # Create DataFrame with timestamps as index
114
+ df = pd.DataFrame(df_dict)
115
+ dt_index = pd.DatetimeIndex(pd.to_datetime(timestamps))
116
+ df.index = dt_index.tz_localize(None)
117
+ df.index.name = "time"
118
+
119
+ # Sort by time
120
+ df = df.sort_index()
121
+
122
+ # Rename columns to Meteostat parameter names
123
+ rename_map = {}
124
+ for gsadh_param, meteostat_param in PARAMETER_MAPPING.items():
125
+ if gsadh_param in df.columns:
126
+ rename_map[gsadh_param] = meteostat_param
127
+
128
+ df = df.rename(columns=rename_map)
129
+
130
+ # Convert units where necessary
131
+ if Parameter.WSPD in df.columns:
132
+ df[Parameter.WSPD] = df[Parameter.WSPD].apply(ms_to_kmh)
133
+
134
+ if Parameter.WPGT in df.columns:
135
+ df[Parameter.WPGT] = df[Parameter.WPGT].apply(ms_to_kmh)
136
+
137
+ if Parameter.TSUN in df.columns:
138
+ df[Parameter.TSUN] = df[Parameter.TSUN].apply(hours_to_minutes)
139
+
140
+ # Round values
141
+ df = df.round(1)
142
+
143
+ return df
144
+
145
+ except Exception as error:
146
+ logger.warning(f"Error parsing response: {error}", exc_info=True)
147
+ return None
148
+
149
+
150
+ def fetch(req: ProviderRequest) -> Optional[pd.DataFrame]:
151
+ """
152
+ Fetch hourly data from GeoSphere Austria Data Hub
153
+ """
154
+ if "national" not in req.station.identifiers:
155
+ return None
156
+
157
+ station_id = req.station.identifiers["national"]
158
+
159
+ # Map Meteostat parameters to GeoSphere Austria parameters
160
+ gsa_params = []
161
+ for param in req.parameters:
162
+ if param in METEOSTAT_TO_GSA:
163
+ gsa_params.append(METEOSTAT_TO_GSA[param])
164
+
165
+ if not gsa_params:
166
+ logger.info("No mappable parameters for GeoSphere Austria hourly data")
167
+ return None
168
+
169
+ # Fetch data
170
+ df = get_data(station_id, gsa_params, req.start, req.end)
171
+
172
+ if df is None or df.empty:
173
+ return None
174
+
175
+ return df
@@ -0,0 +1,192 @@
1
+ """
2
+ GeoSphere Austria Data Hub monthly data import routine
3
+
4
+ Get monthly climate data for weather stations in Austria.
5
+
6
+ License: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
7
+ """
8
+
9
+ from datetime import datetime
10
+ from typing import Dict, Optional
11
+
12
+ import pandas as pd
13
+
14
+ from meteostat.enumerations import TTL, Parameter
15
+ from meteostat.core.logger import logger
16
+ from meteostat.typing import ProviderRequest
17
+ from meteostat.core.cache import cache_service
18
+ from meteostat.api.config import config
19
+ from meteostat.core.network import network_service
20
+ from meteostat.utils.conversions import (
21
+ hours_to_minutes,
22
+ ms_to_kmh,
23
+ percentage_to_okta,
24
+ pres_to_msl,
25
+ )
26
+
27
+
28
+ RESOURCE_ID = "klima-v2-1m"
29
+
30
+ # Mapping from GeoSphere Austria parameter names to Meteostat parameters
31
+ # See: https://dataset.api.hub.geosphere.at/v1/station/historical/klima-v2-1m/metadata
32
+ PARAMETER_MAPPING: Dict[str, Parameter] = {
33
+ "tl_mittel": Parameter.TEMP, # Mean air temperature (°C) - Lufttemperatur 2m Mittelwert
34
+ "tlmin": Parameter.TXMN, # Minimum air temperature (°C) - Lufttemperatur 2m Minimalwert
35
+ "tlmax": Parameter.TXMX, # Maximum air temperature (°C) - Lufttemperatur 2m Maximalwert
36
+ "tlmin_mittel": Parameter.TMIN, # Mean minimum air temperature (°C) - Lufttemperatur 2m Mittelwert der Minima
37
+ "tlmax_mittel": Parameter.TMAX, # Mean maximum air temperature (°C) - Lufttemperatur 2m Mittelwert der Maxima
38
+ "rf_mittel": Parameter.RHUM, # Mean relative humidity (%) - Relative Feuchte Mittelwert
39
+ "rr": Parameter.PRCP, # Precipitation sum (mm) - Niederschlag Summe der 24h-Summen
40
+ "vv_mittel": Parameter.WSPD, # Mean wind speed (m/s) - Windgeschwindigkeit Mittelwert
41
+ "p": Parameter.PRES, # Mean air pressure (hPa) - Luftdruck Mittelwert
42
+ "so_h": Parameter.TSUN, # Sunshine duration (h) - Sonnenscheindauer
43
+ "bewm_mittel": Parameter.CLDC, # Mean cloud cover (%) - Bedeckung Mittelwert
44
+ }
45
+
46
+ # Inverse mapping
47
+ METEOSTAT_TO_GSA = {v: k for k, v in PARAMETER_MAPPING.items()}
48
+
49
+
50
+ @cache_service.cache(TTL.MONTH, "pickle")
51
+ def get_data(
52
+ station_id: str,
53
+ elevation: int | None,
54
+ parameters: list[str],
55
+ start: datetime,
56
+ end: datetime,
57
+ ) -> Optional[pd.DataFrame]:
58
+ """
59
+ Fetch data from GeoSphere Austria Data Hub API
60
+ """
61
+ logger.debug(
62
+ f"Fetching monthly data for station '{station_id}' from {start} to {end}"
63
+ )
64
+
65
+ # Format dates as ISO 8601 (full date for monthly data)
66
+ start_str = start.strftime("%Y-%m-%d")
67
+ end_str = end.strftime("%Y-%m-%d")
68
+
69
+ # Build URL
70
+ url = f"{config.gsa_api_base_url}/station/historical/{RESOURCE_ID}"
71
+
72
+ # Make request
73
+ response = network_service.get(
74
+ url,
75
+ params={
76
+ "parameters": ",".join(parameters),
77
+ "station_ids": station_id,
78
+ "start": start_str,
79
+ "end": end_str,
80
+ "output_format": "geojson",
81
+ },
82
+ )
83
+
84
+ if response.status_code != 200:
85
+ logger.warning(
86
+ f"Failed to fetch monthly data for station {station_id} (status: {response.status_code}): {response.json()}",
87
+ )
88
+ return None
89
+
90
+ try:
91
+ data = response.json()
92
+
93
+ if not data.get("features"):
94
+ logger.info(f"No monthly data returned for station {station_id}")
95
+ return None
96
+
97
+ # Get timestamps array
98
+ timestamps = data.get("timestamps")
99
+ if not timestamps:
100
+ logger.warning("No timestamps in monthly response")
101
+ return None
102
+
103
+ # Extract time series data from GeoJSON response
104
+ # New API format has timestamps at top level and data as arrays
105
+ feature = data["features"][0]
106
+ props = feature.get("properties", {})
107
+ params_data = props.get("parameters", {})
108
+
109
+ if not params_data:
110
+ logger.info(f"No parameter data returned for station {station_id}")
111
+ return None
112
+
113
+ # Build DataFrame from timestamps and parameter arrays
114
+ df_dict = {}
115
+ for param in parameters:
116
+ if param in params_data:
117
+ param_info = params_data[param]
118
+ if "data" in param_info:
119
+ df_dict[param] = param_info["data"]
120
+
121
+ if not df_dict:
122
+ return None
123
+
124
+ # Create DataFrame with timestamps as index
125
+ df = pd.DataFrame(df_dict)
126
+ dt_index = pd.DatetimeIndex(pd.to_datetime(timestamps))
127
+ df.index = dt_index.tz_localize(None)
128
+ df.index.name = "time"
129
+
130
+ # Sort by time
131
+ df = df.sort_index()
132
+
133
+ # Rename columns to Meteostat parameter names
134
+ rename_map = {}
135
+ for gsadh_param, meteostat_param in PARAMETER_MAPPING.items():
136
+ if gsadh_param in df.columns:
137
+ rename_map[gsadh_param] = meteostat_param
138
+
139
+ df = df.rename(columns=rename_map)
140
+
141
+ # Convert units where necessary
142
+ if Parameter.WSPD in df.columns:
143
+ df[Parameter.WSPD] = df[Parameter.WSPD].apply(ms_to_kmh)
144
+
145
+ if Parameter.TSUN in df.columns:
146
+ df[Parameter.TSUN] = df[Parameter.TSUN].apply(hours_to_minutes)
147
+
148
+ if Parameter.CLDC in df.columns:
149
+ # Convert cloud cover from % to okta
150
+ df[Parameter.CLDC] = df[Parameter.CLDC].apply(percentage_to_okta)
151
+
152
+ if Parameter.PRES in df.columns:
153
+ df[Parameter.PRES] = df.apply(
154
+ lambda row: pres_to_msl(row, elevation), axis=1
155
+ )
156
+
157
+ # Round values
158
+ df = df.round(1)
159
+
160
+ return df
161
+
162
+ except Exception as error:
163
+ logger.warning(f"Error parsing monthly response: {error}", exc_info=True)
164
+ return None
165
+
166
+
167
+ def fetch(req: ProviderRequest) -> Optional[pd.DataFrame]:
168
+ """
169
+ Fetch monthly data from GeoSphere Austria Data Hub
170
+ """
171
+ if "national" not in req.station.identifiers:
172
+ return None
173
+
174
+ station_id = req.station.identifiers["national"]
175
+
176
+ # Map Meteostat parameters to GeoSphere Austria parameters
177
+ gsa_params = []
178
+ for param in req.parameters:
179
+ if param in METEOSTAT_TO_GSA:
180
+ gsa_params.append(METEOSTAT_TO_GSA[param])
181
+
182
+ if not gsa_params:
183
+ logger.info("No mappable parameters for GeoSphere Austria monthly data")
184
+ return None
185
+
186
+ # Fetch data
187
+ df = get_data(station_id, req.station.elevation, gsa_params, req.start, req.end)
188
+
189
+ if df is None or df.empty:
190
+ return None
191
+
192
+ return df
@@ -0,0 +1,184 @@
1
+ """
2
+ GeoSphere Austria Data Hub SYNOP hourly data import routine
3
+
4
+ Get SYNOP (synoptic observation) hourly data for weather stations in Austria.
5
+
6
+ License: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
7
+ """
8
+
9
+ from datetime import datetime
10
+ from typing import Dict, Optional
11
+
12
+ import pandas as pd
13
+
14
+ from meteostat.enumerations import TTL, Parameter
15
+ from meteostat.core.logger import logger
16
+ from meteostat.typing import ProviderRequest
17
+ from meteostat.core.cache import cache_service
18
+ from meteostat.api.config import config
19
+ from meteostat.core.network import network_service
20
+ from meteostat.utils.conversions import ms_to_kmh, pres_to_msl
21
+
22
+
23
+ RESOURCE_ID = "synop-v1-1h"
24
+
25
+ # Mapping from GeoSphere Austria SYNOP parameter names to Meteostat parameters
26
+ # See: https://dataset.api.hub.geosphere.at/v1/station/historical/synop-v1-1h/metadata
27
+ PARAMETER_MAPPING: Dict[str, Parameter] = {
28
+ "T": Parameter.TEMP, # Air temperature (°C) - Lufttemperatur
29
+ "Pg": Parameter.PRES, # Air pressure (hPa) - Luftdruck auf Stationshöhe
30
+ "rel": Parameter.RHUM, # Relative humidity (%) - Relative Feuchte
31
+ "ff": Parameter.WSPD, # Wind speed (m/s) - Windgeschwindigkeit
32
+ "boe": Parameter.WPGT, # Wind gust (m/s) - Windböe
33
+ "dd": Parameter.WDIR, # Wind direction (°) - Windrichtung
34
+ "RRR": Parameter.PRCP, # Precipitation (mm) - Niederschlagsmenge
35
+ "N": Parameter.CLDC, # Cloud cover (okta) - Bedeckungsgrad
36
+ }
37
+
38
+ # Inverse mapping
39
+ METEOSTAT_TO_GSA = {v: k for k, v in PARAMETER_MAPPING.items()}
40
+
41
+
42
+ @cache_service.cache(TTL.DAY, "pickle")
43
+ def get_data(
44
+ station_id: str,
45
+ elevation: int | None,
46
+ parameters: list[str],
47
+ start: datetime,
48
+ end: datetime,
49
+ ) -> Optional[pd.DataFrame]:
50
+ """
51
+ Fetch SYNOP data from GeoSphere Austria Data Hub API
52
+ """
53
+ logger.debug(
54
+ f"Fetching SYNOP hourly data for station '{station_id}' from {start} to {end}"
55
+ )
56
+
57
+ # Format dates as ISO 8601
58
+ start_str = start.strftime("%Y-%m-%dT%H:%M")
59
+ end_str = end.strftime("%Y-%m-%dT%H:%M")
60
+
61
+ # Build URL
62
+ url = f"{config.gsa_api_base_url}/station/historical/{RESOURCE_ID}"
63
+
64
+ # Make request
65
+ response = network_service.get(
66
+ url,
67
+ params={
68
+ "parameters": ",".join(parameters),
69
+ "station_ids": station_id,
70
+ "start": start_str,
71
+ "end": end_str,
72
+ "output_format": "geojson",
73
+ },
74
+ )
75
+
76
+ if response.status_code != 200:
77
+ logger.warning(
78
+ f"Failed to fetch SYNOP data for station {station_id} (status: {response.status_code})"
79
+ )
80
+ return None
81
+
82
+ try:
83
+ data = response.json()
84
+
85
+ if not data.get("features"):
86
+ logger.info(f"No SYNOP data returned for station {station_id}")
87
+ return None
88
+
89
+ # Get timestamps array
90
+ timestamps = data.get("timestamps")
91
+ if not timestamps:
92
+ logger.warning("No timestamps in SYNOP response")
93
+ return None
94
+
95
+ # Extract time series data from GeoJSON response
96
+ # New API format has timestamps at top level and data as arrays
97
+ feature = data["features"][0]
98
+ props = feature.get("properties", {})
99
+ params_data = props.get("parameters", {})
100
+
101
+ if not params_data:
102
+ logger.info(f"No parameter data returned for station {station_id}")
103
+ return None
104
+
105
+ # Build DataFrame from timestamps and parameter arrays
106
+ df_dict = {}
107
+ for param in parameters:
108
+ if param in params_data:
109
+ param_info = params_data[param]
110
+ if "data" in param_info:
111
+ df_dict[param] = param_info["data"]
112
+
113
+ if not df_dict:
114
+ return None
115
+
116
+ # Create DataFrame with timestamps as index
117
+ df = pd.DataFrame(df_dict)
118
+ dt_index = pd.DatetimeIndex(pd.to_datetime(timestamps))
119
+ df.index = dt_index.tz_localize(None)
120
+ df.index.name = "time"
121
+
122
+ # Sort by time
123
+ df = df.sort_index()
124
+
125
+ # Rename columns to Meteostat parameter names
126
+ rename_map = {}
127
+ for gsadh_param, meteostat_param in PARAMETER_MAPPING.items():
128
+ if gsadh_param in df.columns:
129
+ rename_map[gsadh_param] = meteostat_param
130
+
131
+ df = df.rename(columns=rename_map)
132
+
133
+ # Convert units where necessary
134
+ if Parameter.WSPD in df.columns:
135
+ df[Parameter.WSPD] = df[Parameter.WSPD].apply(ms_to_kmh)
136
+
137
+ if Parameter.WPGT in df.columns:
138
+ df[Parameter.WPGT] = df[Parameter.WPGT].apply(ms_to_kmh)
139
+
140
+ # RRR returns -1 for no precipitation; convert to 0
141
+ if Parameter.PRCP in df.columns:
142
+ df[Parameter.PRCP] = df[Parameter.PRCP].replace(-1, 0)
143
+
144
+ if Parameter.PRES in df.columns:
145
+ df[Parameter.PRES] = df.apply(
146
+ lambda row: pres_to_msl(row, elevation), axis=1
147
+ )
148
+
149
+ # Round values
150
+ df = df.round(1)
151
+
152
+ return df
153
+
154
+ except Exception as error:
155
+ logger.warning(f"Error parsing SYNOP response: {error}", exc_info=True)
156
+ return None
157
+
158
+
159
+ def fetch(req: ProviderRequest) -> Optional[pd.DataFrame]:
160
+ """
161
+ Fetch SYNOP hourly data from GeoSphere Austria Data Hub
162
+ """
163
+ if "wmo" not in req.station.identifiers:
164
+ return None
165
+
166
+ station_id = req.station.identifiers["wmo"]
167
+
168
+ # Map Meteostat parameters to GeoSphere Austria SYNOP parameters
169
+ gsa_params = []
170
+ for param in req.parameters:
171
+ if param in METEOSTAT_TO_GSA:
172
+ gsa_params.append(METEOSTAT_TO_GSA[param])
173
+
174
+ if not gsa_params:
175
+ logger.info("No mappable parameters for GeoSphere Austria SYNOP data")
176
+ return None
177
+
178
+ # Fetch data
179
+ df = get_data(station_id, req.station.elevation, gsa_params, req.start, req.end)
180
+
181
+ if df is None or df.empty:
182
+ return None
183
+
184
+ return df
@@ -473,6 +473,113 @@ PROVIDER_METNO_FORECAST = ProviderSpec(
473
473
  module="meteostat.providers.metno.forecast",
474
474
  )
475
475
 
476
+ PROVIDER_GSA_HOURLY = ProviderSpec(
477
+ id=Provider.GSA_HOURLY,
478
+ name="GeoSphere Austria Hourly",
479
+ granularity=Granularity.HOURLY,
480
+ priority=Priority.HIGHEST,
481
+ grade=Grade.RECORD,
482
+ license=License(
483
+ commercial=True,
484
+ attribution="GeoSphere Austria",
485
+ name="CC BY 4.0",
486
+ url="https://creativecommons.org/licenses/by/4.0/",
487
+ ),
488
+ requires=["identifiers"],
489
+ countries=["AT"],
490
+ parameters=[
491
+ Parameter.TEMP,
492
+ Parameter.PRCP,
493
+ Parameter.PRES,
494
+ Parameter.WSPD,
495
+ Parameter.WDIR,
496
+ Parameter.RHUM,
497
+ Parameter.TSUN,
498
+ ],
499
+ start=date(1880, 4, 1),
500
+ module="meteostat.providers.gsa.hourly",
501
+ )
502
+
503
+ PROVIDER_GSA_SYNOP = ProviderSpec(
504
+ id=Provider.GSA_SYNOP,
505
+ name="GeoSphere Austria SYNOP",
506
+ granularity=Granularity.HOURLY,
507
+ priority=Priority.HIGH,
508
+ grade=Grade.OBSERVATION,
509
+ license=License(
510
+ commercial=True,
511
+ attribution="GeoSphere Austria",
512
+ name="CC BY 4.0",
513
+ url="https://creativecommons.org/licenses/by/4.0/",
514
+ ),
515
+ requires=["identifiers"],
516
+ countries=["AT"],
517
+ parameters=[
518
+ Parameter.TEMP,
519
+ Parameter.PRES,
520
+ Parameter.RHUM,
521
+ Parameter.WSPD,
522
+ Parameter.WDIR,
523
+ Parameter.PRCP,
524
+ ],
525
+ start=date(2000, 1, 1),
526
+ module="meteostat.providers.gsa.synop",
527
+ )
528
+
529
+ PROVIDER_GSA_DAILY = ProviderSpec(
530
+ id=Provider.GSA_DAILY,
531
+ name="GeoSphere Austria Daily",
532
+ granularity=Granularity.DAILY,
533
+ priority=Priority.HIGHEST,
534
+ grade=Grade.RECORD,
535
+ license=License(
536
+ commercial=True,
537
+ attribution="GeoSphere Austria",
538
+ name="CC BY 4.0",
539
+ url="https://creativecommons.org/licenses/by/4.0/",
540
+ ),
541
+ requires=["identifiers"],
542
+ countries=["AT"],
543
+ parameters=[
544
+ Parameter.TEMP,
545
+ Parameter.TMIN,
546
+ Parameter.TMAX,
547
+ Parameter.PRCP,
548
+ Parameter.PRES,
549
+ Parameter.RHUM,
550
+ Parameter.WSPD,
551
+ Parameter.TSUN,
552
+ ],
553
+ start=date(1880, 4, 1),
554
+ module="meteostat.providers.gsa.daily",
555
+ )
556
+
557
+ PROVIDER_GSA_MONTHLY = ProviderSpec(
558
+ id=Provider.GSA_MONTHLY,
559
+ name="GeoSphere Austria Monthly",
560
+ granularity=Granularity.MONTHLY,
561
+ priority=Priority.HIGHEST,
562
+ grade=Grade.RECORD,
563
+ license=License(
564
+ commercial=True,
565
+ attribution="GeoSphere Austria",
566
+ name="CC BY 4.0",
567
+ url="https://creativecommons.org/licenses/by/4.0/",
568
+ ),
569
+ requires=["identifiers"],
570
+ countries=["AT"],
571
+ parameters=[
572
+ Parameter.TEMP,
573
+ Parameter.TMIN,
574
+ Parameter.TMAX,
575
+ Parameter.PRCP,
576
+ Parameter.PRES,
577
+ Parameter.TSUN,
578
+ ],
579
+ start=date(1880, 4, 1),
580
+ module="meteostat.providers.gsa.monthly",
581
+ )
582
+
476
583
 
477
584
  DEFAULT_PROVIDERS = [
478
585
  PROVIDER_HOURLY,
@@ -493,4 +600,8 @@ DEFAULT_PROVIDERS = [
493
600
  PROVIDER_CLIMAT,
494
601
  PROVIDER_METAR,
495
602
  PROVIDER_METNO_FORECAST,
603
+ PROVIDER_GSA_HOURLY,
604
+ PROVIDER_GSA_SYNOP,
605
+ PROVIDER_GSA_DAILY,
606
+ PROVIDER_GSA_MONTHLY,
496
607
  ]
@@ -11,9 +11,6 @@ from meteostat.typing import ProviderRequest
11
11
  from meteostat.utils.conversions import percentage_to_okta
12
12
  from meteostat.core.cache import cache_service
13
13
 
14
-
15
- ENDPOINT = config.metno_forecast_endpoint
16
- USER_AGENT = config.metno_user_agent
17
14
  CONDICODES = {
18
15
  "clearsky": 1,
19
16
  "cloudy": 3,
@@ -130,13 +127,26 @@ def map_data(record):
130
127
 
131
128
  @cache_service.cache(TTL.HOUR, "pickle")
132
129
  def get_df(latitude: float, longitude: float, elevation: int) -> Optional[pd.DataFrame]:
133
- file_url = ENDPOINT.format(
130
+ endpoint = config.metno_forecast_endpoint
131
+ user_agent = config.metno_user_agent
132
+
133
+ if not endpoint:
134
+ logger.warning("MET Norway forecast endpoint is not configured.")
135
+ return None
136
+
137
+ if not user_agent:
138
+ logger.warning(
139
+ "MET Norway requires a unique user agent as per their terms of service. Please use config to specify your user agent. For now, this provider is skipped."
140
+ )
141
+ return None
142
+
143
+ file_url = endpoint.format(
134
144
  latitude=latitude,
135
145
  longitude=longitude,
136
146
  elevation=elevation,
137
147
  )
138
148
 
139
- headers = {"User-Agent": USER_AGENT}
149
+ headers = {"User-Agent": user_agent}
140
150
 
141
151
  try:
142
152
  response = network_service.get(file_url, headers=headers)
@@ -173,12 +183,6 @@ def get_df(latitude: float, longitude: float, elevation: int) -> Optional[pd.Dat
173
183
 
174
184
 
175
185
  def fetch(req: ProviderRequest) -> Optional[pd.DataFrame]:
176
- if not USER_AGENT:
177
- logger.warning(
178
- "MET Norway requires a unique user agent as per their terms of service. Please use config to specify your user agent. For now, this provider is skipped."
179
- )
180
- return None
181
-
182
186
  return get_df(
183
187
  req.station.latitude,
184
188
  req.station.longitude,
@@ -82,7 +82,7 @@ def create_df(element, dict_element):
82
82
  df_element = move_col_to_front(col, df_element)
83
83
 
84
84
  # Convert numerical values to float
85
- df_element.loc[:, element] = df_element.loc[:, element].astype(float)
85
+ df_element[element] = df_element[element].astype(float)
86
86
 
87
87
  return df_element
88
88
 
@@ -65,13 +65,26 @@ def get_df(usaf: str, wban: str, year: int) -> Optional[pd.DataFrame]:
65
65
  try:
66
66
  df = pd.read_fwf(
67
67
  f"{ISD_LITE_ENDPOINT}{year}/{filename}",
68
- parse_dates={"time": [0, 1, 2, 3]},
69
68
  na_values=["-9999", -9999],
70
69
  header=None,
71
70
  colspecs=COLSPECS,
72
71
  compression="gzip",
73
72
  )
74
73
 
74
+ # Parse datetime from first 4 columns (year, month, day, hour)
75
+ df[0] = pd.to_datetime(
76
+ df[0].astype(str)
77
+ + "-"
78
+ + df[1].astype(str).str.zfill(2)
79
+ + "-"
80
+ + df[2].astype(str).str.zfill(2)
81
+ + " "
82
+ + df[3].astype(str).str.zfill(2)
83
+ + ":00",
84
+ format="%Y-%m-%d %H:%M",
85
+ )
86
+ df = df.drop(columns=[1, 2, 3])
87
+
75
88
  # Rename columns
76
89
  df.columns = COLUMN_NAMES
77
90
 
@@ -8,7 +8,7 @@ The code is licensed under the MIT license.
8
8
  import math
9
9
  from typing import Optional
10
10
 
11
- from numpy import isnan
11
+ import pandas as pd
12
12
 
13
13
  from meteostat.enumerations import Parameter, Unit, UnitSystem
14
14
 
@@ -73,14 +73,21 @@ def kelvin_to_celsius(value):
73
73
  """
74
74
  Convert Kelvin to Celsius
75
75
  """
76
- return value - 273.15 if value is not None and not isnan(value) else None
76
+ return value - 273.15 if not pd.isna(value) else None
77
77
 
78
78
 
79
79
  def ms_to_kmh(value):
80
80
  """
81
81
  Convert m/s to km/h
82
82
  """
83
- return value * 3.6 if value is not None and not isnan(value) else None
83
+ return value * 3.6 if not pd.isna(value) else None
84
+
85
+
86
+ def hours_to_minutes(value):
87
+ """
88
+ Convert duration from hours to minutes
89
+ """
90
+ return value * 60 if not pd.isna(value) else None
84
91
 
85
92
 
86
93
  def temp_dwpt_to_rhum(row: dict):
@@ -93,38 +100,30 @@ def temp_dwpt_to_rhum(row: dict):
93
100
  math.exp((17.625 * row["dwpt"]) / (243.04 + row["dwpt"]))
94
101
  / math.exp((17.625 * row["temp"]) / (243.04 + row["temp"]))
95
102
  )
96
- if row["temp"] is not None and row["dwpt"] is not None
103
+ if not pd.isna(row["temp"]) and not pd.isna(row["dwpt"])
97
104
  else None
98
105
  )
99
106
 
100
107
 
101
108
  def pres_to_msl(row: dict, altitude: Optional[int] = None, temp: str = Parameter.TEMP):
102
- """
103
- Convert local air pressure to MSL
104
- """
105
109
  try:
106
- return (
107
- None
108
- if (
109
- not row[Parameter.PRES]
110
- or not row[temp]
111
- or not altitude
112
- or row[Parameter.PRES] == -999
113
- )
114
- else round(
115
- row[Parameter.PRES]
116
- * math.pow(
117
- (
118
- 1
119
- - (
120
- (0.0065 * altitude)
121
- / (row[temp] + 0.0065 * altitude + 273.15)
122
- )
123
- ),
124
- -5.257,
125
- ),
126
- 1,
127
- )
110
+ pres = row.get(Parameter.PRES)
111
+ t = row.get(temp)
112
+
113
+ if pd.isna(pres) or pd.isna(t) or altitude is None or pres == -999:
114
+ return None
115
+
116
+ # Type narrowing for arithmetic operations
117
+ if not isinstance(pres, (int, float)) or not isinstance(t, (int, float)):
118
+ return None
119
+
120
+ return round(
121
+ pres
122
+ * math.pow(
123
+ 1 - (0.0065 * altitude) / (t + 0.0065 * altitude + 273.15),
124
+ -5.257,
125
+ ),
126
+ 1,
128
127
  )
129
128
  except Exception:
130
129
  return None
@@ -134,14 +133,14 @@ def percentage_to_okta(value):
134
133
  """
135
134
  Convert cloud cover percentage to oktas
136
135
  """
137
- return round(value / 12.5) if value is not None and not isnan(value) else None
136
+ return round(value / 12.5) if not pd.isna(value) else None
138
137
 
139
138
 
140
139
  def jcm2_to_wm2(value):
141
140
  """
142
141
  Convert Joule/CM^2 to Watt/M^2
143
142
  """
144
- return round(value * 2.78) if value is not None and not isnan(value) else None
143
+ return round(value * 2.78) if not pd.isna(value) else None
145
144
 
146
145
 
147
146
  def to_direction(value):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meteostat
3
- Version: 2.0.1
3
+ Version: 2.1.0
4
4
  Summary: Access and analyze historical weather and climate data with Python.
5
5
  License-File: LICENSE
6
6
  Author: Meteostat
@@ -10,7 +10,7 @@ Classifier: Programming Language :: Python :: 3.11
10
10
  Classifier: Programming Language :: Python :: 3.12
11
11
  Classifier: Programming Language :: Python :: 3.13
12
12
  Classifier: Programming Language :: Python :: 3.14
13
- Requires-Dist: pandas (>=2.3.3,<3.0.0)
13
+ Requires-Dist: pandas (>=2.3.0,<4.0.0)
14
14
  Requires-Dist: pytz (>=2023.3.post1,<2024.0)
15
15
  Requires-Dist: requests (>=2.31.0,<3.0.0)
16
16
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
- meteostat/__init__.py,sha256=uoBFnarB7WQtLj61mM-U2FHBq06FLHKLfdSKlTVq7i4,1555
1
+ meteostat/__init__.py,sha256=bKXwCYMUdZrlNTPTHImXKbELKuEe0JS_YhBhZnAgWrI,1555
2
2
  meteostat/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- meteostat/api/config.py,sha256=DpZ20TLcc7S1uJV_N_SBv2qtVorNgB0hFzX2sHs0tXM,5065
3
+ meteostat/api/config.py,sha256=NYRZBHZkxXsGQXgPptO_ic-Ct2dtczWWgpCIt2LgL78,5166
4
4
  meteostat/api/daily.py,sha256=SUYxR7HC5Cqyd6cBG42QmSpEP74HwDq5VnqfSznM46Y,2371
5
5
  meteostat/api/hourly.py,sha256=KGJ4xBwO__fBrQ5ll5ruLEkrpOCD8bybSBcXJ94-ntE,2556
6
6
  meteostat/api/interpolate.py,sha256=6i128g_x5ea3X2Nkn6IlrtPaKqRfakozOcSheTbmxIM,12602
@@ -19,17 +19,17 @@ meteostat/core/parameters.py,sha256=7BJIkFQaxcBRi1yIDgpAYm2VZxBfVPbi5_UdZL9teXE,
19
19
  meteostat/core/providers.py,sha256=DChq-vILb9uWEcsZmUcUnyeuVGtl3Ote49QhibdeNBo,5490
20
20
  meteostat/core/schema.py,sha256=Jbp84bTgWmfhPYhORluGWLEnogevsFiAjwC21Y4Mbwg,5070
21
21
  meteostat/core/validator.py,sha256=pJOJIU8FtYO4k7ihGqFxM6CSs9O6oO-fByYyJeJ9pZY,887
22
- meteostat/enumerations.py,sha256=1lWrnzejtV1uP5CjyeyN-UcYt8wBIqlc5sxwzwh5Rqg,3271
22
+ meteostat/enumerations.py,sha256=FxB1s9gP1Ros2hN0zDZa1TSABfSgym11E0pigHqXPPY,3389
23
23
  meteostat/interpolation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  meteostat/interpolation/idw.py,sha256=AyZWxXjq8mS9BEQtxgSEHQzn7TZYIdkc22kPkF5qZww,4435
25
25
  meteostat/interpolation/lapserate.py,sha256=D5Y2DLMgjCNmqfdm2J5MU67w0O6L0vx_TbgMaGrMQuc,2637
26
26
  meteostat/interpolation/nearest.py,sha256=xZg_6OCvU8uI-i7UbPQHjsJmtlz_DbZqrDz00dJthEM,782
27
27
  meteostat/parameters.py,sha256=w7G3ktLVasAbaR9OnjuMAvrq_c1zLz5PVt3zofDFun4,10074
28
28
  meteostat/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
- meteostat/providers/dwd/climat.py,sha256=jEHvg9m8L0161n1j7DHOiBWyky7WL0SD-TrlkljoYCI,4875
29
+ meteostat/providers/dwd/climat.py,sha256=vLCTk0PZgPliyTsNwbNWnKeUEXChA9dA23UtCxOa7tA,4866
30
30
  meteostat/providers/dwd/daily.py,sha256=qVZjMUu6a1GODaWrKpzbOYdXS9HyEkKp9XwnY47Md44,4030
31
31
  meteostat/providers/dwd/hourly.py,sha256=LAjnQJrp80wOhX24JwuceAtCi104DC8EDwEZetHDrso,6249
32
- meteostat/providers/dwd/monthly.py,sha256=JaXklCJrH_xlQPm1G6PXoVvV81qwnhT2o1mXXEzM2LQ,3658
32
+ meteostat/providers/dwd/monthly.py,sha256=FiUdMzLwnGLFu1Nxt9KW5VAUyRYA9tXCbsr2T1iCw_w,3719
33
33
  meteostat/providers/dwd/mosmix.py,sha256=uDalWL-_Vayyq3iiiCUWPFmKXSYqQttXiDOF2Lk_qB4,8892
34
34
  meteostat/providers/dwd/poi.py,sha256=Qv4eDsi_a8hdBQKl1LPQJ0BO2UbTxfba9gH5Ht2giZ0,3087
35
35
  meteostat/providers/dwd/shared.py,sha256=z4LT0fd7LFUqpVPJRJyNWJxK_6TRzJFDvolv6_Y9PME,2917
@@ -37,28 +37,33 @@ meteostat/providers/eccc/daily.py,sha256=K-akr9JXYMnsdG1YlM9SPXUcDfVzv61KsCqD9Ie
37
37
  meteostat/providers/eccc/hourly.py,sha256=W2qMX0HxffMs1IlBzKY4b0M58wPJvtDw7Py4xyDuNO8,3209
38
38
  meteostat/providers/eccc/monthly.py,sha256=eA5JRpDa7-2H-Ce4FOstY24ECWj_gTY5vRbvZa_LCLE,1805
39
39
  meteostat/providers/eccc/shared.py,sha256=2m00uWYdWQrVqvxwMioLIKFxlrjLmCfqIYwjx30r_9k,1324
40
- meteostat/providers/index.py,sha256=6UrzxtynwbILOxg_lmE_Ylh747GZzDW_6ytGIEQHTNc,12670
40
+ meteostat/providers/gsa/__init__.py,sha256=a4SQxle7PvurmZWrzw8mxOpWxv1cztnhf0I6OXza2Lk,44
41
+ meteostat/providers/gsa/daily.py,sha256=OBD2oq2RQJ8CQBAKLsEyzfGWSkA2QylFLFlp5n_46Q8,6298
42
+ meteostat/providers/gsa/hourly.py,sha256=yDeF-periIV0aq2PSHDHVpcgfHMaNA_twXZ-Ob6sP5o,5392
43
+ meteostat/providers/gsa/monthly.py,sha256=hniTVaSvsidP0I7No42OncmeGBVU0k05jDnFrZrabhI,6376
44
+ meteostat/providers/gsa/synop.py,sha256=P46RtF6nF5eaMiFUECML1XZiKzMcxbV0G5S6JCLNWRs,5827
45
+ meteostat/providers/index.py,sha256=W4Jd3Fgq-iYpPLZopqKcFGPU_hCUjhTejLVw-l3WYk4,15511
41
46
  meteostat/providers/meteostat/daily.py,sha256=32vh0J5caMT9gshfUBQXoH8CB3r3qf4BUlB3VztvPN0,1795
42
47
  meteostat/providers/meteostat/daily_derived.py,sha256=_fiLSKxkaH9Caxp-Tg77rH39UXwy7SPDlCS_M-7kIqE,3170
43
48
  meteostat/providers/meteostat/hourly.py,sha256=INO5JVaDbd3zIEo3_spIwACoBpYqDXinyrL-pW1AIAk,1798
44
49
  meteostat/providers/meteostat/monthly.py,sha256=4N2ThwBroQVz2wI5UI2WpvIUGOH61gS63rP8ZVaMqOE,1253
45
50
  meteostat/providers/meteostat/monthly_derived.py,sha256=8BNSd2-xqABdr3lDU_9noypF91qhRJPhz5liTK0eoR8,3075
46
51
  meteostat/providers/meteostat/shared.py,sha256=mK2K3tbgtCcKLMITR5NJVuJSBkPm_jLcN0dBRT9hM90,2571
47
- meteostat/providers/metno/forecast.py,sha256=x-q21HiGkJ4yiv7vKcPnVSFin3rFGHWAZqtzJE0j4Tk,5661
48
- meteostat/providers/noaa/ghcnd.py,sha256=AaR-efQkbgc3afwcvMd_H25_l1dV5CFmautn9TWQ6Cw,6789
49
- meteostat/providers/noaa/isd_lite.py,sha256=YfqhWJjNjxirbqeGaRsQo07koEREvY-APVETkZ3eBKM,3817
52
+ meteostat/providers/metno/forecast.py,sha256=aMoOikEh2agMCw6ZLFMcHPnT-9VMQ1_qpx7e2K3B_-g,5785
53
+ meteostat/providers/noaa/ghcnd.py,sha256=xOvp0DGi6XARZQBwbImDI5o_5QoNEXXr2e2JtUAUTyk,6775
54
+ meteostat/providers/noaa/isd_lite.py,sha256=bPnGEFzvb3LA8n8OmurCToW5HTb5aJSa1FXJQgC_3rI,4200
50
55
  meteostat/providers/noaa/metar.py,sha256=wqH0zEUZO0gS0inK5vvxKulz-LF3NEZIAsYHmR0Do64,4188
51
56
  meteostat/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
57
  meteostat/typing.py,sha256=Q4_le4P_yOVyv1FAxtccNDfFc0OPzYKm3p-GTTptGAY,3504
53
58
  meteostat/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
- meteostat/utils/conversions.py,sha256=B1qaL3_OSbWniIsFCwDPnwWtmglRiBBswFK1q0NH4bI,4755
59
+ meteostat/utils/conversions.py,sha256=qR4eSyN8hpaSDKk-VvDmgT-paJq0fjxaT-lz6X1pmYk,4652
55
60
  meteostat/utils/data.py,sha256=d_aQy53jdhDAvFvo0qVPyhR2NH-WKIAeNm_kIobJOq8,6119
56
61
  meteostat/utils/geo.py,sha256=n_imXze6-u0vzFdYrgPyStKqGNuERcUmXUA-PsAja2w,664
57
62
  meteostat/utils/guards.py,sha256=fuBd4_6BXfZT_jPYU6GzXgDBnCc_GVLDpI6PSK3PCno,1746
58
63
  meteostat/utils/parsers.py,sha256=2y-QSubyrh_xdXJBdgWNiWNI_DDXCGxddcjGOZB9hCU,4646
59
64
  meteostat/utils/types.py,sha256=_hzGcVueIGZdjCB6mR4vVFedrPUWw0iMRaLkpnqaN2Y,3470
60
65
  meteostat/utils/validators.py,sha256=iBywF68ZhL3eD6fgemY2Tng3a-409eFU2oNZVqpYCpY,524
61
- meteostat-2.0.1.dist-info/METADATA,sha256=cAXHfPnAXC-3gEpeUMfmRqURQWjOufDsXHfX_fjm650,5024
62
- meteostat-2.0.1.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
63
- meteostat-2.0.1.dist-info/licenses/LICENSE,sha256=kqpl7FVzWOCe11BZqJBZ1aRQi-aK87j3ljtG7P3VxLc,1066
64
- meteostat-2.0.1.dist-info/RECORD,,
66
+ meteostat-2.1.0.dist-info/METADATA,sha256=wgyYCQMPf3x7L7AJVxfuIyMl0D02e5UYxGOwdSz5waM,5024
67
+ meteostat-2.1.0.dist-info/WHEEL,sha256=kJCRJT_g0adfAJzTx2GUMmS80rTJIVHRCfG0DQgLq3o,88
68
+ meteostat-2.1.0.dist-info/licenses/LICENSE,sha256=kqpl7FVzWOCe11BZqJBZ1aRQi-aK87j3ljtG7P3VxLc,1066
69
+ meteostat-2.1.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.2.1
2
+ Generator: poetry-core 2.3.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any