← projects 02 complete 2025

FlightEye

A native Android app that fetches live ADS-B position data and overlays aircraft onto the camera viewfinder in real time. Four coordinate systems, a rotation matrix, and a perspective divide — get any one wrong and the overlay points at the ground.

"the math behind it was really cool"

AndroidKotlinJetpack ComposeMath
4 coordinate systems geodetic · ENU · device · camera
1 rotation matrix 3×3, from sensor fusion
~30 FPS overlay Canvas draw on Compose
0 third-party math libs all manual

Coordinate pipeline

01

Geodetic

lat / lon / alt from the ADS-B API.

02

ENU

Local East-North-Up frame centred on the user. East scaled by cos(φ).

03

Device

Apply Rᵀ from TYPE_ROTATION_VECTOR to get sensor-relative coordinates.

04

Camera

Negate Z — sensor +Z faces user, camera +Z faces scene.

05

Screen

Perspective divide by Cz. Scale by FOV tangent. Flip Y. Done.

Two tricky details

cos(φ) in the East term

Lines of longitude converge toward the poles. One degree of longitude is ~111 km at the equator and much less near the Arctic. Multiplying East by cos(latitude) scales it to match the North term — without this, east-west positions drift at higher latitudes.

TYPE_ROTATION_VECTOR not GAME

The game rotation variant fuses only accelerometer and gyroscope — no magnetometer. It has no absolute azimuth reference and drifts over time. For an AR overlay that needs to agree with GPS bearings, you need TYPE_ROTATION_VECTOR.

Device → camera transform

ProjectionUtils.kt
// Apply Rᵀ: maps ENU vector into device-frame axes
val dX = R[0]*E + R[1]*N + R[2]*U
val dY = R[3]*E + R[4]*N + R[5]*U
val dZ = R[6]*E + R[7]*N + R[8]*U

// Camera frame: device +Z → user's face, camera +Z → scene
val cX = dX;  val cY = dY;  val cZ = -dZ

// Behind camera — don't draw
if (cZ <= 0f) return null

// Perspective divide + FOV scale
val ndcX = cX / (cZ * tan(fovH / 2f))
val ndcY = cY / (cZ * tan(fovV / 2f))

The -dZ is not a hack. It is the fundamental difference between the sensor and camera conventions.

Tech stack

UI Jetpack Compose + Canvas overlay
Architecture ViewModel + StateFlow (UDF)
Sensor Android TYPE_ROTATION_VECTOR
Camera CameraX preview surface
Data OpenSky Network REST API
FOV CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS

Each failure mode is distinct. Forget the Y-axis flip and aircraft above the horizon appear below centre. Use the wrong sensor and the overlay drifts. The pipeline structure tells you which transformation broke before you even look at the code.