# 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.232025, longitude 4.784498. Default northeast corner: latitude 52.253985, longitude 4.863525. Command-line bounds use longitude first: 4.784498 52.232025 4.863525 52.253985. - 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-height 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 height above the map's average ground elevation. - 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 covering the requested map bounds, excluding the surrounding collection area. Use an area-weighted mean or an equivalent equal-area sampling method. - Calculate each track's average height as its average aircraft altitude over the portion inside the requested map bounds minus the map's average ground elevation. Outside-map extensions must not affect this calculation. - Use compatible units and a common vertical reference for aircraft altitude and terrain elevation, applying conversions where needed. Do not subtract terrain elevation directly from uncorrected barometric pressure altitude. - Label the result explicitly as average height above the map's average ground level, with units. This is not height above the terrain directly beneath each aircraft position and must not be presented as terrain clearance. - Indicate when the height cannot be calculated because compatible aircraft altitude or sufficient terrain data is unavailable; do not silently substitute altitude above sea level or assume zero ground elevation. - 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 ground elevation, vertical-reference compatibility, average-height calculation, missing elevation data, 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 heights in metres. - Accept local terrain GeoTIFFs with a declared vertical reference. For orthometric terrain, accept a matching geoid-separation raster for converting ellipsoidal aircraft heights. Require explicit confirmation of the geometric altitude interpretation before calculating heights; otherwise label them unavailable. Never silently substitute barometric altitude. - Estimate average ground elevation using a 64 by 64 equal-area sampling grid. Any missing terrain sample makes the average unavailable. - Use time-weighted aircraft 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.