Skip to content

Wind and other velocity fields

A query that returns a regular grid of velocities becomes a Flow field layer: an animated field of streamlines, the paths a massless particle would take through the grid. Nothing to configure to get one — give the panel coordinates and a velocity and the layer appears — and everything to configure once you have it, in the layer's own panel inside the map.

sql
SELECT ts AS "time", lat, lon, speed, direction
FROM wind_grid
WHERE level_hpa = 700
ORDER BY lat, lon;

This one is not a table of places

Every other layer draws your rows. This one does not. The rows describe a grid of vectors, and what gets drawn are paths traced through that grid — geometry the layer computes at draw time, which your query never contained.

Your rows do reach the map: the dataset holds the lattice the query returned, columns and all, and the layer is pointed at the ones that carry the velocity. What it draws is not in them.

That has a few consequences worth holding on to: the number of lines has nothing to do with the number of rows, the lines are recomputed when you pan or zoom, and there is nothing to hover — a streamline is not a row.

The dots are the grid

kepler also guesses a Point layer from the same coordinates. The panel removes it: those dots are the lattice itself, honestly drawn and not what the query is about. Add one back from Add Layer if you want to see where your samples are.

Three ways to give velocity

Two of them name the velocity directly; both pairs are all-or-nothing, since half of one describes nothing. The third does not name a velocity at all — it derives one from a single scalar column.

Speed and direction

What most sources give you: Open-Meteo, GFS, national weather services.

RoleDetected from
Speedwind_speed, windspeed, wind_speed_10m, speed, ws
Directionwind_direction, winddirection, wind_direction_10m, direction, wind_dir, wd

direction is read as the meteorological convention by default: the bearing the wind blows from. A direction of 270° is a westerly — air moving towards the east. If your source gives the direction of travel instead, as ocean currents often do, set Field → Direction is to Where it goes in the layer's panel.

Ask the source rather than guessing, because a field drawn backwards looks entirely plausible. A CF-compliant dataset says which it means in the variable's standard_name: ..._from_direction needs no correction, ..._to_direction needs Where it goes. Wave data is where this bites — the word "oceanographic" suggests direction of travel, but WAVEWATCH III's peak wave direction is published as sea_surface_wave_from_direction_at_variance_spectral_density_maximum, a from direction like the wind.

U and V components

RoleDetected from
U (eastward)u, u10, u_wind, wind_u, ugrd, u_component
V (northward)v, v10, v_wind, wind_v, vgrd, v_component

ugrd and vgrd are the GRIB2 short names GFS ships, so a table converted straight from a GRIB file needs no renaming at all.

The columns are the layer's, not the panel's

Autodetection only picks the layer's opening guess. The four velocity columns are chosen in the layer's own Columns section, which also carries the switch between the two spellings — so a query whose names are in neither list above needs no aliasing, just a different column picked.

The gradient of a value

A single column of numbers over a lattice — terrain height, pressure, temperature, a rainfall anomaly — already describes a flow, and this mode draws it: water runs down a hill the way air runs along a pressure field.

Choose Gradient of a value in the layer's Columns section and point value at the column. There is no role for it and nothing detects it: any numeric column could be a scalar field, and a panel that guessed would turn every query with an elevation column into a flow map. The choice is yours to make, on the layer.

Field → Direction then decides what the gradient means:

DirectionWhat it draws
DownhillThe fall line — runoff over a terrain model. The default.
UphillThe same lines, reversed.
Along the contoursA quarter turn, so the flow runs along the level lines with the high ground on its right.

The third is the one that makes sense of pressure or temperature: air does not pour off a high, it circles it, which is the geostrophic reading of a synoptic chart. Downhill over a pressure field draws something that looks plausible and is wrong.

The colour ramp means slope here, not speed

These vectors are a gradient, so their magnitude is metres of fall per metre travelled — a slope, not m/s. The animation is unaffected, since the tracer normalises it to a legible number of pixels per cycle either way, but Colour by speed is colouring by steepness.

Smoothing matters more in this mode than in any other. A derivative amplifies whatever noise the samples carry, and one bad sample in a terrain model becomes a pit steep enough to turn the flow beside it back up the real slope. It is applied to the scalar before the derivative is taken, so Field → Smoothing is smoothing the ground rather than the flow.

When the wind is in an archive, not in a table

A forecast archive holds the two components already, but it is not a table and no SELECT reaches into it. Both roles still arrive as ordinary columns — the tile server is asked for the whole lattice at once and the query turns its answer into rows. Worked end to end, with the trap that makes the field look calm instead of broken: a wind field from an Icechunk archive.

When the field is on an ERDDAP server

Oceanographic agencies publish through ERDDAP, and it will hand you a rectangle of any gridded dataset as headerless CSV — so read_csv is the entire adapter, with no extension and no client library. The roles arrive by aliasing two variables to speed and direction. The trap there is the land mask: a cell the model does not cover must be absent, never a row of NULLs, because Number(null) is zero and zero draws as dead calm. Gridded ocean data from ERDDAP.

What disqualifies a query

A trip id column. A GPS trace that happens to carry a speed column is a trajectory, not a field, and shredding it into streamlines would draw lines that mean nothing. The rule is explicit: coordinates, plus a velocity pair, and no trip id.

The grid

The grid must really be regular

The axes are inferred, and the values have to lie on a regular lattice within 5% of the inferred step. A source that snaps your requested coordinates to its own model grid — Open-Meteo does — will break that: the inference fails, the field is never built, and you get rows in the panel, an empty map, and no error anywhere.

Round the coordinates back to the lattice you asked for, and space that lattice wider than the snapping error. See Tutorial 6.

Rows are cells. The grid itself is inferred, so the query may return them in any order — ORDER BY lat, lon is for your own benefit when you inspect the rows, not the panel's.

When the query spans several timesteps the earliest is used. Select a single one to be explicit rather than relying on that.

Holes are holes, not calm air

Cells the query omits are treated as absent, and streamlines stop at their edge. This is the mechanism for excluding a pressure level that runs below ground: omit those cells and the lines end at the mountains, instead of drawing weather through rock.

sql
SELECT ts AS "time", lat, lon, speed, direction
FROM wind_grid
WHERE level_hpa = 700
  AND surface_pressure_hpa >= 700   -- drop cells where this level is underground

Smoothing

The field is blurred before tracing, over a radius of 3 cells by default — the same smoothing Esri's own wind demo applies. At a 0.25° grid that is roughly 28 km per cell, so the blur averages over about a synoptic feature: enough to stop the tracer jittering between adjacent cells, not enough to erase anything a 25 km model actually resolves.

Smoothing (cells) under Field changes it. Zero traces the grid as it came, which on a coarse lattice makes the lines visibly wobble between cells. In the gradient mode the same knob blurs the scalar before the slope is taken from it, where it is doing considerably more than tidying.

The lines follow the view

Nothing is drawn until you press play

At the start of the animation window every trail has zero length. A paused field is a blank map — press play on the timeline.

Density and on-screen length hold steady as you zoom, because the field is re-traced whenever the map settles — a fixed budget of lines per screen, each a fixed number of pixels long. Only the share of the screen your data actually covers gets drawn.

The lines are seeded by picking points on the screen and asking the camera what ground is under them, so a tilted or rotated map is covered to its edges. That matters more than it sounds: tilt the camera and the ground on screen stops being a rectangle and becomes a trapezoid reaching towards the horizon — measured at a pitch of 50°, two and a half times deeper up-range than the flat rectangle is tall. A field seeded into the rectangle simply stops halfway up the screen.

Re-tracing reads the grid already in the browser, so panning costs no database work, and it does not touch the dataset — the playhead keeps running through it.

Density

Lines per screen under Streamlines: 9,000 by default, from 500 to 20,000, and per layer. Stack three pressure levels and each asks for its own allowance, so lower them together if the levels stop being tellable apart.

What the budget is for

Zoom response, next to it, decides that — and it is what to reach for when zooming in does not feel like zooming in.

At 0, the default and what this drew before the knob existed, the budget is for the screen: the same number of lines whether the screen shows the whole field or a corner of it, so the map looks the same at every scale. That is Esri's model, and it is the right one for a map that is looked at at one scale.

At 1 the budget is for the field: zooming in shows only the share of it that is on screen, so the lines thin out and separate as you go in. Measured on the tutorial's grid, a map holding 3,100 lines zoomed out drops to 930 zoomed three levels in — the field opens up instead of staying uniformly busy.

In between the two are blended, which is usually where a map that is read across several scales wants to sit.

There is no figure that suits every map:

  • A country covering a third of the view wants more lines than a pair of three-kilometre patches around two weather stations.
  • A field of parallel arrows reads as a solid block at a density a swirling one reads well at.
  • Zooming into a single station's patch fills the screen with what was a comfortable count across a country.

Lower it when the field looks matted; raise it when it looks sparse. For reference, Esri's own demo spends 6,000 on a single field, so the default is deliberately generous.

Several levels at once

One query per level, in the same panel. Each becomes its own layer.

To separate them in the vertical, give each layer a height and tilt the camera with the 3D control. Two ways, and the layer takes the first that applies:

  1. A height column — return one and bind it to the layer's optional altitude column. Its first value is the level's height; one query is one level, so it is read as a constant.
  2. Height (m), when no column — under Field. For the ordinary case, where the height of a level is a property of the query rather than of its rows and there is no column to return.

Height is opt-in either way. Nothing autodetects an altitude: an elevation column picked up by accident lifts a layer kilometres into the air, where it vanishes as soon as the camera descends below it.

The exaggeration is computed for you, and it is not a fixed factor. Pressure levels a few kilometres apart are invisible over a country hundreds of kilometres wide — 1000 to 700 hPa is 2.9 km over about 650 km, four parts in a thousand — so the stack is scaled to a share of the current view instead, about 15% of its width. It is derived from the tallest level on the map, not from each layer's own height, so the levels keep their real proportions to each other and the stack occupies the same share of the screen at every zoom.

Vertical exaggeration under Field multiplies that. Set it the same on every level, or they stop being in proportion.

The two height knobs do different jobs

Because the exaggeration normalises the tallest level on the map, the metres of a single layer decide only whether it sits on the ground or is lifted — not how high it is drawn. That is the exaggeration's job.

The metres earn their keep with a second level: they are what puts 850 hPa and 700 hPa in their real proportion to each other. One layer at 3,000 m and one at 500 m draw six times apart; one layer at 3,000 m on its own draws exactly where one at 500 m would.

Arrows and wind barbs

The same grid can be marked instead of traced. Switch the layer's type to Vector field in its panel — or add one from Add Layer — and every column, the colour ramp and the speed range carry over.

Arrows on a screen grid over the swell off the coast of Ecuador, drawn on top of the same field's streamlines

KnobDoes what
SymbolArrow points where the flow goes. Classified arrow sizes and colours it by class — as many classes as the ramp has colours, the same bins as the legend. Wind barb is the WMO symbol, its staff pointing where the wind comes from.
Data speed unitBarbs only. Barbs count in knots — a half barb is 5, a full barb 10, a pennant 50 — so the layer needs to know what unit the query is in.
Size, Size by speed, Size rangeA fixed size in pixels, or a size between two that follows the speed.
PlacementScreen grid puts one symbol in every cell of the screen, interpolated, the same density at every zoom. Data cells puts one on every sample the query returned, exactly as it came.
Spacing (px)The screen grid's cell, 50 by default.

Barbs south of the equator fly on the other side of the staff, as the WMO convention has it, and a field over Ecuador draws both. Over a gradient there is no wind to count in knots, so the layer offers arrows only. Smoothing starts at 0 here: a symbol on a sample should show that sample. That is only true of a layer added fresh, though — kepler copies a knob to the new type when the name matches, so a layer switched from a flow field keeps its smoothing of 3, and switching it back brings that 0 along with it.

Styling

Every knob is in the layer's own panel, grouped as Colour, Streamlines, Animation and Field.

KnobWhereDoes what
Colour by speedColourOn, each line takes its colour from its mean speed through the ramp below. Off, the whole field is one colour.
Opacity by speedColourFades each line from Opacity in calm air (0.2 by default) up to opaque as its speed rises. Off by default.
Fixed speed rangeColourMeasures colour, width and opacity against Range (min, max) instead of the field's own range. Off by default.
Lines per screenStreamlinesThe density budget.
Zoom responseStreamlinesWhether that budget is for the screen (0) or for the whole field (1).
Stroke widthStreamlinesLine width in pixels, so it holds as you zoom.
Width by speedStreamlinesReplaces the one width with Width range (px): slow lines draw at its low end, fast ones at its high end. Off by default.
Trail lengthStreamlinesHow much of the cycle the moving trail spans, as a percentage.
Line lengthStreamlinesVertices per streamline — how far a line reaches, not how much of it is lit.
CycleAnimationThe length of the loop, in seconds.
Line lifetimeAnimationThe share of the cycle one line lives for, and so how much of the field is lit at once.
Seamless loopAnimationCarries a line whose life runs past the end of the cycle round to the start of it. On by default.
SmoothingFieldBlur radius in cells, not kilometres — so the same number means something very different on a 0.25° lattice and on a 0.5° one.
Height (m)FieldWhat this level is, when no altitude column is bound. Counts against the other levels.
Vertical exaggerationFieldHow tall the stack is drawn. The one that moves a lone layer.

Comparing by colour

With Colour by speed on, the ramp is stretched over the speeds of the whole field — every cell of the grid, not just the lines on screen — so panning and zooming never repaint a line. The map's Legend control shows the same ramp, bin by bin, under Speed; in the gradient mode it reads Slope.

That range still belongs to this field, though: two levels, two panels, or two moments of a forecast each get their own, and cannot be compared by eye. To compare them, switch on Fixed speed range in each and give them the same Range (min, max). The first time it is switched on it starts at the field's own range, rounded outwards to the slider's step. A speed beyond it takes the colour at that end of the ramp.

When the magnitude itself is the point, a layer underneath can still paint it with breaks of its own — see painting the field as well as tracing it.

Width by speed and Opacity by speed follow the same range the colour does, so all three agree on what counts as fast. Each line is drawn at one width and one opacity, from its mean speed. The calm end of the opacity is 0.2 rather than zero on purpose: slack air that vanishes altogether reads as holes in the data.

Trail length is the one that changes the character of the map most: short reads as drifting particles, long as complete streamlines, closer to a classic wind chart. It is a share of the cycle rather than an absolute number, so lengthening the cycle does not silently shorten every trail.

Every line lives for a little over half the cycle by default, with births scattered through it, so trails appear and fade continuously rather than the whole field restarting in unison.

Turning the seamless loop off brings back a visible wave

Without it a line has to end before the cycle does. Nothing is then born in the window's last stretch and nothing has been alive long at its start, so the field empties into the loop and refills out of it — measured on the map, the amount of line on screen swings by a factor of two through the cycle, against 3% with it on.

What it costs is a second drawing of every line that crosses the seam: about half again as many lines at the default lifetime, and nearly double at a lifetime of 1. That is the reason it can be switched off at all — a very dense field on a modest machine.

With the seamless loop on, Line lifetime says only how much of the field is lit at once: at 0.55 a little over half the lines are mid-flight at any instant, and at 1 all of them are. Raise it for a fuller field, lower it for scattered particles. Either way the density holds steady through the cycle.

A line cut short by a hole or by the edge of the field lives for less of the cycle than a whole one, rather than being stretched over all of it, so it moves at the pace of the wind it is in. Next to the edges and the holes, fewer lines are lit at once.

Apache-2.0. Bundles kepler.gl (MIT) and flowmap.gl (Apache-2.0).