Competition task replay: pilots fly over 3D terrain along their own GPS track

Project
In-house project
Year
2026
What we did
ProcessWire websites
Stack
ProcessWire, PHP, Interactive 3D maps, three.js
Website
replay.sla.events/a241c30d592e

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.

38
pilots in one replay
Peregrine Falkon competition, task 3: every track on a shared timeline
B
1.6MB
was 3.9 MB
every track of the task
324,000 GPS fixes: delta-encoded MessagePack against the same response as JSON, 14 September 2026
B
306KB
initial load
gzip; three.js, another 189 KB, loads only when the visitor turns on 3D
B
3
apps on one engine
replay.sla.events, the task and flying-site maps on sla.events, and the task.er task planner
B

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.

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.