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
@@ -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
@@ -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
@@ -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