env_canada 0.15.0__tar.gz

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.
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2018 The Python Packaging Authority
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE
@@ -0,0 +1,2 @@
1
+ include env_canada/10x20.pbm
2
+ include env_canada/10x20.pil
@@ -0,0 +1,342 @@
1
+ Metadata-Version: 2.4
2
+ Name: env_canada
3
+ Version: 0.15.0
4
+ Summary: A package to access meteorological data from Environment Canada
5
+ Author-email: Michael Davie <michael.davie@gmail.com>
6
+ Maintainer-email: Michael Davie <michael.davie@gmail.com>
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://github.com/michaeldavie/env_canada
9
+ Project-URL: Documentation, https://github.com/michaeldavie/env_canada
10
+ Project-URL: Repository, https://github.com/michaeldavie/env_canada
11
+ Project-URL: Issues, https://github.com/michaeldavie/env_canada/issues
12
+ Project-URL: Changelog, https://github.com/michaeldavie/env_canada/blob/main/CHANGELOG.md
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Operating System :: OS Independent
15
+ Requires-Python: >=3.11
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: aiohttp>=3.9.0
19
+ Requires-Dist: geopy>=2.3.0
20
+ Requires-Dist: lxml>=5.3.0
21
+ Requires-Dist: pandas>=2.2.3
22
+ Requires-Dist: Pillow>=10.0.1
23
+ Requires-Dist: python-dateutil>=2.9
24
+ Requires-Dist: voluptuous>=0.15.2
25
+ Dynamic: license-file
26
+
27
+ # Environment Canada (env_canada)
28
+
29
+ [![PyPI version](https://badge.fury.io/py/env-canada.svg)](https://badge.fury.io/py/env-canada)
30
+ [![Snyk rating](https://snyk-widget.herokuapp.com/badge/pip/env-canada/badge.svg)](https://snyk.io/vuln/pip:env-canada@0.8.0?utm_source=badge)
31
+ [![Python Lint and Test](../..//actions/workflows/python-app.yml/badge.svg)](../../actions/workflows/python-app.yml)
32
+
33
+ This package provides access to various data sources published by [Environment and Climate Change Canada](https://www.canada.ca/en/environment-climate-change.html).
34
+
35
+ > [!IMPORTANT]
36
+ > If you're using the library in a Jupyter notebook, replace `asyncio.run(...)` with `await ...` in the examples below. For example:
37
+ >
38
+ > ```python
39
+ > asyncio.run(ec_en.update())
40
+ > ```
41
+ >
42
+ > becomes
43
+ >
44
+ > ```python
45
+ > await ec_en.update()
46
+ > ```
47
+
48
+ ## Weather Observations and Forecasts
49
+
50
+ `ECWeather` provides current conditions and forecasts. It automatically determines which weather station to use based on latitude/longitude provided. It is also possible to specify a station code in multiple flexible formats:
51
+
52
+ - **Full format**: `"AB/s0000123"` (province code and full station ID)
53
+ - **Station ID only**: `"s0000123"` (station ID without province - province is resolved automatically)
54
+ - **Numeric only**: `"123"` (just the station number - province is resolved automatically)
55
+
56
+ Station codes are based on those listed in [this CSV file](https://dd.weather.gc.ca/today/citypage_weather/docs/site_list_towns_en.csv). For example:
57
+
58
+ ```python
59
+ import asyncio
60
+
61
+ from env_canada import ECWeather
62
+
63
+ # Using coordinates (automatic station selection)
64
+ ec_coords = ECWeather(coordinates=(50, -100))
65
+
66
+ # Using station ID - multiple formats supported:
67
+ ec_full = ECWeather(station_id="ON/s0000430", language="french") # Full format
68
+ ec_station = ECWeather(station_id="s0000430") # Station ID only
69
+ ec_numeric = ECWeather(station_id="430") # Numeric only
70
+
71
+ asyncio.run(ec_coords.update())
72
+
73
+ # current conditions
74
+ ec_coords.conditions
75
+
76
+ # daily forecasts
77
+ ec_coords.daily_forecasts
78
+
79
+ # hourly forecasts
80
+ ec_coords.hourly_forecasts
81
+
82
+ # alerts (categorised dict with warnings, watches, advisories, statements, endings)
83
+ ec_coords.alerts
84
+
85
+ # raw WFS alert feature properties (includes text, area, confidence, impact, etc.)
86
+ ec_coords.alert_features
87
+ ```
88
+
89
+ > [!NOTE]
90
+ > As of version 0.11.0, `ECWeather` automatically handles Environment Canada's new timestamped weather file URL structure (effective June 2025). The library dynamically discovers the most recent weather files, ensuring continued functionality during Environment Canada's infrastructure changes.
91
+
92
+ ## Weather Alerts
93
+
94
+ `ECAlerts` provides direct access to Environment Canada weather alerts via the GeoMet WFS `Current-Alerts` layer. It returns richer data than the alerts embedded in `ECWeather`, including full bilingual alert text, affected area names, risk colour, confidence, and impact. `ECWeather.update()` uses `ECAlerts` internally, so both `ECWeather.alerts` and `ECWeather.alert_features` are automatically populated.
95
+
96
+ Use `ECAlerts` directly when you only need alerts (no weather conditions or forecasts):
97
+
98
+ ```python
99
+ import asyncio
100
+
101
+ from env_canada import ECAlerts
102
+
103
+ ec_alerts = ECAlerts(coordinates=(50, -100))
104
+
105
+ asyncio.run(ec_alerts.update())
106
+
107
+ # categorised alerts dict — same structure as ECWeather.alerts
108
+ # keys: warnings, watches, advisories, statements, endings
109
+ ec_alerts.alerts
110
+
111
+ # list of raw WFS feature property dicts, one per alert polygon
112
+ ec_alerts.alert_features
113
+ ```
114
+
115
+ Each entry in `alerts[category]["value"]` is a dict with these keys:
116
+
117
+ | Key | Description |
118
+ | ------------------ | ----------------------------------------- |
119
+ | `title` | Alert name (title-cased) |
120
+ | `date` | Publication datetime (ISO 8601 UTC) |
121
+ | `expiryTime` | Expiration datetime (ISO 8601 UTC) |
122
+ | `alertColourLevel` | Risk colour (e.g. `"Yellow"`, `"Red"`) |
123
+ | `text` | Full alert text |
124
+ | `area` | Affected geographic area name |
125
+ | `status` | Alert status (e.g. `"active"`, `"ended"`) |
126
+ | `confidence` | Confidence level (e.g. `"Likely"`) |
127
+ | `impact` | Impact level (e.g. `"High"`) |
128
+ | `alert_code` | Short alert type code (e.g. `"WS"`) |
129
+
130
+ ## Weather Radar
131
+
132
+ `ECRadar` provides Environment Canada meteorological [radar imagery](https://weather.gc.ca/radar/index_e.html).
133
+
134
+ ```python
135
+ import asyncio
136
+
137
+ from env_canada import ECRadar
138
+
139
+ radar_coords = ECRadar(coordinates=(50, -100))
140
+
141
+ # Conditions Available
142
+ animated_gif = asyncio.run(radar_coords.get_loop())
143
+ latest_png = asyncio.run(radar_coords.get_latest_frame())
144
+ ```
145
+
146
+ ## Weather Maps
147
+
148
+ `ECMap` provides Environment Canada WMS weather map imagery with support for various meteorological layers.
149
+
150
+ ```python
151
+ import asyncio
152
+
153
+ from env_canada import ECMap
154
+
155
+ # Create a map with rain radar layer
156
+ map_coords = ECMap(coordinates=(50, -100), layer="rain")
157
+
158
+ # Get the latest image with the specified layer
159
+ latest_png = asyncio.run(map_coords.get_latest_frame())
160
+
161
+ # Get an animated GIF with the specified layer
162
+ animated_gif = asyncio.run(map_coords.get_loop())
163
+
164
+ # Customize the map appearance
165
+ custom_map = ECMap(
166
+ coordinates=(50, -100),
167
+ layer="snow",
168
+ width=1200,
169
+ height=800,
170
+ radius=300,
171
+ layer_opacity=80,
172
+ legend=True,
173
+ timestamp=True,
174
+ language="french",
175
+ )
176
+ ```
177
+
178
+ Available layers include:
179
+
180
+ - `rain`: Precipitation rain radar
181
+ - `snow`: Precipitation snow radar
182
+ - `precip_type`: Surface precipitation type
183
+
184
+ Additional configuration options:
185
+
186
+ - `width`/`height`: Image dimensions (default: 800x800)
187
+ - `radius`: Map radius in km around coordinates (default: 200km)
188
+ - `layer_opacity`: Layer transparency 0-100% (default: 65%)
189
+ - `legend`: Show legend (default: True)
190
+ - `timestamp`: Show timestamp (default: True)
191
+ - `language`: "english" or "french" (default: "english")
192
+
193
+ > **Note**: ECMap automatically discovers available legend styles from Environment Canada's WMS capabilities, ensuring compatibility with any future style changes.
194
+
195
+ ## Air Quality Health Index (AQHI)
196
+
197
+ `ECAirQuality` provides Environment Canada [air quality](https://weather.gc.ca/airquality/pages/index_e.html) data.
198
+
199
+ ```python
200
+ import asyncio
201
+
202
+ from env_canada import ECAirQuality
203
+
204
+ aqhi_coords = ECAirQuality(coordinates=(50, -100))
205
+
206
+ asyncio.run(aqhi_coords.update())
207
+
208
+ # Data available
209
+ aqhi_coords.current
210
+ aqhi_coords.forecasts
211
+ ```
212
+
213
+ ## Water Level and Flow
214
+
215
+ `ECHydro` provides Environment Canada [hydrometric](https://wateroffice.ec.gc.ca/mainmenu/real_time_data_index_e.html) data.
216
+
217
+ ```python
218
+ import asyncio
219
+
220
+ from env_canada import ECHydro
221
+
222
+ hydro_coords = ECHydro(coordinates=(50, -100))
223
+
224
+ asyncio.run(hydro_coords.update())
225
+
226
+ # Data available
227
+ hydro_coords.measurements
228
+ ```
229
+
230
+ ## Historical Weather Data
231
+
232
+ `ECHistorical` provides historical daily weather data.
233
+ The ECHistorical object is instantiated with a station ID, year, language, format (one of xml or csv) and granularity (hourly, daily data).
234
+ Once updated asynchronously, historical weather data is contained with the `station_data` property. If `xml` is requested, `station_data` will appear in a dictionary form. If `csv` is requested, `station_data` will contain a CSV-readable buffer. For example:
235
+
236
+ ```python
237
+ import asyncio
238
+
239
+ from env_canada import ECHistorical
240
+ from env_canada.ec_historical import get_historical_stations
241
+
242
+ # search for stations, response contains station_ids
243
+ coordinates = [53.916944, -122.749444] # [lat, long]
244
+
245
+ # coordinates: [lat, long]
246
+ # radius: km
247
+ # limit: response limit, value one of [10, 25, 50, 100]
248
+ # The result contains station names and ID values.
249
+ stations = asyncio.run(get_historical_stations(coordinates, radius=200, limit=100))
250
+
251
+ ec_en_xml = ECHistorical(station_id=31688, year=2020, language="english", format="xml")
252
+ ec_fr_xml = ECHistorical(station_id=31688, year=2020, language="french", format="xml")
253
+ ec_en_csv = ECHistorical(station_id=31688, year=2020, language="english", format="csv")
254
+ ec_fr_csv = ECHistorical(station_id=31688, year=2020, language="french", format="csv")
255
+
256
+ # timeframe argument can be passed to change the granularity
257
+ # timeframe=1 hourly (need to create of for every month in that case, use ECHistoricalRange to handle it automatically)
258
+ # timeframe=2 daily (default)
259
+ ec_en_xml = ECHistorical(
260
+ station_id=31688, year=2020, month=1, language="english", format="xml", timeframe=1
261
+ )
262
+ ec_en_csv = ECHistorical(
263
+ station_id=31688, year=2020, month=1, language="english", format="csv", timeframe=1
264
+ )
265
+
266
+ asyncio.run(ec_en_xml.update())
267
+ asyncio.run(ec_en_csv.update())
268
+
269
+ # metadata describing the station
270
+ ec_en_xml.metadata
271
+
272
+ # historical weather data, in dictionary form
273
+ ec_en_xml.station_data
274
+
275
+ # csv-generated responses return csv-like station data
276
+ import pandas as pd
277
+
278
+ df = pd.read_csv(ec_en_csv.station_data)
279
+ ```
280
+
281
+ `ECHistoricalRange` provides historical weather data within a specific range and handles the update by itself.
282
+
283
+ The ECHistoricalRange object is instantiated with at least a station ID and a daterange.
284
+ One could add language, and granularity (hourly, daily (default)).
285
+
286
+ The data can then be used as pandas DataFrame, XML (requires pandas >=1.3.0) and csv
287
+
288
+ For example :
289
+
290
+ ```python
291
+ import pandas as pd
292
+ import asyncio
293
+ from env_canada import ECHistoricalRange
294
+ from env_canada.ec_historical import get_historical_stations
295
+ from datetime import datetime
296
+
297
+ coordinates = ["48.508333", "-68.467667"]
298
+
299
+ stations = pd.DataFrame(
300
+ asyncio.run(
301
+ get_historical_stations(
302
+ coordinates, start_year=2022, end_year=2022, radius=200, limit=100
303
+ )
304
+ )
305
+ ).T
306
+
307
+ ec = ECHistoricalRange(
308
+ station_id=int(stations.iloc[0, 2]),
309
+ timeframe="daily",
310
+ daterange=(datetime(2022, 7, 1, 12, 12), datetime(2022, 8, 1, 12, 12)),
311
+ )
312
+
313
+ ec.get_data()
314
+
315
+ # yield an XML formated str.
316
+ # For more options, use ec.to_xml(*arg, **kwargs) with pandas options
317
+ ec.xml
318
+
319
+ # yield an CSV formated str.
320
+ # For more options, use ec.to_csv(*arg, **kwargs) with pandas options
321
+ ec.csv
322
+ ```
323
+
324
+ In this example `ec.df` will be:
325
+
326
+ | Date/Time | Longitude (x) | Latitude (y) | Station Name | Climate ID | Year | Month | Day | Data Quality | Max Temp (°C) | Max Temp Flag | Min Temp (°C) | Min Temp Flag | Mean Temp (°C) | Mean Temp Flag | Heat Deg Days (°C) | Heat Deg Days Flag | Cool Deg Days (°C) | Cool Deg Days Flag | Total Rain (mm) | Total Rain Flag | Total Snow (cm) | Total Snow Flag | Total Precip (mm) | Total Precip Flag | Snow on Grnd (cm) | Snow on Grnd Flag | Dir of Max Gust (10s deg) | Dir of Max Gust Flag | Spd of Max Gust (km/h) | Spd of Max Gust Flag | |
327
+ | ---------- | ------------- | ------------ | --------------------- | ---------- | ---- | ----- | --- | ------------ | -------------- | ------------- | -------------- | ------------- | --------------- | -------------- | ------------------- | ------------------ | ------------------- | ------------------ | --------------- | --------------- | --------------- | --------------- | ----------------- | ----------------- | ----------------- | ----------------- | ------------------------- | -------------------- | ---------------------- | -------------------- | --- |
328
+ | 2022-07-02 | -68,47 | 48,51 | POINTE-AU-PERE (INRS) | 7056068 | 2022 | 7 | 2 | | 22,8 | | 12,5 | | 17,7 | | 0,3 | | 0 | | | | | | 0 | | | | 26 | | 37 | | |
329
+ | 2022-07-03 | -68,47 | 48,51 | POINTE-AU-PERE (INRS) | 7056068 | 2022 | 7 | 3 | | 21,7 | | 10,1 | | 15,9 | | 2,1 | | 0 | | | | | | 0,4 | | | | 28 | | 50 | | |
330
+ | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … |
331
+ | 2022-07-31 | -68,47 | 48,51 | POINTE-AU-PERE (INRS) | 7056068 | 2022 | 7 | 31 | | 23,5 | | 14,1 | | 18,8 | | 0 | | 0,8 | | | | | | 0 | | | | 23 | | 31 | | |
332
+ | 2022-08-01 | -68,47 | 48,51 | POINTE-AU-PERE (INRS) | 7056068 | 2022 | 8 | 1 | | 23 | | 15 | | 19 | | 0 | | 1 | | | | | | 0 | | | | 21 | | 35 | | |
333
+
334
+ One should note that july 1st is excluded as the time provided contains specific hours, so it yields only data after or at exactly
335
+ the time provided.
336
+
337
+ To have all the july 1st data in that case, one can provide a datarange without time: `datetime(2022, 7, 7)` instead
338
+ of `datetime(2022, 7, 1, 12, 12)`
339
+
340
+ # License
341
+
342
+ The code is available under terms of [MIT License](https://github.com/michaeldavie/env_canada/tree/main/LICENSE.md)
@@ -0,0 +1,316 @@
1
+ # Environment Canada (env_canada)
2
+
3
+ [![PyPI version](https://badge.fury.io/py/env-canada.svg)](https://badge.fury.io/py/env-canada)
4
+ [![Snyk rating](https://snyk-widget.herokuapp.com/badge/pip/env-canada/badge.svg)](https://snyk.io/vuln/pip:env-canada@0.8.0?utm_source=badge)
5
+ [![Python Lint and Test](../..//actions/workflows/python-app.yml/badge.svg)](../../actions/workflows/python-app.yml)
6
+
7
+ This package provides access to various data sources published by [Environment and Climate Change Canada](https://www.canada.ca/en/environment-climate-change.html).
8
+
9
+ > [!IMPORTANT]
10
+ > If you're using the library in a Jupyter notebook, replace `asyncio.run(...)` with `await ...` in the examples below. For example:
11
+ >
12
+ > ```python
13
+ > asyncio.run(ec_en.update())
14
+ > ```
15
+ >
16
+ > becomes
17
+ >
18
+ > ```python
19
+ > await ec_en.update()
20
+ > ```
21
+
22
+ ## Weather Observations and Forecasts
23
+
24
+ `ECWeather` provides current conditions and forecasts. It automatically determines which weather station to use based on latitude/longitude provided. It is also possible to specify a station code in multiple flexible formats:
25
+
26
+ - **Full format**: `"AB/s0000123"` (province code and full station ID)
27
+ - **Station ID only**: `"s0000123"` (station ID without province - province is resolved automatically)
28
+ - **Numeric only**: `"123"` (just the station number - province is resolved automatically)
29
+
30
+ Station codes are based on those listed in [this CSV file](https://dd.weather.gc.ca/today/citypage_weather/docs/site_list_towns_en.csv). For example:
31
+
32
+ ```python
33
+ import asyncio
34
+
35
+ from env_canada import ECWeather
36
+
37
+ # Using coordinates (automatic station selection)
38
+ ec_coords = ECWeather(coordinates=(50, -100))
39
+
40
+ # Using station ID - multiple formats supported:
41
+ ec_full = ECWeather(station_id="ON/s0000430", language="french") # Full format
42
+ ec_station = ECWeather(station_id="s0000430") # Station ID only
43
+ ec_numeric = ECWeather(station_id="430") # Numeric only
44
+
45
+ asyncio.run(ec_coords.update())
46
+
47
+ # current conditions
48
+ ec_coords.conditions
49
+
50
+ # daily forecasts
51
+ ec_coords.daily_forecasts
52
+
53
+ # hourly forecasts
54
+ ec_coords.hourly_forecasts
55
+
56
+ # alerts (categorised dict with warnings, watches, advisories, statements, endings)
57
+ ec_coords.alerts
58
+
59
+ # raw WFS alert feature properties (includes text, area, confidence, impact, etc.)
60
+ ec_coords.alert_features
61
+ ```
62
+
63
+ > [!NOTE]
64
+ > As of version 0.11.0, `ECWeather` automatically handles Environment Canada's new timestamped weather file URL structure (effective June 2025). The library dynamically discovers the most recent weather files, ensuring continued functionality during Environment Canada's infrastructure changes.
65
+
66
+ ## Weather Alerts
67
+
68
+ `ECAlerts` provides direct access to Environment Canada weather alerts via the GeoMet WFS `Current-Alerts` layer. It returns richer data than the alerts embedded in `ECWeather`, including full bilingual alert text, affected area names, risk colour, confidence, and impact. `ECWeather.update()` uses `ECAlerts` internally, so both `ECWeather.alerts` and `ECWeather.alert_features` are automatically populated.
69
+
70
+ Use `ECAlerts` directly when you only need alerts (no weather conditions or forecasts):
71
+
72
+ ```python
73
+ import asyncio
74
+
75
+ from env_canada import ECAlerts
76
+
77
+ ec_alerts = ECAlerts(coordinates=(50, -100))
78
+
79
+ asyncio.run(ec_alerts.update())
80
+
81
+ # categorised alerts dict — same structure as ECWeather.alerts
82
+ # keys: warnings, watches, advisories, statements, endings
83
+ ec_alerts.alerts
84
+
85
+ # list of raw WFS feature property dicts, one per alert polygon
86
+ ec_alerts.alert_features
87
+ ```
88
+
89
+ Each entry in `alerts[category]["value"]` is a dict with these keys:
90
+
91
+ | Key | Description |
92
+ | ------------------ | ----------------------------------------- |
93
+ | `title` | Alert name (title-cased) |
94
+ | `date` | Publication datetime (ISO 8601 UTC) |
95
+ | `expiryTime` | Expiration datetime (ISO 8601 UTC) |
96
+ | `alertColourLevel` | Risk colour (e.g. `"Yellow"`, `"Red"`) |
97
+ | `text` | Full alert text |
98
+ | `area` | Affected geographic area name |
99
+ | `status` | Alert status (e.g. `"active"`, `"ended"`) |
100
+ | `confidence` | Confidence level (e.g. `"Likely"`) |
101
+ | `impact` | Impact level (e.g. `"High"`) |
102
+ | `alert_code` | Short alert type code (e.g. `"WS"`) |
103
+
104
+ ## Weather Radar
105
+
106
+ `ECRadar` provides Environment Canada meteorological [radar imagery](https://weather.gc.ca/radar/index_e.html).
107
+
108
+ ```python
109
+ import asyncio
110
+
111
+ from env_canada import ECRadar
112
+
113
+ radar_coords = ECRadar(coordinates=(50, -100))
114
+
115
+ # Conditions Available
116
+ animated_gif = asyncio.run(radar_coords.get_loop())
117
+ latest_png = asyncio.run(radar_coords.get_latest_frame())
118
+ ```
119
+
120
+ ## Weather Maps
121
+
122
+ `ECMap` provides Environment Canada WMS weather map imagery with support for various meteorological layers.
123
+
124
+ ```python
125
+ import asyncio
126
+
127
+ from env_canada import ECMap
128
+
129
+ # Create a map with rain radar layer
130
+ map_coords = ECMap(coordinates=(50, -100), layer="rain")
131
+
132
+ # Get the latest image with the specified layer
133
+ latest_png = asyncio.run(map_coords.get_latest_frame())
134
+
135
+ # Get an animated GIF with the specified layer
136
+ animated_gif = asyncio.run(map_coords.get_loop())
137
+
138
+ # Customize the map appearance
139
+ custom_map = ECMap(
140
+ coordinates=(50, -100),
141
+ layer="snow",
142
+ width=1200,
143
+ height=800,
144
+ radius=300,
145
+ layer_opacity=80,
146
+ legend=True,
147
+ timestamp=True,
148
+ language="french",
149
+ )
150
+ ```
151
+
152
+ Available layers include:
153
+
154
+ - `rain`: Precipitation rain radar
155
+ - `snow`: Precipitation snow radar
156
+ - `precip_type`: Surface precipitation type
157
+
158
+ Additional configuration options:
159
+
160
+ - `width`/`height`: Image dimensions (default: 800x800)
161
+ - `radius`: Map radius in km around coordinates (default: 200km)
162
+ - `layer_opacity`: Layer transparency 0-100% (default: 65%)
163
+ - `legend`: Show legend (default: True)
164
+ - `timestamp`: Show timestamp (default: True)
165
+ - `language`: "english" or "french" (default: "english")
166
+
167
+ > **Note**: ECMap automatically discovers available legend styles from Environment Canada's WMS capabilities, ensuring compatibility with any future style changes.
168
+
169
+ ## Air Quality Health Index (AQHI)
170
+
171
+ `ECAirQuality` provides Environment Canada [air quality](https://weather.gc.ca/airquality/pages/index_e.html) data.
172
+
173
+ ```python
174
+ import asyncio
175
+
176
+ from env_canada import ECAirQuality
177
+
178
+ aqhi_coords = ECAirQuality(coordinates=(50, -100))
179
+
180
+ asyncio.run(aqhi_coords.update())
181
+
182
+ # Data available
183
+ aqhi_coords.current
184
+ aqhi_coords.forecasts
185
+ ```
186
+
187
+ ## Water Level and Flow
188
+
189
+ `ECHydro` provides Environment Canada [hydrometric](https://wateroffice.ec.gc.ca/mainmenu/real_time_data_index_e.html) data.
190
+
191
+ ```python
192
+ import asyncio
193
+
194
+ from env_canada import ECHydro
195
+
196
+ hydro_coords = ECHydro(coordinates=(50, -100))
197
+
198
+ asyncio.run(hydro_coords.update())
199
+
200
+ # Data available
201
+ hydro_coords.measurements
202
+ ```
203
+
204
+ ## Historical Weather Data
205
+
206
+ `ECHistorical` provides historical daily weather data.
207
+ The ECHistorical object is instantiated with a station ID, year, language, format (one of xml or csv) and granularity (hourly, daily data).
208
+ Once updated asynchronously, historical weather data is contained with the `station_data` property. If `xml` is requested, `station_data` will appear in a dictionary form. If `csv` is requested, `station_data` will contain a CSV-readable buffer. For example:
209
+
210
+ ```python
211
+ import asyncio
212
+
213
+ from env_canada import ECHistorical
214
+ from env_canada.ec_historical import get_historical_stations
215
+
216
+ # search for stations, response contains station_ids
217
+ coordinates = [53.916944, -122.749444] # [lat, long]
218
+
219
+ # coordinates: [lat, long]
220
+ # radius: km
221
+ # limit: response limit, value one of [10, 25, 50, 100]
222
+ # The result contains station names and ID values.
223
+ stations = asyncio.run(get_historical_stations(coordinates, radius=200, limit=100))
224
+
225
+ ec_en_xml = ECHistorical(station_id=31688, year=2020, language="english", format="xml")
226
+ ec_fr_xml = ECHistorical(station_id=31688, year=2020, language="french", format="xml")
227
+ ec_en_csv = ECHistorical(station_id=31688, year=2020, language="english", format="csv")
228
+ ec_fr_csv = ECHistorical(station_id=31688, year=2020, language="french", format="csv")
229
+
230
+ # timeframe argument can be passed to change the granularity
231
+ # timeframe=1 hourly (need to create of for every month in that case, use ECHistoricalRange to handle it automatically)
232
+ # timeframe=2 daily (default)
233
+ ec_en_xml = ECHistorical(
234
+ station_id=31688, year=2020, month=1, language="english", format="xml", timeframe=1
235
+ )
236
+ ec_en_csv = ECHistorical(
237
+ station_id=31688, year=2020, month=1, language="english", format="csv", timeframe=1
238
+ )
239
+
240
+ asyncio.run(ec_en_xml.update())
241
+ asyncio.run(ec_en_csv.update())
242
+
243
+ # metadata describing the station
244
+ ec_en_xml.metadata
245
+
246
+ # historical weather data, in dictionary form
247
+ ec_en_xml.station_data
248
+
249
+ # csv-generated responses return csv-like station data
250
+ import pandas as pd
251
+
252
+ df = pd.read_csv(ec_en_csv.station_data)
253
+ ```
254
+
255
+ `ECHistoricalRange` provides historical weather data within a specific range and handles the update by itself.
256
+
257
+ The ECHistoricalRange object is instantiated with at least a station ID and a daterange.
258
+ One could add language, and granularity (hourly, daily (default)).
259
+
260
+ The data can then be used as pandas DataFrame, XML (requires pandas >=1.3.0) and csv
261
+
262
+ For example :
263
+
264
+ ```python
265
+ import pandas as pd
266
+ import asyncio
267
+ from env_canada import ECHistoricalRange
268
+ from env_canada.ec_historical import get_historical_stations
269
+ from datetime import datetime
270
+
271
+ coordinates = ["48.508333", "-68.467667"]
272
+
273
+ stations = pd.DataFrame(
274
+ asyncio.run(
275
+ get_historical_stations(
276
+ coordinates, start_year=2022, end_year=2022, radius=200, limit=100
277
+ )
278
+ )
279
+ ).T
280
+
281
+ ec = ECHistoricalRange(
282
+ station_id=int(stations.iloc[0, 2]),
283
+ timeframe="daily",
284
+ daterange=(datetime(2022, 7, 1, 12, 12), datetime(2022, 8, 1, 12, 12)),
285
+ )
286
+
287
+ ec.get_data()
288
+
289
+ # yield an XML formated str.
290
+ # For more options, use ec.to_xml(*arg, **kwargs) with pandas options
291
+ ec.xml
292
+
293
+ # yield an CSV formated str.
294
+ # For more options, use ec.to_csv(*arg, **kwargs) with pandas options
295
+ ec.csv
296
+ ```
297
+
298
+ In this example `ec.df` will be:
299
+
300
+ | Date/Time | Longitude (x) | Latitude (y) | Station Name | Climate ID | Year | Month | Day | Data Quality | Max Temp (°C) | Max Temp Flag | Min Temp (°C) | Min Temp Flag | Mean Temp (°C) | Mean Temp Flag | Heat Deg Days (°C) | Heat Deg Days Flag | Cool Deg Days (°C) | Cool Deg Days Flag | Total Rain (mm) | Total Rain Flag | Total Snow (cm) | Total Snow Flag | Total Precip (mm) | Total Precip Flag | Snow on Grnd (cm) | Snow on Grnd Flag | Dir of Max Gust (10s deg) | Dir of Max Gust Flag | Spd of Max Gust (km/h) | Spd of Max Gust Flag | |
301
+ | ---------- | ------------- | ------------ | --------------------- | ---------- | ---- | ----- | --- | ------------ | -------------- | ------------- | -------------- | ------------- | --------------- | -------------- | ------------------- | ------------------ | ------------------- | ------------------ | --------------- | --------------- | --------------- | --------------- | ----------------- | ----------------- | ----------------- | ----------------- | ------------------------- | -------------------- | ---------------------- | -------------------- | --- |
302
+ | 2022-07-02 | -68,47 | 48,51 | POINTE-AU-PERE (INRS) | 7056068 | 2022 | 7 | 2 | | 22,8 | | 12,5 | | 17,7 | | 0,3 | | 0 | | | | | | 0 | | | | 26 | | 37 | | |
303
+ | 2022-07-03 | -68,47 | 48,51 | POINTE-AU-PERE (INRS) | 7056068 | 2022 | 7 | 3 | | 21,7 | | 10,1 | | 15,9 | | 2,1 | | 0 | | | | | | 0,4 | | | | 28 | | 50 | | |
304
+ | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … | … |
305
+ | 2022-07-31 | -68,47 | 48,51 | POINTE-AU-PERE (INRS) | 7056068 | 2022 | 7 | 31 | | 23,5 | | 14,1 | | 18,8 | | 0 | | 0,8 | | | | | | 0 | | | | 23 | | 31 | | |
306
+ | 2022-08-01 | -68,47 | 48,51 | POINTE-AU-PERE (INRS) | 7056068 | 2022 | 8 | 1 | | 23 | | 15 | | 19 | | 0 | | 1 | | | | | | 0 | | | | 21 | | 35 | | |
307
+
308
+ One should note that july 1st is excluded as the time provided contains specific hours, so it yields only data after or at exactly
309
+ the time provided.
310
+
311
+ To have all the july 1st data in that case, one can provide a datarange without time: `datetime(2022, 7, 7)` instead
312
+ of `datetime(2022, 7, 1, 12, 12)`
313
+
314
+ # License
315
+
316
+ The code is available under terms of [MIT License](https://github.com/michaeldavie/env_canada/tree/main/LICENSE.md)