meteostat 1.7.6__py3-none-any.whl → 2.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. meteostat/__init__.py +38 -19
  2. meteostat/api/config.py +158 -0
  3. meteostat/api/daily.py +76 -0
  4. meteostat/api/hourly.py +80 -0
  5. meteostat/api/interpolate.py +378 -0
  6. meteostat/api/inventory.py +59 -0
  7. meteostat/api/merge.py +103 -0
  8. meteostat/api/monthly.py +73 -0
  9. meteostat/api/normals.py +144 -0
  10. meteostat/api/point.py +30 -0
  11. meteostat/api/stations.py +234 -0
  12. meteostat/api/timeseries.py +334 -0
  13. meteostat/core/cache.py +212 -59
  14. meteostat/core/data.py +203 -0
  15. meteostat/core/logger.py +9 -0
  16. meteostat/core/network.py +82 -0
  17. meteostat/core/parameters.py +112 -0
  18. meteostat/core/providers.py +184 -0
  19. meteostat/core/schema.py +170 -0
  20. meteostat/core/validator.py +38 -0
  21. meteostat/enumerations.py +149 -0
  22. meteostat/interpolation/idw.py +120 -0
  23. meteostat/interpolation/lapserate.py +91 -0
  24. meteostat/interpolation/nearest.py +31 -0
  25. meteostat/parameters.py +354 -0
  26. meteostat/providers/dwd/climat.py +166 -0
  27. meteostat/providers/dwd/daily.py +144 -0
  28. meteostat/providers/dwd/hourly.py +218 -0
  29. meteostat/providers/dwd/monthly.py +138 -0
  30. meteostat/providers/dwd/mosmix.py +351 -0
  31. meteostat/providers/dwd/poi.py +117 -0
  32. meteostat/providers/dwd/shared.py +155 -0
  33. meteostat/providers/eccc/daily.py +87 -0
  34. meteostat/providers/eccc/hourly.py +104 -0
  35. meteostat/providers/eccc/monthly.py +66 -0
  36. meteostat/providers/eccc/shared.py +45 -0
  37. meteostat/providers/index.py +496 -0
  38. meteostat/providers/meteostat/daily.py +65 -0
  39. meteostat/providers/meteostat/daily_derived.py +110 -0
  40. meteostat/providers/meteostat/hourly.py +66 -0
  41. meteostat/providers/meteostat/monthly.py +45 -0
  42. meteostat/providers/meteostat/monthly_derived.py +106 -0
  43. meteostat/providers/meteostat/shared.py +93 -0
  44. meteostat/providers/metno/forecast.py +186 -0
  45. meteostat/providers/noaa/ghcnd.py +228 -0
  46. meteostat/providers/noaa/isd_lite.py +142 -0
  47. meteostat/providers/noaa/metar.py +163 -0
  48. meteostat/typing.py +113 -0
  49. meteostat/utils/conversions.py +231 -0
  50. meteostat/utils/data.py +194 -0
  51. meteostat/utils/geo.py +28 -0
  52. meteostat/utils/guards.py +51 -0
  53. meteostat/utils/parsers.py +161 -0
  54. meteostat/utils/types.py +113 -0
  55. meteostat/utils/validators.py +31 -0
  56. meteostat-2.0.1.dist-info/METADATA +130 -0
  57. meteostat-2.0.1.dist-info/RECORD +64 -0
  58. {meteostat-1.7.6.dist-info → meteostat-2.0.1.dist-info}/WHEEL +1 -2
  59. meteostat/core/loader.py +0 -103
  60. meteostat/core/warn.py +0 -34
  61. meteostat/enumerations/granularity.py +0 -22
  62. meteostat/interface/base.py +0 -39
  63. meteostat/interface/daily.py +0 -118
  64. meteostat/interface/hourly.py +0 -154
  65. meteostat/interface/meteodata.py +0 -210
  66. meteostat/interface/monthly.py +0 -109
  67. meteostat/interface/normals.py +0 -245
  68. meteostat/interface/point.py +0 -143
  69. meteostat/interface/stations.py +0 -252
  70. meteostat/interface/timeseries.py +0 -237
  71. meteostat/series/aggregate.py +0 -48
  72. meteostat/series/convert.py +0 -28
  73. meteostat/series/count.py +0 -17
  74. meteostat/series/coverage.py +0 -20
  75. meteostat/series/fetch.py +0 -28
  76. meteostat/series/interpolate.py +0 -47
  77. meteostat/series/normalize.py +0 -76
  78. meteostat/series/stations.py +0 -22
  79. meteostat/units.py +0 -149
  80. meteostat/utilities/__init__.py +0 -0
  81. meteostat/utilities/aggregations.py +0 -37
  82. meteostat/utilities/endpoint.py +0 -33
  83. meteostat/utilities/helpers.py +0 -70
  84. meteostat/utilities/mutations.py +0 -89
  85. meteostat/utilities/validations.py +0 -30
  86. meteostat-1.7.6.dist-info/METADATA +0 -112
  87. meteostat-1.7.6.dist-info/RECORD +0 -39
  88. meteostat-1.7.6.dist-info/top_level.txt +0 -1
  89. /meteostat/{core → api}/__init__.py +0 -0
  90. /meteostat/{enumerations → interpolation}/__init__.py +0 -0
  91. /meteostat/{interface → providers}/__init__.py +0 -0
  92. /meteostat/{interface/interpolate.py → py.typed} +0 -0
  93. /meteostat/{series → utils}/__init__.py +0 -0
  94. {meteostat-1.7.6.dist-info → meteostat-2.0.1.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,117 @@
1
+ from typing import Optional, Union
2
+ from urllib.error import HTTPError
3
+
4
+ import pandas as pd
5
+
6
+ from meteostat.core.cache import cache_service
7
+ from meteostat.core.logger import logger
8
+ from meteostat.enumerations import TTL, Parameter
9
+ from meteostat.typing import ProviderRequest
10
+ from meteostat.utils.conversions import percentage_to_okta
11
+
12
+ ENDPOINT = "https://opendata.dwd.de/weather/weather_reports/poi/{station}-BEOB.csv"
13
+ # TODO: add col 11 for solar radiation support (Globalstrahlung)
14
+ USECOLS = [0, 1, 2, 9, 14, 21, 22, 23, 33, 35, 36, 37, 40, 41]
15
+ NAMES = {
16
+ "Wolkenbedeckung": Parameter.CLDC,
17
+ "Temperatur (2m)": Parameter.TEMP,
18
+ "Sichtweite": Parameter.VSBY,
19
+ "Windgeschwindigkeit": Parameter.WSPD,
20
+ "Windboen (letzte Stunde)": Parameter.WPGT,
21
+ "Niederschlag (letzte Stunde)": Parameter.PRCP,
22
+ "Relative Feuchte": Parameter.RHUM,
23
+ "Windrichtung": Parameter.WDIR,
24
+ "Druck (auf Meereshoehe)": Parameter.PRES,
25
+ "Sonnenscheindauer (letzte Stunde)": Parameter.TSUN,
26
+ "aktuelles Wetter": Parameter.COCO,
27
+ "Schneehoehe": Parameter.SNWD,
28
+ }
29
+ COCO_MAP = {
30
+ "1": 1,
31
+ "2": 2,
32
+ "3": 3,
33
+ "4": 4,
34
+ "5": 5,
35
+ "6": 6,
36
+ "7": 7,
37
+ "8": 8,
38
+ "9": 9,
39
+ "10": 10,
40
+ "11": 11,
41
+ "12": 12,
42
+ "13": 13,
43
+ "14": 14,
44
+ "15": 15,
45
+ "16": 16,
46
+ "17": 24,
47
+ "18": 17,
48
+ "19": 18,
49
+ "20": 19,
50
+ "21": 20,
51
+ "22": 21,
52
+ "23": 22,
53
+ "24": 19,
54
+ "25": 20,
55
+ "26": 23,
56
+ "27": 25,
57
+ "28": 26,
58
+ "29": 25,
59
+ "30": 26,
60
+ "31": 27,
61
+ }
62
+
63
+
64
+ def get_coco(code: str | int) -> Union[int, None]:
65
+ """
66
+ Map DWD POI weather condition codes to Meteostat condicodes
67
+ """
68
+ return COCO_MAP.get(str(code))
69
+
70
+
71
+ @cache_service.cache(TTL.HOUR, "pickle")
72
+ def get_df(station: str) -> Optional[pd.DataFrame]:
73
+ try:
74
+ # Read CSV data from DWD server
75
+ df = pd.read_csv( # type: ignore
76
+ ENDPOINT.format(station=station),
77
+ sep=";",
78
+ skiprows=2,
79
+ na_values="---",
80
+ usecols=USECOLS,
81
+ decimal=",",
82
+ )
83
+
84
+ # Rename columns
85
+ df = df.rename(columns=NAMES)
86
+
87
+ # Snow cm -> mm
88
+ df[Parameter.SNWD] = df[Parameter.SNWD].multiply(10)
89
+ df[Parameter.VSBY] = df[Parameter.VSBY].multiply(1000)
90
+
91
+ # Change coco
92
+ df[Parameter.COCO] = df[Parameter.COCO].apply(get_coco)
93
+ df[Parameter.CLDC] = df[Parameter.CLDC].apply(percentage_to_okta)
94
+
95
+ # Set index
96
+ df["time"] = pd.to_datetime(
97
+ df["Datum"] + " " + df["Uhrzeit (UTC)"], format="%d.%m.%y %H:%M"
98
+ )
99
+ df = df.set_index(["time"])
100
+ df = df.drop(["Datum", "Uhrzeit (UTC)"], axis=1)
101
+
102
+ return df
103
+
104
+ except HTTPError as error:
105
+ logger.info(
106
+ f"Couldn't load DWD POI data for weather station {station} (status: {error.status})"
107
+ )
108
+ return None
109
+
110
+ except Exception as error:
111
+ logger.warning(error)
112
+ return None
113
+
114
+
115
+ def fetch(req: ProviderRequest) -> Optional[pd.DataFrame]:
116
+ if "wmo" in req.station.identifiers:
117
+ return get_df(req.station.identifiers["wmo"])
@@ -0,0 +1,155 @@
1
+ from typing import Union
2
+ from ftplib import FTP
3
+
4
+ from meteostat.api.config import config
5
+
6
+
7
+ DWD_FTP_SERVER = config.dwd_ftp_host
8
+
9
+
10
+ def get_ftp_connection() -> FTP:
11
+ """
12
+ Get DWD Open Data FTP connection
13
+ """
14
+ dwd_ftp_connection = FTP(DWD_FTP_SERVER)
15
+ dwd_ftp_connection.login()
16
+ return dwd_ftp_connection
17
+
18
+
19
+ def get_condicode(code: str) -> Union[int, None]:
20
+ """
21
+ Map DWD codes to Meteostat condicodes
22
+
23
+ https://opendata.dwd.de/climate_environment/CDC/observations_germany/climate/hourly/weather_phenomena/recent/Wetter_Beschreibung.txt
24
+ """
25
+ condicodes = {
26
+ "0": 1,
27
+ "1": 2,
28
+ "2": 1,
29
+ "3": 3,
30
+ "11": 5,
31
+ "12": 5,
32
+ "13": 23,
33
+ "17": 25,
34
+ "21": 8,
35
+ "22": 15,
36
+ "23": 12,
37
+ "24": 10,
38
+ "25": 17,
39
+ "26": 21,
40
+ "27": 24,
41
+ "28": 5,
42
+ "29": 25,
43
+ "40": 5,
44
+ "41": 5,
45
+ "42": 5,
46
+ "43": 5,
47
+ "44": 5,
48
+ "45": 5,
49
+ "46": 5,
50
+ "47": 5,
51
+ "48": 5,
52
+ "49": 5,
53
+ "50": 7,
54
+ "51": 7,
55
+ "52": 8,
56
+ "53": 8,
57
+ "54": 9,
58
+ "55": 9,
59
+ "56": 10,
60
+ "57": 11,
61
+ "58": 7,
62
+ "59": 8,
63
+ "60": 7,
64
+ "61": 7,
65
+ "62": 8,
66
+ "63": 8,
67
+ "64": 9,
68
+ "65": 9,
69
+ "66": 10,
70
+ "67": 11,
71
+ "68": 12,
72
+ "69": 13,
73
+ "70": 14,
74
+ "71": 14,
75
+ "72": 15,
76
+ "73": 15,
77
+ "74": 16,
78
+ "75": 16,
79
+ "80": 17,
80
+ "81": 18,
81
+ "82": 18,
82
+ "83": 19,
83
+ "84": 20,
84
+ "85": 21,
85
+ "86": 22,
86
+ "87": 19,
87
+ "88": 20,
88
+ "89": 24,
89
+ "90": 24,
90
+ "91": 25,
91
+ "92": 26,
92
+ "93": 25,
93
+ "94": 26,
94
+ "95": 25,
95
+ "96": 25,
96
+ "97": 26,
97
+ "98": 25,
98
+ "99": 26,
99
+ "100": 3,
100
+ "101": 2,
101
+ "102": 3,
102
+ "103": 3,
103
+ "120": 5,
104
+ "123": 8,
105
+ "124": 15,
106
+ "125": 10,
107
+ "126": 25,
108
+ "130": 5,
109
+ "131": 5,
110
+ "132": 5,
111
+ "133": 5,
112
+ "134": 5,
113
+ "135": 6,
114
+ "150": 8,
115
+ "151": 7,
116
+ "152": 8,
117
+ "153": 9,
118
+ "154": 10,
119
+ "155": 10,
120
+ "156": 11,
121
+ "157": 7,
122
+ "158": 8,
123
+ "160": 8,
124
+ "161": 7,
125
+ "162": 8,
126
+ "163": 9,
127
+ "164": 10,
128
+ "165": 10,
129
+ "166": 11,
130
+ "167": 12,
131
+ "168": 13,
132
+ "170": 15,
133
+ "171": 14,
134
+ "172": 15,
135
+ "173": 16,
136
+ "177": 14,
137
+ "181": 7,
138
+ "182": 8,
139
+ "183": 9,
140
+ "184": 9,
141
+ "185": 14,
142
+ "186": 15,
143
+ "187": 16,
144
+ "189": 24,
145
+ "190": 25,
146
+ "191": 25,
147
+ "192": 25,
148
+ "193": 25,
149
+ "194": 26,
150
+ "195": 26,
151
+ "196": 26,
152
+ "199": 27,
153
+ }
154
+
155
+ return condicodes.get(str(code), None)
@@ -0,0 +1,87 @@
1
+ from datetime import datetime
2
+ from typing import Optional
3
+
4
+ import pandas as pd
5
+
6
+ from meteostat.enumerations import TTL, Parameter
7
+ from meteostat.core.cache import cache_service
8
+ from meteostat.core.network import network_service
9
+ from meteostat.providers.eccc.shared import ENDPOINT, get_meta_data
10
+ from meteostat.typing import ProviderRequest
11
+
12
+ BATCH_LIMIT = 9000
13
+ PROPERTIES = {
14
+ "LOCAL_DATE": "time",
15
+ "MAX_TEMPERATURE": Parameter.TMAX,
16
+ "MEAN_TEMPERATURE": Parameter.TEMP,
17
+ "MIN_TEMPERATURE": Parameter.TMIN,
18
+ "SPEED_MAX_GUST": Parameter.WPGT,
19
+ "TOTAL_PRECIPITATION": Parameter.PRCP,
20
+ "SNOW_ON_GROUND": Parameter.SNWD,
21
+ "TOTAL_SNOW": Parameter.SNOW,
22
+ }
23
+
24
+
25
+ @cache_service.cache(TTL.DAY, "pickle")
26
+ def get_df(climate_id: str, year: int) -> Optional[pd.DataFrame]:
27
+ # Process start & end date
28
+ # ECCC uses the station's local time zone
29
+ start = datetime(year, 1, 1, 0, 0, 0).strftime("%Y-%m-%dT%H:%M:%S")
30
+ end = datetime(year, 12, 31, 23, 59, 59).strftime("%Y-%m-%dT%H:%M:%S")
31
+
32
+ response = network_service.get(
33
+ f"{ENDPOINT}/collections/climate-daily/items",
34
+ params={
35
+ "CLIMATE_IDENTIFIER": climate_id,
36
+ "datetime": f"{start}/{end}",
37
+ "f": "json",
38
+ "properties": ",".join(PROPERTIES.keys()),
39
+ "limit": BATCH_LIMIT,
40
+ },
41
+ )
42
+
43
+ data = response.json()
44
+
45
+ # Extract features from the response
46
+ features = map(
47
+ lambda feature: feature["properties"] if "properties" in feature else {},
48
+ data.get("features", []),
49
+ )
50
+
51
+ # Create a DataFrame from the extracted features
52
+ df = pd.DataFrame(features)
53
+ df = df.rename(columns=PROPERTIES)
54
+
55
+ # Handle time column & set index
56
+ df["time"] = pd.to_datetime(df["time"])
57
+ df = df.set_index(["time"])
58
+
59
+ return df
60
+
61
+
62
+ def fetch(req: ProviderRequest) -> Optional[pd.DataFrame]:
63
+ if (
64
+ "national" not in req.station.identifiers
65
+ or req.start is None
66
+ or req.end is None
67
+ ):
68
+ return None
69
+
70
+ meta_data = get_meta_data(req.station.identifiers["national"])
71
+ climate_id = meta_data.get("CLIMATE_IDENTIFIER")
72
+ archive_first = meta_data.get("DLY_FIRST_DATE")
73
+ archive_last = meta_data.get("DLY_LAST_DATE")
74
+
75
+ if not (climate_id and archive_first and archive_last):
76
+ return None
77
+
78
+ archive_start = datetime.strptime(archive_first, "%Y-%m-%d %H:%M:%S")
79
+ archive_end = datetime.strptime(archive_last, "%Y-%m-%d %H:%M:%S")
80
+
81
+ years = range(
82
+ max(req.start.year, archive_start.year),
83
+ min(req.end.year, archive_end.year) + 1,
84
+ )
85
+ data = [get_df(climate_id, year) for year in years]
86
+
87
+ return pd.concat(data) if len(data) and not all(d is None for d in data) else None
@@ -0,0 +1,104 @@
1
+ from datetime import datetime
2
+ from typing import Optional
3
+
4
+ import pandas as pd
5
+ import pytz
6
+
7
+ from meteostat.enumerations import TTL, Parameter
8
+ from meteostat.core.cache import cache_service
9
+ from meteostat.core.network import network_service
10
+ from meteostat.providers.eccc.shared import ENDPOINT, get_meta_data
11
+ from meteostat.typing import ProviderRequest
12
+
13
+ BATCH_LIMIT = 9000
14
+ PROPERTIES = {
15
+ "UTC_DATE": "time",
16
+ "RELATIVE_HUMIDITY": Parameter.RHUM,
17
+ "WIND_DIRECTION": Parameter.WDIR,
18
+ "WIND_SPEED": Parameter.WSPD,
19
+ "VISIBILITY": Parameter.VSBY,
20
+ "PRECIP_AMOUNT": Parameter.PRCP,
21
+ "TEMP": Parameter.TEMP,
22
+ }
23
+
24
+
25
+ @cache_service.cache(TTL.DAY, "pickle")
26
+ def get_df(climate_id: str, year: int, tz: str) -> Optional[pd.DataFrame]:
27
+ # Process start & end date
28
+ # ECCC uses the station's local time zone
29
+ from_timezone = pytz.timezone("UTC")
30
+ to_timezone = pytz.timezone(tz)
31
+ start = (
32
+ from_timezone.localize(datetime(year, 1, 1, 0, 0, 0))
33
+ .astimezone(to_timezone)
34
+ .strftime("%Y-%m-%dT%H:%M:%S")
35
+ )
36
+ end = (
37
+ from_timezone.localize(datetime(year, 12, 31, 23, 59, 59))
38
+ .astimezone(to_timezone)
39
+ .strftime("%Y-%m-%dT%H:%M:%S")
40
+ )
41
+
42
+ response = network_service.get(
43
+ f"{ENDPOINT}/collections/climate-hourly/items",
44
+ params={
45
+ "CLIMATE_IDENTIFIER": climate_id,
46
+ "datetime": f"{start}/{end}",
47
+ "f": "json",
48
+ "properties": ",".join(PROPERTIES.keys()),
49
+ "limit": BATCH_LIMIT,
50
+ },
51
+ )
52
+
53
+ data = response.json()
54
+
55
+ # Extract features from the response
56
+ features = map(
57
+ lambda feature: feature["properties"] if "properties" in feature else {},
58
+ data.get("features", []),
59
+ )
60
+
61
+ # Create a DataFrame from the extracted features
62
+ df = pd.DataFrame(features)
63
+ df = df.rename(columns=PROPERTIES)
64
+
65
+ # Handle time column & set index
66
+ df["time"] = pd.to_datetime(df["time"])
67
+ df = df.set_index(["time"])
68
+
69
+ # Convert data units
70
+ df[Parameter.WDIR] = df[Parameter.WDIR] * 10 # Wind direction is provided 10's deg
71
+ df[Parameter.VSBY] = (
72
+ df[Parameter.VSBY] * 1000
73
+ ) # Visibility is provided in kilometres
74
+
75
+ return df
76
+
77
+
78
+ def fetch(req: ProviderRequest) -> Optional[pd.DataFrame]:
79
+ if (
80
+ "national" not in req.station.identifiers
81
+ or req.start is None
82
+ or req.end is None
83
+ ):
84
+ return None
85
+
86
+ meta_data = get_meta_data(req.station.identifiers["national"])
87
+ climate_id = meta_data.get("CLIMATE_IDENTIFIER")
88
+ archive_first = meta_data.get("HLY_FIRST_DATE")
89
+ archive_last = meta_data.get("HLY_LAST_DATE")
90
+ timezone = meta_data.get("TIMEZONE")
91
+
92
+ if not (climate_id and archive_first and archive_last and timezone):
93
+ return None
94
+
95
+ archive_start = datetime.strptime(archive_first, "%Y-%m-%d %H:%M:%S")
96
+ archive_end = datetime.strptime(archive_last, "%Y-%m-%d %H:%M:%S")
97
+
98
+ years = range(
99
+ max(req.start.year, archive_start.year),
100
+ min(req.end.year, archive_end.year) + 1,
101
+ )
102
+ data = [get_df(climate_id, year, timezone) for year in years]
103
+
104
+ return pd.concat(data) if len(data) and not all(d is None for d in data) else None
@@ -0,0 +1,66 @@
1
+ from typing import Optional
2
+
3
+ import pandas as pd
4
+
5
+ from meteostat.enumerations import TTL, Parameter
6
+ from meteostat.core.cache import cache_service
7
+ from meteostat.core.network import network_service
8
+ from meteostat.providers.eccc.shared import ENDPOINT, get_meta_data
9
+ from meteostat.typing import ProviderRequest
10
+
11
+ BATCH_LIMIT = 9000
12
+ PROPERTIES = {
13
+ "LOCAL_DATE": "time",
14
+ "MAX_TEMPERATURE": Parameter.TXMX,
15
+ "MEAN_TEMPERATURE": Parameter.TEMP,
16
+ "MIN_TEMPERATURE": Parameter.TXMN,
17
+ "TOTAL_PRECIPITATION": Parameter.PRCP,
18
+ "DAYS_WITH_PRECIP_GE_1MM": Parameter.PDAY,
19
+ "TOTAL_SNOWFALL": Parameter.SNOW,
20
+ }
21
+
22
+
23
+ @cache_service.cache(TTL.WEEK, "pickle")
24
+ def get_df(
25
+ climate_id: str,
26
+ ) -> Optional[pd.DataFrame]:
27
+ response = network_service.get(
28
+ f"{ENDPOINT}/collections/climate-monthly/items",
29
+ params={
30
+ "CLIMATE_IDENTIFIER": climate_id,
31
+ "f": "json",
32
+ "properties": ",".join(PROPERTIES.keys()),
33
+ "limit": BATCH_LIMIT,
34
+ },
35
+ )
36
+
37
+ data = response.json()
38
+
39
+ # Extract features from the response
40
+ features = map(
41
+ lambda feature: feature["properties"] if "properties" in feature else {},
42
+ data.get("features", []),
43
+ )
44
+
45
+ # Create a DataFrame from the extracted features
46
+ df = pd.DataFrame(features)
47
+ df = df.rename(columns=PROPERTIES)
48
+
49
+ # Handle time column & set index
50
+ df["time"] = pd.to_datetime(df["time"])
51
+ df = df.set_index(["time"])
52
+
53
+ return df
54
+
55
+
56
+ def fetch(req: ProviderRequest) -> Optional[pd.DataFrame]:
57
+ if "national" not in req.station.identifiers:
58
+ return None
59
+
60
+ meta_data = get_meta_data(req.station.identifiers["national"])
61
+ climate_id = meta_data.get("CLIMATE_IDENTIFIER")
62
+
63
+ if not climate_id:
64
+ return None
65
+
66
+ return get_df(climate_id)
@@ -0,0 +1,45 @@
1
+ from typing import Optional
2
+
3
+ from meteostat.enumerations import TTL
4
+ from meteostat.core.cache import cache_service
5
+ from meteostat.core.network import network_service
6
+ from meteostat.core.logger import logger
7
+
8
+
9
+ ENDPOINT = "https://api.weather.gc.ca"
10
+
11
+
12
+ @cache_service.cache(TTL.WEEK)
13
+ def get_meta_data(station: str) -> Optional[dict]:
14
+ response = network_service.get(
15
+ f"{ENDPOINT}/collections/climate-stations/items",
16
+ params={
17
+ "STN_ID": station,
18
+ "properties": ",".join(
19
+ [
20
+ "CLIMATE_IDENTIFIER",
21
+ "TIMEZONE",
22
+ "HLY_FIRST_DATE",
23
+ "HLY_LAST_DATE",
24
+ "DLY_FIRST_DATE",
25
+ "DLY_LAST_DATE",
26
+ "MLY_FIRST_DATE",
27
+ "MLY_LAST_DATE",
28
+ ]
29
+ ),
30
+ "f": "json",
31
+ },
32
+ )
33
+
34
+ if response.status_code == 200:
35
+ data = response.json()
36
+ try:
37
+ return data["features"][0]["properties"]
38
+ except (IndexError, KeyError):
39
+ logger.info(f"ECCC climate identifier for station {station} not found")
40
+ else:
41
+ logger.warning(
42
+ f"ECCC climate identifier for station {station} not found (status: {response.status_code})"
43
+ )
44
+
45
+ return None