NOTAM Integration Investigation for Vulture
Moved from vulture-ios/Docs/NOTAM-investigation.md
Status: On hold — waiting for an affordable European NOTAM data source. Date: 2026-08-12
Context
Section titled “Context”The goal is to integrate NOTAMs into Vulture so pilots can see active airspace restrictions along their planned route and during flight. Currently pilots check NOTAMs outside the app, then mentally correlate them with airspace on the map — a safety and planning gap.
The codebase already has partial NOTAM scaffolding:
CubStyle.notamandAltStyle.notamenum cases exist in the CUB decoderCubItem.swifthasextraDataandactiveTimefields with TODO comments for NOTAM parsingWeatherManager.DataType.notamis stubbed but unused- The
inactiveAirspacefield onWaypoints.Taskis used by SoaringSpot competition tasks
API Source Research
Section titled “API Source Research”Evaluated Sources
Section titled “Evaluated Sources”| Source | Coverage | Query by location? | Geometry? | Auth | Cost estimate (~1000 users) | Verdict |
|---|---|---|---|---|---|---|
| FAA NOTAM API | US (+ some intl) | Yes (lat/lon + radius, max 100 NM) | GeoJSON | OAuth 2.0 (free registration) | Free | Best for US coverage |
| Notamify | Global | ICAO codes only (no lat/lon) | GeoJSON, AI summaries | API key | ~$50-100/month | Richest data but expensive, no spatial query |
| SkyLink | US-primary (via FAA SWIM) | ICAO codes only | Parsed fields | API key | Free tier 1K req/mo, $16+/mo | EU coverage unclear |
| ICAO API | Global | Yes (lat/lon/radius) | Limited | API key | ~$0.275/call = very expensive | Too expensive at scale |
| Eurocontrol EAD | European | Yes | Full | Legal agreement | Thousands EUR/year + insurance | Designed for ANSPs, not indie devs |
| AVWX | Was global | N/A | N/A | Bearer token (already have) | N/A | Deprecated/broken — pending new data source |
| openAIP | EU-focused | N/A | N/A | Already integrated | N/A | Not a NOTAM source — only airspace data |
Key Finding: No Single Perfect Source
Section titled “Key Finding: No Single Perfect Source”- AVWX (already integrated for METAR) has deprecated its NOTAM endpoint — cannot be used.
- openAIP does not provide NOTAMs at all — only static airspace data.
- ICAO API is too expensive ($0.275/call) and updates only every 12 hours.
- Eurocontrol EAD has the best European data but requires legal agreements, liability insurance, and thousands EUR/year — prohibitive for an indie app.
- FAA NOTAM API is the best free option but is US-primary. It does include some international NOTAMs but European coverage is incomplete.
- Notamify has the richest parsed data (GeoJSON geometry, AI summaries, 42 categories) but only queries by ICAO code (not lat/lon), and costs ~$0.20-0.30/call.
Recommended Approach: Server-Side Caching Proxy
Section titled “Recommended Approach: Server-Side Caching Proxy”A vulture.aero backend endpoint is essential regardless of upstream source:
- Caches NOTAMs by region — one upstream fetch serves all app users in that area
- Hides API keys — no secrets in the iOS app
- Combines sources — FAA for US + a commercial source for Europe
- Pre-filters — only glider-relevant Q-codes forwarded to clients
- Normalizes — consistent response format regardless of upstream source
- Estimated cost: $15-50/month with caching vs. hundreds without
The app would call vulture.aero/api/notams?lat=X&lon=Y&radius=Z and get a normalized JSON response.
Competitor Comparison
Section titled “Competitor Comparison”| App | NOTAM approach |
|---|---|
| SkyDemon | Live NOTAM overlay on map, sourced from official national AIS (funded by $150+/year subscription) |
| XCSoar / LK8000 | No live NOTAMs — users manually import OpenAir files from sites like notaminfo.com |
Vulture would sit between these: live NOTAMs via API, but filtered for glider relevance to manage data volume and cost.
NOTAM Data Model Design
Section titled “NOTAM Data Model Design”ICAO NOTAM Format (relevant fields)
Section titled “ICAO NOTAM Format (relevant fields)”Q) FIR/QCODE/TRAFFIC/PURPOSE/SCOPE/LOWER/UPPER/COORDS+RADIUSA) Location (ICAO code)B) Start validity (YYMMDDHHmm)C) End validity (YYMMDDHHmm or PERM)D) Schedule (e.g. "MON-FRI 0600-1800")E) Description (free text)F) Lower limitG) Upper limitQ-Codes Most Relevant for Glider Pilots
Section titled “Q-Codes Most Relevant for Glider Pilots”Critical (airspace activation/restriction):
QRTCA— Temporary restricted area activatedQRRCA— Restricted area activatedQRDCA— Danger area activatedQRACA— Airspace reservation activated
Important (activity warnings):
QWELW— Military exercisesQWMLW— Missile/rocket firingQWGLW— Glider flying/competitionsQWPLW— Parachute/paragliding activityQWALW— Air displays
Filter criteria: Traffic type V or IV (VFR-relevant), Scope E or W (en-route/warning).
Proposed Swift Model
Section titled “Proposed Swift Model”// In Packages/Airspace/Sources/Airspace/Models/struct NOTAM: Identifiable, Hashable { let id: String // e.g. "A0123/24" let type: NOTAMType // .new, .replace, .cancel let fir: String? // FIR identifier (e.g. "EBBU") let qCode: String? // Q-code (e.g. "QRTCA") let effectiveStart: Date let effectiveEnd: Date? // nil = permanent/UFN let schedule: String? // D) field let location: CLLocationCoordinate2D? // Center point from Q-line let radius: CLLocationDistance? // Radius in NM from Q-line let lowerLimit: Airspace.Altitude? let upperLimit: Airspace.Altitude? let rawText: String // E) field let geometry: [CLLocationCoordinate2D]? // Parsed polygon (if API provides) let relevance: NOTAMRelevance // Categorized for glider pilots let affectedAirspaceId: UInt64? // Link to existing Airspace.id}Integration Points in the Codebase
Section titled “Integration Points in the Codebase”Where NOTAMs Fit
Section titled “Where NOTAMs Fit”| Integration point | File | How |
|---|---|---|
| NOTAM model | Packages/Airspace/Sources/Airspace/Models/ (new files) | Data types for NOTAM, alongside existing Airspace model |
| API client | Packages/Eagle/Sources/Eagle/Modules/NOTAM/ (new) | Cara Request + CodableSerializer, following MetarRequest pattern |
| Route corridor query | Packages/Waypoints/ or Packages/Map/ | Compute bounding box from Task.waypoints with ~20km buffer |
| Map rendering | Modules/App/Sources/Components/Operations/MapDataManager.swift | Add NOTAM airspaces to publishVisibleAirspaces() pipeline |
| Map styling | Modules/App/Sources/Components/Map/Layers/Airspace/ (new layer) | Orange/amber fill + hatched border for NOTAM-affected airspace |
| Task planning | Modules/App/Sources/Modules/Detail Map/Task/TaskMapViewModel.swift | Trigger NOTAM fetch on task load/edit, badge in banner |
| In-flight | Modules/App/Sources/Modules/Detail Map/In Flight/InFlightMapViewModel.swift | Periodic refresh, feed into infringement detection |
| Infringement detection | Modules/App/Sources/Modules/Detail Map/Flight/Flight/FlightViewModel.swift | Include NOTAM-generated airspace in existing point-in-polygon pipeline |
| Detail UI | Modules/App/Sources/Modules/Detail/ (new NOTAM section) | Table view with NOTAM text, validity, affected area |
| CUB parser | Packages/Airspace/Sources/Airspace/Models/CUB/CubItem.swift | Complete TODO: parse extraData/activeTime for NOTAM activation |
| SoaringSpot tasks | Already uses inactiveAirspace field on Waypoints.Task | Link competition-deactivated airspace to NOTAM system |
Architecture Flow
Section titled “Architecture Flow”vulture.aero/api/notams (backend proxy) ↓NOTAMRequest (Eagle, Cara) → NOTAMResponse → NOTAMMapper ↓[NOTAM] cached locally (30-min TTL) ↓NOTAMManager publishes via Combine ↓┌─────────────────────┬──────────────────────┐│ Task Planning │ In-Flight ││ Route corridor → │ Current pos + radius →││ fetch along route │ periodic refresh │└────────┬────────────┴──────────┬───────────┘ ↓ ↓ Match to existing Airspace objects (by name/ICAO) Create synthetic Airspace for unmatched NOTAMs with geometry ↓ MapDataManager.publishVisibleAirspaces() ↓ ┌─────────────┬──────────────────┐ │ Map overlay │ Infringement │ │ (orange/amb) │ detection │ └─────────────┴──────────────────┘Risks & Open Questions
Section titled “Risks & Open Questions”-
European data source gap — No affordable, high-quality European NOTAM API exists for indie developers. Eurocontrol EAD is the gold standard but prohibitively expensive. Options: (a) proxy a commercial source like Notamify, (b) scrape national AIS websites (fragile), (c) accept FAA API with partial international coverage as a starting point.
-
Backend work required — The vulture.aero backend needs a new
/api/notamsendpoint with caching, source aggregation, and Q-code filtering. This is outside the iOS codebase. -
NOTAM text parsing — Extracting geometry from free-text E-field NOTAMs is notoriously hard. Phase 1 should rely on structured API fields (coordinates, radius from Q-line). Notamify’s AI-parsed GeoJSON is the cleanest option but costly.
-
Data volume — A busy European FIR can have hundreds of active NOTAMs. Filtering for glider-relevant Q-codes and altitude ranges is essential to avoid information overload.
-
Offline — Cached NOTAMs work offline but must clearly show staleness (“last updated 2h ago”).
-
Legal/liability — NOTAM data is safety-critical. The app should clearly state that NOTAMs are provided for information only and that pilots must verify through official sources (AIS briefing).
Suggested Implementation Order
Section titled “Suggested Implementation Order”- Backend first — Build
vulture.aero/api/notamsendpoint (start with FAA API as upstream, add European source later) - Data model + API client —
NOTAMstruct in Airspace package,NOTAMRequestin Eagle - Task planning map — Fetch NOTAMs along route, overlay on map with distinct styling
- In-flight — Periodic refresh + infringement detection
- Detail UI — NOTAM list panel with text, validity, affected area
- CUB NOTAM parsing — Complete the existing TODO in
CubItem.swift
