12 KiB
12 KiB
Flight Map Requirements
Purpose
Create a Python program that generates an SVG map of a rectangular geographic area with recorded aircraft flight tracks overlaid, suitable for drawing with a physical pen plotter.
Separate collection and plotting workflow
- Provide separate command-line commands for flight-data collection and SVG generation. Run collection first; generate maps later from the saved database.
- The collection command accepts optional opposite map corners and the SQLite database destination. If corners are omitted, use the default map area including Uithoorn in the Netherlands. It records flight data without generating an SVG or requiring map or terrain downloads.
- Store a recording-session record containing a session identifier, the normalized requested map bounds, the expanded collection bounds, the flight data source, the collection interval, and the recording start and end times. Preserve this metadata across interruption or restart.
- Associate every stored flight observation with its recording session so its map area and collection context remain identifiable.
- The plotting command accepts the database, a recording-session selection, an optional time interval within that recording, and output settings. Use the selected session's stored map bounds rather than requiring the user to enter the map area again.
- Plotting must not contact OpenSky or start flight-data collection. It may obtain OpenStreetMap-derived vector data and terrain elevation data separately as needed.
- Allow multiple maps with different time selections and output settings to be generated from the same recording without modifying the recorded flight observations or session metadata.
- Report missing sessions, invalid time selections, and absence of usable observations explicitly.
- Provide a purge command requiring an exact session identifier and explicit confirmation to permanently delete that session's observations and metadata in one transaction. Preserve all other sessions and their observations.
- Purging a missing session or database must report an error without creating a database. Stop collection for the selected session before purging it.
Map input and output
- At collection time, accept the longitude and latitude of two opposite corners of the map, validate coordinates, and normalize their order.
- Provide a predefined default rectangular map area that includes the city of Uithoorn in the Netherlands. Explicitly supplied corners override this default.
- Default southwest corner: latitude 52.232, longitude 4.784. Default northeast corner: latitude 52.258, longitude 4.864. Command-line bounds use longitude first: 4.784 52.232 4.864 52.258.
- Store the actual bounds used, including when the default is selected, in the recording session. Later plotting must use those stored bounds rather than a potentially changed application default.
- At plotting time, load the map bounds from the selected recording session.
- Obtain OpenStreetMap-derived vector map data, either as vector tiles or geographic feature data, following the source's usage policy and including the required attribution. Standard raster map tiles are not suitable for the pen-plotter output.
- Cache downloaded vector map data persistently by source, geographic bounds, and feature query. Reuse it across plotting runs without downloading it again. Provide a configurable cache directory and an explicit refresh option.
- Write cache entries atomically so interrupted downloads or writes do not replace a previously usable entry. Report damaged cache entries explicitly.
- Generate an SVG using a consistent geographic projection for the map and flight tracks.
- Handle invalid input and download failures with clear error messages.
Pen-plotter output
- Render map features and flight tracks as vector paths or line geometry, without embedded raster images or reliance on filled areas, shading, or other screen-only effects.
- Render all labels and attribution as paths, using single-line stroke lettering suitable for pen plotting rather than relying on installed fonts or tracing filled glyph outlines.
- Resolve clipping into the exported geometry so map features stop at the map boundary and flight-track extensions remain within the output page. Do not rely on SVG clipping masks being supported by the plotter software.
- Specify physical page dimensions and a matching SVG viewBox so the intended drawing size is preserved when imported into plotter software.
- Keep map geometry, flight tracks, and lettering in separate named SVG groups for independent selection and plotting.
- Select and simplify map features to keep the drawing legible at its physical output size. Omit minor pedestrian and cycle paths and suppress building outlines whose shorter dimension is smaller than three pen widths.
Flight data and local recording
- Use OpenSky Network as the initial flight-data source.
- Retrieve live aircraft state vectors for the selected geographic area.
- Query an area slightly larger than the map so recorded flight paths can extend beyond the map boundary.
- Collect positions periodically, respecting OpenSky's current authentication requirements, rate limits, and usage terms.
- Store observations locally in SQLite so maps can be generated later without fetching those observations again.
- Store the aircraft's ICAO identifier, callsign when available, position timestamp, longitude, latitude, and available altitude measurements.
- Support generating a map from locally recorded observations for a selected time interval.
- Do not require historical data from before local recording began. Unrestricted historical access is not assumed.
- Report recording failures explicitly and handle missing fields and gaps in coverage without inventing observations.
- Initial use is non-commercial, subject to OpenSky's applicable terms and attribution requirements.
Flight-track rendering
- Overlay recorded flight paths that cross the requested map area.
- Fit smooth interpolating curves through recorded flight positions rather than drawing angular polylines. Export flight paths as cubic Bezier geometry suitable for a pen plotter. Two-point tracks may remain straight.
- Do not smooth across track gaps, on-ground interruptions, or flight changes. Handle repeated positions without generating invalid curve coordinates.
- Clip the actual curves to the output bounds, not just their sample points, and use the same curved in-map portions for time and average-altitude selection.
- Extend the visible tracks a little beyond the map boundary using actual recorded positions from the surrounding area, not fabricated extrapolation.
- Keep the map background within its requested bounds and provide space in the SVG for the extended tracks and their labels.
- Identify each flight track with its date, time, callsign or flight number when available, and average reported barometric (pressure) altitude in metres.
- Do not present an OpenSky callsign as a verified passenger-facing flight number. Callsign-to-flight-number lookup is not required for the initial version.
- Calculate the map's average ground elevation from terrain elevation data for landmass within the requested map bounds, excluding mapped water and the surrounding collection area. Preserve islands in water multipolygon relations. Use an area-weighted mean or an equivalent equal-area sampling method.
- Average valid land samples only. Report the percentage and counts of sampled land with valid elevations, warn about missing land samples, and never classify missing terrain as water. Show land coverage in the SVG legend and metadata.
- Obtain AHN DTM height maps automatically through PDOK during plotting for the Netherlands. Cache validated GeoTIFF subsets persistently and atomically, record source and NAP reference, and support explicit refresh and disabling of automatic terrain retrieval. Explicit local terrain inputs take precedence.
- If AHN height maps are unavailable or the area is outside coverage, warn explicitly and continue rendering with the land-elevation summary unavailable. Reported aircraft altitude must remain independent of terrain availability.
- Calculate each track's time-weighted average reported barometric altitude over the portion inside the requested map bounds. Outside-map extensions must not affect this calculation.
- Do not subtract ground elevation or apply geoid, pressure, or vertical-reference corrections to aircraft altitude. The average land elevation is a separate, informational summary only.
- Label aircraft values explicitly as reported barometric altitude with units, not height above ground, terrain clearance, or verified altitude above sea level.
- Missing barometric altitude on a contributing segment makes that track's average unavailable, with an explicit warning. Do not fall back to geometric altitude. Zero and negative reported values are valid.
- Make labels readable and indicate unavailable metadata explicitly.
Implementation and validation
- Implement the program in Python with a command-line interface.
- Document installation, the separate collection and plotting commands, session selection, map generation, configuration, data-source limitations, and attribution.
- Test coordinate handling, projection, local recording, track selection, average land elevation, average reported barometric altitude, missing altitude and elevation data, terrain-independent flight labels, and SVG generation.
- Test persistence of session bounds and observation associations, plotting after collection has stopped, and repeated plotting without changing the recorded flight data. Verify that collection does not require map or terrain services and plotting makes no OpenSky requests.
- Test that omitting map corners selects the Uithoorn default, explicit corners override it, and the selected bounds are persisted for later plotting.
- Validate that the exported SVG contains drawable vector geometry, path-based lettering, correct physical dimensions, and geometry clipped to its intended bounds, with no raster images or dependence on text rendering or clipping masks.
- Include an end-to-end test using fixed sample data without requiring live network access.
- Test cache reuse, separate entries for different map areas, explicit refresh, and preservation of cached data when refresh fails.
Initial implementation defaults and limitations
- Use Overpass to obtain OpenStreetMap roads, waterways, building outlines, and water outlines, with local Overpass JSON also accepted for offline plotting.
- Default to A4 landscape (297 by 210 mm), 12 mm page margins, a 0.3 mm pen, and track extensions of up to 4 mm beyond the map border.
- Collect every 30 seconds within a 2,000 m margin around the saved map area.
- Display timestamps in UTC using date and 24-hour time, and altitudes in metres.
- Use PDOK AHN DTM
dtm_05mvia WCS for Dutch areas, requesting a 512 by 512 nearest-neighbour subset in EPSG:28992, with elevations in metres relative to NAP. Accept local terrain GeoTIFFs with a declared vertical reference for the separate land-elevation summary. No geoid or aircraft datum conversion inputs are required or used. - Estimate average land elevation using a 64 by 64 equal-area sampling grid. Exclude mapped water, average available land elevations, and warn about missing land coverage. No valid land samples makes the average unavailable.
- Use time-weighted reported barometric altitude with linear interpolation between recorded timestamps, restricted to the smoothed path's portion inside the map. Use centripetal Catmull-Rom interpolation converted to cubic Bezier curves for flight paths. Split tracks at changes in callsign, on-ground observations, or gaps exceeding 120 seconds by default.
- Reject antimeridian-crossing bounds and latitudes outside [-85, 85] explicitly.