All values come from the Copernicus Sentinel-5P TROPOMI instrument, accessed as monthly composite images via Google Earth Engine and aggregated to the 600 upazilas of Bangladesh (mean of all pixels intersecting each upazila boundary). This is satellite-observed atmospheric column data, not ground-station surface-level air quality (AQI). It answers "how much of this gas sits in the air column above this area, on average, in this month" — it does not measure what people breathe at ground level, so it should not be read against WHO/AQI health thresholds, which are surface concentration standards.
| Gas | Earth Engine product | Native unit | Displayed as |
|---|---|---|---|
| NO₂ — nitrogen dioxide | COPERNICUS/S5P/OFFL/L3_NO2 (tropospheric column) | mol/m² | µmol/m² (×10⁶) |
| SO₂ — sulfur dioxide | COPERNICUS/S5P/OFFL/L3_SO2 (total column) | mol/m² | µmol/m² (×10⁶) |
| CO — carbon monoxide | COPERNICUS/S5P/OFFL/L3_CO (total column) | mol/m² | mmol/m² (×10³) |
| CH₄ — methane | COPERNICUS/S5P/OFFL/L3_CH4 (column-averaged dry-air mixing ratio) | ppb | ppb (unchanged) |
The numbers above are column densities: the total amount of a gas in the entire vertical column of air above a point, in mol/m² (or ppb for CH₄). A regulatory Air Quality Index — whether the US EPA's or Bangladesh DoE's, both scaled 0–500 — is defined on something different: the surface concentration in µg/m³, averaged over a fixed window (NO₂ 1-hour, SO₂ 1-hour, CO 8-hour, PM2.5 24-hour), compared against health-based breakpoints.
Those are not the same physical quantity, and one cannot be converted to the other without boundary-layer height, the vertical profile of the gas, and either a chemical transport model or a dense network of ground monitors. So this dashboard does not compute an AQI, and any dashboard that claims to derive one from raw TROPOMI columns alone is overstating what the data supports.
The Satellite Column Index applies the same mathematics as EPA AQI — piecewise-linear interpolation between breakpoints onto a 0–500 scale, with a six-band colour ladder — but derives its breakpoints from Bangladesh's own observed distribution rather than from health thresholds. It answers "how does this reading rank against everything ever recorded here?", not "is this air safe to breathe?"
The breakpoints are set from the pooled upazila-month record for each gas over a frozen 2019–2024 baseline. Freezing the window matters: it means a newly uploaded month is scored against a stable yardstick instead of silently redefining the scale on every upload.
| Percentile of the 2019–2024 record | SCI anchor | Band | Meaning |
|---|---|---|---|
| 0th (minimum) | 0 | Very low | At or below the national median |
| 50th (median) | 50 | Low | |
| 75th | 100 | Moderate | Top quartile |
| 90th | 150 | Elevated | Top decile |
| 97th | 200 | High | Top 3% of all readings |
| 99.5th | 300 | Very high | Top 0.5% — extreme |
| 100th (maximum) | 500 | — | The worst column in the record |
Between anchors the index interpolates linearly, exactly as EPA AQI does:
I = (Ihi − Ilo) / (Chi − Clo) × (C − Clo) + Ilo.
So an SCI of 100 means the reading is worse than 75% of everything on record for that gas.
Breakpoints are computed at upazila level on purpose — aggregating to district or division first
would compress the tails and flatten the very hotspots the index exists to surface.
Previously the choropleth stretched its colour ramp across the minimum and maximum of whatever was currently on screen. The darkest red therefore meant a different number in every view, every month and every drilldown level — a clean month and a filthy month looked identical. SCI colours are absolute: one colour means one severity everywhere, so months, regions, levels and even different gases become directly comparable on a single ruler.
The bands are called Very low → Very high rather than the EPA's "Good / Moderate / Unhealthy for Sensitive Groups / Unhealthy / Very Unhealthy / Hazardous". Borrowing the health language would imply an exposure judgement this data cannot support. The colours follow the AQI ladder so severity reads instantly; the words stay honest about what is being measured.
If ground-monitor data (CAMS/DoE continuous air quality monitoring stations) becomes available for Bangladesh, the correct next step is to regress column against surface concentration and publish a validated conversion. Until then, SCI is the honest ceiling on what this dataset can claim.
Each gas has one fixed "identity" colour, used for its trend lines, legend chips and the all-variable overview chart, plus a matching light-to-dark choropleth ramp for the map. These follow the closest thing to a professional standard for these specific products — ESA's own Sentinel Hub Sentinel-5P evalscripts (the official visualisation scripts used in the Copernicus Browser) — plus well-established chemistry associations for the two gases ESA's scripts don't colour distinctly:
| Gas | Colour | Why |
|---|---|---|
| NO₂ | Orange-red | ESA's official NO₂ evalscript ramp runs yellow → orange → red at high values; NO₂ gas itself is physically reddish-brown (it's why smog looks brown). |
| SO₂ | Gold/amber | Elemental sulfur is a distinctive yellow — the standard chemistry association — kept visually apart from NO₂'s red-orange. |
| CO | Slate grey | Conventional association with combustion soot / vehicle exhaust. |
| CH₄ | Green | Conventional association with organic/biogenic sources (rice paddies, wetlands, landfills) and greenhouse-gas messaging. |
ESA's official SO₂, CO and CH₄ evalscripts actually all reuse the same generic blue→red ramp (only NO₂ has a bespoke one), which isn't distinctive enough for a dashboard that plots all four gases together — so SO₂/CO/CH₄'s colours above come from the chemistry conventions instead, while NO₂ follows ESA directly.
600 upazilas (thanas/sub-districts) × 90 months, January 2019 through June 2026 at the time of writing, for each of the 4 gases — 216,000 upazila-month readings. Note: the source files were supplied covering 2019 onward and turned out to already include data through mid-2026, so the dashboard shows the full span rather than stopping at 2025.
This range is not fixed. The dashboard asks the database what it actually holds each time it loads, so once further months are uploaded through the admin panel the charts, dropdowns and every date label here extend on their own. The range shown in the site header is always the true current coverage.
District and division figures are the simple average of their constituent upazilas (equal weight per upazila, not population- or area-weighted).
The "Seasonal profile" chart and the Seasonal time mode use the standard Bangladesh Meteorological Department 4-season convention:
| Season | Months |
|---|---|
| Winter | December – February (December counted in the following year's winter) |
| Pre-monsoon | March – May |
| Monsoon | June – September |
| Post-monsoon | October – November |
About a quarter of all SO₂ readings are slightly negative. This is expected retrieval noise, not a real negative concentration — TROPOMI's SO₂ signal is close to the instrument's detection limit over most of Bangladesh outside industrial hot-spots (brick-kiln belts, power plants), so background pixels scatter around zero. Treat small negative and small positive SO₂ values as "near background," and focus on relative differences between areas/periods rather than the absolute sign.
About 35% of CH₄ upazila-months have no valid reading — heavily concentrated in June–September (79% of all gaps), because CH₄ retrieval needs a clear sky and the monsoon's persistent cloud cover blocks it. NO₂, SO₂ and CO retrievals are far more resilient (CO/SO₂ are essentially 100% complete; NO₂ is missing a single reading in the whole dataset). Every KPI, chart and map cell carries an n / expected month count and a completeness flag so a monsoon CH₄ average built from 2 months isn't mistaken for one built from 4.
Shown on hover on the map and in the narrative line — replaces the "reliability" concept used in the DHS survey atlas with something appropriate for satellite time series: the share of expected months in the current window that actually have a valid retrieval.
| Flag | Meaning |
|---|---|
| Full coverage | ≥ 90% of months have a valid reading |
| Partial coverage | 50–89% of months have a valid reading |
| Sparse (cloud gaps) | < 50% of months have a valid reading — usually monsoon CH₄ |
| No data | No valid reading in the selected window |
The dropdown above the map (next to "Full extent") switches between Drilldown (click a division to zoom into its districts, click a district to zoom into its upazilas — the default) and three at-a-glance overview modes that show every division, district, or upazila nationwide at once, regardless of what's currently selected in the analytics panel. Clicking a region always updates the charts/KPIs either way; in overview mode the map itself stays at the fixed nationwide zoom instead of drilling in, so you can keep comparing the whole country while reading the details of whichever one you clicked. Permanent text labels auto-hide above 100 shapes (i.e. the all-upazila view) since 600 labels would just be noise — the colour pattern is still fully visible.
Division, District and Upazila are always fully populated and never greyed out. With nothing selected above them, District lists all 64 districts grouped by division and Upazila lists all 600 upazilas grouped by district, so you can jump straight to any place without drilling down first — the levels above fill themselves in, because each geocode carries its own parentage (2-digit division + 2-digit district + 2-digit upazila). Narrow the selection above and the lists below scope to it. Choosing "All …" steps back up to the nearest level still selected.
Three dropdowns drive the time window: Time mode (Monthly / Seasonal / Yearly / All-time), Year, and a single Period dropdown that relists itself to match the mode — all twelve months in Monthly mode, all four Bangladesh seasons with their month ranges (e.g. "Monsoon (Jun–Sep)") in Seasonal mode, and hidden in Yearly and All-time where the Year dropdown alone already defines the window.
The month list always shows all twelve; months that fall beyond the end of the record (currently June 2026) are marked "— no data" rather than being removed, so the list never changes shape underneath you. As new months are uploaded those markers clear themselves. The Year dropdown always offers All years (2019–2026) as well as each individual year — combined with Monthly that means "every January pooled together", and with Seasonal "every monsoon pooled together", which is how you see a period's typical signature rather than one particular year's.
The ⇄ Compare button opens with All-variable overview selected by default — all 4 gases plotted together for the currently selected region, each indexed to its own 2019–2026 average = 100 so the very different units (µmol/m², mmol/m², ppb) become directly comparable in shape and relative movement. Untick any gas to drop it from the chart (1–4 at a time). The "Compare by" dropdown switches to three other perspectives, all for a single gas you choose (pre-set to whichever pollutant you were already looking at): Region vs region (two places, full monthly trend), Year vs year (one place, two chosen years overlaid Jan–Dec), and Season vs season (one place, the four BD seasons for two chosen years side by side).
This is a view-only dashboard — there are no CSV, PNG or JPG export buttons. The four source CSVs sit
beside index.html and are the authoritative raw data if numbers need to be taken away.
Every CSV row is keyed by GEO_CODE (an 8-digit code = 2-digit division + 2-digit district +
2-digit upazila, e.g. 20030004 = Chattogram → Bandarban → Alikadam). The same field, in the
same format, is present on the upazila.geojson, district.geojson (4-digit) and
division.geojson (2-digit) boundary files, so upazila readings join to polygons directly and
roll up to district/division by simple average.
Readings are held in a MySQL database and served as compact JSON by api/series.php;
the boundary aggregation and all statistics are then computed in the browser. New data is added through the
admin panel rather than by editing files, and the dashboard reads its date range from the database on every
load — which is why a newly uploaded month extends the site with no code change.
If the database is unreachable (for example while it is still being set up), the dashboard automatically falls back to reading the four CSV files that ship alongside the site, so visitors never see a broken page. The badge in the top bar shows which source is in use.
To extend the time range, append new rows to the 4 Upazila_<GAS>_monthly.csv files
(same columns, especially GEO_CODE, YEAR, MONTH, MEAN_VALUE)
and update CONFIG.time.endYear / endMonth in assets/js/config.js.
No other file needs to change.