There's been a lot of political noise about European air quality: EU targets, diesel bans, clean air zones, industrial permits. But what does a decade of satellite and model data actually show?

We pulled 10 years of Copernicus CAMS reanalysis data for four major European cities — Paris, London, Berlin, and Warsaw — and let the numbers speak.

The dataset

All data comes from the CAMS European Reanalysis (CAMS-REG-REAN), a Copernicus service produced by ECMWF. It combines satellite observations, ground-station measurements, and atmospheric modelling into a gridded product at 0.1° resolution (~11 km) with hourly timesteps, spanning 2013 to present.

We queried annual mean NO₂ and PM2.5 for each city using the Jiskta API. Instead of supplying manual bounding boxes, we pass area="paris" — the API resolves the exact OSM administrative boundary automatically. For the OLS trend (slope in µg/m³/year), a single aggregate="trend" call returns the regression coefficients directly:

import requests

HEADERS = {"X-API-Key": "your_key"}
BASE    = "https://api.jiskta.com/api/v1/climate/query"

# ── Annual means (for time-series charts) ──────────────────────────────────
# One call per city; area= uses the OSM boundary
for city in ["paris", "london", "berlin", "warsaw"]:
    r = requests.get(BASE, headers=HEADERS, params={
        "area": city,
        "time_start": "2013-01", "time_end": "2023-12",
        "variables": "no2",
        "format": "csv", "aggregate": "area_monthly",
    })
    df = pd.read_csv(StringIO(r.json()["output"]))
    print(city, df.groupby(df.year_month.str[:4])["no2_mean"].mean())

# ── OLS trend — single call, returns slope + R² directly ──────────────────
r = requests.get(BASE, headers=HEADERS, params={
    "area": "paris",
    "time_start": "2013-01", "time_end": "2023-12",
    "variables": "no2",
    "aggregate": "trend",
})
result = r.json()
# slope (µg/m³/year), r2 — area-averaged
slope = result["slope"]
r2    = result["r2"]
print(f"Paris NO₂ trend: {slope:+.3f} µg/m³/yr  (R²={r2:.2f})")

NO₂: the big picture

Nitrogen dioxide comes primarily from road transport and industrial combustion. It's the headline pollutant for urban air quality — and the one where the EU has consistently missed its own targets.

Annual mean NO₂ (µg/m³) at city centres, 2013–2023:

City 2013 2017 2019 2020 2021 2023 Change
Paris 31.6 31.2 27.3 22.7 26.4 22.8 −28%
London 27.4 27.8 26.9 20.7 21.6 22.9 −17%
Berlin 15.5 17.3 15.6 14.3 15.3 13.4 −14%
Warsaw 13.9 19.8 17.1 17.7 18.7 17.6 +26%

Western European cities show a clear downward trend. Paris fell 28% thanks to its ZFE (zone à faibles émissions) and the shift away from diesel-heavy vehicle fleets. Berlin improved 14% and London 17%, driven by the Ultra Low Emission Zone (launched 2019). Warsaw tells a different story: NO₂ actually increased 26% between 2013 and 2023, reflecting rapid traffic growth and slower vehicle fleet electrification in Poland compared to its Western European neighbours.

The 2020 anomaly: Every Western European city shows a sharp drop in 2020 — Paris fell from 27.3 to 22.7 µg/m³ as lockdowns eliminated most road traffic. This is the COVID lockdown effect: the most dramatic natural experiment in urban air quality ever recorded. Warsaw is the outlier: NO₂ was essentially flat in 2020 (17.7 vs 17.1 in 2019), suggesting Poland's lighter lockdown restrictions and continued industrial activity masked any traffic-related reduction. In Paris and London, 2021 shows a near-full rebound toward 2019 levels, indicating that structural fleet and policy changes were still outweighed by traffic recovering.

PM2.5: a different story

Fine particulate matter (PM2.5) tells a more complex story than NO₂. While NO₂ is predominantly traffic-sourced and responds quickly to vehicle fleet changes, PM2.5 has many sources: industry, agriculture (ammonia → secondary particles), residential heating, and long-range transport from Eastern Europe and North Africa.

City 2013 2019 2020 2023 WHO limit Status
Paris ~15 ~12 ~10 ~10 5 µg/m³ 2× over limit
London ~13 ~11 ~9 ~9 5 µg/m³ 1.7× over limit
Berlin ~15 ~13 ~11 ~10 5 µg/m³ 2× over limit
Warsaw ~22 ~18 ~16 ~16 5 µg/m³ 3.2× over limit

PM2.5 values are approximate estimates based on EEA monitoring station data and CAMS model outputs. Direct PM2.5 queries via the Jiskta API are currently in beta.

Even with meaningful reductions over 10 years, every city remains well above the WHO's 2021 revised guideline of 5 µg/m³. Warsaw is the most challenging — coal-fired residential heating creates severe winter peaks that dominate the annual average.

Seasonal patterns: when is air quality worst?

Aggregating to monthly means reveals clear seasonal signals. To get these with one call:

r = requests.get(
    "https://api.jiskta.com/api/v1/climate/query",
    params={
        "lat": 52.23, "lon": 21.01,  # Warsaw
        "time_start": "2013-01", "time_end": "2023-12",
        "variables": "no2",
        "format": "csv", "aggregate": "seasonal",
    },
    headers={"X-API-Key": "your_key"},
)
# lat,lon,season,no2_mean
# 52.2500,21.0500,DJF,23.2   ← winter inversion season
# 52.2500,21.0500,MAM,18.5
# 52.2500,21.0500,JJA,14.7
# 52.2500,21.0500,SON,19.7

Warsaw's winter NO₂ is 1.6× higher than summer — driven by atmospheric inversions that trap pollutants close to the ground during cold months, compounding the effect of coal-fired residential heating. In Paris and London, the seasonal NO₂ pattern is similar but less pronounced, with winter peaks driven primarily by lower atmospheric mixing height rather than heating emissions.

What this means for analysts

This kind of analysis used to require downloading hundreds of gigabytes of NetCDF files, installing specialised libraries, and spending days on data wrangling. The queries above took under a second and cost well under €1 in API credits.

If you're doing location risk assessment, ESG scoring, environmental impact modelling, or academic research — the same queries work for any location globally, any time range from 2013 onwards.

Run this analysis yourself

500 free credits included — enough to replicate everything in this post and more.

Get started free →