Surface GPX/FIT parse errors instead of silently rendering nothing
_addLayers handed MapLibre a URL and let it fetch the GeoJSON internally — a 422 (a file REXML/Fit can't parse) just meant the layer never appeared, with nothing telling the user why. It now fetches the file itself first, alerts with the actual error and un-checks the box on failure.
This commit is contained in:
1 parent
a0476f1204
commit
11fc14824e
2 files changed
+41
-7
No files matched your search
@@ -189,7 +189,13 @@ equivalent, so it never produces `Point` features. MapLibre's
|
|||||||
`geometry-type` expression treats `MultiLineString` the same as
|
`geometry-type` expression treats `MultiLineString` the same as
|
||||||
`LineString`, so a single GeoJSON source per file with a `line` layer
|
`LineString`, so a single GeoJSON source per file with a `line` layer
|
||||||
and a `circle` layer (each filtered by geometry type) is enough to
|
and a `circle` layer (each filtered by geometry type) is enough to
|
||||||
render both.
|
render both. Checking a file fetches its GeoJSON itself
|
||||||
|
(`public/js/gpx.js`'s `_addLayers`) and hands the parsed result to
|
||||||
|
`addSource` — rather than passing MapLibre a URL and letting it fetch
|
||||||
|
internally — specifically so a `422` (a file REXML or `Fit` can't
|
||||||
|
parse) surfaces as an alert and un-checks the box instead of the layer
|
||||||
|
just silently never appearing, which is what a `data: url` source does
|
||||||
|
on a failed fetch.
|
||||||
|
|
||||||
FIT parsing:: No gem was added for this — the one Ruby option
|
FIT parsing:: No gem was added for this — the one Ruby option
|
||||||
(`fit-parser`, tjwallace/fit) hasn't been touched since 2022 and pulls
|
(`fit-parser`, tjwallace/fit) hasn't been touched since 2022 and pulls
|
||||||
|
|||||||
+34
-6
@@ -206,7 +206,7 @@ export class GpxPanelControl {
|
|||||||
const checkbox = document.createElement("input");
|
const checkbox = document.createElement("input");
|
||||||
checkbox.type = "checkbox";
|
checkbox.type = "checkbox";
|
||||||
checkbox.checked = this._visible.has(file.path);
|
checkbox.checked = this._visible.has(file.path);
|
||||||
checkbox.addEventListener("change", () => this._toggleFile(file.path, checkbox.checked));
|
checkbox.addEventListener("change", () => this._toggleFile(file.path, checkbox.checked, checkbox));
|
||||||
const name = document.createElement("span");
|
const name = document.createElement("span");
|
||||||
name.className = "gpx-tree__file-name";
|
name.className = "gpx-tree__file-name";
|
||||||
name.textContent = file.name;
|
name.textContent = file.name;
|
||||||
@@ -269,21 +269,48 @@ export class GpxPanelControl {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_toggleFile(path, visible) {
|
async _toggleFile(path, visible, checkbox) {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
this._visible.add(path);
|
this._visible.add(path);
|
||||||
this._addLayers(path);
|
const ok = await this._addLayers(path);
|
||||||
|
if (!ok) {
|
||||||
|
// Loading failed (see _addLayers) — don't leave the box checked
|
||||||
|
// for a file that isn't actually showing anything.
|
||||||
|
this._visible.delete(path);
|
||||||
|
if (checkbox) checkbox.checked = false;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this._visible.delete(path);
|
this._visible.delete(path);
|
||||||
this._removeLayers(path);
|
this._removeLayers(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_addLayers(path) {
|
// Fetches the file's GeoJSON itself (rather than handing maplibre a URL
|
||||||
|
// to fetch internally) so a parse failure — a real GPX a device/app
|
||||||
|
// wrote in some way REXML doesn't like, say — surfaces as an alert
|
||||||
|
// instead of the layer just silently never appearing. Returns whether it
|
||||||
|
// succeeded.
|
||||||
|
async _addLayers(path) {
|
||||||
const id = sourceId(path);
|
const id = sourceId(path);
|
||||||
if (this._map.getSource(id)) return;
|
if (this._map.getSource(id)) return true;
|
||||||
|
|
||||||
this._map.addSource(id, { type: "geojson", data: gpxFileUrl(path) });
|
let geojson;
|
||||||
|
try {
|
||||||
|
const response = await fetch(gpxFileUrl(path));
|
||||||
|
if (!response.ok) {
|
||||||
|
const { error } = await response.json().catch(() => ({}));
|
||||||
|
window.alert(error || `Could not load "${path}".`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
geojson = await response.json();
|
||||||
|
} catch (err) {
|
||||||
|
window.alert(`Could not load "${path}": ${err.message}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._map.getSource(id)) return true; // added concurrently (e.g. a style.load race) while this was fetching
|
||||||
|
|
||||||
|
this._map.addSource(id, { type: "geojson", data: geojson });
|
||||||
this._map.addLayer({
|
this._map.addLayer({
|
||||||
id: `${id}-line`,
|
id: `${id}-line`,
|
||||||
type: "line",
|
type: "line",
|
||||||
@@ -307,6 +334,7 @@ export class GpxPanelControl {
|
|||||||
|
|
||||||
this._registerPointInteractivity(`${id}-points`);
|
this._registerPointInteractivity(`${id}-points`);
|
||||||
this._registerLineInteractivity(`${id}-line`);
|
this._registerLineInteractivity(`${id}-line`);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Registered once per layer id, ever — map.on(type, layerId, fn) is a
|
// Registered once per layer id, ever — map.on(type, layerId, fn) is a
|
||||||
|
|||||||
Reference in new issue
Block a user