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/interface/normals.py
DELETED
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Normals Interface Class
|
|
3
|
-
|
|
4
|
-
Meteorological data provided by Meteostat (https://dev.meteostat.net)
|
|
5
|
-
under the terms of the Creative Commons Attribution-NonCommercial
|
|
6
|
-
4.0 International Public License.
|
|
7
|
-
|
|
8
|
-
The code is licensed under the MIT license.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
from copy import copy
|
|
12
|
-
from typing import Optional, Union
|
|
13
|
-
from datetime import datetime
|
|
14
|
-
import numpy as np
|
|
15
|
-
import pandas as pd
|
|
16
|
-
from meteostat.core.cache import file_in_cache, get_local_file_path
|
|
17
|
-
from meteostat.core.loader import load_handler
|
|
18
|
-
from meteostat.utilities.endpoint import generate_endpoint_path
|
|
19
|
-
from meteostat.enumerations.granularity import Granularity
|
|
20
|
-
from meteostat.core.warn import warn
|
|
21
|
-
from meteostat.interface.meteodata import MeteoData
|
|
22
|
-
from meteostat.interface.point import Point
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class Normals(MeteoData):
|
|
26
|
-
"""
|
|
27
|
-
Retrieve climate normals for one or multiple weather stations or
|
|
28
|
-
a single geographical point
|
|
29
|
-
"""
|
|
30
|
-
|
|
31
|
-
# The cache subdirectory
|
|
32
|
-
cache_subdir = "normals"
|
|
33
|
-
|
|
34
|
-
# Granularity
|
|
35
|
-
granularity = Granularity.NORMALS
|
|
36
|
-
|
|
37
|
-
# The list of weather Stations
|
|
38
|
-
_stations: Optional[pd.Index] = None
|
|
39
|
-
|
|
40
|
-
# The first year of the period
|
|
41
|
-
_start: Optional[int] = None
|
|
42
|
-
|
|
43
|
-
# The last year of the period
|
|
44
|
-
_end: Optional[int] = None
|
|
45
|
-
|
|
46
|
-
# The data frame
|
|
47
|
-
_data: pd.DataFrame = pd.DataFrame()
|
|
48
|
-
|
|
49
|
-
# Columns
|
|
50
|
-
_columns = [
|
|
51
|
-
"start",
|
|
52
|
-
"end",
|
|
53
|
-
"month",
|
|
54
|
-
"tmin",
|
|
55
|
-
"tmax",
|
|
56
|
-
"prcp",
|
|
57
|
-
"wspd",
|
|
58
|
-
"pres",
|
|
59
|
-
"tsun",
|
|
60
|
-
]
|
|
61
|
-
|
|
62
|
-
# Index of first meteorological column
|
|
63
|
-
_first_met_col = 3
|
|
64
|
-
|
|
65
|
-
# Which columns should be parsed as dates?
|
|
66
|
-
_parse_dates = None
|
|
67
|
-
|
|
68
|
-
def _load_data(self, station: str, year: Optional[int] = None) -> None:
|
|
69
|
-
"""
|
|
70
|
-
Load file for a single station from Meteostat
|
|
71
|
-
"""
|
|
72
|
-
|
|
73
|
-
# File name
|
|
74
|
-
file = generate_endpoint_path(self.granularity, station, year)
|
|
75
|
-
|
|
76
|
-
# Get local file path
|
|
77
|
-
path = get_local_file_path(self.cache_dir, self.cache_subdir, file)
|
|
78
|
-
|
|
79
|
-
# Check if file in cache
|
|
80
|
-
if self.max_age > 0 and file_in_cache(path, self.max_age):
|
|
81
|
-
# Read cached data
|
|
82
|
-
df = pd.read_pickle(path)
|
|
83
|
-
|
|
84
|
-
else:
|
|
85
|
-
# Get data from Meteostat
|
|
86
|
-
df = load_handler(
|
|
87
|
-
self.endpoint,
|
|
88
|
-
file,
|
|
89
|
-
self.proxy,
|
|
90
|
-
self._columns,
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
# Validate and prepare data for further processing
|
|
94
|
-
if not df.empty:
|
|
95
|
-
# Add weather station ID
|
|
96
|
-
df["station"] = station
|
|
97
|
-
|
|
98
|
-
# Set index
|
|
99
|
-
df = df.set_index(["station", "start", "end", "month"])
|
|
100
|
-
|
|
101
|
-
# Save as Pickle
|
|
102
|
-
if self.max_age > 0:
|
|
103
|
-
df.to_pickle(path)
|
|
104
|
-
|
|
105
|
-
# Filter time period and append to DataFrame
|
|
106
|
-
if self.granularity == Granularity.NORMALS and not df.empty and self._end:
|
|
107
|
-
# Get time index
|
|
108
|
-
end = df.index.get_level_values("end")
|
|
109
|
-
# Filter & return
|
|
110
|
-
return df.loc[end == self._end]
|
|
111
|
-
|
|
112
|
-
# Return
|
|
113
|
-
return df
|
|
114
|
-
|
|
115
|
-
def __init__(
|
|
116
|
-
self,
|
|
117
|
-
loc: Union[pd.DataFrame, Point, list, str],
|
|
118
|
-
start: int = None,
|
|
119
|
-
end: int = None,
|
|
120
|
-
) -> None:
|
|
121
|
-
# Set list of weather stations
|
|
122
|
-
if isinstance(loc, pd.DataFrame):
|
|
123
|
-
self._stations = loc.index
|
|
124
|
-
|
|
125
|
-
elif isinstance(loc, Point):
|
|
126
|
-
if start and end:
|
|
127
|
-
stations = loc.get_stations(
|
|
128
|
-
"monthly", datetime(start, 1, 1), datetime(end, 12, 31)
|
|
129
|
-
)
|
|
130
|
-
else:
|
|
131
|
-
stations = loc.get_stations()
|
|
132
|
-
|
|
133
|
-
self._stations = stations.index
|
|
134
|
-
|
|
135
|
-
else:
|
|
136
|
-
if not isinstance(loc, list):
|
|
137
|
-
loc = [loc]
|
|
138
|
-
|
|
139
|
-
self._stations = pd.Index(loc)
|
|
140
|
-
|
|
141
|
-
# Check period
|
|
142
|
-
if (start and end) and (
|
|
143
|
-
end - start != 29 or end % 10 != 0 or end >= datetime.now().year
|
|
144
|
-
):
|
|
145
|
-
raise ValueError("Invalid reference period")
|
|
146
|
-
|
|
147
|
-
# Set period
|
|
148
|
-
self._start = start
|
|
149
|
-
self._end = end
|
|
150
|
-
|
|
151
|
-
# Get data for all weather stations
|
|
152
|
-
self._data = self._get_data()
|
|
153
|
-
|
|
154
|
-
# Interpolate data
|
|
155
|
-
if isinstance(loc, Point):
|
|
156
|
-
self._resolve_point(loc.method, stations, loc.alt, loc.adapt_temp)
|
|
157
|
-
|
|
158
|
-
# Clear cache
|
|
159
|
-
if self.max_age > 0 and self.autoclean:
|
|
160
|
-
self.clear_cache()
|
|
161
|
-
|
|
162
|
-
def normalize(self):
|
|
163
|
-
"""
|
|
164
|
-
Normalize the DataFrame
|
|
165
|
-
"""
|
|
166
|
-
|
|
167
|
-
# Create temporal instance
|
|
168
|
-
temp = copy(self)
|
|
169
|
-
|
|
170
|
-
if self.count() == 0:
|
|
171
|
-
warn("Pointless normalization of empty DataFrame")
|
|
172
|
-
|
|
173
|
-
# Go through list of weather stations
|
|
174
|
-
for station in temp._stations:
|
|
175
|
-
# The list of periods
|
|
176
|
-
periods: pd.Index = pd.Index([])
|
|
177
|
-
# Get periods
|
|
178
|
-
if self.count() > 0:
|
|
179
|
-
periods = temp._data[
|
|
180
|
-
temp._data.index.get_level_values("station") == station
|
|
181
|
-
].index.unique("end")
|
|
182
|
-
elif periods.size == 0 and self._end:
|
|
183
|
-
periods = pd.Index([self._end])
|
|
184
|
-
# Go through all periods
|
|
185
|
-
for period in periods:
|
|
186
|
-
# Create DataFrame
|
|
187
|
-
df = pd.DataFrame(
|
|
188
|
-
columns=temp._columns[temp._first_met_col :], dtype="float64"
|
|
189
|
-
)
|
|
190
|
-
# Populate index columns
|
|
191
|
-
df["month"] = range(1, 13)
|
|
192
|
-
df["station"] = station
|
|
193
|
-
df["start"] = period - 29
|
|
194
|
-
df["end"] = period
|
|
195
|
-
# Set index
|
|
196
|
-
df.set_index(["station", "start", "end", "month"], inplace=True)
|
|
197
|
-
# Merge data
|
|
198
|
-
temp._data = (
|
|
199
|
-
pd.concat([temp._data, df], axis=0)
|
|
200
|
-
.groupby(["station", "start", "end", "month"], as_index=True)
|
|
201
|
-
.first()
|
|
202
|
-
if temp._data.index.size > 0
|
|
203
|
-
else df
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
# None -> nan
|
|
207
|
-
temp._data = temp._data.fillna(np.nan)
|
|
208
|
-
|
|
209
|
-
# Return class instance
|
|
210
|
-
return temp
|
|
211
|
-
|
|
212
|
-
def fetch(self) -> pd.DataFrame:
|
|
213
|
-
"""
|
|
214
|
-
Fetch DataFrame
|
|
215
|
-
"""
|
|
216
|
-
|
|
217
|
-
# Copy DataFrame
|
|
218
|
-
temp = copy(self._data)
|
|
219
|
-
|
|
220
|
-
# Add avg. temperature column
|
|
221
|
-
temp.insert(
|
|
222
|
-
0,
|
|
223
|
-
"tavg",
|
|
224
|
-
pd.to_numeric(
|
|
225
|
-
temp[["tmin", "tmax"]].dropna(how="any").mean(axis=1),
|
|
226
|
-
errors="coerce"
|
|
227
|
-
).round(1)
|
|
228
|
-
)
|
|
229
|
-
|
|
230
|
-
# Remove station index if it's a single station
|
|
231
|
-
if len(self._stations) == 1 and "station" in temp.index.names:
|
|
232
|
-
temp = temp.reset_index(level="station", drop=True)
|
|
233
|
-
|
|
234
|
-
# Remove start & end year if period is set
|
|
235
|
-
if self._start and self._end and self.count() > 0:
|
|
236
|
-
temp = temp.reset_index(level="start", drop=True)
|
|
237
|
-
temp = temp.reset_index(level="end", drop=True)
|
|
238
|
-
|
|
239
|
-
# Return data frame
|
|
240
|
-
return temp
|
|
241
|
-
|
|
242
|
-
# Import methods
|
|
243
|
-
from meteostat.series.convert import convert
|
|
244
|
-
from meteostat.series.count import count
|
|
245
|
-
from meteostat.core.cache import clear_cache
|
meteostat/interface/point.py
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Point Class
|
|
3
|
-
|
|
4
|
-
Meteorological data provided by Meteostat (https://dev.meteostat.net)
|
|
5
|
-
under the terms of the Creative Commons Attribution-NonCommercial
|
|
6
|
-
4.0 International Public License.
|
|
7
|
-
|
|
8
|
-
The code is licensed under the MIT license.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
from datetime import datetime
|
|
12
|
-
import pandas as pd
|
|
13
|
-
from meteostat.interface.stations import Stations
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class Point:
|
|
17
|
-
"""
|
|
18
|
-
Automatically select weather stations by geographic location
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
# The interpolation method (weighted or nearest)
|
|
22
|
-
method: str = "nearest"
|
|
23
|
-
|
|
24
|
-
# Maximum radius for nearby stations
|
|
25
|
-
radius: int = 35000
|
|
26
|
-
|
|
27
|
-
# Maximum difference in altitude
|
|
28
|
-
alt_range: int = 350
|
|
29
|
-
|
|
30
|
-
# Maximum number of stations
|
|
31
|
-
max_count: int = 4
|
|
32
|
-
|
|
33
|
-
# Adapt temperature data based on altitude
|
|
34
|
-
adapt_temp: bool = True
|
|
35
|
-
|
|
36
|
-
# Distance Weight
|
|
37
|
-
weight_dist: float = 0.6
|
|
38
|
-
|
|
39
|
-
# Altitude Weight
|
|
40
|
-
weight_alt: float = 0.4
|
|
41
|
-
|
|
42
|
-
# The list of weather stations
|
|
43
|
-
_stations: pd.Index = None
|
|
44
|
-
|
|
45
|
-
# The latitude
|
|
46
|
-
_lat: float = None
|
|
47
|
-
|
|
48
|
-
# The longitude
|
|
49
|
-
_lon: float = None
|
|
50
|
-
|
|
51
|
-
# The altitude
|
|
52
|
-
_alt: int = None
|
|
53
|
-
|
|
54
|
-
def __init__(self, lat: float, lon: float, alt: int = None) -> None:
|
|
55
|
-
self._lat = lat
|
|
56
|
-
self._lon = lon
|
|
57
|
-
self._alt = alt
|
|
58
|
-
|
|
59
|
-
if alt is None:
|
|
60
|
-
self.adapt_temp = False
|
|
61
|
-
|
|
62
|
-
def get_stations(
|
|
63
|
-
self,
|
|
64
|
-
freq: str = None,
|
|
65
|
-
start: datetime = None,
|
|
66
|
-
end: datetime = None,
|
|
67
|
-
model: bool = True,
|
|
68
|
-
) -> pd.DataFrame:
|
|
69
|
-
"""
|
|
70
|
-
Get list of nearby weather stations
|
|
71
|
-
"""
|
|
72
|
-
|
|
73
|
-
# Get nearby weather stations
|
|
74
|
-
stations = Stations()
|
|
75
|
-
stations = stations.nearby(self._lat, self._lon, self.radius)
|
|
76
|
-
|
|
77
|
-
# Guess altitude if not set
|
|
78
|
-
if self._alt is None:
|
|
79
|
-
self._alt = stations.fetch().head(self.max_count)["elevation"].mean()
|
|
80
|
-
|
|
81
|
-
# Captue unfiltered weather stations
|
|
82
|
-
unfiltered = stations.fetch()
|
|
83
|
-
if self.alt_range:
|
|
84
|
-
unfiltered = unfiltered[
|
|
85
|
-
abs(self._alt - unfiltered["elevation"]) <= self.alt_range
|
|
86
|
-
]
|
|
87
|
-
|
|
88
|
-
# Apply inventory filter
|
|
89
|
-
if freq and start and end:
|
|
90
|
-
age = (datetime.now() - end).days
|
|
91
|
-
if model is False or age > 180:
|
|
92
|
-
stations = stations.inventory(freq, (start, end))
|
|
93
|
-
|
|
94
|
-
# Apply altitude filter
|
|
95
|
-
stations = stations.fetch()
|
|
96
|
-
if self.alt_range:
|
|
97
|
-
stations = stations[
|
|
98
|
-
abs(self._alt - stations["elevation"]) <= self.alt_range
|
|
99
|
-
]
|
|
100
|
-
|
|
101
|
-
# Fill up stations
|
|
102
|
-
selected: int = len(stations.index)
|
|
103
|
-
if selected < self.max_count:
|
|
104
|
-
# Remove already included stations from unfiltered
|
|
105
|
-
unfiltered = unfiltered.loc[~unfiltered.index.isin(stations.index)]
|
|
106
|
-
# Append to existing DataFrame
|
|
107
|
-
stations = pd.concat((stations, unfiltered.head(self.max_count - selected)))
|
|
108
|
-
|
|
109
|
-
# Score values
|
|
110
|
-
if self.radius:
|
|
111
|
-
# Calculate score values
|
|
112
|
-
stations["score"] = (
|
|
113
|
-
(1 - (stations["distance"] / self.radius)) * self.weight_dist
|
|
114
|
-
) + (
|
|
115
|
-
(1 - (abs(self._alt - stations["elevation"]) / self.alt_range))
|
|
116
|
-
* self.weight_alt
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
# Sort by score (descending)
|
|
120
|
-
stations = stations.sort_values("score", ascending=False)
|
|
121
|
-
|
|
122
|
-
# Capture result
|
|
123
|
-
self._stations = stations.index[: self.max_count]
|
|
124
|
-
|
|
125
|
-
return stations.head(self.max_count)
|
|
126
|
-
|
|
127
|
-
@property
|
|
128
|
-
def alt(self) -> int:
|
|
129
|
-
"""
|
|
130
|
-
Returns the point's altitude
|
|
131
|
-
"""
|
|
132
|
-
|
|
133
|
-
# Return altitude
|
|
134
|
-
return self._alt
|
|
135
|
-
|
|
136
|
-
@property
|
|
137
|
-
def stations(self) -> pd.Index:
|
|
138
|
-
"""
|
|
139
|
-
Returns the point's weather stations
|
|
140
|
-
"""
|
|
141
|
-
|
|
142
|
-
# Return weather stations
|
|
143
|
-
return self._stations
|
meteostat/interface/stations.py
DELETED
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Stations Class
|
|
3
|
-
|
|
4
|
-
Meteorological data provided by Meteostat (https://dev.meteostat.net)
|
|
5
|
-
under the terms of the Creative Commons Attribution-NonCommercial
|
|
6
|
-
4.0 International Public License.
|
|
7
|
-
|
|
8
|
-
The code is licensed under the MIT license.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
from copy import copy
|
|
12
|
-
from datetime import datetime, timedelta
|
|
13
|
-
from typing import Union
|
|
14
|
-
import pandas as pd
|
|
15
|
-
from meteostat.core.cache import get_local_file_path, file_in_cache
|
|
16
|
-
from meteostat.core.loader import load_handler
|
|
17
|
-
from meteostat.interface.base import Base
|
|
18
|
-
from meteostat.utilities.helpers import get_distance
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class Stations(Base):
|
|
22
|
-
"""
|
|
23
|
-
Select weather stations from the full list of stations
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
# The cache subdirectory
|
|
27
|
-
cache_subdir: str = "stations"
|
|
28
|
-
|
|
29
|
-
# The list of selected weather Stations
|
|
30
|
-
_data: pd.DataFrame = None
|
|
31
|
-
|
|
32
|
-
# Raw data columns
|
|
33
|
-
_columns: list = [
|
|
34
|
-
"id",
|
|
35
|
-
"name",
|
|
36
|
-
"country",
|
|
37
|
-
"region",
|
|
38
|
-
"wmo",
|
|
39
|
-
"icao",
|
|
40
|
-
"latitude",
|
|
41
|
-
"longitude",
|
|
42
|
-
"elevation",
|
|
43
|
-
"timezone",
|
|
44
|
-
"hourly_start",
|
|
45
|
-
"hourly_end",
|
|
46
|
-
"daily_start",
|
|
47
|
-
"daily_end",
|
|
48
|
-
"monthly_start",
|
|
49
|
-
"monthly_end",
|
|
50
|
-
]
|
|
51
|
-
|
|
52
|
-
# Processed data columns with types
|
|
53
|
-
_types: dict = {
|
|
54
|
-
"id": "string",
|
|
55
|
-
"name": "object",
|
|
56
|
-
"country": "string",
|
|
57
|
-
"region": "string",
|
|
58
|
-
"wmo": "string",
|
|
59
|
-
"icao": "string",
|
|
60
|
-
"latitude": "float64",
|
|
61
|
-
"longitude": "float64",
|
|
62
|
-
"elevation": "float64",
|
|
63
|
-
"timezone": "string",
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
# Columns for date parsing
|
|
67
|
-
_parse_dates: list = [10, 11, 12, 13, 14, 15]
|
|
68
|
-
|
|
69
|
-
def _load(self) -> None:
|
|
70
|
-
"""
|
|
71
|
-
Load file from Meteostat
|
|
72
|
-
"""
|
|
73
|
-
|
|
74
|
-
# File name
|
|
75
|
-
file = "stations/slim.csv.gz"
|
|
76
|
-
|
|
77
|
-
# Get local file path
|
|
78
|
-
path = get_local_file_path(self.cache_dir, self.cache_subdir, file)
|
|
79
|
-
|
|
80
|
-
# Check if file in cache
|
|
81
|
-
if self.max_age > 0 and file_in_cache(path, self.max_age):
|
|
82
|
-
# Read cached data
|
|
83
|
-
df = pd.read_pickle(path)
|
|
84
|
-
|
|
85
|
-
else:
|
|
86
|
-
# Get data from Meteostat
|
|
87
|
-
df = load_handler(
|
|
88
|
-
self.endpoint,
|
|
89
|
-
file,
|
|
90
|
-
self.proxy,
|
|
91
|
-
self._columns,
|
|
92
|
-
self._types,
|
|
93
|
-
self._parse_dates,
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
# Add index
|
|
97
|
-
df = df.set_index("id")
|
|
98
|
-
|
|
99
|
-
# Save as Pickle
|
|
100
|
-
if self.max_age > 0:
|
|
101
|
-
df.to_pickle(path)
|
|
102
|
-
|
|
103
|
-
# Set data
|
|
104
|
-
self._data = df
|
|
105
|
-
|
|
106
|
-
def __init__(self) -> None:
|
|
107
|
-
# Get all weather stations
|
|
108
|
-
self._load()
|
|
109
|
-
|
|
110
|
-
def nearby(self, lat: float, lon: float, radius: int = None) -> "Stations":
|
|
111
|
-
"""
|
|
112
|
-
Sort/filter weather stations by physical distance
|
|
113
|
-
"""
|
|
114
|
-
|
|
115
|
-
# Create temporal instance
|
|
116
|
-
temp = copy(self)
|
|
117
|
-
|
|
118
|
-
# Get distance for each station
|
|
119
|
-
temp._data["distance"] = get_distance(
|
|
120
|
-
lat, lon, temp._data["latitude"], temp._data["longitude"]
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
# Filter by radius
|
|
124
|
-
if radius:
|
|
125
|
-
temp._data = temp._data[temp._data["distance"] <= radius]
|
|
126
|
-
|
|
127
|
-
# Sort stations by distance
|
|
128
|
-
temp._data.columns.str.strip()
|
|
129
|
-
temp._data = temp._data.sort_values("distance")
|
|
130
|
-
|
|
131
|
-
# Return self
|
|
132
|
-
return temp
|
|
133
|
-
|
|
134
|
-
def region(self, country: str, state: str = None) -> "Stations":
|
|
135
|
-
"""
|
|
136
|
-
Filter weather stations by country/region code
|
|
137
|
-
"""
|
|
138
|
-
|
|
139
|
-
# Create temporal instance
|
|
140
|
-
temp = copy(self)
|
|
141
|
-
|
|
142
|
-
# Country code
|
|
143
|
-
temp._data = temp._data[temp._data["country"] == country]
|
|
144
|
-
|
|
145
|
-
# State code
|
|
146
|
-
if state is not None:
|
|
147
|
-
temp._data = temp._data[temp._data["region"] == state]
|
|
148
|
-
|
|
149
|
-
# Return self
|
|
150
|
-
return temp
|
|
151
|
-
|
|
152
|
-
def bounds(self, top_left: tuple, bottom_right: tuple) -> "Stations":
|
|
153
|
-
"""
|
|
154
|
-
Filter weather stations by geographical bounds
|
|
155
|
-
"""
|
|
156
|
-
|
|
157
|
-
# Create temporal instance
|
|
158
|
-
temp = copy(self)
|
|
159
|
-
|
|
160
|
-
# Return stations in boundaries
|
|
161
|
-
temp._data = temp._data[
|
|
162
|
-
(temp._data["latitude"] <= top_left[0])
|
|
163
|
-
& (temp._data["latitude"] >= bottom_right[0])
|
|
164
|
-
& (temp._data["longitude"] <= bottom_right[1])
|
|
165
|
-
& (temp._data["longitude"] >= top_left[1])
|
|
166
|
-
]
|
|
167
|
-
|
|
168
|
-
# Return self
|
|
169
|
-
return temp
|
|
170
|
-
|
|
171
|
-
def inventory(
|
|
172
|
-
self, freq: str, required: Union[datetime, tuple, bool] = True
|
|
173
|
-
) -> "Stations":
|
|
174
|
-
"""
|
|
175
|
-
Filter weather stations by inventory data
|
|
176
|
-
"""
|
|
177
|
-
|
|
178
|
-
# Create temporal instance
|
|
179
|
-
temp = copy(self)
|
|
180
|
-
|
|
181
|
-
if required is True:
|
|
182
|
-
# Make sure data exists at all
|
|
183
|
-
temp._data = temp._data[~pd.isna(temp._data[f"{freq}_start"])]
|
|
184
|
-
|
|
185
|
-
elif isinstance(required, tuple):
|
|
186
|
-
# Make sure data exists across period
|
|
187
|
-
temp._data = temp._data[
|
|
188
|
-
(~pd.isna(temp._data[f"{freq}_start"]))
|
|
189
|
-
& (temp._data[freq + "_start"] <= required[0])
|
|
190
|
-
& (
|
|
191
|
-
temp._data[freq + "_end"] + timedelta(seconds=temp.max_age)
|
|
192
|
-
>= required[1]
|
|
193
|
-
)
|
|
194
|
-
]
|
|
195
|
-
|
|
196
|
-
else:
|
|
197
|
-
# Make sure data exists on a certain day
|
|
198
|
-
temp._data = temp._data[
|
|
199
|
-
(~pd.isna(temp._data[f"{freq}_start"]))
|
|
200
|
-
& (temp._data[freq + "_start"] <= required)
|
|
201
|
-
& (
|
|
202
|
-
temp._data[freq + "_end"] + timedelta(seconds=temp.max_age)
|
|
203
|
-
>= required
|
|
204
|
-
)
|
|
205
|
-
]
|
|
206
|
-
|
|
207
|
-
return temp
|
|
208
|
-
|
|
209
|
-
def convert(self, units: dict) -> "Stations":
|
|
210
|
-
"""
|
|
211
|
-
Convert columns to a different unit
|
|
212
|
-
"""
|
|
213
|
-
|
|
214
|
-
# Create temporal instance
|
|
215
|
-
temp = copy(self)
|
|
216
|
-
|
|
217
|
-
# Change data units
|
|
218
|
-
for parameter, unit in units.items():
|
|
219
|
-
if parameter in temp._data.columns.values:
|
|
220
|
-
temp._data[parameter] = temp._data[parameter].apply(unit)
|
|
221
|
-
|
|
222
|
-
# Return class instance
|
|
223
|
-
return temp
|
|
224
|
-
|
|
225
|
-
def count(self) -> int:
|
|
226
|
-
"""
|
|
227
|
-
Return number of weather stations in current selection
|
|
228
|
-
"""
|
|
229
|
-
|
|
230
|
-
return len(self._data.index)
|
|
231
|
-
|
|
232
|
-
def fetch(self, limit: int = None, sample: bool = False) -> pd.DataFrame:
|
|
233
|
-
"""
|
|
234
|
-
Fetch all weather stations or a (sampled) subset
|
|
235
|
-
"""
|
|
236
|
-
|
|
237
|
-
# Copy DataFrame
|
|
238
|
-
temp = copy(self._data)
|
|
239
|
-
|
|
240
|
-
# Return limited number of sampled entries
|
|
241
|
-
if sample and limit:
|
|
242
|
-
return temp.sample(limit)
|
|
243
|
-
|
|
244
|
-
# Return limited number of entries
|
|
245
|
-
if limit:
|
|
246
|
-
return temp.head(limit)
|
|
247
|
-
|
|
248
|
-
# Return all entries
|
|
249
|
-
return temp
|
|
250
|
-
|
|
251
|
-
# Import additional methods
|
|
252
|
-
from meteostat.core.cache import clear_cache
|