fix: deterministic multi-GPX trackpoint ordering and catch-path completion

Pre-allocate fileResults[idx] slots so GPX files always concatenate in URL
order regardless of fetch arrival order (Bug 1). Both .then and .catch now
call computeDistance() after decrementing pending so a failed last fetch no
longer leaves the distance element permanently blank (Bug 2).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WPJztrVGbwic2xTG7G9fjM
This commit is contained in:
2026-06-19 22:56:38 +02:00
parent 0dc9095b4b
commit 48b877c439
+26 -13
View File
@@ -144,33 +144,46 @@ function haversine(lat1, lng1, lat2, lng2) {
var distEl = document.getElementById('stat-distance'); var distEl = document.getElementById('stat-distance');
if (GPX_URLS.length > 0) { if (GPX_URLS.length > 0) {
// Mode A: sum haversine between all GPX trackpoints // Mode A: sum haversine between all GPX trackpoints (deterministic order)
var pending = GPX_URLS.length; var pending = GPX_URLS.length;
var masterPts = []; var fileResults = new Array(GPX_URLS.length);
GPX_URLS.forEach(function(url) { GPX_URLS.forEach(function(url, idx) {
fetch(url) fetch(url)
.then(function(r) { return r.text(); }) .then(function(r) { return r.text(); })
.then(function(text) { .then(function(text) {
var xml = new DOMParser().parseFromString(text, 'text/xml'); var xml = new DOMParser().parseFromString(text, 'text/xml');
var trkpts = xml.querySelectorAll('trkpt'); var trkpts = xml.querySelectorAll('trkpt');
var pts = [];
trkpts.forEach(function(pt) { trkpts.forEach(function(pt) {
masterPts.push({ pts.push({
lat: parseFloat(pt.getAttribute('lat')), lat: parseFloat(pt.getAttribute('lat')),
lon: parseFloat(pt.getAttribute('lon')) lon: parseFloat(pt.getAttribute('lon'))
}); });
}); });
fileResults[idx] = pts;
pending--; pending--;
if (pending === 0) { if (pending === 0) { computeDistance(); }
var total = 0;
for (var i = 1; i < masterPts.length; i++) {
total += haversine(masterPts[i-1].lat, masterPts[i-1].lon,
masterPts[i].lat, masterPts[i].lon);
}
distEl.textContent = masterPts.length < 2 ? '—' : Math.round(total).toLocaleString();
}
}) })
.catch(function(err) { console.warn('GPX load failed:', url, err); pending--; }); .catch(function(err) {
console.warn('GPX load failed:', url, err);
fileResults[idx] = [];
pending--;
if (pending === 0) { computeDistance(); }
});
}); });
function computeDistance() {
var masterPts = [];
fileResults.forEach(function(pts) {
if (pts) { pts.forEach(function(p) { masterPts.push(p); }); }
});
var total = 0;
for (var i = 1; i < masterPts.length; i++) {
total += haversine(masterPts[i-1].lat, masterPts[i-1].lon,
masterPts[i].lat, masterPts[i].lon);
}
distEl.textContent = masterPts.length < 2 ? '—' : Math.round(total).toLocaleString();
}
} else { } else {
// Mode B: sum haversine between consecutive entry lat/lng points // Mode B: sum haversine between consecutive entry lat/lng points
var total = 0; var total = 0;