Skip to content

Take-off airfield on Android

Take-off place and time zone is partial on Android. The time zone lookup is ported. The take-off airfield is not, because Android has no airports table (airports were out of scope for v1). Android has the columns and syncs them, but never fills them. Only flights that came from iOS show an airfield.

The result today:

  • A flight imported on Android shows no take-off airfield or country in the detail.
  • Search by airfield name or code finds nothing for those flights.
  • The logbook’s countries filter is built but stays hidden, because no flight has a country.
  • The Charron scoreboard shows EBZW instead of “Genk 🇧🇪” (CharronAirports.NONE).

This analysis ports the airports table and the take-off lookup only. The airport screens (nearest airports, airport detail, frequencies) stay out (Nearest airports remains not-planned).

The rule is in Take-off airfield. In short: once per flight, at its first save to the logbook, the nearest airfield to the take-off fix, if it is 3 km away or closer, gives takeoffCode, takeoffName and takeoffCountryCode, and isTakeoffProcessed becomes true.

What the pilot sees afterwards, all of it already built on Android and only waiting for data:

PlaceShows
Flight detailTake-off code and location (name + flag). Android shows the name without the flag today (see the Android table).
Logbook searchMatches the code and the name
Logbook filterThe Countries section, one row per take-off country
Charron scoreboardAirfield name and flag in the start column

Flights imported on Android before this change keep no airfield (decision 3). Only flights imported from now on get one, as on iOS, where the lookup only runs when a flight is created.

So the lookup runs on a flight’s first analysis only (isFirstAnalysis, analysisVersion == 0), not whenever isTakeoffProcessed is false. Otherwise a later re-analysis (an ANALYSIS_VERSION bump, or a forced one) would fill old flights after all. Old flights keep isTakeoffProcessed = false; nothing marks them.

CaseResult
No take-off detected (a ground track, a broken file)No airfield; processed.
Out-landing field, hill site, ridge launchUsually no airfield within 3 km; processed.
Two airfields near the same spotThe nearest wins. On a tie, the first in the file wins.
Flight synced from iOSAlready processed there; Android doesn’t touch it.
Pilot later edits the flightThe lookup never runs again.
Flight imported on Android before this changeStays without an airfield, also after a re-analysis.
  • Airports table: copy vulture-ios/Packages/Airports/Sources/Airports/Resources/airports.json byte for byte, as was done for timezones.json. It adds about 1.3 MB to the APK (compressed). Load it lazily, off the main thread, once per process. Decode only the fields used here (a–e); frequencies and elevation can be skipped.
  • Database: no change. FlightEntity already has takeoffCode, takeoffName, takeoffCountryCode, isTakeoffProcessed and an index on takeoffCountryCode.
  • Sync: no change to the sync schema. Filling the fields is a local change, so the row is marked dirty and goes up like any other edit.

None.

None new. Country names and flags come from the platform (Android already has flag() and countryName() in :feature:serviceflights).

#QuestionDecisionDate
1How much of the airports feature?The table and the take-off lookup (plus the Charron code lookup it unlocks). No airport screens.2026-10-20
2Which data?iOS’s airports.json, byte for byte, refreshed only from vulture-ios.2026-10-20
3Existing Android flights?Not filled: only flights imported from now on get an airfield. No backfill, not on re-analysis either.2026-10-20
4Charron scoreboard?Same as iOS: the start code is looked up in the same table and shows name and flag; ZZZZ and unknown codes stay as sent. Part of this work.2026-10-20

None.

Nothing to build. Read:

WhatPath (vulture-ios)
Table loading, nearest, 3 km limit, code lookupPackages/Airports/Sources/Airports/AirportManager.swift
JSON keysPackages/Airports/Sources/Airports/Models/Airport.swift
Once-per-flight lookupPackages/Persistence/Sources/Persistence/Extensions/CoreData/CoreFlight+CoreData.swift (processTakeoff)
Countries filterModules/App/Sources/Shell/Sidebar/List/Filter/ListFilterItem.swift, ListConfiguration+Data.swift
Charron start columnPackages/Charron/Sources/Charron/Models/CharronFlight.swift
Data scriptScripts/prepare_airports.rb
AreaExisting code (vulture-android)Change
Table and lookupdomain/flight/.../timezone/TimeZoneLocator.kt is the pattern (bundled JSON, lazy decode); the distance is core.geo.distance, see the caveat belowA JVM module (for example domain/airports) with airports.json in its resources, nearest(coordinate) with the 3 km limit and byCode(icao). Unit tests against iOS results for a set of coordinates (at least EBZW → “Genk”/BE, an out-landing, a tie, 2,999 m and 3,001 m).
Lookup at savedata/db/.../FlightRows.kt (withAnalysis, its KDoc says “a later phase ports” it), FlightRepository.ktOn the first analysis only (isFirstAnalysis and !isTakeoffProcessed), look up flight.takeoffFix and set the three fields and the flag, in the same transaction (decision 3).
Charrondata/charron/.../CharronAirports.kt (NONE), di/CharronModuleBind an implementation over the table (decision 4).
Detailfeature/flightdetail/.../FlightDetailViewModel.kt (toRecord: takeoffLocation = takeoffName)Add the flag, as iOS CoreFlight.takeoffLocation (takeoffName + flag, space-joined). Reuse the flag helper from :feature:serviceflights (move it to a shared module).
Filterfeature/flightlist/.../filter/Nothing: it shows the Countries section once there are countries. Update the comments that say it waits for airports.
Docsdocs/analysis/00-roadmap.md, decisions §7Note that the airports table is ported, the screens are not.
ChangelogCHANGELOG.md“Added: the take-off airfield and country of flights imported on this phone, and the countries filter in the Library.”

Android caveats:

  • Don’t use android.location.Location distances (CLAUDE.md). Unlike the time zone lookup, iOS’s airport lookup measures with the Map package’s haversine (R = 6,371 km), not CLLocation.distance, so Android uses its port core.geo.distance, not ellipsoidalDistance (found while building, 2026-10-20; Take-off airfield corrected).
  • Scanning 48,000 airfields per flight is fine for an import. Load the table once and reuse it (a multi-file import looks up many flights in a row).

No changes.

  • vulture-android: airports table module with the lookup and its tests.
  • vulture-android: lookup on the first analysis in withAnalysis, marked dirty for sync; a test that a re-analysis of an old flight leaves it empty.
  • vulture-android: Charron CharronAirports binding (decision 4).
  • vulture-android: flag after the take-off name in the flight detail.
  • vulture-android: roadmap, decisions note, comments, changelog.
  • vulture-docs: when built, set Android done on Take-off place and time zone and status: shipped here.