Competition task replay: pilots fly over 3D terrain along their own GPS track
A typical competition task: 38 pilots fly over 3D terrain along their own tracks, and 324,000 GPS fixes reach the browser as a 1.6 MB file.
The problem
sla.events is our platform for paragliding and hang gliding competitions: calendar, results, rankings. After every task, pilots upload their GPS tracks to it. All well and good, but the site badly lacked a track replay. It is a hard enough problem that only a handful of popular replay services exist, and all of them are built on the same library, Cesium. It is easy for developers to pick up, and the picture it produces is detailed and beautiful. The flip side is a lot of data over the wire and heavy demands on hardware. Using such a replay in the field is very hard, often impossible. What was needed was an alternative replay: perhaps not as spectacular as the Cesium ones, but fast and light on hardware.
The solution
We put together a technology stack of our own: light and fast, yet it gives a good-quality picture even on low-end devices. Several dozen tracks from one task can be watched at once on one map, on a shared timeline. The limit is no longer the bandwidth of the connection or what the device can handle, but the viewer’s comfort: following more than 10–15 tracks at once is hard enough even on a big screen. The cylinders and the optimal route sit on the map exactly as they do in the task itself. The map can be tilted and switched to 3D terrain. The pilots then fly at their real altitude, and a translucent “curtain” hangs from each track down to the ground, showing both how high a pilot is above the slope and where they climbed.
2D and 3D




The same moment of the task: on a flat map and on the terrain
How a track reaches the browser
On the server. Pilots upload tracks as IGC, GPX or KML, and the server converts anything that is not IGC to IGC. For the replay, a track is trimmed to the task window, the time the pilot stood still is dropped, and the points are delta-encoded: the first one in full, each next one as its difference from the one before.
On the wire. The response goes out in the binary MessagePack format. In a task with 38 pilots, 324,000 points take 1.6 MB instead of 3.9 MB as JSON. The server takes the task geometry from an FSComp or XCTrack file and computes the optimal route itself, by FAI rules.
Track transfer
{
title: 'МС. Параплан-маршрутный полет Peregrine Falkon. nonFAI, Задача 3',
tracks: [
{ number: 30, name: '…', pts: [
[4283512, 4721035, 612, 1782976516], // first point: latitude×1e5, longitude×1e5, altitude, time
[3, -2, 1, 1], // after that, only the difference from the previous one
[4, -1, 2, 1],
…
] },
… // 37 more pilots
],
geo: { … }, // cylinders, start and finish lines, the optimal route
marks: [{ text: 'Stop', ts: … }],
} The shape of the replay API response; the coordinates and times in the example are made up.
three.js inside MapLibre
The map is MapLibre GL with terrain from open data. three.js draws the tracks in 3D, not on a separate canvas over the map but inside it: each track is a MapLibre layer of its own, and all the layers share one WebGL context and one camera with the map. So when the map turns or tilts, the tracks never fall behind the terrain, not by a single frame.
A pilot’s marker keeps the same size on screen at any zoom. And three.js itself is another 189 KB, which the browser downloads only when the visitor turns on 3D.
A link is a set of pilots
In a replay address, the part after # holds the numbers of the pilots shown on the map. A pilot can send a link to their own flight, or to a comparison with a rival, and it opens with the same pilots. Change the numbers right in the address, and the app loads only the tracks it does not have yet.
Then there is a timeline with speeds from 1× to 8×, seeking from the keyboard, and a camera that follows the chosen pilot.
One engine, three apps
The map, the terrain, the track player and the widgets live in a separate engine written in TypeScript. It runs the replay itself, the task and flying-site maps on sla.events, and task.er, a task planner for organisers that works offline too. A fix in the engine reaches all three.


