Skip to content

aeolus.networks

Functions for working with discrete monitoring networks (AURN, SAQN, Breathe London, etc.).

Functions

Get monitoring site metadata for a network.

Networks are discrete monitoring networks operated by organizations with finite numbers of sites that can be listed completely.

Parameters:

Name Type Description Default
network str

Network name ("AURN", "SAQN", "BREATHE_LONDON", etc.)

required
**filters Any

Optional network-specific filters

{}

Returns:

Type Description
DataFrame

DataFrame with site metadata including: - site_code: Unique site identifier - site_name: Human-readable site name - latitude: Site latitude - longitude: Site longitude - source_network: Network name (raw adapter metadata; aeolus.find_sites() returns the public schema with network)

Raises:

Type Description
ValueError

If network is unknown or not a network type

Examples:

>>> # Get all AURN sites
>>> sites = aeolus.networks.get_metadata("AURN")
>>>
>>> # Get Breathe London sites
>>> sites = aeolus.networks.get_metadata("BREATHE_LONDON")
>>>
>>> # Some networks support filters
>>> sites = aeolus.networks.get_metadata("BREATHE_LONDON", borough="Camden")
Source code in src/aeolus/networks/api.py
def get_metadata(network: str, **filters: Any) -> pd.DataFrame:
    """
    Get monitoring site metadata for a network.

    Networks are discrete monitoring networks operated by organizations with
    finite numbers of sites that can be listed completely.

    Args:
        network: Network name ("AURN", "SAQN", "BREATHE_LONDON", etc.)
        **filters: Optional network-specific filters

    Returns:
        DataFrame with site metadata including:
            - site_code: Unique site identifier
            - site_name: Human-readable site name
            - latitude: Site latitude
            - longitude: Site longitude
            - source_network: Network name (raw adapter metadata; ``aeolus.find_sites()`` returns the public schema with ``network``)

    Raises:
        ValueError: If network is unknown or not a network type

    Examples:
        >>> # Get all AURN sites
        >>> sites = aeolus.networks.get_metadata("AURN")
        >>>
        >>> # Get Breathe London sites
        >>> sites = aeolus.networks.get_metadata("BREATHE_LONDON")
        >>>
        >>> # Some networks support filters
        >>> sites = aeolus.networks.get_metadata("BREATHE_LONDON", borough="Camden")
    """
    source_spec = get_source(network)

    if not source_spec:
        raise ValueError(unknown_source_message(network))

    # Verify it's a network
    source_type = source_spec.get("type", "network")
    if source_type != "network":
        raise ValueError(
            f"{network} is a {source_type}, not a network.\n"
            f"Use aeolus.portals.find_sites() for portals."
        )

    # Get metadata fetcher
    fetcher = source_spec.get("fetch_metadata")
    if not fetcher:
        raise ValueError(f"Network {network} does not support metadata fetching")

    return fetcher(**filters)

Download air quality data from a network.

Parameters:

Name Type Description Default
network str

Network name ("AURN", "SAQN", "BREATHE_LONDON", etc.)

required
sites list[str]

List of site codes to download

required
start_date datetime | None

Start of date range (inclusive)

None
end_date datetime | None

End of date range (inclusive)

None
last str | None

Date range shorthand, e.g. "6h", "30d", "2w", "6m", "1y". Mutually exclusive with start_date/end_date.

None

Returns:

Type Description
DataFrame

DataFrame with standardised schema: - site_code: Site identifier - date_time: Measurement timestamp - measurand: Pollutant measured (e.g., "NO2", "PM2.5") - value: Measured value - units: Units of measurement - network / backend / qa_code / qa_tier / ratification_stage: the 0.5 contract columns - source_network, ratification: deprecated mirrors (AEOLUS_LEGACY_COLUMNS=0 drops them) - created_at: When record was fetched

Raises:

Type Description
ValueError

If network is unknown or not a network type

Examples:

>>> from datetime import datetime
>>> data = aeolus.networks.download(
...     "AURN",
...     ["MY1", "MY2"],
...     datetime(2024, 1, 1),
...     datetime(2024, 1, 31)
... )
>>> # Or with last= shorthand
>>> data = aeolus.networks.download("AURN", ["MY1"], last="30d")
Source code in src/aeolus/networks/api.py
def download(
    network: str,
    sites: list[str],
    start_date: datetime | None = None,
    end_date: datetime | None = None,
    last: str | None = None,
) -> pd.DataFrame:
    """
    Download air quality data from a network.

    Args:
        network: Network name ("AURN", "SAQN", "BREATHE_LONDON", etc.)
        sites: List of site codes to download
        start_date: Start of date range (inclusive)
        end_date: End of date range (inclusive)
        last: Date range shorthand, e.g. "6h", "30d", "2w", "6m", "1y".
              Mutually exclusive with start_date/end_date.

    Returns:
        DataFrame with standardised schema:
            - site_code: Site identifier
            - date_time: Measurement timestamp
            - measurand: Pollutant measured (e.g., "NO2", "PM2.5")
            - value: Measured value
            - units: Units of measurement
            - network / backend / qa_code / qa_tier / ratification_stage: the 0.5 contract columns
            - source_network, ratification: deprecated mirrors (``AEOLUS_LEGACY_COLUMNS=0`` drops them)
            - created_at: When record was fetched

    Raises:
        ValueError: If network is unknown or not a network type

    Examples:
        >>> from datetime import datetime
        >>> data = aeolus.networks.download(
        ...     "AURN",
        ...     ["MY1", "MY2"],
        ...     datetime(2024, 1, 1),
        ...     datetime(2024, 1, 31)
        ... )
        >>> # Or with last= shorthand
        >>> data = aeolus.networks.download("AURN", ["MY1"], last="30d")
    """
    source_spec = get_source(network)

    if not source_spec:
        raise ValueError(unknown_source_message(network))

    # Verify it's a network
    source_type = source_spec.get("type", "network")
    if source_type != "network":
        raise ValueError(
            f"{network} is a {source_type}, not a network.\n"
            f"Use aeolus.portals.download() for portals."
        )

    # Get data fetcher
    fetcher = source_spec.get("fetch_data")
    if not fetcher:
        raise ValueError(f"Network {network} does not support data downloading")

    start_date, end_date = resolve_dates(start_date, end_date, last)

    from .. import cache as _cache
    from ..schema import finalise_data_frame

    # Finalise BEFORE caching, so the cache only ever holds public frames.
    def fetch_public(sites_, start_, end_):
        return finalise_data_frame(fetcher(sites_, start_, end_), network)

    return _cache.fetch_with_cache(
        network, sites, start_date, end_date, fetch_public, last=last
    )

List all available networks.

Returns:

Type Description
list[str]

List of network names

Examples:

>>> networks = aeolus.networks.list_networks()
>>> print(networks)
['AURN', 'SAQN', 'WAQN', 'NI', 'BREATHE_LONDON', ...]
Source code in src/aeolus/networks/api.py
def list_networks(include_all: bool = False) -> list[str]:
    """
    List all available networks.

    Returns:
        List of network names

    Examples:
        >>> networks = aeolus.networks.list_networks()
        >>> print(networks)
        ['AURN', 'SAQN', 'WAQN', 'NI', 'BREATHE_LONDON', ...]
    """
    from ..registry import SOURCES

    return [
        name
        for name, spec in SOURCES.items()
        if spec.get("type") == "network"
        and (include_all or spec.get("primary", True))
    ]

Usage Examples

Get Network Metadata

import aeolus

# Get all AURN sites
sites = aeolus.networks.get_metadata("AURN")

# View columns
print(sites.columns)
# Index(['site_code', 'site_name', 'latitude', 'longitude', 'site_type', ...])

# Filter to London sites
london = sites[sites['site_name'].str.contains('London')]

Download Network Data

import aeolus
from datetime import datetime

data = aeolus.networks.download(
    network="AURN",
    sites=["MY1", "KC1"],
    start_date=datetime(2024, 1, 1),
    end_date=datetime(2024, 1, 31)
)

List Available Networks

networks = aeolus.networks.list_networks()
print(networks)
# ['AIRNOW', 'AIRQO', 'BREATHE_LONDON', 'EEA', 'AURN', 'SAQN', 'NI', 'WAQN',
#  'AQE', 'LAQN', 'LMAM', 'SENSOR_COMMUNITY', 'SONITUS']

Supported Networks

UK Regulatory Networks (no API key required)

Network Description
AURN UK Automatic Urban and Rural Network
SAQN Scottish Air Quality Network
WAQN Welsh Air Quality Network
NI Northern Ireland Network
AQE Air Quality England
LAQN London Air Quality Network

Other Networks

Network Description API Key
BREATHE_LONDON London low-cost sensors Yes (BL_API_KEY)
AIRQO African cities network Yes (AIRQO_API_KEY)
AIRNOW US EPA real-time data Yes (AIRNOW_API_KEY)
SENSOR_COMMUNITY Global citizen science No