Skip to content

Trajectories

Give each row a trip id, a time and a position, and the panel groups the points into paths and builds an animated Trip layer with a playback timeline. No layer configuration.

sql
SELECT vehicle_id AS trip_id,
       ST_Y(geom)  AS latitude,
       ST_X(geom)  AS longitude,
       ts          AS time
FROM vehicle_positions
ORDER BY vehicle_id, ts;

The three columns that make a trip

All three are required — two of them is not a trajectory:

RoleDetected fromNotes
Trip idtrip_id, tripid, track_id, trackid, trajectory_id, vehicle_id, journey_idgroups the points
Timeany column the data source typed as timeorders them
Latitude / longitudethe usual namespositions them
Altitudenothing — see belowoptional third dimension

Miss any of the first three and you get a plain point layer instead, which is a useful diagnostic: if you expected animation and got dots, one of the three was not recognised.

Why the panel builds this layer and kepler does not

kepler has its own Trip layer detection, but its heuristic insists on a column literally named id. Real trajectory tables call it vehicle_id, trip_id, imei or device. So the panel emits the layer configuration itself — in kepler's saved-config shape, so kepler parses it rather than trying to use it verbatim.

Altitude is opt-in, on purpose

The altitude role has no detected column names at all. That is not an oversight.

GPS traces routinely carry an elevation column holding metres above sea level, and deck.gl reads a path's third coordinate as height above the ground. A trail through Cuenca at 2,650 m would be drawn 2.65 km up — so it looks perfectly fine zoomed out, and vanishes the moment the camera descends below it, at roughly zoom 15. That reads as a rendering bug rather than as a mapping choice, which is exactly the wrong failure to hand someone.

So height on a trip path is opt-in: map it yourself under Field mapping → Altitude when you actually want a 3D path, and be aware you are asking for height above terrain, not elevation.

Table columns or GeoJSON

Trip layer in the panel options picks between kepler's two Trip layer shapes. The default is the right answer almost always.

Table columns (default)GeoJSON
Rowsone per GPS pingone per trip
Columns keptall of themonly the path
Colour by speed, mode, driveryesno
Filter on a per-point columnyesno
Tooltipsfull rownothing useful
Payload sizefullfar smaller

table mode hands kepler the rows as they come and lets it group them by id, so speed_kmh, mode_of_transport and everything else stays available for colour, filters and tooltips.

geojson mode folds every ping of a trip into a single row carrying a LineString whose coordinates are [lng, lat, alt, timestamp]. Everything that was not one of those four is discarded — that is the trade you are making. It is worth making when all you want is the animated line over a very large number of points, and it is also the shape older saved map configurations point at.

What the folding does

In either mode the points are sorted by time within each trip, because queries rarely guarantee order and an unordered path draws as a scribble. Trips with fewer than two points are dropped, since a line needs two. Points missing a coordinate or a timestamp are skipped rather than poisoning the whole line with a null.

Sorting in SQL as well is still worth doing — ORDER BY vehicle_id, ts — because it makes the data legible when you inspect it in a table panel beside the map.

Playback

A Trip layer gives you kepler's playback timeline at the bottom of the map: play, pause, speed, and a scrubber. Trail Length in the layer settings decides how much of the path stays visible behind the moving head — short reads as a comet, long reads as the whole route drawing itself.

The playhead couples to the dashboard in three different ways, covered in Time range sync, and it can be shared with the other maps on the dashboard without touching the dashboard time range at all — see Peer time sync.

Drawing a trajectory table as points instead

Sometimes you want the pings, not the paths. Set Field mapping → Trip ID → None.

This is why the editor has a None entry rather than just an empty field: clearing the field would hand the role back to autodetection, which would find vehicle_id again and rebuild the trips. None switches the role off outright.

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