← SoaringScoring

Case study · SoaringScoring

Finding close calls in a day of gliding.

A safety officer picks a contest day, and a minute or so later has a ranked list of every time two gliders came too close. They dismiss the false alarms, and turn the real ones into a video that can be shared with pilots.

50 m
default proximity threshold
1 Hz
common time grid for every logger
10 km
grid cells keep comparisons local

Built with Python on AWS Lambda · S3 · MongoDB Atlas · Node.js and Express · React · canvas and MediaRecorder · SRTM terrain data · OpenStreetMap tiles · YouTube Data API

The problem

A gliding contest produces one flight log per pilot per day, often 30 to 60 files, each recorded by a different logger at a different rate. Finding every close encounter means comparing each log against every other, which is slow to do by hand or in desktop tools and easy to get wrong. A tool for this has to be quick enough to use the same evening, and trusted enough that officers act on what it shows.

How it works

  1. Upload. Pilots' IGC files land in S3, named by day, registration and sequence, in a folder per class and day. This is the same path scoring uses.
  2. Analyse. From the contest's Analysis page, a scorer or safety officer clicks Analyse. The Express server starts a Python Lambda asynchronously, passing the day, a 50 m threshold and, optionally, the day's QNH.
  3. Compare. The Lambda fetches and parses the day's files with eight threads, corrects altitude, resamples every track to 1 Hz, and compares only nearby pairs in 3D. One row per event is written to MongoDB, and the page polls until results appear.
  4. Vet. The officer reviews each event and excludes false positives. Excluded events disappear from the results and from the video, and the whole day can be cleared and re-run.
  5. Check the floor. A separate check in the Node server compares each fix with SRTM terrain elevation to find flying below the contest's minimum height above ground. It ignores a radius around the home airfield, groups fixes into runs so a five-minute excursion is one event, skips outlandings, and reports missing terrain tiles so a clean day is never falsely clean.
  6. Check the FLARM. Transmit range comes from Open Glider Network data through a partner API. Receive range is decoded from the IGC files and compared with minimums the scorer sets.
  7. Render. The video is drawn entirely in the officer's browser: map tiles and tracks on a canvas, recorded with MediaRecorder at 30 fps. Playback runs at 5× and eases down to 0.5× with a zoom on the pair as they close, then holds on the moment of closest approach. Floor events get their own segments.
  8. Publish. Only when the officer clicks Upload, the browser sends the video to the server, which publishes it to YouTube as unlisted. The privacy setting is fixed on the server, so it can't be made public from the interface.

Decisions worth explaining

Python on Lambda
The work is bursty and heavy, with tens of megabytes of tracks and a pairwise loop, and it only runs on request. A Lambda keeps it off the web server and scales per run. The trade-off is operational: the deployed code is a zip, so every change needs a manual rebuild and upload.
Resample to 1 Hz
Loggers record every one to four seconds or more, so comparing raw fixes misses encounters between gliders on different rates. Interpolating to a shared one-second grid fixes that, at two to four times the memory. Gaps over 20 seconds are not bridged, because a logger dropout isn't evidence of anything.
A spatial grid
Points go into 10 km cells and each second only a glider's own and neighbouring cells are compared. Work grows with the number of nearby pairs, not with every possible pair.
Altitude correction
Raw pressure altitude can carry a large baseline error. The preferred fix subtracts the glider's pressure altitude on the ground before take-off and adds the airfield's elevation. If that isn't available it falls back to the day's QNH, and with neither it applies no correction. The video shifts each trace to these corrected altitudes, otherwise its vertical separation would mean nothing.
Video in the browser
There's no video infrastructure to run or pay for, and the officer sees and checks the result before anything leaves their machine. The map and canvas code already lives in the browser. The cost is that it needs a modern desktop browser and takes about as long to render as it plays.
Anonymous and unlisted
The video shows no registrations or competition numbers, and is shared by link with contest participants. It involves identifiable safety events, so it shouldn't be searchable. The Analysis page shows identities to officers, and the video does not.

What went wrong, and what I did about it

Noisy detectors. I built two other kinds of conflict detection and removed both. A blind-spot detector was shipped and pulled within a day, and a head-on detector went about ten days later. Both produced too many false positives even after repeated tuning. A third, opposite-direction thermalling, is built and tested but paused: four separate false positives turned up against real contest data in four days. Each of the first three had a real cause and was fixed, but new failure modes kept arriving, so I stopped rather than ship noise. For safety tooling a noisy detector erodes an officer's trust in the quiet, accurate ones, so I narrowed scope to one dependable detector and made dismissing false positives a first-class part of the workflow.

Bugs found on real contest days.

What I can and can't claim

See SoaringScoring Get in touch