Skip to content

How a query becomes a map

The panel does not ask you to describe your data. It reads the columns your query already returned, works out what part each one plays, and builds the layer. This page is the whole pipeline in one place; the pages after it go role by role.

A hexagonal grid raised into 3D from one query: the panel read its columns and made them the height and the colour

The pipeline

One data frameField roleskepler datasetLayerper querydetected, then yourscolumns renamedkepler's, or oursrefId A, B, C…latitude, time, tripId…latitude, time, trip_id…point, trip, flow…

One query, one dataset

Each query in the panel becomes one kepler dataset, identified by its Grafana refId. A panel with queries A, B and C gives you three datasets you can style, filter and reorder independently — which is how you overlay a road network on a heatmap on a set of trajectories in a single map.

That id is derived from the refId rather than generated, and the stability matters: it is what lets a refresh replace the rows underneath a dataset while leaving the layers you configured sitting on top of them. Most panels of this kind tear the dataset down and rebuild it on every refresh, which throws your styling away each time the query re-runs.

Detection, then your overrides

Roles are guessed twice over. First by column name, matched case-insensitively and exactly:

RoleDetected from
latitudelatitude, lat, y
longitudelongitude, lon, lng, long, x
tripIdtrip_id, tripid, track_id, trackid, trajectory_id, vehicle_id, journey_id
geometrygeom, geometry, the_geom, wkb_geometry, geojson, wkt, shape
h3h3, h3_index, hex_id, hexagon, h3index

The exact match is deliberate. Substring matching would let origin_lat satisfy lat, and an origin–destination table would silently collapse into a plain point layer. The full table for all twenty-one roles is in Field roles.

Time is the exception: it is found by type, not by name. Grafana already knows which column is temporal, and real dashboards call it everything from time to recorded_at to __timestamp.

Then your overrides are applied, per query, from Field mapping in the panel options. A role can be in one of three states — and the third one is why the editor has a None entry:

StateHowEffect
Autodetectedleave it alonethe guess stands, shown as a placeholder
Pointed at a columnpick a columnyour choice wins
Switched offpick Nonethe role is removed entirely

Switching a role off is a real answer, not the absence of one. It is the only way to draw a query carrying a vehicle_id as loose points rather than as trajectories — clearing the field would just hand the role back to autodetection, which would find vehicle_id again.

Columns are renamed, not configured

kepler builds its own default layers by looking at column names. So a role is expressed by renaming your column on the way in, rather than by writing a layer configuration:

RoleYour column, renamed to
latitude / longitudelatitude / longitude
timetime
tripIdtrip_id
altitudealtitude
geometry_geojson
h3h3
origin/destination lat/lnglat0, lng0, lat1, lng1
origin/destination H3source_h3, target_h3
countcount

Every other column is passed through untouched, which is what keeps it available for tooltips, colour scales and filters. Nothing is dropped for not having a role.

Who builds the layer

kepler auto-detects some layers from those column names and not others, so the panel fills the gaps:

LayerBuilt byWhy
Pointkeplerit recognises latitude/longitude
GeoJSON / polygonkeplerit recognises _geojson
H3, S2keplerit recognises the index columns
Tripthe panelkepler's own trip heuristic insists on a column literally named id
Flowthe panelkepler does not auto-detect flows at all
Velocity fieldthe panelthe rows are a grid, not places — see below

The two layers the panel builds from scratch

A trajectory query — one with a trip id, a time and a position — gets a Trip layer emitted explicitly, in kepler's saved-config shape so kepler parses it as a configuration rather than trying to use it verbatim. See Trajectories.

An origin–destination query gets a Flow layer the same way, in LAT_LNG or H3 column mode. H3 wins when both are present, on the grounds that a hexagon flow is the more specific description. See Origin–destination flows.

The one query that is not a table of places

A velocity field is different in kind. Its rows describe a grid of vectors, and what gets drawn is not the rows but the paths traced through them. The rows travel to kepler as they came — one timestep of the lattice, columns and all — and a Flow field layer over them builds the field, smooths it and traces the streamlines, at draw time, from the current viewport. See Wind and other velocity fields.

A query qualifies when it has coordinates and a velocity and no trip id. That last condition is load-bearing: a GPS trace that happens to carry a speed column is a trajectory, and shredding it into streamlines would draw lines that mean nothing.

The query that returns an address instead of rows

There is a second departure from the pipeline, and it goes the other way. A column named raster_url — or a wms_url and wms_layer pair — does not describe where a row is. It says the query is not really about rows at all: the thing to draw is an image, and what the query can contribute is where to find it.

Those queries produce a second dataset with no rows alongside the ordinary one, holding only the address and, for a raster, the server that can read it. Your rows are still a dataset — a catalogue search returns a scene id, a date and a cloud cover worth keeping — so nothing is lost by one of its columns being an image.

Return one row per date and the map's time widget walks them, which is the whole of the multitemporal arrangement. See Rasters, WMS services and Imagery over time.

What survives a refresh

Datasets kepler already holds have their rows swapped in place; only a genuinely new query — one that gained a refId — creates a dataset. Your layers, colours, filters and interactions are untouched by a refresh.

They are not, however, saved anywhere by default. Layer styling lives in kepler's store, not Grafana's, so it survives a refresh but not a page reload until you capture it explicitly with Map configuration → Save current map. See Map configuration.

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