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.
- env_canada-0.15.0/LICENSE +19 -0
- env_canada-0.15.0/MANIFEST.in +2 -0
- env_canada-0.15.0/PKG-INFO +342 -0
- env_canada-0.15.0/README.md +316 -0
- env_canada-0.15.0/env_canada/DejaVuSans.ttf +0 -0
- env_canada-0.15.0/env_canada/__init__.py +19 -0
- env_canada-0.15.0/env_canada/constants.py +1 -0
- env_canada-0.15.0/env_canada/ec_alerts.py +164 -0
- env_canada-0.15.0/env_canada/ec_aqhi.py +233 -0
- env_canada-0.15.0/env_canada/ec_cache.py +47 -0
- env_canada-0.15.0/env_canada/ec_exc.py +2 -0
- env_canada-0.15.0/env_canada/ec_historical.py +502 -0
- env_canada-0.15.0/env_canada/ec_hydro.py +152 -0
- env_canada-0.15.0/env_canada/ec_legend.py +183 -0
- env_canada-0.15.0/env_canada/ec_map.py +380 -0
- env_canada-0.15.0/env_canada/ec_radar.py +105 -0
- env_canada-0.15.0/env_canada/ec_weather.py +704 -0
- env_canada-0.15.0/env_canada.egg-info/PKG-INFO +342 -0
- env_canada-0.15.0/env_canada.egg-info/SOURCES.txt +22 -0
- env_canada-0.15.0/env_canada.egg-info/dependency_links.txt +1 -0
- env_canada-0.15.0/env_canada.egg-info/requires.txt +7 -0
- env_canada-0.15.0/env_canada.egg-info/top_level.txt +1 -0
- env_canada-0.15.0/pyproject.toml +73 -0
- env_canada-0.15.0/setup.cfg +4 -0
|
@@ -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,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
|
+
[](https://badge.fury.io/py/env-canada)
|
|
30
|
+
[](https://snyk.io/vuln/pip:env-canada@0.8.0?utm_source=badge)
|
|
31
|
+
[](../../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
|
+
[](https://badge.fury.io/py/env-canada)
|
|
4
|
+
[](https://snyk.io/vuln/pip:env-canada@0.8.0?utm_source=badge)
|
|
5
|
+
[](../../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)
|
|
Binary file
|