I made an app for competitive hot air balloon pilots

Mar 20, 2025 | 32 minute read

This is a fairly long story of why I built Balloon Navigator, what’s involved to fly with centimeters level precision when you can only use wind and how technology is shaping modern ballooning today.

Flying hot air balloon
This is me (right) with my brother and a laptop for navigation.

Table of contents:

What the hell is competitive hot air ballooning?

Hot air balloons are known as colorful, romantic bulbs of air gently soaring through the skies. But as with everything humans do, people of course perform extreme ballooning, too:

Beating world records: there are well-known brave balloonists like Richard Branson, but there is another level of crazy, which is sparsely documented and you can usually only hear about it from the people involved. There is one great book I can recommend, though: Nine Lives: A Memoir of Extreme Ballooning

Stunts: the usual Redbull/GoPro ideas like hanging a skatepark or a swing under a balloon or slacklining between two balloons

Sport: Running competitions to find out who is the better pilot. Since it’s fairly difficult (and boring) to race with balloons, pilots compete on precision of flying. And you can be very precise with a balloon.


Let’s focus on the Sport aspect. Take a look at the simplest task in a hot air balloon competition - Judge Declared Goal:

JDG task

Target
Balloon flying over a target marked with pink cross on the ground

Judge Declared Goal (JDG) is a coordinate on the ground a pilot must reach and drop a small weighted bag called marker as close to the center as possible. Pilot uses available wind directions on different altitudes to steer towards a target.

Result is a distance of marker’s landing point to the target.

Target
Flying low, preparing to drop a marker next to a target

You can see on the photos that best drops are right on the target, with only a few centimeters from the center. To achieve such result, the pilot needs to fly with incredible precision since their only available input is changing altitude.

This is achieved by reading winds. But reading winds is much more involved than opening random weather app. First, ballooning competition employs a meteorologist (or a team of them). They wake up early, take their fancy equipment to the field and perform wind measurements, compare it with radar and weather forecast models and prepare a very precise weather forecast for the next 2-3 hours.

This is used by competition director to plan tasks and by pilots to plan their flights. Usually right before the take-off window opens, they prepare another forecast so pilots can have more accurate data.

It doesn’t stop there, because every pilot also measures winds themselves from the ground. It’s a little less fancy, with a helium balloon and a compass or GPS to check in which direction it flies away. Or simply by looking at trees and clouds, which share a lot of information to a trained eye.

It doesn’t stop there, too. Ground crews also measure winds for the pilot during the flight. For example, they can quickly drive to target on the ground and report wind direction in real-time, so pilot can plan their approach accordingly. Sometimes ground crews deploy small weather balloons to measure winds at multiple altitudes as weather balloon rises up.

And at last, pilots in the air can observe other balloons at different altitudes and see how the wind is changing. They also share this data between themselves on the radio.

With all this knowledge, one interesting approach to target might look like this:

JDG Drawing
Top view

Color lines are wind directions at altitudes:

  • blue: current balloon altitude
  • green: halfway to the ground
  • orange: ground level winds

Pilot will miss the target on current altitude and going lower to green winds will even make it worse, pushing them further away. Pilot wants to not only be over the target, but be there at the lowest altitude possible so they can drop a marker precisely.

What needs to be done is to descent as fast as possible to the ground, slicing through the green layer and minimizing its effects on flight trajectory, overshoot a target when in green layer and actually fly backwards to target in orange layer. If they miss and still have some luck left, they can fly back up to green layer to try and reach the target again.

Now, there are some additional caveats:

  • Known wind directions are not 100% precise and are constantly changing
  • Wind speed is also different in different layers
  • It takes time to reach given altitude, and every wind layer on the way influences flight trajectory
  • High inertia of a balloon requires lots of “feeling it out”
  • To achieve their target, pilots often dangerously reach ascent and descent speeds higher than allowed by balloon manufacturers (7-9m/s). At this speed, envelope starts to deform and is at the risk of collapse. You can watch a close call on YouTube.

Let’s try with some other task: a Donut. Donut consists of two cylinders, and measurement area is between them. The goal is to record the longest possible distance within the area.

Donut task sheet

Donut drawing
Top view

Color lines represents flight tracks of three pilots

  • blue: Drops out of the measurement area on the inside where distance is not scored and pilot achieves poor result
  • green: Stays in the measurement area, but uses only a single wind layer
  • orange: Uses different wind layers to keep themselves in the measurement area as long as possible, achieving highest result

Time to dial it up a little - a 3D task, Cake/Pie . The goal is the same: achieve highest possible distance logged within measurement area. Different parts of the cake have distance calculation multipliers (ex. 1 meter in top part is calculated as 3 meters).

Cake task

Cake drawing
Top (left) and side (right) view

Color lines represents flight tracks of three pilots

  • blue: Is not aware of wind changes in higher parts of the task, gets blown away from the center
  • green: Stays in the measurement area, but flies with low precision which makes them miss some parts of the task
  • orange: Knows the exact directions of wind on all layers and keeps themselves in the measurement area as long as possible, achieving highest result

Maximum score per single task is 1000 points. Single flight has 4 tasks on average. With two flights a day, a weekend competition pool is (1000 * 4 * 2 * 2) = 16,000 points total.

Pilot with best result always gets 1000 points, and other pilots get points based on their result difference from the best. The top 5 result might be: 1000, 980, 960, 900, 850 (notice only 150 points difference between 1st and 5th result). If you completely fail the task, ex. never reach a target to drop a marker, you usually get no more than 400-500 points. You can even lose points when breaking a rule.

So if you’re aiming for the podium, you need stay on top the whole time and achieve high score in every task. But it’s not that easy, as tasks are often mixed with each other: middle of task #1 might be a beginning of task #2. Task #2 allows to choose from 3 different targets, but you will reach Task #3 only from one of them. Task #4 has to be declared in the air between task #2 and #3, with set minimum distances from task #1, #2 and #3, and so on…

Failing only one task out of 16 total in the competition might set you back so much that you can forget about winning. And it’s pretty easy to fumble: don’t declare target on time, declare it underground, drop wrong marker color, enter restricted zone by flying 30 meters too high, take off too late and reach target 15 seconds after it closes, miscalculate descent rate going from 2 kilometers to the ground and overshoot a target by 50 meters…

Winners don’t fly well, they fly consistently well.

Flying with computers

Computer with markers and task list
Computer used for navigation, with blue and pink markers and task list on the right

To aid with complexity and improve precision, we now fly with computers (or tablets) as a navigation aid. These are either smaller, older devices or rugged laptops. Ballooning is dirty and hard landing can break a lot of stuff, so nobody brings a Macbook to their basket.

During flight, computer is connected to GPS receiver and displays all navigation data on the screen. Map of competition area shows all important information: protected zones, waypoints, task areas, tracks, estimated flight path, time to target and more. Pilot can modify these during flight, draw additional helpers like wind direction lines, set up proximity alarms etc.

Ideally, all of it should be done in minimal time - if you look at the screen, you don’t look outside, and you’d rather want to know what’s happening around you, especially when it gets crowded.

In the clouds
Discussing a plan for the next task (this laptop didn’t have a very bright screen)

Rugged Dell
Rugged Dell laptop with handheld GPS, variometer and airband handheld radio

The most popular navigation app used in ballooning today is OziExplorer, software dating back to previous century, originally created for offroad navigation in Australia. OziExplorer has great support for over hundred map projections (which was necessary when competition maps were sourced from old military maps with crazy datums), advanced navigation features and connects to many GPS receivers on the market.

OziExplorer
Screenshot of OziExplorer from the official website

At the same time, OziExplorer is ancient and it feels. User interface clunkiness and complexity was the reason for me to look into building my own navigation software. I saw an opportunity to build something better, faster, modern and with features optimised for flying. I was also spoiled by modern online maps and wanted high-quality experience, not a pixelated JPG file with undecipherable text.

Low map quality
Example of low resolution and low detail maps I often had to fly on

So, in 2017 I began learning…

Principles

To make sure I ever finish this project in full, I gave myself a few rules:

Avoid burnout at all cost

I knew it will take many, many years to complete (like every side project with any meaningful complexity), so I needed to make sure my own developer experience is very good. Stick with what I know, don’t reinvent the wheel and reduce recurring upkeep cost to zero, so I don’t feel bad when taking a break. And I will allow myself to take long breaks.

Offline first

It should be always 100% functional when offline. It will be running in the fields and high altitudes over countryside where mobile connectivity is spotty already on the ground.

Narrow scope

I’m building very specialized software probably only for me, or at most few hundred people around the world. I cannot compete on the number of features, so I should build the best tool dedicated to a single task.

Easy to use with batteries included

I want pilots to have everything they need for their flight in the app already, ex. no need to source maps of new area, simply click a button and it’s there. All the difficult stuff should happen automagically behind the scenes.

Timeline of technology choices

Balloon Navigator
Balloon Navigator user interface at the time of writing this post

At the beginning, I discarded desktop native option. I looked into Qt particularly and, having professional experience working with web-oriented software all my life (Ruby, PHP, vanilla Javascript), it seemed like it would require learning a completely new way of creating things (and C++) - a showstopper at the very first step. Also, I believe I didn’t find any good mapping libraries for my use case.

So, the choice was pretty easy - I’m going HTML, CSS and Javascript, the things I sort-of know. One of the must-haves was support for GPS receivers, which connect through serial ports. At the time, the Javascript-based option was Node.js and serialport package. How to use browser and Node.js? Apparently, the whole Internet was talking about this one new thing…

Electron. I had a hard time with Electron, though. I wasted many hours trying to debug weird errors which I never knew if they were caused by Electron itself, Webpack, serialport, npm modules not compatible with it or anything else like division between main/renderer process. It was too many concepts introduced at once and I simply couldn’t understand the source of many bugs.


This was exhausting, so I took a break and shelved the project for one or two years.


I decided to simplify. Got rid of Electron and separated frontend from backend. Backend was a small app managing serial port connections and running a webserver which served the frontend app. It wasn’t feeling very native, but worked pretty well and I thought I’ll figure out how to make it nicer a little later.

Separate backend allowed me to build a server in Go, which greatly simplified cross-platform distribution as I could build it for every system as a single binary. Backend was proxying raw NMEA streams to frontend through Websockets which then decoded it into proper coordinates.

As for the frontend, I needed reactivity. Quick look at JSX made me scream and run away from React and I decided on Vue.js. Vue.js was okay at first, but when I inevitably reached higher complexity, speed of development slowed down to a crawl. I’m not a developer and this was my first foray into Javscript frameworks, or basically anything bigger than single vanilla Javascript function. I had issues mainly with stores not updating, crashing with errors I didn’t understand and managing state in general. It felt like a slog, to be honest.

The second most important frontend building block was a map library. The ecosystem was amazing, with three big players: Leaflet, Mapbox GL JS and OpenLayers. Leaflet lost quickly as I realised it didn’t support map rotation and any workarounds were hacky. I found Mapbox docs little clunky and couldn’t really work out what’s paid and what’s free. Didn’t spend a lot of time on them, though. In the end, I went with OpenLayers, which seemed to be the best choice feature-wise and worst choice for easy-to-use. It took me some time to really grasp how it works, as documentation is definitely not written to accomodate beginners, but in the end it worked well. Today, Mapbox GL JS is no longer open-source and was forked into MapLibre which has some features I envy, like 3D view, but I didn’t have a chance to test it myself yet.

As for the maps themselves, golden standard for the web is tiled maps, small square images loaded dynamically when user pans over the area, with different images for every zoom level. Together, they form a pyramid where a single tile for low zoom level is divided into more tiles for higher zoom level.

Tile pyramid

Source: https://www.e-education.psu.edu/geog585/node/706

This works well for online maps as web server can deliver static images pretty quickly, but to cover the whole planet in good quality you need 275 billions of those images, which I suppose require a lot of hard drive space. Of course not everything is cached as tiles which are not delivered often are generated in real time, but this requires more compute than simply serving static files.

One of the principles is to work 100% offline, so I had to figure out how to allow pilots to download tiles of the area they need. I wrote a script translating bounding box of area into list of all tiles required for all zoom levels. App would download these tiles (tens of thousands of them), which the webserver then could serve from a disk. I opted for using IndexedDB instead (at least for now), so I could keep the backend only for serial port communication. Each downloaded image was saved to IndexedDB with its tile XYZ coordinates and then served to OpenLayers using URL.createObjectURL method. After tile has been loaded, I revoked the URL with URL.revokeObjectURL to avoid memory leaks. Worked great.

In development, I was leeching those tiles from OpenStreetMap servers, but bulk downloads are breaching their Terms of Service and I knew that in the end, I would have to provide my own server - which breaks my first principle of keeping the maintenance costs at zero. Also, to provide any other style than default OSM, I would have to build my own tiles, ideally on request instead of caching everything on a disk. Which required stronger (and more expensive) machine.

It was a serious obstacle, so I kept researching. I learned about vector tiles, and they solved all my problems: they don’t require lot of storage, they can be styled however I want in real-time, they are always razor sharp (no more pixelated JPGs), they load instantly. All in all, a superior technology.

They were still tiles, though, and on top of that (if I remember correctly), existing vector tile providers were commercial businesses selling cloud-based licenses not allowing offline usage of their data. I had to figure out how to build my own tiles from OpenStreetMap dataset, which seemed to be a daunting task at the time.


I ended up taking a break…

…which took 3 years.


I returned mid 2022 to discover some really cool new technologies:

WebSerial API was like a gift from heavens. Now I could talk with serial ports directly from the browser, without any backend wrapper.

I got rid of backend component and transformed my frontend into Progressive Web App (PWA), so it can be installed and work offline. Thanks to being 100% browser based, Balloon Navigator now worked not only on Windows/Mac/Linux, but also on every mobile device: iPad, iPhone, Android. Basically everything with a browser and enough performance to support vector maps.

If Vue.js was a slog, Svelte 3 turned it into a fresh breeze. I migrated and my code became so simple, so straightforward, it felt nice to work on it again. It didn’t completely fix my issues with stores and reactivity right away, but now I had a better understanding what’s happening and fixed everything.

Protomaps completely transformed map management for me. Protomaps are vector maps, where all tiles are packed into a single .pmtiles static file I could host on CDN, and request particular tiles from the file using HTTP range requests. PMTiles file of the whole planet is only 70GB. Now I could extract parts of the planet file into smaller areas using CLI extract command running on my Synology server in a garage.

Protomaps also greatly simplified working with offline maps. Downloading thousands of small tiles took a really long time compared to requesting a single file from CDN, saving it in IndexedDB and using URL.createObjectURL only once.

Present and future

Today, Balloon Navigator is deployed to production and you can fly with it, but not all advanced features have been implemented yet. Of course it could have fewer bugs, better performance, and nicer UI in many places, but I hope I will be able to work on it for many years to come until it achieves perfection.

The app is forever free for all client-side features. The only upkeep cost is a domain, as it is hosted for free as a static page. For server-side functionalities like user accounts and saving data between devices, I easily fit within the free tier of Supabase or any other similar provider. For sharing real-time data between pilots (like live tracking) I fit within the free tier of any real-time messaging platform like Ably.

For the future, I want to include airspaces, elevation data, fully customizable app and map themes (including building custom UI modules out of existing drag-and-drop elements), automatic wind readings synced between multiple balloons, 3D flight analysis and more.

What’s the timeline for all of this? Who knows. Ten years seems like a reasonable estimation. One thing I know for sure: balloons and balloon pilots will still be here, flying, competing, doing stunts and beating records.

Clouds
One of the rare occasions of being both above and below clouds



Hot Air Balloons Development