env_canada

Provides access to various data sources published by Environment and Climate Change Canada.
https://github.com/michaeldavie/env_canada

Category: Sustainable Development
Sub Category: Data Catalogs and Interfaces

Keywords from Contributors

varta varta-storage static-code-analysis

Last synced: about 6 hours ago
JSON representation

Repository metadata

Environment Canada Weather Data

README.md

Environment Canada (env_canada)

PyPI version
Python Lint and Test

This package provides access to various data sources published by Environment and Climate Change Canada.

[!IMPORTANT]
If you're using the library in a Jupyter notebook, replace asyncio.run(...) with await ... in the examples below. For example:

asyncio.run(ec_en.update())

becomes

await ec_en.update()

Weather Observations and Forecasts

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:

  • Full format: "AB/s0000123" (province code and full station ID)
  • Station ID only: "s0000123" (station ID without province - province is resolved automatically)
  • Numeric only: "123" (just the station number - province is resolved automatically)

Station codes are based on those listed in this CSV file. For example:

import asyncio

from env_canada import ECWeather

# Using coordinates (automatic station selection)
ec_coords = ECWeather(coordinates=(50, -100))

# Using station ID - multiple formats supported:
ec_full = ECWeather(station_id="ON/s0000430", language="french")  # Full format
ec_station = ECWeather(station_id="s0000430")  # Station ID only
ec_numeric = ECWeather(station_id="430")  # Numeric only

asyncio.run(ec_coords.update())

# current conditions
ec_coords.conditions

# daily forecasts
ec_coords.daily_forecasts

# hourly forecasts
ec_coords.hourly_forecasts

# alerts (categorised dict with warnings, watches, advisories, statements, endings)
ec_coords.alerts

# raw WFS alert feature properties (includes text, area, confidence, impact, etc.)
ec_coords.alert_features

[!NOTE]
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.

Weather Alerts

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.

Use ECAlerts directly when you only need alerts (no weather conditions or forecasts):

import asyncio

from env_canada import ECAlerts

ec_alerts = ECAlerts(coordinates=(50, -100))

asyncio.run(ec_alerts.update())

# categorised alerts dict — same structure as ECWeather.alerts
# keys: warnings, watches, advisories, statements, endings
ec_alerts.alerts

# list of raw WFS feature property dicts, one per alert polygon
ec_alerts.alert_features

Each entry in alerts[category]["value"] is a dict with these keys:

Key Description
title Alert name (title-cased)
date Publication datetime (ISO 8601 UTC)
expiryTime Expiration datetime (ISO 8601 UTC)
alertColourLevel Risk colour (e.g. "Yellow", "Red")
text Full alert text
area Affected geographic area name
status Alert status (e.g. "active", "ended")
confidence Confidence level (e.g. "Likely")
impact Impact level (e.g. "High")
alert_code Short alert type code (e.g. "WS")

Weather Radar

ECRadar provides Environment Canada meteorological radar imagery.

import asyncio

from env_canada import ECRadar

radar_coords = ECRadar(coordinates=(50, -100))

# Conditions Available
animated_gif = asyncio.run(radar_coords.get_loop())
latest_png = asyncio.run(radar_coords.get_latest_frame())

# Use the 8-colour radar scale instead of the default 14-colour one
radar_coords_8 = ECRadar(coordinates=(50, -100), colors=8)

Weather Maps

ECMap provides Environment Canada WMS weather map imagery with support for various meteorological layers.

import asyncio

from env_canada import ECMap

# Create a map with rain radar layer
map_coords = ECMap(coordinates=(50, -100), layer="rain")

# Get the latest image with the specified layer
latest_png = asyncio.run(map_coords.get_latest_frame())

# Get an animated GIF with the specified layer
animated_gif = asyncio.run(map_coords.get_loop())

# Customize the map appearance
custom_map = ECMap(
    coordinates=(50, -100),
    layer="snow",
    width=1200,
    height=800,
    radius=300,
    layer_opacity=80,
    legend=True,
    timestamp=True,
    language="french",
)

Available layers include:

  • rain: Precipitation rain radar
  • snow: Precipitation snow radar
  • precip_type: Surface precipitation type

Additional configuration options:

  • width/height: Image dimensions (default: 800x800)
  • radius: Map radius in km around coordinates (default: 200km)
  • layer_opacity: Layer transparency 0-100% (default: 65%)
  • legend: Show legend (default: True)
  • timestamp: Show timestamp (default: True)
  • language: "english" or "french" (default: "english")
  • fps: Frame rate of the animated GIF loop (default: 5)
  • loop_minutes: How far back the animated GIF loop goes, in minutes (default: 0, meaning the full available range)
  • colors: Number of colours in the rain/snow radar scale, 8 or 14 (default: 14)
  • interpolation: Smooth the WMS-rendered radar layer instead of leaving it pixelated (default: False)
  • webp: Request the radar layer from Environment Canada's WMS as WebP instead of PNG, and return get_latest_frame()/get_loop() output as WebP instead of PNG/GIF. Reduces bandwidth at the cost of higher latency per frame (default: False)
  • future_minutes: Extend get_loop() past "now" using Environment Canada's radar extrapolation (nowcast) layer, in minutes. Only has an effect for rain/snow (precip_type has no extrapolation layer, so it's silently ignored); frames are capped at whatever the extrapolation layer actually has available, which is normally on the order of an hour. Uses the same styles/colours as the observed layer. get_latest_frame() is unaffected and always returns the latest real observation (default: 0)

Note: Legends are rendered by the library itself, in the style of Environment Canada's weather map, rather than fetched from the WMS server. A change to the server's styles therefore needs a matching update to ec_legend.py.

Precipitation Forecast Series

ECPrecipForecast assembles the numeric precipitation series needed to draw a
precipitation histogram of the kind weather apps show. It gathers and structures
the data only; rendering is left to the caller.

Two independent tracks are produced:

  • nowcast — precipitation rate at 6-minute resolution, from observed radar for
    the recent past and from Environment Canada's radar extrapolation (nowcast)
    layers for roughly the next hour.
  • hourly — hourly precipitation amount, probability and type for up to the next
    48 hours, from the HRDPS model and its "Weather Elements on Grid" (WEonG)
    diagnostic suite.
import asyncio

from env_canada import ECPrecipForecast

precip = ECPrecipForecast(coordinates=(45.42, -75.70))
asyncio.run(precip.update())

# 6-minute rate series
for entry in precip.nowcast:
    print(entry)
# {'timestamp': datetime.datetime(2025, 2, 13, 16, 54, tzinfo=tzutc()),
#  'rate': 1.2391, 'unit': 'mm/h', 'label': '1.0 - 2.0 (mm/h)',
#  'precip_type': 'rain', 'forecast': False}

# hourly series
for entry in precip.hourly:
    print(entry)
# {'timestamp': datetime.datetime(2025, 2, 13, 17, 0, tzinfo=tzutc()),
#  'amount': 1.726, 'probability': 57, 'conditional_amount': 0.909,
#  'expected_amount': 0.518, 'precip_type': 'Rain', 'label': '0.5 - 1 mm'}

Configuration options:

  • precip_type: "auto", "rain" or "snow" (default: "auto"). "auto" reads
    Environment Canada's radar surface precipitation type at the most recent
    observation, falling back to the season when radar detects nothing
  • past_minutes: How far back the nowcast series reaches, 0-180 (default: 60)
  • future_minutes: How far ahead the nowcast series reaches, 0-120 (default: 72).
    Capped at whatever the extrapolation layer actually has, normally around 72 minutes
  • hourly_hours: Length of the hourly series, 0-48 (default: 24). 0 disables
    the hourly track entirely
  • language: "english" or "french" (default: "english"), used for the attribution

nowcast entries carry:

  • rate: The value exactly as served — mm/h for rain, cm/h for snow, per unit
  • label: Environment Canada's own classification of that value, e.g.
    "1.0 - 2.0 (mm/h)" or "Undetected". The extrapolation layer reports a small
    non-zero floor below its detection threshold, which this label identifies
  • forecast: False for observed radar, True for extrapolation

hourly entries describe the hour ending at timestamp, matching how the model
reports accumulation:

  • amount: Precipitation for that hour in mm. The model publishes accumulation
    cumulatively from the start of its run, so this is a difference between
    successive steps, with every request pinned to a single model run
  • probability: Probability of precipitation, 0-100%
  • conditional_amount: Amount expected in mm if precipitation occurs
  • expected_amount: probability / 100 * conditional_amount
  • precip_type: Dominant precipitation type, e.g. "Rain", "Snow", "None"

metadata carries the attribution, the timestamp of the latest radar observation,
and the model run each forecast track is pinned to.

Note: Environment Canada's WMS server answers one point query per layer per
timestep, so a cold update() at the defaults issues roughly 120 requests
(taking a few seconds). Responses are cached — 5 minutes for radar, 3 hours for
the model, which runs every 6 hours — so subsequent updates only fetch newly
published radar frames. Pick a polling interval accordingly; every 5-10 minutes
is ample.

Air Quality Health Index (AQHI)

ECAirQuality provides Environment Canada air quality data.

import asyncio

from env_canada import ECAirQuality

aqhi_coords = ECAirQuality(coordinates=(50, -100))

asyncio.run(aqhi_coords.update())

# Data available
aqhi_coords.current
aqhi_coords.forecasts

Water Level and Flow

ECHydro provides Environment Canada hydrometric data.

import asyncio

from env_canada import ECHydro

hydro_coords = ECHydro(coordinates=(50, -100))

asyncio.run(hydro_coords.update())

# Data available
hydro_coords.measurements

Historical Weather Data

ECHistorical provides historical daily weather data.
The ECHistorical object is instantiated with a station ID, year, language, format (one of xml or csv) and granularity (hourly, daily data).
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:

import asyncio

from env_canada import ECHistorical
from env_canada.ec_historical import get_historical_stations

# search for stations, response contains station_ids
coordinates = [53.916944, -122.749444]  # [lat, long]

# coordinates: [lat, long]
# radius: km
# limit: response limit, value one of [10, 25, 50, 100]
# The result contains station names and ID values.
stations = asyncio.run(get_historical_stations(coordinates, radius=200, limit=100))

ec_en_xml = ECHistorical(station_id=31688, year=2020, language="english", format="xml")
ec_fr_xml = ECHistorical(station_id=31688, year=2020, language="french", format="xml")
ec_en_csv = ECHistorical(station_id=31688, year=2020, language="english", format="csv")
ec_fr_csv = ECHistorical(station_id=31688, year=2020, language="french", format="csv")

# timeframe argument can be passed to change the granularity
# timeframe=1 hourly (need to create of for every month in that case, use ECHistoricalRange to handle it automatically)
# timeframe=2 daily (default)
ec_en_xml = ECHistorical(
    station_id=31688, year=2020, month=1, language="english", format="xml", timeframe=1
)
ec_en_csv = ECHistorical(
    station_id=31688, year=2020, month=1, language="english", format="csv", timeframe=1
)

asyncio.run(ec_en_xml.update())
asyncio.run(ec_en_csv.update())

# metadata describing the station
ec_en_xml.metadata

# historical weather data, in dictionary form
ec_en_xml.station_data

# csv-generated responses return csv-like station data
import pandas as pd

df = pd.read_csv(ec_en_csv.station_data)

In the dictionary form, daily records are keyed by date ("2021-01-01") and hourly ones by date and time ("2021-05-01 13:00"). A station ID the service doesn't know raises env_canada.ec_exc.UnknownStationId from update().

ECHistoricalRange provides historical weather data within a specific range and handles the update by itself.

The ECHistoricalRange object is instantiated with at least a station ID. The daterange is the last year or so up to today unless given, and may be given in either order.
One could add language, and granularity (hourly, daily (default)).

The data can then be used as pandas DataFrame, XML (requires pandas >=1.3.0) and csv

For example :

import pandas as pd
import asyncio
from env_canada import ECHistoricalRange
from env_canada.ec_historical import get_historical_stations
from datetime import datetime

coordinates = ["48.508333", "-68.467667"]

stations = pd.DataFrame(
    asyncio.run(
        get_historical_stations(
            coordinates, start_year=2022, end_year=2022, radius=200, limit=100
        )
    )
).T

ec = ECHistoricalRange(
    station_id=int(stations.iloc[0, 2]),
    timeframe="daily",
    daterange=(datetime(2022, 7, 1, 12, 12), datetime(2022, 8, 1, 12, 12)),
)

ec.get_data()  # from async code, use `await ec.update()` instead

# yield an XML formated str.
# For more options, use ec.to_xml(*arg, **kwargs) with pandas options
ec.xml

# yield an CSV formated str.
# For more options, use ec.to_csv(*arg, **kwargs) with pandas options
ec.csv

In this example ec.df will be:

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
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
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
… … … … … … … … … … … … … … … … … … … … … … … … … … … … … … … …
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
2022-08-01 -68,47 48,51 POINTE-AU-PERE (INRS) 7056068 2022 8 1 23 15 19 0 1 0 21 35

One should note that july 1st is excluded as the time provided contains specific hours, so it yields only data after or at exactly
the time provided.

To have all the july 1st data in that case, one can provide a datarange without time: datetime(2022, 7, 7) instead
of datetime(2022, 7, 1, 12, 12). A date given as the end of the range covers that whole day.

License

The code is available under terms of MIT License


Owner metadata


GitHub Events

Total
Last Year

Committers metadata

Last synced: 1 day ago

Total Commits: 424
Total Committers: 23
Avg Commits per committer: 18.435
Development Distribution Score (DDS): 0.304

Commits in past year: 95
Committers in past year: 7
Avg Commits per committer in past year: 13.571
Development Distribution Score (DDS) in past year: 0.337

Name Email Commits
michaeldavie m****e@g****m 295
Glenn Waters g****n@w****a 45
dependabot[bot] 4****] 25
Brian Fitzgerald f****b 16
Glenn Waters g****s@g****m 10
lletac l****c@3****a 8
JM Lopez j****z@u****a 4
MrDizzystick M****k 3
snyk-bot s****t@s****o 3
Marc-Antoine Ruel m****l@g****m 2
Andreas Billmeier b@e****t 1
Daniel Nicoara d****a@g****m 1
Darren Wiens d****s@g****m 1
Fabian Affolter f****n@a****h 1
Justin Lam j****m@g****m 1
Marc Mueller 3****p 1
Oliver Gründel 4****l 1
Paul Tarjan g****b@p****m 1
Rob Leveille r****e@g****m 1
Shixian Sheng s****2@f****u 1
everett e****r@g****m 1
marawan31 m****1@g****m 1
stevendinardo 1****o 1

Committer domains:


Issue and Pull Request metadata

Last synced: 1 day ago

Total issues: 71
Total pull requests: 111
Average time to close issues: 3 months
Average time to close pull requests: 5 days
Total issue authors: 46
Total pull request authors: 22
Average comments per issue: 2.75
Average comments per pull request: 0.74
Merged pull request: 100
Bot issues: 0
Bot pull requests: 21

Past year issues: 6
Past year pull requests: 33
Past year average time to close issues: 1 day
Past year average time to close pull requests: about 18 hours
Past year issue authors: 4
Past year pull request authors: 6
Past year average comments per issue: 1.5
Past year average comments per pull request: 0.27
Past year merged pull request: 31
Past year bot issues: 0
Past year bot pull requests: 21

More stats: https://issues.ecosyste.ms/repositories/lookup?url=https://github.com/michaeldavie/env_canada

Top Issue Authors

  • gwww (8)
  • martinguthrie93 (3)
  • glassbase (3)
  • fitzb (3)
  • alexandery (3)
  • maruel (3)
  • cpw (2)
  • michaeldavie (2)
  • deftdawg (2)
  • ikifar2012 (2)
  • Diode-exe (2)
  • PhilBaobab (2)
  • jm66 (2)
  • A-c0rN (2)
  • kayrozen (1)

Top Pull Request Authors

  • gwww (35)
  • dependabot[bot] (21)
  • michaeldavie (20)
  • fitzb (7)
  • jm66 (4)
  • maruel (3)
  • cdce8p (2)
  • everettsp (2)
  • KPCOFGS (2)
  • ogruendel (2)
  • Biorix (2)
  • darrenwiens (1)
  • justinmklam (1)
  • infectedroot (1)
  • MrDizzystick (1)

Top Issue Labels

  • enhancement (4)
  • bug (2)

Top Pull Request Labels

  • dependencies (21)
  • python:uv (13)
  • github_actions (8)

Package metadata

nixpkgs-24.05: python311Packages.env-canada

Python library to get Environment Canada weather data

pypi.org: env_canada

A package to access meteorological data from Environment Canada

  • Homepage: https://github.com/michaeldavie/env_canada
  • Documentation: https://github.com/michaeldavie/env_canada
  • Licenses: MIT
  • Latest release: 0.20.5 (published 14 days ago)
  • Last Synced: 2026-10-16T00:08:38.214Z (1 day ago)
  • Versions: 134
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Downloads: 46,713 Last month
  • Docker Downloads: 755,296,949
  • Rankings:
    • Downloads: 1.941%
    • Dependent packages count: 7.381%
    • Average: 17.023%
    • Dependent repos count: 41.747%
  • Maintainers (1)
nixpkgs-24.11: python311Packages.env-canada

Python library to get Environment Canada weather data

nixpkgs-23.11: python310Packages.env-canada

Python library to get Environment Canada weather data

nixpkgs-24.11: python312Packages.env-canada

Python library to get Environment Canada weather data

nixpkgs-23.05: python310Packages.env-canada

Python library to get Environment Canada weather data

nixpkgs-23.05: python311Packages.env-canada

Python library to get Environment Canada weather data

nixpkgs-24.05: python312Packages.env-canada

Python library to get Environment Canada weather data

nixpkgs-unstable: python313Packages.env-canada

Python library to get Environment Canada weather data

nixpkgs-23.11: python311Packages.env-canada

Python library to get Environment Canada weather data

nixpkgs-unstable: python314Packages.env-canada

Python library to get Environment Canada weather data


Dependencies

.github/workflows/codeql-analysis.yml actions
  • actions/checkout v3 composite
  • github/codeql-action/analyze v2 composite
  • github/codeql-action/autobuild v2 composite
  • github/codeql-action/init v2 composite
.github/workflows/python-app.yml actions
  • actions/checkout v2 composite
  • actions/setup-python v2 composite
.github/workflows/python-publish.yml actions
  • actions/checkout v2 composite
  • actions/setup-python v2 composite
pyproject.toml pypi
  • aiohttp >=3.9.0
  • geopy >= 2.3.0
  • lxml >= 5.3.0
  • pandas >=2.2.3
  • pillow >=10.0.1
  • python-dateutil >= 2.9
  • voluptuous >= 0.15.2
uv.lock pypi
  • aiohappyeyeballs 2.6.1
  • aiohttp 3.14.3
  • aiosignal 1.4.0
  • astroid 4.0.4
  • attrs 26.1.0
  • colorama 0.4.6
  • colorlog 6.10.1
  • dill 0.4.1
  • env-canada 0.19.1
  • freezegun 1.5.5
  • frozenlist 1.8.0
  • geographiclib 2.1
  • geopy 2.4.1
  • idna 3.15
  • iniconfig 2.3.0
  • isort 8.0.1
  • librt 0.8.1
  • lxml 6.1.0
  • lxml-stubs 0.5.1
  • mccabe 0.7.0
  • multidict 6.7.1
  • mypy 1.20.0
  • mypy-extensions 1.1.0
  • numpy 2.4.4
  • packaging 26.0
  • pandas 3.0.2
  • pandas-stubs 3.0.0.260204
  • pathspec 1.0.4
  • pillow 12.3.0
  • platformdirs 4.9.4
  • pluggy 1.6.0
  • propcache 0.4.1
  • pygments 2.20.0
  • pylint 4.0.5
  • pytest 9.0.3
  • pytest-asyncio 1.3.0
  • python-dateutil 2.9.0.post0
  • ruff 0.15.9
  • six 1.17.0
  • syrupy 5.1.0
  • tomlkit 0.14.0
  • types-python-dateutil 2.9.0.20260402
  • typing-extensions 4.15.0
  • tzdata 2026.1
  • voluptuous 0.16.0
  • yarl 1.23.0
.github/workflows/dependabot-auto-merge.yml actions
  • dependabot/fetch-metadata 25dd0e34f4fe68f24cc83900b1fe3fe149efef98 composite

Score: 28.305565493886526