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.
- meteostat/__init__.py +38 -19
- meteostat/api/config.py +158 -0
- meteostat/api/daily.py +76 -0
- meteostat/api/hourly.py +80 -0
- meteostat/api/interpolate.py +378 -0
- meteostat/api/inventory.py +59 -0
- meteostat/api/merge.py +103 -0
- meteostat/api/monthly.py +73 -0
- meteostat/api/normals.py +144 -0
- meteostat/api/point.py +30 -0
- meteostat/api/stations.py +234 -0
- meteostat/api/timeseries.py +334 -0
- meteostat/core/cache.py +212 -59
- meteostat/core/data.py +203 -0
- meteostat/core/logger.py +9 -0
- meteostat/core/network.py +82 -0
- meteostat/core/parameters.py +112 -0
- meteostat/core/providers.py +184 -0
- meteostat/core/schema.py +170 -0
- meteostat/core/validator.py +38 -0
- meteostat/enumerations.py +149 -0
- meteostat/interpolation/idw.py +120 -0
- meteostat/interpolation/lapserate.py +91 -0
- meteostat/interpolation/nearest.py +31 -0
- meteostat/parameters.py +354 -0
- meteostat/providers/dwd/climat.py +166 -0
- meteostat/providers/dwd/daily.py +144 -0
- meteostat/providers/dwd/hourly.py +218 -0
- meteostat/providers/dwd/monthly.py +138 -0
- meteostat/providers/dwd/mosmix.py +351 -0
- meteostat/providers/dwd/poi.py +117 -0
- meteostat/providers/dwd/shared.py +155 -0
- meteostat/providers/eccc/daily.py +87 -0
- meteostat/providers/eccc/hourly.py +104 -0
- meteostat/providers/eccc/monthly.py +66 -0
- meteostat/providers/eccc/shared.py +45 -0
- meteostat/providers/index.py +496 -0
- meteostat/providers/meteostat/daily.py +65 -0
- meteostat/providers/meteostat/daily_derived.py +110 -0
- meteostat/providers/meteostat/hourly.py +66 -0
- meteostat/providers/meteostat/monthly.py +45 -0
- meteostat/providers/meteostat/monthly_derived.py +106 -0
- meteostat/providers/meteostat/shared.py +93 -0
- meteostat/providers/metno/forecast.py +186 -0
- meteostat/providers/noaa/ghcnd.py +228 -0
- meteostat/providers/noaa/isd_lite.py +142 -0
- meteostat/providers/noaa/metar.py +163 -0
- meteostat/typing.py +113 -0
- meteostat/utils/conversions.py +231 -0
- meteostat/utils/data.py +194 -0
- meteostat/utils/geo.py +28 -0
- meteostat/utils/guards.py +51 -0
- meteostat/utils/parsers.py +161 -0
- meteostat/utils/types.py +113 -0
- meteostat/utils/validators.py +31 -0
- meteostat-2.0.1.dist-info/METADATA +130 -0
- meteostat-2.0.1.dist-info/RECORD +64 -0
- {meteostat-1.7.6.dist-info → meteostat-2.0.1.dist-info}/WHEEL +1 -2
- meteostat/core/loader.py +0 -103
- meteostat/core/warn.py +0 -34
- meteostat/enumerations/granularity.py +0 -22
- meteostat/interface/base.py +0 -39
- meteostat/interface/daily.py +0 -118
- meteostat/interface/hourly.py +0 -154
- meteostat/interface/meteodata.py +0 -210
- meteostat/interface/monthly.py +0 -109
- meteostat/interface/normals.py +0 -245
- meteostat/interface/point.py +0 -143
- meteostat/interface/stations.py +0 -252
- meteostat/interface/timeseries.py +0 -237
- meteostat/series/aggregate.py +0 -48
- meteostat/series/convert.py +0 -28
- meteostat/series/count.py +0 -17
- meteostat/series/coverage.py +0 -20
- meteostat/series/fetch.py +0 -28
- meteostat/series/interpolate.py +0 -47
- meteostat/series/normalize.py +0 -76
- meteostat/series/stations.py +0 -22
- meteostat/units.py +0 -149
- meteostat/utilities/__init__.py +0 -0
- meteostat/utilities/aggregations.py +0 -37
- meteostat/utilities/endpoint.py +0 -33
- meteostat/utilities/helpers.py +0 -70
- meteostat/utilities/mutations.py +0 -89
- meteostat/utilities/validations.py +0 -30
- meteostat-1.7.6.dist-info/METADATA +0 -112
- meteostat-1.7.6.dist-info/RECORD +0 -39
- meteostat-1.7.6.dist-info/top_level.txt +0 -1
- /meteostat/{core → api}/__init__.py +0 -0
- /meteostat/{enumerations → interpolation}/__init__.py +0 -0
- /meteostat/{interface → providers}/__init__.py +0 -0
- /meteostat/{interface/interpolate.py → py.typed} +0 -0
- /meteostat/{series → utils}/__init__.py +0 -0
- {meteostat-1.7.6.dist-info → meteostat-2.0.1.dist-info/licenses}/LICENSE +0 -0
meteostat/parameters.py
ADDED
|
@@ -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")]
|