Skip to content

Latest commit

 

History

History
100 lines (79 loc) · 7.64 KB

File metadata and controls

100 lines (79 loc) · 7.64 KB

Bable — Scoped Swift Rebuild of GPSBabel for Modern macOS

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 into Bable.app by scripts/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.

Context

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 / Non-goals

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. BableCore stays 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.

Architecture

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/FormatWriter protocols + a registry keyed by file extension/UTType, so adding a format never touches the app layer.
  • Filter protocol (func apply(to: GPSDocument) -> GPSDocument) composed into an ordered chain.
  • XML via XMLParser (stream read) and a small writer helper; KMZ via Compression/ZIP; FIT via the community FitFileParser SPM package if its license fits, otherwise a decode-only FIT reader for record/lap/session messages (spec is public in Garmin's FIT SDK).

Implementation phases

Phase 1 — Core + GPX (first milestone, proves the architecture)

  • 1. git init, create docs/ and save this plan as docs/PLAN.md, SPM package BableCore, 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 gpsbabel CLI (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 simplify semantics), 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 CFBundleDocumentTypes in 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; bable CLI target (full gpsbabel-style interface, not just the nice-to-have).

Verification

  • 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.