meteostat 1.7.6__py3-none-any.whl → 2.0.1__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.
Files changed (94) hide show
  1. meteostat/__init__.py +38 -19
  2. meteostat/api/config.py +158 -0
  3. meteostat/api/daily.py +76 -0
  4. meteostat/api/hourly.py +80 -0
  5. meteostat/api/interpolate.py +378 -0
  6. meteostat/api/inventory.py +59 -0
  7. meteostat/api/merge.py +103 -0
  8. meteostat/api/monthly.py +73 -0
  9. meteostat/api/normals.py +144 -0
  10. meteostat/api/point.py +30 -0
  11. meteostat/api/stations.py +234 -0
  12. meteostat/api/timeseries.py +334 -0
  13. meteostat/core/cache.py +212 -59
  14. meteostat/core/data.py +203 -0
  15. meteostat/core/logger.py +9 -0
  16. meteostat/core/network.py +82 -0
  17. meteostat/core/parameters.py +112 -0
  18. meteostat/core/providers.py +184 -0
  19. meteostat/core/schema.py +170 -0
  20. meteostat/core/validator.py +38 -0
  21. meteostat/enumerations.py +149 -0
  22. meteostat/interpolation/idw.py +120 -0
  23. meteostat/interpolation/lapserate.py +91 -0
  24. meteostat/interpolation/nearest.py +31 -0
  25. meteostat/parameters.py +354 -0
  26. meteostat/providers/dwd/climat.py +166 -0
  27. meteostat/providers/dwd/daily.py +144 -0
  28. meteostat/providers/dwd/hourly.py +218 -0
  29. meteostat/providers/dwd/monthly.py +138 -0
  30. meteostat/providers/dwd/mosmix.py +351 -0
  31. meteostat/providers/dwd/poi.py +117 -0
  32. meteostat/providers/dwd/shared.py +155 -0
  33. meteostat/providers/eccc/daily.py +87 -0
  34. meteostat/providers/eccc/hourly.py +104 -0
  35. meteostat/providers/eccc/monthly.py +66 -0
  36. meteostat/providers/eccc/shared.py +45 -0
  37. meteostat/providers/index.py +496 -0
  38. meteostat/providers/meteostat/daily.py +65 -0
  39. meteostat/providers/meteostat/daily_derived.py +110 -0
  40. meteostat/providers/meteostat/hourly.py +66 -0
  41. meteostat/providers/meteostat/monthly.py +45 -0
  42. meteostat/providers/meteostat/monthly_derived.py +106 -0
  43. meteostat/providers/meteostat/shared.py +93 -0
  44. meteostat/providers/metno/forecast.py +186 -0
  45. meteostat/providers/noaa/ghcnd.py +228 -0
  46. meteostat/providers/noaa/isd_lite.py +142 -0
  47. meteostat/providers/noaa/metar.py +163 -0
  48. meteostat/typing.py +113 -0
  49. meteostat/utils/conversions.py +231 -0
  50. meteostat/utils/data.py +194 -0
  51. meteostat/utils/geo.py +28 -0
  52. meteostat/utils/guards.py +51 -0
  53. meteostat/utils/parsers.py +161 -0
  54. meteostat/utils/types.py +113 -0
  55. meteostat/utils/validators.py +31 -0
  56. meteostat-2.0.1.dist-info/METADATA +130 -0
  57. meteostat-2.0.1.dist-info/RECORD +64 -0
  58. {meteostat-1.7.6.dist-info → meteostat-2.0.1.dist-info}/WHEEL +1 -2
  59. meteostat/core/loader.py +0 -103
  60. meteostat/core/warn.py +0 -34
  61. meteostat/enumerations/granularity.py +0 -22
  62. meteostat/interface/base.py +0 -39
  63. meteostat/interface/daily.py +0 -118
  64. meteostat/interface/hourly.py +0 -154
  65. meteostat/interface/meteodata.py +0 -210
  66. meteostat/interface/monthly.py +0 -109
  67. meteostat/interface/normals.py +0 -245
  68. meteostat/interface/point.py +0 -143
  69. meteostat/interface/stations.py +0 -252
  70. meteostat/interface/timeseries.py +0 -237
  71. meteostat/series/aggregate.py +0 -48
  72. meteostat/series/convert.py +0 -28
  73. meteostat/series/count.py +0 -17
  74. meteostat/series/coverage.py +0 -20
  75. meteostat/series/fetch.py +0 -28
  76. meteostat/series/interpolate.py +0 -47
  77. meteostat/series/normalize.py +0 -76
  78. meteostat/series/stations.py +0 -22
  79. meteostat/units.py +0 -149
  80. meteostat/utilities/__init__.py +0 -0
  81. meteostat/utilities/aggregations.py +0 -37
  82. meteostat/utilities/endpoint.py +0 -33
  83. meteostat/utilities/helpers.py +0 -70
  84. meteostat/utilities/mutations.py +0 -89
  85. meteostat/utilities/validations.py +0 -30
  86. meteostat-1.7.6.dist-info/METADATA +0 -112
  87. meteostat-1.7.6.dist-info/RECORD +0 -39
  88. meteostat-1.7.6.dist-info/top_level.txt +0 -1
  89. /meteostat/{core → api}/__init__.py +0 -0
  90. /meteostat/{enumerations → interpolation}/__init__.py +0 -0
  91. /meteostat/{interface → providers}/__init__.py +0 -0
  92. /meteostat/{interface/interpolate.py → py.typed} +0 -0
  93. /meteostat/{series → utils}/__init__.py +0 -0
  94. {meteostat-1.7.6.dist-info → meteostat-2.0.1.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,354 @@
1
+ """
2
+ Meteostat Parameter Definitions
3
+ """
4
+
5
+ from meteostat.enumerations import Granularity, Parameter, Unit
6
+ from meteostat.typing import ParameterSpec
7
+ from meteostat.utils.validators import maximum, minimum
8
+
9
+
10
+ DEFAULT_PARAMETERS = [
11
+ ParameterSpec(
12
+ id=Parameter.TEMP,
13
+ name="Air Temperature",
14
+ granularity=Granularity.HOURLY,
15
+ unit=Unit.CELSIUS,
16
+ dtype="Float64",
17
+ validators=[minimum(-100), maximum(65)],
18
+ ),
19
+ ParameterSpec(
20
+ id=Parameter.TEMP,
21
+ name="Mean Air Temperature",
22
+ granularity=Granularity.DAILY,
23
+ unit=Unit.CELSIUS,
24
+ dtype="Float64",
25
+ validators=[minimum(-100), maximum(65)],
26
+ ),
27
+ ParameterSpec(
28
+ id=Parameter.TEMP,
29
+ name="Mean Air Temperature",
30
+ granularity=Granularity.MONTHLY,
31
+ unit=Unit.CELSIUS,
32
+ dtype="Float64",
33
+ validators=[minimum(-100), maximum(65)],
34
+ ),
35
+ ParameterSpec(
36
+ id=Parameter.TEMP,
37
+ name="Mean Air Temperature",
38
+ granularity=Granularity.NORMALS,
39
+ unit=Unit.CELSIUS,
40
+ dtype="Float64",
41
+ validators=[minimum(-100), maximum(65)],
42
+ ),
43
+ ParameterSpec(
44
+ id=Parameter.TMIN,
45
+ name="Minimum Air Temperature",
46
+ granularity=Granularity.DAILY,
47
+ unit=Unit.CELSIUS,
48
+ dtype="Float64",
49
+ validators=[minimum(-100), maximum(65)],
50
+ ),
51
+ ParameterSpec(
52
+ id=Parameter.TMIN,
53
+ name="Mean Daily Minimum Air Temperature",
54
+ granularity=Granularity.MONTHLY,
55
+ unit=Unit.CELSIUS,
56
+ dtype="Float64",
57
+ validators=[minimum(-100), maximum(65)],
58
+ ),
59
+ ParameterSpec(
60
+ id=Parameter.TMIN,
61
+ name="Mean Daily Minimum Air Temperature",
62
+ granularity=Granularity.NORMALS,
63
+ unit=Unit.CELSIUS,
64
+ dtype="Float64",
65
+ validators=[minimum(-100), maximum(65)],
66
+ ),
67
+ ParameterSpec(
68
+ id=Parameter.TMAX,
69
+ name="Maximum Air Temperature",
70
+ granularity=Granularity.DAILY,
71
+ unit=Unit.CELSIUS,
72
+ dtype="Float64",
73
+ validators=[minimum(-100), maximum(65)],
74
+ ),
75
+ ParameterSpec(
76
+ id=Parameter.TMAX,
77
+ name="Mean Daily Maximum Air Temperature",
78
+ granularity=Granularity.MONTHLY,
79
+ unit=Unit.CELSIUS,
80
+ dtype="Float64",
81
+ validators=[minimum(-100), maximum(65)],
82
+ ),
83
+ ParameterSpec(
84
+ id=Parameter.TMAX,
85
+ name="Mean Daily Maximum Air Temperature",
86
+ granularity=Granularity.NORMALS,
87
+ unit=Unit.CELSIUS,
88
+ dtype="Float64",
89
+ validators=[minimum(-100), maximum(65)],
90
+ ),
91
+ ParameterSpec(
92
+ id=Parameter.TXMN,
93
+ name="Absolute Minimum Air Temperature",
94
+ granularity=Granularity.MONTHLY,
95
+ unit=Unit.CELSIUS,
96
+ dtype="Float64",
97
+ validators=[minimum(-100), maximum(65)],
98
+ ),
99
+ ParameterSpec(
100
+ id=Parameter.TXMN,
101
+ name="Absolute Minimum Air Temperature",
102
+ granularity=Granularity.NORMALS,
103
+ unit=Unit.CELSIUS,
104
+ dtype="Float64",
105
+ validators=[minimum(-100), maximum(65)],
106
+ ),
107
+ ParameterSpec(
108
+ id=Parameter.TXMX,
109
+ name="Absolute Maximum Air Temperature",
110
+ granularity=Granularity.MONTHLY,
111
+ unit=Unit.CELSIUS,
112
+ dtype="Float64",
113
+ validators=[minimum(-100), maximum(65)],
114
+ ),
115
+ ParameterSpec(
116
+ id=Parameter.TXMX,
117
+ name="Absolute Maximum Air Temperature",
118
+ granularity=Granularity.NORMALS,
119
+ unit=Unit.CELSIUS,
120
+ dtype="Float64",
121
+ validators=[minimum(-100), maximum(65)],
122
+ ),
123
+ ParameterSpec(
124
+ id=Parameter.DWPT,
125
+ name="Dew Point",
126
+ granularity=Granularity.HOURLY,
127
+ unit=Unit.CELSIUS,
128
+ dtype="Float64",
129
+ validators=[minimum(-100), maximum(65)],
130
+ ),
131
+ ParameterSpec(
132
+ id=Parameter.RHUM,
133
+ name="Relative Humidity",
134
+ granularity=Granularity.HOURLY,
135
+ unit=Unit.PERCENTAGE,
136
+ dtype="UInt8",
137
+ validators=[minimum(0), maximum(100)],
138
+ ),
139
+ ParameterSpec(
140
+ id=Parameter.RHUM,
141
+ name="Relative Humidity",
142
+ granularity=Granularity.DAILY,
143
+ unit=Unit.PERCENTAGE,
144
+ dtype="UInt8",
145
+ validators=[minimum(0), maximum(100)],
146
+ ),
147
+ ParameterSpec(
148
+ id=Parameter.RHUM,
149
+ name="Relative Humidity",
150
+ granularity=Granularity.MONTHLY,
151
+ unit=Unit.PERCENTAGE,
152
+ dtype="UInt8",
153
+ validators=[minimum(0), maximum(100)],
154
+ ),
155
+ ParameterSpec(
156
+ id=Parameter.RHUM,
157
+ name="Relative Humidity",
158
+ granularity=Granularity.NORMALS,
159
+ unit=Unit.PERCENTAGE,
160
+ dtype="UInt8",
161
+ validators=[minimum(0), maximum(100)],
162
+ ),
163
+ ParameterSpec(
164
+ id=Parameter.PRCP,
165
+ name="Total Precipitation",
166
+ granularity=Granularity.HOURLY,
167
+ unit=Unit.MILLIMETERS,
168
+ dtype="Float64",
169
+ validators=[minimum(0), maximum(350)],
170
+ ),
171
+ ParameterSpec(
172
+ id=Parameter.PRCP,
173
+ name="Total Precipitation",
174
+ granularity=Granularity.DAILY,
175
+ unit=Unit.MILLIMETERS,
176
+ dtype="Float64",
177
+ validators=[minimum(0), maximum(2000)],
178
+ ),
179
+ ParameterSpec(
180
+ id=Parameter.PRCP,
181
+ name="Total Precipitation",
182
+ granularity=Granularity.MONTHLY,
183
+ unit=Unit.MILLIMETERS,
184
+ dtype="Float64",
185
+ validators=[minimum(0), maximum(10000)],
186
+ ),
187
+ ParameterSpec(
188
+ id=Parameter.PRCP,
189
+ name="Total Precipitation",
190
+ granularity=Granularity.NORMALS,
191
+ unit=Unit.MILLIMETERS,
192
+ dtype="Float64",
193
+ validators=[minimum(0), maximum(10000)],
194
+ ),
195
+ ParameterSpec(
196
+ id=Parameter.SNOW,
197
+ name="Snowfall",
198
+ granularity=Granularity.HOURLY,
199
+ unit=Unit.CENTIMETERS,
200
+ dtype="UInt8",
201
+ validators=[minimum(0), maximum(10000)],
202
+ ),
203
+ ParameterSpec(
204
+ id=Parameter.SNWD,
205
+ name="Snow Depth",
206
+ granularity=Granularity.HOURLY,
207
+ unit=Unit.CENTIMETERS,
208
+ dtype="UInt16",
209
+ validators=[minimum(0), maximum(1200)],
210
+ ),
211
+ ParameterSpec(
212
+ id=Parameter.SNWD,
213
+ name="Snow Depth",
214
+ granularity=Granularity.DAILY,
215
+ unit=Unit.CENTIMETERS,
216
+ dtype="UInt16",
217
+ validators=[minimum(0), maximum(1200)],
218
+ ),
219
+ ParameterSpec(
220
+ id=Parameter.WDIR,
221
+ name="Wind Direction",
222
+ granularity=Granularity.HOURLY,
223
+ unit=Unit.DEGREES,
224
+ dtype="UInt16",
225
+ validators=[minimum(0), maximum(360)],
226
+ ),
227
+ ParameterSpec(
228
+ id=Parameter.WSPD,
229
+ name="Wind Speed",
230
+ granularity=Granularity.HOURLY,
231
+ unit=Unit.KMH,
232
+ dtype="Float64",
233
+ validators=[minimum(0), maximum(250)],
234
+ ),
235
+ ParameterSpec(
236
+ id=Parameter.WSPD,
237
+ name="Average Wind Speed",
238
+ granularity=Granularity.DAILY,
239
+ unit=Unit.KMH,
240
+ dtype="Float64",
241
+ validators=[minimum(0), maximum(150)],
242
+ ),
243
+ ParameterSpec(
244
+ id=Parameter.WPGT,
245
+ name="Peak Wind Gust",
246
+ granularity=Granularity.HOURLY,
247
+ unit=Unit.KMH,
248
+ dtype="Float64",
249
+ validators=[minimum(0), maximum(500)],
250
+ ),
251
+ ParameterSpec(
252
+ id=Parameter.WPGT,
253
+ name="Peak Wind Gust",
254
+ granularity=Granularity.DAILY,
255
+ unit=Unit.KMH,
256
+ dtype="Float64",
257
+ validators=[minimum(0), maximum(500)],
258
+ ),
259
+ ParameterSpec(
260
+ id=Parameter.PRES,
261
+ name="Air Pressure (MSL)",
262
+ granularity=Granularity.HOURLY,
263
+ unit=Unit.HPA,
264
+ dtype="Float64",
265
+ validators=[minimum(850), maximum(1090)],
266
+ ),
267
+ ParameterSpec(
268
+ id=Parameter.PRES,
269
+ name="Average Air Pressure (MSL)",
270
+ granularity=Granularity.DAILY,
271
+ unit=Unit.HPA,
272
+ dtype="Float64",
273
+ validators=[minimum(850), maximum(1090)],
274
+ ),
275
+ ParameterSpec(
276
+ id=Parameter.PRES,
277
+ name="Average Air Pressure (MSL)",
278
+ granularity=Granularity.MONTHLY,
279
+ unit=Unit.HPA,
280
+ dtype="Float64",
281
+ validators=[minimum(850), maximum(1090)],
282
+ ),
283
+ ParameterSpec(
284
+ id=Parameter.PRES,
285
+ name="Average Air Pressure (MSL)",
286
+ granularity=Granularity.NORMALS,
287
+ unit=Unit.HPA,
288
+ dtype="Float64",
289
+ validators=[minimum(850), maximum(1090)],
290
+ ),
291
+ ParameterSpec(
292
+ id=Parameter.TSUN,
293
+ name="Sunshine Duration",
294
+ granularity=Granularity.HOURLY,
295
+ unit=Unit.MINUTES,
296
+ dtype="UInt8",
297
+ validators=[minimum(0), maximum(60)],
298
+ ),
299
+ ParameterSpec(
300
+ id=Parameter.TSUN,
301
+ name="Sunshine Duration",
302
+ granularity=Granularity.DAILY,
303
+ unit=Unit.MINUTES,
304
+ dtype="UInt16",
305
+ validators=[minimum(0), maximum(1440)],
306
+ ),
307
+ ParameterSpec(
308
+ id=Parameter.TSUN,
309
+ name="Sunshine Duration",
310
+ granularity=Granularity.MONTHLY,
311
+ unit=Unit.MINUTES,
312
+ dtype="UInt16",
313
+ validators=[minimum(0), maximum(44640)],
314
+ ),
315
+ ParameterSpec(
316
+ id=Parameter.TSUN,
317
+ name="Sunshine Duration",
318
+ granularity=Granularity.NORMALS,
319
+ unit=Unit.MINUTES,
320
+ dtype="UInt16",
321
+ validators=[minimum(0), maximum(44640)],
322
+ ),
323
+ ParameterSpec(
324
+ id=Parameter.CLDC,
325
+ name="Cloud Cover",
326
+ granularity=Granularity.HOURLY,
327
+ unit=Unit.OKTAS,
328
+ dtype="UInt8",
329
+ validators=[minimum(0), maximum(8)],
330
+ ),
331
+ ParameterSpec(
332
+ id=Parameter.CLDC,
333
+ name="Average Cloud Cover",
334
+ granularity=Granularity.DAILY,
335
+ unit=Unit.OKTAS,
336
+ dtype="UInt8",
337
+ validators=[minimum(0), maximum(8)],
338
+ ),
339
+ ParameterSpec(
340
+ id=Parameter.VSBY,
341
+ name="Visibility",
342
+ granularity=Granularity.HOURLY,
343
+ unit=Unit.METERS,
344
+ dtype="UInt16",
345
+ validators=[minimum(0)],
346
+ ),
347
+ ParameterSpec(
348
+ id=Parameter.COCO,
349
+ name="Weather Condition Code",
350
+ granularity=Granularity.HOURLY,
351
+ dtype="UInt8",
352
+ validators=[minimum(0), maximum(27)],
353
+ ),
354
+ ]
@@ -0,0 +1,166 @@
1
+ """
2
+ DWD Global CLIMAT Data
3
+ """
4
+
5
+ from datetime import datetime
6
+ from ftplib import FTP
7
+ from io import BytesIO
8
+ from typing import List, Optional
9
+
10
+ import pandas as pd
11
+
12
+ from meteostat.core.logger import logger
13
+ from meteostat.api.config import config
14
+ from meteostat.enumerations import TTL, Parameter
15
+ from meteostat.typing import ProviderRequest, Station
16
+ from meteostat.core.cache import cache_service
17
+ from meteostat.providers.dwd.shared import get_ftp_connection
18
+
19
+ # Constants
20
+ BASE_DIR = "/climate_environment/CDC/observations_global/CLIMAT/monthly/qc/"
21
+
22
+ # Monthly column stubnames mapping template
23
+ MONTHS_MAP = {
24
+ "jan": 1,
25
+ "feb": 2,
26
+ "mrz": 3,
27
+ "apr": 4,
28
+ "mai": 5,
29
+ "jun": 6,
30
+ "jul": 7,
31
+ "aug": 8,
32
+ "sep": 9,
33
+ "okt": 10,
34
+ "nov": 11,
35
+ "dez": 12,
36
+ }
37
+
38
+ # Parameter directory configurations
39
+ PARAMETERS = [
40
+ ("precipitation_total", Parameter.PRCP),
41
+ ("air_temperature_mean", Parameter.TEMP),
42
+ ("air_temperature_mean_of_daily_max", Parameter.TMAX),
43
+ ("air_temperature_mean_of_daily_min", Parameter.TMIN),
44
+ ("air_temperature_absolute_max", Parameter.TXMX),
45
+ ("air_temperature_absolute_min", Parameter.TXMN),
46
+ ("mean_sea_level_pressure", Parameter.PRES),
47
+ ("sunshine_duration", Parameter.TSUN),
48
+ ]
49
+
50
+ # Precomputed parameter configs with stubnames
51
+ PARAMETER_CONFIGS = {
52
+ param: {
53
+ "dir": dir_name,
54
+ "stubnames": {
55
+ "jahr": "year",
56
+ **{
57
+ month: f"{param}{i + 1}"
58
+ for i, (month, _) in enumerate(MONTHS_MAP.items())
59
+ },
60
+ },
61
+ }
62
+ for dir_name, param in PARAMETERS
63
+ }
64
+
65
+
66
+ def find_file(ftp: FTP, mode: str, directory: str, search_term: str) -> Optional[str]:
67
+ """
68
+ Find a file in the FTP directory matching a pattern.
69
+ """
70
+ try:
71
+ ftp.cwd(f"{BASE_DIR}{directory}/{mode}")
72
+ matches = [f for f in ftp.nlst() if search_term in f]
73
+ return sorted(matches)[-1] if matches else None
74
+ except Exception:
75
+ logger.debug("Error while searching for file", exc_info=True)
76
+ return None
77
+
78
+
79
+ @cache_service.cache(TTL.WEEK, "pickle")
80
+ def get_df(parameter: str, mode: str, station_code: str) -> Optional[pd.DataFrame]:
81
+ """
82
+ Download and parse a CLIMAT dataset from DWD FTP.
83
+ """
84
+ param_config = PARAMETER_CONFIGS.get(parameter)
85
+ if not param_config:
86
+ logger.debug(f"Unknown parameter '{parameter}'")
87
+ return None
88
+
89
+ ftp = get_ftp_connection()
90
+ search_term = station_code if mode == "recent" else f"{station_code}_"
91
+ remote_file = find_file(ftp, mode, param_config["dir"], search_term)
92
+
93
+ if not remote_file:
94
+ logger.debug(
95
+ f"No file found for parameter '{parameter}', mode '{mode}', station '{station_code}'"
96
+ )
97
+ return None
98
+
99
+ buffer = BytesIO()
100
+ ftp.retrbinary(f"RETR {remote_file}", buffer.write)
101
+ ftp.close()
102
+
103
+ buffer.seek(0)
104
+ df = pd.read_csv(buffer, sep=";").rename(columns=lambda col: col.strip().lower())
105
+ df.rename(columns=param_config["stubnames"], inplace=True)
106
+
107
+ # Convert wide to long format
108
+ df = pd.wide_to_long(
109
+ df, stubnames=parameter, i="year", j="month", sep="", suffix="\\d+"
110
+ ).reset_index()
111
+
112
+ if parameter == Parameter.TSUN:
113
+ df[Parameter.TSUN] *= 60 # convert hours to minutes
114
+
115
+ # Create datetime index
116
+ df["time"] = pd.to_datetime(
117
+ df["year"].astype(str) + "-" + df["month"].astype(str).str.zfill(2) + "-01"
118
+ )
119
+
120
+ return df.drop(columns=["year", "month"]).set_index("time")
121
+
122
+
123
+ def get_parameter(
124
+ parameter: str, modes: List[str], station: Station
125
+ ) -> Optional[pd.DataFrame]:
126
+ """
127
+ Fetch and merge data for a parameter over multiple modes (e.g., recent, historical).
128
+ """
129
+ try:
130
+ station_code = station.identifiers.get("wmo")
131
+ if not station_code:
132
+ return None
133
+
134
+ datasets = [get_df(parameter, mode, station_code) for mode in modes]
135
+ datasets = [df for df in datasets if df is not None]
136
+
137
+ if not datasets:
138
+ return None
139
+
140
+ return pd.concat(datasets).loc[lambda df: ~df.index.duplicated(keep="first")]
141
+ except Exception as e:
142
+ logger.warning(
143
+ f"Failed to fetch data for parameter '{parameter}': {e}", exc_info=True
144
+ )
145
+ return None
146
+
147
+
148
+ def fetch(req: ProviderRequest) -> pd.DataFrame:
149
+ """
150
+ Entry point to fetch all requested parameters for a station query.
151
+ """
152
+ station_code = req.station.identifiers.get("wmo")
153
+ if not station_code:
154
+ return pd.DataFrame()
155
+
156
+ modes = ["historical"]
157
+ if (datetime.now() - req.end).days < 5 * 365:
158
+ modes.append("recent")
159
+
160
+ data_frames = [
161
+ get_parameter(param.value, config.dwd_climat_modes or modes, req.station)
162
+ for param in req.parameters
163
+ if param in PARAMETER_CONFIGS
164
+ ]
165
+
166
+ return pd.concat([df for df in data_frames if df is not None], axis=1)
@@ -0,0 +1,144 @@
1
+ """
2
+ DWD national daily data import routine
3
+
4
+ Get daily data for weather stations in Germany.
5
+
6
+ The code is licensed under the MIT license.
7
+ """
8
+
9
+ from datetime import datetime
10
+ from ftplib import FTP
11
+ from io import BytesIO
12
+ from typing import Optional
13
+ from zipfile import ZipFile
14
+
15
+ import pandas as pd
16
+
17
+ from meteostat.api.config import config
18
+ from meteostat.enumerations import TTL, Parameter
19
+ from meteostat.typing import ProviderRequest
20
+ from meteostat.core.cache import cache_service
21
+ from meteostat.utils.conversions import ms_to_kmh, pres_to_msl
22
+ from meteostat.providers.dwd.shared import get_ftp_connection
23
+
24
+ BASE_DIR = "/climate_environment/CDC/observations_germany/climate/daily/kl/"
25
+ USECOLS = [1, 3, 4, 6, 8, 9, 10, 12, 13, 14, 15, 16] # CSV cols which should be read
26
+ NAMES = {
27
+ "FX": Parameter.WPGT,
28
+ "FM": Parameter.WSPD,
29
+ "RSK": Parameter.PRCP,
30
+ "SDK": Parameter.TSUN,
31
+ "SHK_TAG": Parameter.SNWD,
32
+ "NM": Parameter.CLDC,
33
+ "PM": Parameter.PRES,
34
+ "TMK": Parameter.TEMP,
35
+ "UPM": Parameter.RHUM,
36
+ "TXK": Parameter.TMAX,
37
+ "TNK": Parameter.TMIN,
38
+ }
39
+
40
+
41
+ def find_file(ftp: FTP, mode: str, needle: str):
42
+ """
43
+ Find file in directory
44
+ """
45
+ match = None
46
+
47
+ try:
48
+ ftp.cwd(BASE_DIR + mode)
49
+ files = ftp.nlst()
50
+ matching = [f for f in files if needle in f]
51
+ match = matching[0]
52
+ except BaseException:
53
+ pass
54
+
55
+ return match
56
+
57
+
58
+ @cache_service.cache(TTL.DAY, "pickle")
59
+ def get_df(station: str, elevation: int, mode: str) -> Optional[pd.DataFrame]:
60
+ """
61
+ Get a file from DWD FTP server and convert to DataFrame
62
+ """
63
+ ftp = get_ftp_connection()
64
+ remote_file = find_file(ftp, mode, f"_{station}_")
65
+
66
+ if remote_file is None:
67
+ return None
68
+
69
+ buffer = BytesIO()
70
+ ftp.retrbinary("RETR " + remote_file, buffer.write)
71
+
72
+ ftp.close()
73
+
74
+ # Unzip file
75
+ with ZipFile(buffer, "r") as zipped:
76
+ filelist = zipped.namelist()
77
+ raw = None
78
+ for file in filelist:
79
+ if file[:7] == "produkt":
80
+ with zipped.open(file, "r") as reader:
81
+ raw = BytesIO(reader.read())
82
+
83
+ # Convert raw data to DataFrame
84
+ df: pd.DataFrame = pd.read_csv( # type: ignore
85
+ raw,
86
+ sep=r"\s*;\s*",
87
+ date_format="%Y%m%d",
88
+ na_values=["-999", -999],
89
+ usecols=USECOLS,
90
+ engine="python",
91
+ )
92
+
93
+ # Rename columns
94
+ df = df.rename(columns=lambda x: x.strip())
95
+ df = df.rename(columns=NAMES)
96
+
97
+ # Parse date column (first column contains the date)
98
+ df["time"] = pd.to_datetime(df.iloc[:, 0], format="%Y%m%d")
99
+ df = df.drop(df.columns[0], axis=1)
100
+
101
+ # Convert data
102
+ df[Parameter.SNWD] = df[Parameter.SNWD] * 10
103
+ df[Parameter.WPGT] = df[Parameter.WPGT].apply(ms_to_kmh)
104
+ df[Parameter.WSPD] = df[Parameter.WSPD].apply(ms_to_kmh)
105
+ df[Parameter.TSUN] = df[Parameter.TSUN] * 60
106
+ df[Parameter.PRES] = df.apply(lambda row: pres_to_msl(row, elevation), axis=1)
107
+
108
+ # Set index
109
+ df = df.set_index("time")
110
+
111
+ # Round decimals
112
+ df = df.round(1)
113
+
114
+ return df
115
+
116
+
117
+ def fetch(req: ProviderRequest):
118
+ if "national" not in req.station.identifiers:
119
+ return pd.DataFrame()
120
+
121
+ # Check which modes to consider for data fetching
122
+ #
123
+ # The dataset is divided into a versioned part with completed quality check ("historical"),
124
+ # and a part for which the quality check has not yet been completed ("recent").
125
+ #
126
+ # There is no definite answer as to when the quality check is completed. We're assuming a
127
+ # period of 3 years here. If the end date of the query is within this period, we will also
128
+ # consider the "recent" mode.
129
+ modes = ["historical"]
130
+ if abs((req.end - datetime.now()).days) < 3 * 365:
131
+ modes.append("recent")
132
+
133
+ data = [
134
+ get_df(
135
+ req.station.identifiers["national"],
136
+ req.station.elevation,
137
+ mode,
138
+ )
139
+ for mode in config.dwd_daily_modes or modes
140
+ ]
141
+
142
+ df = pd.concat(data)
143
+
144
+ return df.loc[~df.index.duplicated(keep="first")]