Support uploading FIT files (and zipped exports) alongside GPX
CI / check (push) Successful in 2m11s
CI / deploy (push) Successful in 2m27s

Maptool::Fit reads Garmin's binary FIT format directly rather than
adding a gem — the one Ruby option depends on activesupport, unmaintained
since 2022, for a single-purpose binary parser. Only "record" messages
are read (position + altitude; everything else is skipped by byte
length, never interpreted); field numbers and scale/offset are
Garmin's own (fit-python-sdk's profile.py), and the framing plus all
extracted values were checked byte-for-byte against 5 real device
files from python-fitparse's test suite before this shipped.

A .zip upload (Garmin's bulk/FIT export format) is expanded into its
.gpx/.fit entries via rubyzip — that one's an actual dependency, since
ZIP's own format isn't the kind of narrow, single-purpose parsing FIT
above is.
This commit is contained in:
randy committed 2026-09-07 12:23:37 +02:00
1 parent 8d3c126a14
commit fc174d4267
8 files changed
+338 -29

No files matched your search

+5 -2
View File
@@ -33,7 +33,7 @@ export class GpxPanelControl {
mount(sidebar) {
this._container = document.createElement("div");
this._container.className = "sidebar-item";
this._container.innerHTML = `<button type="button" class="sidebar-icon" title="GPX" aria-label="GPX">${GPX_ICON}</button>`;
this._container.innerHTML = `<button type="button" class="sidebar-icon" title="Tracks" aria-label="Tracks">${GPX_ICON}</button>`;
this._icon = this._container.querySelector(".sidebar-icon");
this._icon.addEventListener("click", () => this._togglePanel());
@@ -339,7 +339,10 @@ export class GpxPanelControl {
}
async _uploadFiles(fileList, folder) {
const files = [...fileList].filter((file) => file.name.toLowerCase().endsWith(".gpx"));
// .zip: Garmin's bulk/FIT export format — the server expands it into
// its .gpx/.fit entries, so it's accepted here even though the tree
// never shows a .zip itself.
const files = [...fileList].filter((file) => /\.(gpx|fit|zip)$/i.test(file.name));
if (files.length === 0) return;
this._setStatus(files.length === 1 ? "Uploading…" : `Uploading ${files.length} files…`);