Initial Vibe coded project
This commit is contained in:
commit
0891cf12a5
21 files changed
+2830
No files matched your search
@@ -0,0 +1,284 @@
|
||||
# Flight Map
|
||||
|
||||
A Python command-line application that **records OpenSky aircraft positions to
|
||||
SQLite**, then **independently generates a pen-plotter SVG** from a saved session.
|
||||
Collection never downloads maps or terrain; plotting never contacts OpenSky.
|
||||
|
||||
## Install
|
||||
|
||||
Python 3.11 or newer:
|
||||
|
||||
```sh
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
python -m pip install -e .
|
||||
flight-map --help
|
||||
```
|
||||
|
||||
Alternatively, use `uv venv .venv` and `uv pip install -e .`.
|
||||
|
||||
## 1. Collect flights
|
||||
|
||||
The default map rectangle is in Uithoorn, Netherlands:
|
||||
|
||||
| Corner | Latitude | Longitude |
|
||||
| --- | --- | --- |
|
||||
| Southwest | 52.232025 | 4.784498 |
|
||||
| Northeast | 52.253985 | 4.863525 |
|
||||
|
||||
OpenSky supports OAuth client credentials. Obtain your own credentials through
|
||||
OpenSky and set them in your shell, **not in source code**:
|
||||
|
||||
```sh
|
||||
export OPENSKY_CLIENT_ID='your-client-id'
|
||||
export OPENSKY_CLIENT_SECRET='your-client-secret'
|
||||
flight-map collect --db flights.sqlite --duration 3600
|
||||
```
|
||||
|
||||
An existing token can instead be supplied through `OPENSKY_ACCESS_TOKEN`.
|
||||
Anonymous access is attempted when no credentials are supplied, with a warning;
|
||||
availability and daily quotas depend on OpenSky's current policy. Client
|
||||
credentials automatically obtain and refresh tokens; a manually provided token
|
||||
does not refresh.
|
||||
|
||||
Collection prints the session ID immediately. Omit `--duration` to run until
|
||||
Ctrl-C. Observations and progress are committed after each successful poll.
|
||||
Ctrl-C preserves the observations and marks the session interrupted. Service
|
||||
errors stop collection with a nonzero exit status and mark the session failed;
|
||||
HTTP rate-limit responses include `Retry-After` when available.
|
||||
|
||||
Custom bounds are **longitude, latitude, longitude, latitude**, not latitude first:
|
||||
|
||||
```sh
|
||||
flight-map collect --db flights.sqlite \
|
||||
--bounds 4.784498 52.232025 4.863525 52.253985 \
|
||||
--interval 30 --collection-margin 2000 --duration 3600
|
||||
flight-map sessions --db flights.sqlite
|
||||
flight-map collect --db flights.sqlite --resume SESSION_ID --duration 1800
|
||||
```
|
||||
|
||||
Reversed corners are normalized. The requested map rectangle, expanded collection
|
||||
rectangle, interval, source, session times, status, and observation associations
|
||||
are saved in the database. Resume uses those saved settings; it does not change
|
||||
the map area or interval. After an abrupt process termination, the last committed
|
||||
checkpoint remains available even if the status still says `recording`.
|
||||
Do not run two collectors against the same session simultaneously.
|
||||
|
||||
The default poll interval is 30 seconds, with a minimum of 10 seconds. **A valid
|
||||
interval does not guarantee enough daily API credits**: select an interval and
|
||||
duration appropriate to your account's current quota. No historical observations
|
||||
from before collection starts are fetched.
|
||||
|
||||
## Purge a recording
|
||||
|
||||
To permanently delete one session, including its saved map bounds and all flight
|
||||
observations:
|
||||
|
||||
```sh
|
||||
flight-map sessions --db flights.sqlite
|
||||
flight-map purge --db flights.sqlite --session SESSION_ID --yes
|
||||
```
|
||||
|
||||
Stop any collector writing to that session first. The exact session ID and
|
||||
`--yes` confirmation flag are required. Deletion is transactional: either both
|
||||
observations and session metadata are removed, or neither is. Other sessions,
|
||||
map cache entries, and previously exported SVGs are unchanged. Missing sessions
|
||||
or databases produce an error; a mistyped database path does not create a file.
|
||||
Deletion cannot be undone without a backup. This is logical deletion, not secure
|
||||
erasure or database-file compaction.
|
||||
|
||||
## 2. Plot a recorded session
|
||||
|
||||
```sh
|
||||
flight-map plot --db flights.sqlite --session SESSION_ID --output map.svg
|
||||
```
|
||||
|
||||
The map area is read from the session, not from current application defaults.
|
||||
Flight data is opened read-only. Supply a shorter recording interval if needed:
|
||||
|
||||
```sh
|
||||
flight-map plot --db flights.sqlite --session SESSION_ID --output morning.svg \
|
||||
--from 2026-10-20T08:00:00Z --to 2026-10-20T08:30:00Z
|
||||
```
|
||||
|
||||
Timestamps require an explicit time zone; labels are UTC. Both endpoints must be
|
||||
within the saved recording. Only observations within the selected interval are
|
||||
used. At least two usable consecutive airborne observations are required per
|
||||
track. Observations are separated into tracks at callsign changes, ground
|
||||
observations, and gaps over `--max-gap` seconds (default 120); unknown missing
|
||||
positions are not bridged beyond that threshold. Callsigns are not verified
|
||||
passenger-facing flight numbers. Missing callsigns are labeled explicitly, with
|
||||
the aircraft ICAO identifier retained.
|
||||
|
||||
Flight paths use smooth **centripetal Catmull-Rom interpolation**, exported as
|
||||
cubic Bezier SVG paths passing through every recorded position. Two-point tracks
|
||||
remain straight; duplicate positions are handled without invalid coordinates.
|
||||
Smoothing never joins separate tracks or fills long recording gaps. Curves are
|
||||
interpolations, not evidence of the aircraft's precise motion between samples.
|
||||
The actual curves are clipped geometrically to the output rectangle, and their
|
||||
in-map portions determine the time-weighted height calculation.
|
||||
|
||||
### Map cache and offline plotting
|
||||
|
||||
The first plot downloads OpenStreetMap **vector features**, not raster tiles,
|
||||
using Overpass. Roads, waterways, buildings, and water outlines are rendered as
|
||||
unfilled lines. Minor pedestrian/cycle paths, steps, proposed roads, and roads
|
||||
under construction are omitted from the drawing. Building outlines whose shorter
|
||||
dimension would be smaller than three pen widths are omitted to prevent dense
|
||||
housing from turning into ink blobs; larger paper or a finer pen retains more
|
||||
buildings without another download. Multipolygon relations are not assembled in this initial version;
|
||||
only returned way geometry is drawn.
|
||||
|
||||
Downloaded map data is cached in `.flight-map-cache/`. The cache key includes
|
||||
the service URL, exact map bounds, and feature query. Different time ranges,
|
||||
page sizes, and flight selections for the same map reuse that data, including
|
||||
when offline. Cache entries do not expire automatically:
|
||||
|
||||
```sh
|
||||
flight-map plot --db flights.sqlite --session SESSION_ID --output map.svg \
|
||||
--cache /path/to/shared-map-cache
|
||||
flight-map plot --db flights.sqlite --session SESSION_ID --output updated.svg \
|
||||
--cache /path/to/shared-map-cache --refresh-map
|
||||
```
|
||||
|
||||
Refresh validates the response before atomically replacing the cached entry.
|
||||
A failed refresh leaves the previous entry intact and reports an error. A damaged
|
||||
entry is reported rather than silently ignored; use `--refresh-map` to replace it.
|
||||
Cached responses have no embedded credentials.
|
||||
|
||||
For entirely offline plotting, pass a local Overpass JSON export containing
|
||||
`elements` with way `geometry`, produced by an `out geom;` query:
|
||||
|
||||
```sh
|
||||
flight-map plot --db flights.sqlite --session SESSION_ID --output offline.svg \
|
||||
--map-data local-map.json
|
||||
```
|
||||
|
||||
### Height above the map's average ground level
|
||||
|
||||
**Without suitable elevation inputs the map still plots, but heights are `N/A`
|
||||
and the program emits warnings.** It never substitutes pressure altitude, zero
|
||||
ground elevation, or sea-level altitude.
|
||||
|
||||
The calculation is:
|
||||
|
||||
```text
|
||||
height = time-weighted mean aircraft altitude inside the saved map bounds
|
||||
minus area-weighted mean ground elevation inside those same bounds
|
||||
```
|
||||
|
||||
Outside-map extensions do not contribute. Heights are in metres, not terrain
|
||||
clearance under the aircraft.
|
||||
|
||||
Supply a terrain GeoTIFF whose first band contains ground elevation in metres,
|
||||
has a CRS, and covers the entire map rectangle. Its declared vertical reference
|
||||
must be known. Sampling uses an equal-area 64 by 64 geographic grid and honors
|
||||
nodata, raster band scale, and offset. Any missing sampled ground value makes
|
||||
the mean unavailable. Choose terrain resolution appropriate to the map; the
|
||||
grid is an approximation, not a survey-grade terrain integral.
|
||||
|
||||
For terrain already referenced to the WGS84 ellipsoid:
|
||||
|
||||
```sh
|
||||
flight-map plot --db flights.sqlite --session SESSION_ID --output heights.svg \
|
||||
--terrain terrain-ellipsoid.tif --terrain-reference wgs84-ellipsoid \
|
||||
--confirm-geometric-ellipsoid
|
||||
```
|
||||
|
||||
For orthometric terrain, also supply a geoid separation GeoTIFF in metres,
|
||||
representing **N = h - H**, matching the terrain's vertical datum and covering
|
||||
the track observations used for conversion:
|
||||
|
||||
```sh
|
||||
flight-map plot --db flights.sqlite --session SESSION_ID --output heights.svg \
|
||||
--terrain terrain.tif --terrain-reference orthometric \
|
||||
--geoid matching-geoid.tif --confirm-geometric-ellipsoid
|
||||
```
|
||||
|
||||
The application computes `H = h - N` for each geometric aircraft altitude.
|
||||
OpenSky documents its field as geometric altitude but does not establish every
|
||||
observation's datum or accuracy. The confirmation flag is a deliberate assertion
|
||||
by the operator that treating these heights as WGS84 ellipsoidal is appropriate.
|
||||
Do not set it without verifying your source's interpretation. A missing geometric
|
||||
altitude or geoid value on a contributing segment makes that track's height
|
||||
unavailable. Pressure altitude is recorded for provenance, not used for this
|
||||
calculation. For Dutch terrain in NAP, use a conversion consistent with NAP;
|
||||
do not assume an arbitrary global geoid matches it.
|
||||
|
||||
### Pen-plotter settings
|
||||
|
||||
Defaults: A4 landscape, 12 mm page margin, 0.3 mm pen, 1.8 mm lettering, and up to
|
||||
4 mm of recorded flight extension beyond the map border.
|
||||
|
||||
```sh
|
||||
flight-map plot --db flights.sqlite --session SESSION_ID --output large.svg \
|
||||
--width 420 --height 297 --pen-width 0.3 --font-height 2 --extension 5
|
||||
```
|
||||
|
||||
SVG dimensions are in millimetres. A local equirectangular projection preserves
|
||||
the rectangular geographic bounds and uses a common scale for map and tracks;
|
||||
it is intended for local maps, not large geographic extents. Geometry is
|
||||
physically clipped, not hidden with masks. Groups `map`, `flights`, and
|
||||
`lettering` can be selected separately. Text, including attribution, is drawn
|
||||
with an original single-stroke alphabet: no installed fonts, raster images,
|
||||
filled glyphs, or SVG clipping support are required.
|
||||
|
||||
Each track has a numeric identifier and a legend entry with UTC dates/times,
|
||||
callsign, ICAO identifier, and average height. For dense recordings, select
|
||||
shorter periods or larger paper. The program fails explicitly if the legend
|
||||
does not fit or a track identifier cannot be placed legibly. Flight identifiers
|
||||
avoid each other; intersections with map geometry or flight paths are not fully
|
||||
optimized. Map lines are simplified at 0.05 mm tolerance and identical paths
|
||||
deduplicated; physical pen-path travel optimization is left to plotter software.
|
||||
|
||||
Existing output is protected unless `--force` is supplied. Map bounds crossing
|
||||
the antimeridian, widths above 180 degrees, and latitudes outside [-85, 85] are
|
||||
rejected.
|
||||
|
||||
## Offline synthetic demo
|
||||
|
||||
This creates **invented** tracks, map features, terrain, and geoid data, for
|
||||
checking the complete workflow without network access:
|
||||
|
||||
```sh
|
||||
python examples/make_demo.py demo
|
||||
```
|
||||
|
||||
Run the plotting command printed by the script. Its heights use synthetic,
|
||||
known-compatible references; these inputs are not a real Uithoorn dataset.
|
||||
|
||||
## Development
|
||||
|
||||
```sh
|
||||
python -m pip install -e '.[dev]'
|
||||
python -m pytest
|
||||
ruff check flight_map tests examples
|
||||
ruff format --check flight_map tests examples
|
||||
mypy flight_map
|
||||
```
|
||||
|
||||
Tests cover recording and resume settings, persistence, deduplication, bounds,
|
||||
projection, track gaps, clipping, time-weighted heights, missing terrain, vertical
|
||||
references, cache reuse/refresh/failure, and repeatable offline SVG generation
|
||||
without mutating the database.
|
||||
|
||||
## Data policies and attribution
|
||||
|
||||
- Map: [OpenStreetMap contributors](https://www.openstreetmap.org/copyright),
|
||||
licensed under ODbL. Respect the chosen Overpass service's
|
||||
[usage guidance](https://dev.overpass-api.de/overpass-doc/en/preface/commons.html);
|
||||
cache and avoid unnecessary requests.
|
||||
- Flights: [The OpenSky Network](https://opensky-network.org). This initial
|
||||
application is intended for non-commercial use, subject to its current
|
||||
[terms](https://opensky-network.org/about/terms-of-use) and
|
||||
[API documentation](https://openskynetwork.github.io/opensky-api/rest.html).
|
||||
Coverage and fields vary; collection does not guarantee complete tracks.
|
||||
Cite Matthias Schaefer, Martin Strohmeier, Vincent Lenders, Ivan Martinovic,
|
||||
and Matthias Wilhelm, "Bringing Up OpenSky: A Large-scale ADS-B Sensor Network
|
||||
for Research", IPSN 2014, pp. 83-94, when publishing derived work.
|
||||
- Terrain and geoid: supplied by the operator; follow their separate licenses
|
||||
and attribution requirements when distributing outputs.
|
||||
|
||||
Map and flight attribution is included as plotter paths, with further provenance
|
||||
in SVG metadata. This is an artistic recording/plotting tool, not a navigation
|
||||
or aviation safety product.
|
||||
Reference in new issue
Block a user