File formats & units.
Import a small set of candidate flight tracks and frequency curves. Keep their units, time origins, and height references explicit so that a good fit has a useful meaning.
Recording time is arrival time.
Enter the UTC start of the original recording, even when you analyze a crop. An event sample at times_s = 45 means sound received 45 seconds after that original start. Cropping at 30 seconds does not reset the clock.
Flight positions use absolute Unix UTC seconds. They describe where the aircraft was when sound was emitted. The model adds the travel delay, arrival = emission + range / sound_speed, before comparing the sound to your recording. Do not add that delay to the flight file yourself. File creation, download, and upload times are not evidence of recording time.
All events share one recording start and one searched time offset. Use curves from the same continuous recording, with their original offsets preserved. Each event fits a separate constant emitted frequency.
| Quantity | Required convention |
|---|---|
| Coordinates | WGS84 latitude and longitude in degrees; west/south negative. |
| Numeric flight time | Unix seconds, including fractional seconds. Never Unix milliseconds. |
| Text flight time | ISO 8601 with timezone, for example 2026-11-02T12:00:00Z or 2026-11-02T04:00:00-08:00. |
| Curve time / frequency | Seconds from original recording start / hertz. |
| Normalized altitude | Metres with an explicit datum. |
| Receiver height | WGS84 ellipsoid height in metres, constant across the candidate grid. |
geometric means WGS84 ellipsoid height in this tool. barometric is pressure altitude; unknown means unverified. Mean-sea-level terrain heights need an external geoid conversion before they can be treated as ellipsoid heights. No pressure or geoid correction is applied here. Barometric and unknown heights are accepted with warnings for exploratory comparisons and can systematically bias the result.
Normalized CSV tracks
Choose a .csv file in Flight files. One row is one timestamped aircraft position. Rows are grouped by flight_id. The first five column names below are required; altitude_datum is optional and defaults to unknown with a warning.
flight_id,time_utc,latitude,longitude,altitude_m,altitude_datum
EXAMPLE,2026-11-02T12:00:00Z,49.0,-123.10,3000,geometric
EXAMPLE,2026-11-02T12:00:10Z,49.0,-123.07,3000,geometric
EXAMPLE,2026-11-02T12:00:20Z,49.0,-123.04,3000,geometricEach track needs at least two points. Times are sorted, but duplicate timestamps are rejected. A track cannot mix altitude datums. Quoted CSV fields and escaped quotes are supported. Nonfinite numbers, missing numeric values, and invalid coordinate ranges are rejected.
Download synthetic sample tracks · CSVThis small sample illustrates the schema. It is separate from the built-in two-flyover demonstration, and will not match that demonstration’s recording clock.
Normalized JSON tracks
Use a JSON object containing tracks. Arrays within each track must have equal lengths. Flight IDs must be unique across imported files. The Python workbench’s exported normalized arrays are compatible with this schema.
{
"tracks": [{
"flight_id": "EXAMPLE",
"times_utc": [1793620800, 1793620810, 1793620820],
"latitudes": [49.0, 49.0, 49.0],
"longitudes": [-123.10, -123.07, -123.04],
"altitudes_m": [3000, 3000, 3000],
"altitude_datum": "geometric"
}]
}Time arrays also accept timezone-qualified ISO strings. Additional fields are not used to determine the trajectory. An exported analysis file containing a top-level tracks array can be reimported through Flight files; this restores tracks, not the entire session.
readsb and adsb.lol trace files
Import an individual trace JSON object with icao, timestamp, and trace, following the official readsb schema ↗. The parser adds each point’s relative timestamp to the object’s base Unix timestamp. Aircraft heights in that format are in feet and are converted using exactly 0.3048 metres per foot.
Prefer geometric uses geometric altitude when at least two usable geometric points exist, otherwise barometric altitude. The other choices require the selected source. A track never combines geometric and barometric samples. Points missing the chosen altitude are omitted and counted.
Geometric altitude is read from point index 10, then that point’s extra alt_geom, then index 3 when flag bit 8 marks it geometric. Barometric altitude uses index 3 when that bit is absent, or explicit extra alt_baro. Stale points marked by flag bit 1, ground points, and missing positions are excluded. Missing intervals remain gaps; interpolation does not bridge gaps over 30 seconds.
The browser expands files ending in .gz using its built-in gzip support. If a compressed trace has a .json filename, decompress it first or give it a .json.gz extension. Plain CSV and JSON are supported; decompress CSV archives before importing. A daily tar archive or live aircraft.json snapshot is not an individual trace.
Obtain authorized data separately from sources such as adsb.lol’s historical archive ↗. This page does not download an archive or guarantee its temporal or geographic coverage.
Automatic scanning and curve imports
Choosing a recording starts a full-length scan. The scanner works through overlapping time windows, removes persistent spectral background, follows supported tonal peaks, and tests descending Doppler-like shapes. It rejects stationary or poorly supported traces and groups overlapping harmonic candidates. The displayed start/end timestamps are seconds from the original recording, not from an individual scan window. The default output is a shortlist of at most 20 candidates after ranking and duplicate removal, displayed strongest first; it is not an exhaustive aircraft count.
Measured peak frequencies and the smooth fitted curve remain separate. The fit is a diagnostic overlay, not replacement evidence for an absent sound. A quality score ranks candidates; it is not a probability that an aircraft was present. Review the spectrum and listen to a candidate before using it for flight matching. Some real flyovers can be missed, and other moving or changing-pitch sources can pass the checks.
Use Import curves for a single extracted curve or an object containing an events array. This accepts automatic-scan exports, Python workbench curve exports, browser curve exports, and the events within exported analysis files.
{
"events": [{
"name": "First flyover",
"flight_id": "auto",
"times_s": [45.0, 45.5, 46.0],
"frequencies_hz": [155.2, 154.8, 154.4]
}]
}Every event requires at least three strictly increasing, nonnegative times and matching positive frequencies. flight_id can name an imported track, or auto to compare the imported candidates. Missing IDs default to auto. Importing replaces saved events; it does not load the source recording, restore recording-start UTC, or prove that a curve came from an aircraft. Verify the source and time origin before matching.
Current limits and assumptions
- MP3 uploads: up to 200 MB and two hours. A local worker incrementally decodes compressed chunks and retains channel 0 at 4 kHz, avoiding a full-rate hour-long stereo buffer. Other browser-supported audio/video: up to 100 MB and two hours; browser-native decoding can require substantially more memory. Video analysis uses its audio track. Automatic scans need at least four seconds; optional manual crops are 2–600 seconds.
- Up to 64 MiB per flight file after decompression, 64 MiB of selected compressed/plain files per import, and 500,000 total points across at most 100 imported tracks. Curve uploads are capped at 10 MiB, 20 events, and 500,000 total samples. Smaller selected track windows are faster.
- A local grid of at most 2,500 candidates, with coordinate spans no greater than 5° and latitude within ±85°. No antimeridian crossing. The UI requires at least 25 m grid spacing.
- At most 201 shared time offsets. Additional worker limits bound candidate/offset pairs and total flight comparisons. A large search may need narrower bounds, fewer tracks, or coarser spacing.
- The model assumes a stationary receiver, constant emitted frequency within each event, constant sound speed, and piecewise-linear aircraft motion. It accounts for sound travel time.
- Automatic tonal-candidate discovery is supported; verified aircraft classification is not. There is no countrywide retrieval, unknown-date search, terrain grid, absence-of-aircraft evidence filter, weather/refraction model, or guaranteed geographic accuracy. The synthetic example checks behavior, not real-world performance.
The best candidate minimizes frequency error among the supplied possibilities. A low RMSE is not a probability, proof of aircraft identity, or a confidence region. A fine grid does not establish fine accuracy. Several locations or aircraft may explain the same sound.
Independent implementation
Doppler Lab’s browser analysis is an independent implementation inspired by Colsto’s investigation ↗. It is not the creator’s original source code and is not affiliated with the creator.
The workbench bundles mpg123-decoder 1.0.3 locally for incremental MP3 decoding, with its WebAssembly embedded in the module. The JavaScript wrapper is MIT-licensed; the compiled mpg123 codec is LGPL v2.1. The embedded puff decompressor and simple-yenc component retain their own licenses. These are separate component terms; the whole bundle is not solely MIT-licensed. The analysis, scanning, and flight-model modules are independent implementations. No recording is sent to an external analysis API.
Included notices: wrapper MIT license, mpg123 LGPL notice, mpg123 authors, puff source and license, and simple-yenc license. See dependency provenance and exact source revisions and upstream build instructions for modifying or replacing the separable decoder. Other parts of Tesselate may use additional dependencies.
Imported data keeps its own license. adsb.lol publishes its historical data under ODbL 1.0; see its archive documentation and the license text. The synthetic CSV linked here contains invented positions and no third-party flight data.