{"id":49588398,"url":"https://github.com/rwellinger/xp_swiss_vfr","last_synced_at":"2026-05-03T23:39:11.641Z","repository":{"id":355485784,"uuid":"1223271855","full_name":"rwellinger/xp_swiss_vfr","owner":"rwellinger","description":"X-Plane 12 plugin that fills the Swiss VFR gap left by Navigraph CIFP — the small Swiss airfields that ship no IFR procedures and therefore have no FMS-injectable approach pattern out of the box.","archived":false,"fork":false,"pushed_at":"2026-05-03T20:28:31.000Z","size":2747,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-03T22:24:13.994Z","etag":null,"topics":["linux","macos","plugin","switzerland","vfr-flights","windows","x-plane-12","x-plane12"],"latest_commit_sha":null,"homepage":"https://forums.x-plane.org/files/file/99841-swiss-vfr-approach-procedures-for-x-plane-12/","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rwellinger.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-28T06:59:16.000Z","updated_at":"2026-05-03T20:28:35.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/rwellinger/xp_swiss_vfr","commit_stats":null,"previous_names":["rwellinger/xp_swiss_vfr"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/rwellinger/xp_swiss_vfr","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rwellinger%2Fxp_swiss_vfr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rwellinger%2Fxp_swiss_vfr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rwellinger%2Fxp_swiss_vfr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rwellinger%2Fxp_swiss_vfr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rwellinger","download_url":"https://codeload.github.com/rwellinger/xp_swiss_vfr/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rwellinger%2Fxp_swiss_vfr/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32589264,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-03T22:12:39.696Z","status":"ssl_error","status_checked_at":"2026-05-03T22:09:10.534Z","response_time":103,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["linux","macos","plugin","switzerland","vfr-flights","windows","x-plane-12","x-plane12"],"created_at":"2026-05-03T23:39:11.025Z","updated_at":"2026-05-03T23:39:11.630Z","avatar_url":"https://github.com/rwellinger.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# xp_swiss_vfr\n\nA C++17 X-Plane 12 plugin that fills the Swiss VFR gap left by Navigraph CIFP — the small Swiss airfields that ship no IFR procedures and therefore have no FMS-injectable approach pattern out of the box.\n\n\u003e **Status:** in development, not yet released. Currently supported airports: **LSZB (Bern-Belp)**, **LSZG (Grenchen)**, **LSZF (Birrfeld)**, **LSZI (Fricktal-Schupfart)**, **LSZK (Speck-Fehraltorf)**, **LSZO (Luzern-Beromünster)**, **LSPN (Triengen)**, **LSPV (Wangen-Lachen)**. The list grows as we verify data per airport — the Swiss build is still the focus, then later Germany, France, and Austria.\n\u003e\n\u003e **Data quality:** all airport files are maintained on a **best-effort** basis from public AIP / VAC sources. We cross-check coordinates, elevations, runway directions, and pattern geometry against Skyguide AD 2 and the operator-published VAC, but we do not fly every circuit ourselves before release. Errors and outdated data can slip through — please report any deviation you spot in the sim against the current chart, and always brief the real chart before flight.\n\n---\n\n## What the plugin does\n\nX-Plane only ships FMS-flyable procedures (SIDs / STARs / Approaches) for airports that have IFR procedures published in CIFP. Most Swiss sport airfields — Grenchen, Birrfeld, Schänis, Saanen, Lommis, Bex, Speck-Fehraltorf, Sion VFR-arrivals, … — are VFR-only, so the X1000 / GNS / G530 cabin shows nothing when you spool up to land there. That is the gap this plugin closes.\n\nWhen the pilot opens the **Swiss VFR** menu, picks an airfield, and chooses an arrival sector, the plugin writes the published reporting points (VRPs) and a proper VFR pattern — downwind beginning, downwind end, final approach fix, runway threshold, destination airport — into the active flight plan. The avionics then sequence the pattern like any normal procedure: DTK indications, leg switches, and a sensible VNAV target down to runway elevation.\n\nThe data ships from real-world public sources, not generic templates. Every airport file is built from:\n\n- **AIP Switzerland AD 2** sections (Skyguide, public PDFs hosted by the airport operators) for ARP, elevation, runways, and pattern direction.\n- **Public Visual Approach Charts (VAC)** for arrival routes, sector names, helicopter sectors, and noise-abatement notes.\n- **Navigraph navigation data** (the user's own subscription, never shipped with the plugin) for the precise VRP coordinates registered as official `VPxxx` fixes.\n\nWhere a real-world chart shows a topographic pattern-entry point that is not registered in any navaid database — the typical `_1`-pendants like `E1` or `W1` that exist only on the chart — the plugin generates the corresponding pattern leg from the airport's circuit geometry instead of fabricating coordinates. The pilot still hand-flies the actual transition.\n\nA persistent banner inside the UI window makes it clear at a glance that this is **VFR reference material flown by hand** — Garmin-style autopilots cannot track the 90° turns at pattern speed.\n\n---\n\n## How it works\n\n**Tech stack:** C++17, CMake 3.21+, X-Plane SDK 4.3, Dear ImGui 1.91.9 (OpenGL2 backend), nlohmann/json, Catch2.\n\n**Build targets:** macOS Universal (arm64 + x86_64), Linux x86_64, Windows x86_64. CI builds all three on every push.\n\n### Module layout\n\n```\nsrc/\n├── main.cpp                # XPlugin* entry points\n├── core/                   # plugin lifecycle + airport DB resolution\n├── data/                   # VfrAirport / Waypoint / Runway domain model,\n│                           # JSON loader \u0026 validator, NavigraphSource,\n│                           # find_nearby_airports\n├── geometry/               # equirectangular distance / bearing / offset\n├── procedures/             # build_procedure (pure), procedure_runner\n│                           # (XPLM-coupled FMS write), state machine\n├── ui/                     # ImGui window: master-detail picker + active\n│                           # procedure indicator + warning banner\n└── version.hpp\n```\n\n### Data flow\n\n```\nresources/airports/*.json\n    │   (loaded at plugin start, validated per file —\n    │    one bad file logs a warning and is skipped)\n    ▼\ndata::VfrAirportDatabase\n    │\n    │  optional: Custom Data/earth_fix.dat\n    │  (Navigraph) overrides VRP coordinates\n    │  with the precise published values\n    ▼\nui::procedure_selection_window\n    │  → pilot picks airport + RWY/route\n    ▼\nprocedures::build_procedure(airport, runway, route_label)\n    │  pure SDK-free function: emits ProcedureWaypoints\n    │  from VRPs + circuit geometry (DW-BEG, DW-END,\n    │  FAF, THR @ field elevation)\n    ▼\nprocedures::activate(procedure)\n    │  XPLM410 multi-FPL API against xplm_Fpl_Pilot_Primary:\n    │  snapshot the existing plan + approach slot, append our\n    │  pattern, append the destination airport with its elevation\n    │  as VNAV target\n    ▼\nX-Plane FMS — visible to G1000 / GNS / GTN / X530 / etc.\n```\n\n### Airport JSON shape\n\nEach airfield is one file under `resources/airports/\u003cICAO\u003e_\u003clowercase_name\u003e.json`:\n\n```json\n{\n  \"icao\": \"LSZB\",\n  \"name\": \"Bern-Belp\",\n  \"elevation_ft\": 1673,\n  \"arp\":      { \"lat\": 46.91222, \"lon\": 7.49944 },\n  \"runways\":  [ { \"designator\": \"14\", \"heading_true\": 140.1, \"length_m\": 1730, \"surface\": \"asphalt\", \"circuit_pattern\": \"left\" }, ... ],\n  \"vrps\":     [ { \"name\": \"N\", \"lat\": 47.013889, \"lon\": 7.557500, \"altitude_ft_min\": 3000, \"altitude_ft_max\": 4000, \"mandatory_report\": true }, ... ],\n  \"arrival_routes\": {\n    \"14\": [\n      { \"label\": \"via NOVEMBER (N)\", \"vrps\": [\"N\"] },\n      { \"label\": \"via ECHO (E)\",     \"vrps\": [\"E\"] }\n    ]\n  },\n  \"runway_notes\":    { \"14\": \"RWY 14 - left-hand pattern. Used up to 5 kts tailwind ...\" },\n  \"circuit_pattern\": { \"altitude_ft_agl\": 1000, \"downwind_offset_nm\": 1.0, \"final_distance_nm\": 1.5 },\n  \"frequencies\":     { \"tower\": \"121.025\", \"approach\": \"127.325\" },\n  \"metadata\":        { \"source\": \"AIP Switzerland LSZB AD 2 ...\", \"verified_by\": \"...\", \"last_updated\": \"...\" }\n}\n```\n\nThe schema is enforced at runtime by the loader (`src/data/json_loader.cpp`) and a list of domain validators (`src/data/validation.cpp`); the JSON Schema in `resources/schemas/vfr_airport.schema.json` documents the same contract for editors.\n\n### FMS safety\n\nTouching the active flight plan is destructive by nature. Two guarantees keep the pilot's plan intact:\n\n1. **Snapshot before activate.** The plugin reads every entry of `xplm_Fpl_Pilot_Primary` and `xplm_Fpl_Pilot_Approach` before injecting anything.\n2. **Restore on clear.** Pressing *Clear* (or invoking the bound `xpswissvfr/clear` command) wipes the FPL and re-emits the captured snapshot — origin airport, SID waypoints, en-route fixes, destination airport, and the previously selected approach procedure all come back.\n\nIf the pilot already had the plugin's destination airport loaded, it is *absorbed* during inject and re-emitted at the very end of the procedure block, so there is no `… → LSZG → E → … → RWY → LSZG` duplicate.\n\n### Tests\n\nThe domain logic (parsing, validation, geometry, build_procedure, state machine, NavigraphSource, find_nearby_airports) is fully unit-tested with Catch2 against fixture JSONs under `tests/fixtures/`. SDK-coupled code (FMS write, ImGui rendering) is verified manually inside X-Plane 12.\n\n```\nmake test     # 76 cases / 291 assertions, all green\n```\n\n---\n\n## Installation\n\n\u003e Not yet released. The instructions below describe the intended end-user install once we ship a tagged release.\n\n1. Download `xp_swiss_vfr.zip` from the GitHub Releases page (planned).\n2. Unzip into your X-Plane 12 plugins directory:\n   - Standard layout: `X-Plane 12/Resources/plugins/`\n   - With XLauncher (or any plugin manager that uses an \"available\" stage): `X-Plane 12/Resources/available plugins/`\n3. Restart X-Plane.\n4. Open **Plugins → Swiss VFR → Show pattern selector**, or bind the X-Plane command `xpswissvfr/window/toggle` to a keyboard shortcut.\n\nThe plugin runs without a Navigraph subscription — the shipped JSONs already contain workable VRP coordinates. If Navigraph *is* installed (the plugin auto-detects `Custom Data/cycle_info.txt`), the runtime override layer replaces VRP coordinates with the precise published values and logs each upgrade, e.g.\n\n```\n[xp_swiss_vfr] Navigraph detected; upgraded 7 VRP coordinate(s).\n[xp_swiss_vfr]   LSZG/E: 47.180000,7.550000 → 47.210556,7.600000\n```\n\n### Build from source\n\n```bash\nmake setup        # download X-Plane SDK + ImGui + json.hpp + Catch2\nmake build        # → build/xp_swiss_vfr.xpl  (CMake Release, Universal on macOS)\nmake test         # build and run the Catch2 unit tests\nmake lint         # clang-tidy\nmake install      # codesign + copy into the X-Plane plugins directory\n```\n\n---\n\n## Limitations\n\n- **Coverage is intentionally narrow today.** Currently eight Swiss airfields ship (LSZB, LSZG, LSZF, LSZI, LSZK, LSZO, LSPN, LSPV). Adding an airport is mechanical — one JSON file, no code change — but the data verification step is the bottleneck. We add a place only when its data is traceable to an AIP / VAC source, and even then on a **best-effort** basis: the published JSON reflects the chart we read at import time, not a continuously audited dataset.\n- **VFR reference only.** The injected pattern is a visual reference for *hand-flown* circuits. Garmin-style autopilots cannot track the 90° turns at pattern speed; a built-in warning banner makes this unmissable. There is no certification, no IFR backing, and no expectation that an autopilot will fly a published Swiss VFR circuit cleanly.\n- **Topographic pattern-entry points are computed, not chart-exact.** The `_1`-pendants you see on Swiss VACs (`E1` after sector ECHO, `W1` after sector WHISKEY, …) are not registered as official navaid fixes — they exist only on the chart, defined by a topographic landmark. The plugin generates the corresponding leg geometrically from the airport's circuit-pattern data instead of fabricating coordinates. The pilot must still hand-fly the actual transition.\n- **Pattern direction inference for new airports.** When AIP / VAC text does not state pattern direction explicitly, we infer it from local context (e.g. \"city to the west of runway → right pattern keeps traffic over open terrain\") and mark the assumption in `metadata.verified_by`. These inferences need a real-VAC review before a release.\n- **English UI only.** No localisation (DE / CH-DE / FR) yet.\n- **No window-position persistence across plugin reloads.** ImGui re-centers the window on every X-Plane start.\n- **No procedure preview on the X-Plane map.** The pattern is visible in the FMS / GPS but not drawn onto the world map.\n- **No deep multi-aircraft / cockpit-sharing handling.** The active procedure is plugin-global, not per-aircraft.\n\n---\n\n## Roadmap\n\nThe current priority is depth of Swiss coverage before we open the field to other regions.\n\n**Switzerland — next**\n- LSGK Saanen, LSPL Langenthal, LSGB Bex, LSGY Yverdon-les-Bains, LSME Mollis, LSZW Thun, LSGS Sion (VFR), LSZC Buochs, LSZR St. Gallen-Altenrhein, … — added one at a time, each with an AIP / VAC trace in `metadata.source` and pattern geometry verified to best effort against the current VAC.\n\n**Internationalisation — later phases**\n- Germany (EDxx), France (LFxx), Austria (LOxx). VFR practice is similar enough that the schema and pattern engine carry over; what changes is the data work per airfield.\n\n**Plugin features**\n- *Auto-transitions* in the procedure state machine: detect first-leg engagement and threshold passage from a flight-loop callback, fire mandatory-reporting hooks when crossing a `mandatory_report` VRP.\n- *Map overlay*: render the pattern polyline on X-Plane's local-map drawing phase so the pilot sees it without an avionics-side pane.\n- *Settings persistence*: max search radius, last window position, and a \"I have read the warning\" flag in a `settings.json` next to the plugin.\n- *Localisation* of UI strings (DE / CH-DE / FR / EN), driven by airport metadata where appropriate.\n- *xp_wellys_atc bridge* (later phase): an optional inter-plugin link that forwards `PROCEDURE_ACTIVATED` / `WAYPOINT_REPORTED` / `APPROACH_COMPLETED` events to the ATC voice plugin, failure-tolerantly.\n\n---\n\n## License\n\nGPLv3. See [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frwellinger%2Fxp_swiss_vfr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frwellinger%2Fxp_swiss_vfr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frwellinger%2Fxp_swiss_vfr/lists"}