Status (2026-07-12, end of day): Shipped. All five phases implemented, verified (user click-through of the GUI included), published public at https://github.com/DaveKT/bable with CI and a v0.1.0 release. Checkboxes below mark per-item implementation state; the one open item from the original plan is basic app preferences (nothing has needed a setting yet). Deviations from the design as written:
- The app is an SPM executable target (
Sources/BableApp) assembled intoBable.appbyscripts/make-app.sh— no checked-in Xcode project.- FIT is decoded by our own clean-room reader in
Sources/BableCore/Formats/FIT.swift; no community package was needed.- Beyond plan: forest-green app icon drawn by
scripts/make-icon.swift, GitHub Actions CI (runs the oracle suite via Homebrew gpsbabel and uploads the app bundle), and five differential oracle tests instead of the planned harness sketch.v0.1.1 (2026-07-12): in-app Help window (⌘? / Help menu / ? button in the filter panel) documenting the filter chain and every filter — purpose, parameters, tips, CLI equivalent. Content is a compile-checked switch over
FilterConfig.Kind, so new filters can't ship without help text.
GPSBabel (gpsbabel.org) is a 25-year-old C/C++ GPS data converter whose packaged macOS app no longer works well on the user's OS (macOS Tahoe / upcoming Golden Gate). Rather than a full 1:1 port (multi-year, GPL-encumbered, full of untestable vendor hardware protocols), the user chose a scoped native Swift rebuild covering their actual workflow: file format conversion and track cleanup/filtering. No GPS-hardware/serial-device support.
Project home: /Users/davidkolet-tassara/Github/bable (empty directory already created; will become a new git repo).
Working-documents convention: all working documents — this plan, specs, design notes, TODOs — live in bable/docs/. The first implementation step copies this plan there as docs/PLAN.md, and all future planning artifacts for this project go in that directory.
License note: write everything clean-room from public format specs — do not port or translate GPSBabel source (GPL-2.0). Using the gpsbabel CLI (via Homebrew) as an external test oracle is fine.
Goals
- Native macOS app (SwiftUI) + reusable Swift package core, no Qt, no legacy dependencies.
- Formats: GPX (read/write), KML/KMZ (read/write), TCX (read/write), FIT (read), CSV (read/write, configurable columns), NMEA sentences (read).
- Filters: duplicate removal, track simplification (Douglas-Peucker), interpolation, radius/bounding filtering, reverse, sort, track split/merge, waypoint↔track↔route transforms.
- Drag-and-drop conversion UI with filter chain and MapKit preview.
Deferred (out of scope now, but the architecture must not preclude them)
- iOS build: a future iOS/iPadOS version of the app is anticipated.
BableCorestays 100% platform-independent (Foundation-only, no AppKit), and the SwiftUI app keeps macOS-specific code (save panels, Finder integration) behind thin, isolated wrappers so views can be shared in a multiplatform target later. - GPS hardware connectivity: talking to receivers/data loggers may be wanted eventually. The core exposes data sources through the same reader abstraction, so a future
DeviceSource(USB/serial/Bluetooth) can slot in beside file readers without restructuring.
Non-goals (no current plans)
- Obscure vendor binary file formats (Lowrance, Humminbird, OziExplorer, etc.).
- FIT writing (decode-only initially; revisit later).
- Windows/Linux.
bable/ (new git repo, Swift Package + Xcode app)
├── docs/ PLAN.md + all future specs/design notes
├── Package.swift BableCore — platform-independent SPM library
├── Sources/BableCore/
│ ├── Model/ Waypoint, TrackSegment, Track, Route, GPSDocument
│ ├── Formats/ one folder per format; each conforms to
│ │ FormatReader / FormatWriter protocols
│ └── Filters/ Filter protocol + implementations, chainable
├── Tests/BableCoreTests/ fixtures + round-trip + oracle tests
└── App/Bable.xcodeproj SwiftUI macOS app depending on BableCore
- Canonical internal model mirrors GPX (waypoints, routes, tracks-with-segments, per-point time/ele/extensions) — same design decision GPSBabel made; every format converts to/from this model.
FormatReader/FormatWriterprotocols + a registry keyed by file extension/UTType, so adding a format never touches the app layer.Filterprotocol (func apply(to: GPSDocument) -> GPSDocument) composed into an ordered chain.- XML via
XMLParser(stream read) and a small writer helper; KMZ viaCompression/ZIP; FIT via the communityFitFileParserSPM package if its license fits, otherwise a decode-only FIT reader for record/lap/session messages (spec is public in Garmin's FIT SDK).
Phase 1 — Core + GPX (first milestone, proves the architecture)
- 1.
git init, createdocs/and save this plan asdocs/PLAN.md, SPM packageBableCore, model types, protocols, registry. - 2. GPX 1.1 reader/writer (round-trip clean, preserves time/ele; tolerate GPX 1.0 input).
- 3. CSV reader/writer with column mapping (lat, lon, ele, time, name).
- 4. Test fixtures + round-trip tests; differential test harness comparing output against
gpsbabelCLI (Homebrew) when present. (Five oracle tests: GPX round trip, KML both directions, FIT decode of gpsbabel output, TCX accepted by gpsbabel.)
Phase 2 — Filters
- 5. Dedupe (position/time), Douglas-Peucker simplify (point-count and error targets, matching gpsbabel's
simplifysemantics), interpolate (time/distance), radius + bbox filters, reverse, sort, split/merge, waypoint↔track↔route transforms.
Phase 3 — Remaining formats
- 6. KML write (tracks as
gx:Track, waypoints as Placemarks), then KML read (subset: Placemark/LineString/gx:Track), KMZ both directions. - 7. TCX read/write (activities → tracks with heart rate/cadence carried in extensions).
- 8. FIT read (records → trackpoints; laps optional — laps not decoded, records only).
- 9. NMEA read (GGA/RMC sentences → track).
Phase 4 — macOS app
- 10. SwiftUI app: drop zone / file open → parsed summary (counts, bounds, date range) → filter chain builder (add/reorder/configure) → MapKit preview → export via save panel with format picker.
- 11a. Register UTTypes so Finder "Open With" works (via
CFBundleDocumentTypesin the generated Info.plist). - 11b. App icon (
scripts/make-icon.swift→assets/Bable.icns). - 11c. Basic preferences — not implemented; nothing has needed a user setting yet.
Phase 5 — polish/verify
- 12. End-to-end pass over real files (CLI driven + user's GUI click-through); README with quick start;
bableCLI target (full gpsbabel-style interface, not just the nice-to-have).
swift test— round-trip tests (parse → write → parse, compare models) and filter unit tests with known geometric answers.- Differential tests: same input through
gpsbabel -i gpx -o kml ...(Homebrew install) vs BableCore; compare coordinates/timestamps within tolerance. Skipped automatically if gpsbabel is absent. - Launch the app, drag in a real GPX file, apply simplify + dedupe, preview on the map, export KML/CSV, and open the KML in Google Earth / the CSV in Numbers to confirm.