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.


Need help? Contact support · Where Is Tereza?