Pressure isobar contours
The smooth curving lines on the pressure layer are real isobar contours, computed in the browser from a 5° global pressure grid. The math behind making them readable is borrowed straight from operational meteorology.
The input grid
Open-Meteo publishes mean-sea-level pressure as a 36 × 17 cell grid (10° × 10° resolution, latitude clamped to ±80°). One value per cell, in hPa. Refreshed every 30 minutes.
Drawn as straight isobars at this resolution, the lines look like chains of long straight segments — a topology that no pressure field ever has in reality. We need three pre-processing steps before contouring.
Step 1 — Bilinear upsampling
Each 10° cell is interpolated to ((nx-1)F + 1) × ((ny-1)F + 1) where F is an upsample factor from 4× (zoom level 2-4) up to 24× (zoom level 7+). The interpolation is bilinear:
v(x,y) = (1-tx)(1-ty)*v00 + tx(1-ty)*v01
+ (1-tx)*ty*v10 + tx*ty*v11
This gives us, at maximum factor, a 841 × 385 grid (~324k cells) covering the whole globe. Curves become smooth instead of straight-segment chains.
Step 2 — Gaussian blur
The upsampled grid carries through any numerical noise from the source data — small per-cell wiggles that, when contoured, would produce isobars that bend unnaturally on a scale of tens of kilometres. Standard practice in operational met: a separable 2D Gaussian blur with σ = 1.5 grid cells.
Implementation is the textbook 1D-along-X then 1D-along-Y double pass:
kernel[i] = exp(-(i^2) / (2 * sigma^2))
normalize so sum = 1
pass 1: tmp[y,x] = Σ kernel[k] * input[y, x+k]
pass 2: out[y,x] = Σ kernel[k] * tmp[y+k, x]
For our largest upsampled grid this is ~50 ms in JavaScript on a mid-range laptop — well under the budget for an interactive map.
Step 3 — Polar padding
The data is clamped to ±80° latitude (Open-Meteo doesn't
publish poles). Without padding, isobars cut off in a hard
horizontal line at ±80° on the map. We add padTop and
padBottom rows that copy the boundary row's values, extending
the contour generation to ±90°. The contours flow visually all
the way to the poles, even if the pole values are extrapolated.
Step 4 — d3.contours
d3-contour (the popular JavaScript library) does the actual marching-squares computation. We pass it the smoothed grid plus a list of contour values:
const thresholds = [];
for (let p = 960; p <= 1060; p += 4) thresholds.push(p);
const contours = d3.contours()
.size([nx, ny])
.thresholds(thresholds)(values);
Each contour comes back as a GeoJSON-ish MultiPolygon. We
project each ring's grid coordinates back to lat/lng via the
inverse of the upsampling, then plot as a Leaflet L.polyline
in the gold accent colour.
L / H markers
Local minima (Lows) and maxima (Highs) are detected with a simple 3×3 neighbour comparison after the smoothing pass:
for each cell (x, y):
v = grid[y, x]
if v < all 8 neighbours: it's an L
if v > all 8 neighbours: it's an H
We deduplicate (a wide low can have several near-equal cells in its trough) by keeping only the locally lowest within a 4-cell radius. Each L/H gets a marker with the letter + the actual value in hPa.
Why curves matter
A pressure map is a story — where storms are forming, how the air is flowing. Chains of straight segments break the narrative. Smooth curves make the storm structure recognisable: a comma-shaped low, a building ridge, a frontal system between two highs. Without the math above, none of those would be readable on a 36×17 source grid.
Related
- Pressure layer — user-facing view.
- Weather rendering — how the scalar overlays (temp, wind speed) are produced.
Need help? Contact support · Where Is Tereza?