Architecture - msupino/NavigationApp GitHub Wiki

๐Ÿ‡ฎ๐Ÿ‡ฑ ืขื‘ืจื™ืช

Architecture

NavAid is plain HTML / CSS / JavaScript on top of Leaflet. No build step, no bundler, no transpiler. What ships in docs/ is exactly what runs.

File layout

See Directory Structure for the current docs/ tree. In short: runtime source lives in docs/app/, datasets in docs/data/, locale bundles in docs/i18n/, browser images in docs/assets/, and legacy static-map files in docs/legacy/.

Module load order

<script> tags in index.html load in this exact order โ€” there are no imports:

i18n/<lang>/strings.js
  โ””โ”€โ”€ sets window.S = { โ€ฆ } for i18n
app/core.js
  โ””โ”€โ”€ window.NavAid namespace, map = L.map(...), tuning registry, helpers
app/route-graph-shapes.js
  โ””โ”€โ”€ projects nav waypoints / leg altitudes / comm-change points out of the layer graph
app/terrain.js
  โ””โ”€โ”€ terrain grid + MSA (minimum safe altitude) helpers
app/draw.js
  โ””โ”€โ”€ draw(), scheduleDraw() (rAF-coalesced)
app/interact.js
  โ””โ”€โ”€ canvas overlay event handlers, hit-test, drag
app/io.js
  โ””โ”€โ”€ save/load, exportPNG, flight plan, KML
app/alt-pair-directions.js
  โ””โ”€โ”€ altitude-pair direction helpers
app/gdrive.js
  โ””โ”€โ”€ optional Google Drive route library
app/gps.js
  โ””โ”€โ”€ live position, track recording, in-flight alerts, compass fallback
app/ui.js
  โ””โ”€โ”€ toolbar wiring, layer picker, rotate dial, map controls, bootstrap
app/editor.js
  โ””โ”€โ”€ the ?editor=1 map-data editor
app/assistant.js
  โ””โ”€โ”€ the AI assistant panel (off unless featureAssistant is on)
app/offline-tiles.js
  โ””โ”€โ”€ downloadable offline chart packs

Each module attaches its public surface to window.NavAid or relies on a globally-defined function from a previous module. There is no module system.

State

A single global namespace window.NavAid plus a few module-local variables. The core state shape:

{
  waypoints: [{ lat, lng, name }],
  legs:      [{ inboundAltitude, outboundAltitude, flightSpeed,
                inLabel: { a, p }, outLabel: { a, p } }],
  notes:     [{ lat, lng, text, color, shape }]
}

The number of legs is always max(0, waypoints.length - 1).

Draw pipeline

Rendering happens on a <canvas id="overlay"> positioned over the Leaflet map. Leaflet handles tiles + zoom/pan; NavAid draws everything else.

Each draw() call:

  1. Resize the canvas to match map size.
  2. Clear.
  3. Project all positions via map.latLngToContainerPoint(...) to current pixel coords.
  4. Paint the core route elements in this simplified order (back to front):
    • Navigation waypoints (if toggle on).
    • Airfields (if toggle on).
    • Legs (line, arrow, mid-leg distance badge, return arrow if toggle on, halo if highlight-diff).
    • Waypoints (circle + name / number).
    • Notes (background, then text).
    • Page frame (dashed rectangle if A3/A4 active).
  5. Persist state if not exporting.

This list is intentionally abridged. draw() in docs/app/draw.js is the authority for terrain, areas, rings, VOR/NOTAM overlays, kites, tracks, own-ship, information, the plan card, and their complete paint order.

draw() is wrapped by scheduleDraw() which collapses multiple calls into a single requestAnimationFrame, avoiding redraw churn during pan/zoom/rotate.

Hit testing

interact.js generally mirrors paint priority in reverse so the topmost element wins. Click coordinates are converted to map container pixels; each element type has a hit* function. The simplified core route order is:

hitNote -> hitWaypoint -> hitLegLabel -> hitLeg -> hitPageFrame

This matches the paint order so overlapping notes remain selectable above waypoints. interact.js is the authority for the additional comm callout, overlay marker, NOTAM/bubble, cumulative-kite, and selected-item priority paths.

Map rotation

Via the leaflet-rotate plugin. The dial is a custom DOM control next to Leaflet's zoom buttons. The rotate event triggers refreshDial() + scheduleDraw() and persists map.getBearing() to localStorage (suppressed during export).

PNG export pipeline

Implemented in io.js:

  1. Snapshot the current page-frame geometry.
  2. Choose the target tile zoom: highest native zoom that keeps the rotated bounding box โ‰ค 10 000 ร— 10 000 px.
  3. Build a tile grid covering the rotated frame.
  4. Clip flight-maps.com tile requests to published chart bounds and fetch each tile (direct for OSM / Esri; via the navaid-tiles.supino.org export mirror for the chart layers). HTTP 404 tiles are expected blanks and are skipped.
  5. Composite tiles onto an offscreen canvas, honoring the Map opacity slider.
  6. Project + draw the overlay on top, including Navigation Waypoints and Airfields when enabled.
  7. Convert to a Blob, unlock map interaction, and trigger download.

Service worker

See Service Worker.

Internationalization

See Bilingual UI. At the DOM level: data-i18n="key" / data-i18n-title="key" / data-i18n-placeholder="key" attributes read from window.S. There is no MessageFormat / ICU; just string lookups.

Build / deploy

No build. The docs/ folder is the artifact. A GitHub Actions workflow assembles main/docs/ -> / and dev/docs/ -> /staging/, rewrites ?v= and NavAid.version to the branch short SHA, and publishes to GitHub Pages. See Deployment.

โš ๏ธ **GitHub.com Fallback** โš ๏ธ