meteostat 1.7.6__py3-none-any.whl → 2.0.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 +32 -19
- meteostat/api/daily.py +76 -0
- meteostat/api/hourly.py +80 -0
- meteostat/api/interpolate.py +240 -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/config.py +158 -0
- meteostat/core/data.py +199 -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/parsers.py +168 -0
- meteostat/utils/types.py +113 -0
- meteostat/utils/validators.py +31 -0
- meteostat-2.0.0.dist-info/METADATA +134 -0
- meteostat-2.0.0.dist-info/RECORD +63 -0
- {meteostat-1.7.6.dist-info → meteostat-2.0.0.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.0.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from itertools import combinations
|
|
2
|
+
from statistics import mean
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
from meteostat.api.timeseries import TimeSeries
|
|
8
|
+
from meteostat.core.config import config
|
|
9
|
+
from meteostat.enumerations import Parameter
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def lapse_rate(ts: TimeSeries, parameter: Parameter = Parameter.TEMP) -> float | None:
|
|
13
|
+
"""
|
|
14
|
+
Calculate the lapse rate (temperature gradient) in degrees Celsius per kilometer
|
|
15
|
+
based on temperature and elevation data from multiple stations.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
df : pd.DataFrame
|
|
20
|
+
DataFrame containing temperature and elevation data for multiple stations.
|
|
21
|
+
|
|
22
|
+
Returns
|
|
23
|
+
-------
|
|
24
|
+
float
|
|
25
|
+
Calculated lapse rate in degrees Celsius per kilometer.
|
|
26
|
+
"""
|
|
27
|
+
df = ts.fetch(location=True)
|
|
28
|
+
|
|
29
|
+
if df is None or "elevation" not in df.columns or parameter not in df.columns:
|
|
30
|
+
return None
|
|
31
|
+
|
|
32
|
+
elev_by_station = df["elevation"].groupby(level="station").first()
|
|
33
|
+
temp_by_station = df[parameter].groupby(level="station").mean()
|
|
34
|
+
|
|
35
|
+
if len(elev_by_station) < 2 or len(temp_by_station) < 2:
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
lapse_rates = []
|
|
39
|
+
|
|
40
|
+
for a, b in combinations(elev_by_station.index, 2):
|
|
41
|
+
if (
|
|
42
|
+
pd.isna(elev_by_station[a])
|
|
43
|
+
or pd.isna(elev_by_station[b])
|
|
44
|
+
or pd.isna(temp_by_station[a])
|
|
45
|
+
or pd.isna(temp_by_station[b])
|
|
46
|
+
or elev_by_station[a] == elev_by_station[b]
|
|
47
|
+
):
|
|
48
|
+
continue
|
|
49
|
+
|
|
50
|
+
temp_diff = temp_by_station[a] - temp_by_station[b]
|
|
51
|
+
elev_diff = elev_by_station[a] - elev_by_station[b]
|
|
52
|
+
|
|
53
|
+
# multiply by -1 to get positive lapse rate for decreasing temp
|
|
54
|
+
# with increasing elevation
|
|
55
|
+
lapse_rate = (temp_diff / elev_diff) * 1000 * -1
|
|
56
|
+
lapse_rates.append(lapse_rate)
|
|
57
|
+
|
|
58
|
+
if not lapse_rates:
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
return mean(lapse_rates)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def apply_lapse_rate(
|
|
65
|
+
df: pd.DataFrame, elevation: int, lapse_rate: float
|
|
66
|
+
) -> pd.DataFrame:
|
|
67
|
+
"""
|
|
68
|
+
Calculate approximate temperature at target elevation
|
|
69
|
+
using a given lapse rate.
|
|
70
|
+
|
|
71
|
+
Parameters
|
|
72
|
+
----------
|
|
73
|
+
df : pd.DataFrame
|
|
74
|
+
DataFrame containing the data to be adjusted.
|
|
75
|
+
elevation : int
|
|
76
|
+
Target elevation in meters.
|
|
77
|
+
lapse_rate : float
|
|
78
|
+
Lapse rate (temperature gradient) in degrees Celsius per kilometer.
|
|
79
|
+
|
|
80
|
+
Returns
|
|
81
|
+
-------
|
|
82
|
+
pd.DataFrame
|
|
83
|
+
DataFrame with adjusted temperature values.
|
|
84
|
+
"""
|
|
85
|
+
for col in config.lapse_rate_parameters:
|
|
86
|
+
if col in df.columns:
|
|
87
|
+
df.loc[df[col] != np.nan, col] = round(
|
|
88
|
+
df[col] + ((lapse_rate / 1000) * (df["elevation"] - elevation)), 1
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
return df
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
|
|
3
|
+
from meteostat.api.point import Point
|
|
4
|
+
from meteostat.api.timeseries import TimeSeries
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def nearest_neighbor(df: pd.DataFrame, ts: TimeSeries, _point: Point) -> pd.DataFrame:
|
|
8
|
+
"""
|
|
9
|
+
Get nearest neighbor value for each record in a DataFrame.
|
|
10
|
+
|
|
11
|
+
Parameters
|
|
12
|
+
----------
|
|
13
|
+
df : pd.DataFrame
|
|
14
|
+
DataFrame containing the data to be adjusted.
|
|
15
|
+
ts : TimeSeries
|
|
16
|
+
TimeSeries object containing the target data.
|
|
17
|
+
_point : Point
|
|
18
|
+
Point object representing the target location.
|
|
19
|
+
|
|
20
|
+
Returns
|
|
21
|
+
-------
|
|
22
|
+
pd.DataFrame
|
|
23
|
+
DataFrame with nearest neighbor values for each record.
|
|
24
|
+
"""
|
|
25
|
+
df = (
|
|
26
|
+
df.sort_values("distance")
|
|
27
|
+
.groupby(pd.Grouper(level="time", freq=ts.freq))
|
|
28
|
+
.agg("first")
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
return df
|
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.core.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)
|