fix: compute GPX stats per-file to avoid spurious inter-track segments
Both stats.html.twig and trip.html.twig previously flattened all GPX trackpoints into a single masterPts array before computing haversine distance, elevation, and moving time. This caused the junction between file N's last point and file N+1's first point to be treated as a real segment — e.g. Florence→coast (~79 km, ~42 h) for Italy's 3-file demo data, overstating distance and moving time significantly. Fix: compute all metrics within each file independently and sum the results. fileResults collection and callback consumption are unchanged. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WPJztrVGbwic2xTG7G9fjM
This commit is contained in:
@@ -162,27 +162,24 @@ if (GPX_URLS.length > 0) {
|
|||||||
});
|
});
|
||||||
fileResults[idx] = pts;
|
fileResults[idx] = pts;
|
||||||
pending--;
|
pending--;
|
||||||
if (pending === 0) { computeDistance(); }
|
if (pending === 0) { computeTotalDistance(); }
|
||||||
})
|
})
|
||||||
.catch(function(err) {
|
.catch(function(err) {
|
||||||
console.warn('GPX load failed:', url, err);
|
console.warn('GPX load failed:', url, err);
|
||||||
fileResults[idx] = [];
|
fileResults[idx] = [];
|
||||||
pending--;
|
pending--;
|
||||||
if (pending === 0) { computeDistance(); }
|
if (pending === 0) { computeTotalDistance(); }
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function computeDistance() {
|
function computeTotalDistance() {
|
||||||
var masterPts = [];
|
|
||||||
fileResults.forEach(function(pts) {
|
|
||||||
if (pts) { pts.forEach(function(p) { masterPts.push(p); }); }
|
|
||||||
});
|
|
||||||
var total = 0;
|
var total = 0;
|
||||||
for (var i = 1; i < masterPts.length; i++) {
|
fileResults.forEach(function(pts) {
|
||||||
total += haversine(masterPts[i-1].lat, masterPts[i-1].lon,
|
for (var i = 1; i < pts.length; i++) {
|
||||||
masterPts[i].lat, masterPts[i].lon);
|
total += haversine(pts[i-1].lat, pts[i-1].lon, pts[i].lat, pts[i].lon);
|
||||||
}
|
}
|
||||||
distEl.textContent = masterPts.length < 2 ? '—' : Math.round(total).toLocaleString();
|
});
|
||||||
|
distEl.textContent = total > 0 ? 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
|
||||||
|
|||||||
@@ -437,50 +437,52 @@ function parseGpxFiles(urls, callback) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function computeAndCallback() {
|
function computeAndCallback() {
|
||||||
var masterPts = [];
|
var totalDistance = 0, totalEleGain = 0, totalEleLoss = 0;
|
||||||
|
var globalHighest = NaN, globalLowest = NaN, totalMovingTime = 0;
|
||||||
|
|
||||||
fileResults.forEach(function(pts) {
|
fileResults.forEach(function(pts) {
|
||||||
if (pts) { pts.forEach(function(p) { masterPts.push(p); }); }
|
if (!pts || pts.length < 2) return;
|
||||||
});
|
for (var i = 1; i < pts.length; i++) {
|
||||||
var n = masterPts.length;
|
var p0 = pts[i-1], p1 = pts[i];
|
||||||
if (n < 2) { callback({ distance: 0 }); return; }
|
var d = haversineKm(p0.lat, p0.lon, p1.lat, p1.lon);
|
||||||
var distance = 0, eleGain = 0, eleLoss = 0;
|
totalDistance += d;
|
||||||
var highest = NaN, lowest = NaN, movingTime = 0;
|
|
||||||
for (var i = 1; i < n; i++) {
|
if (!isNaN(p0.ele) && !isNaN(p1.ele)) {
|
||||||
var p0 = masterPts[i-1], p1 = masterPts[i];
|
var dEle = p1.ele - p0.ele;
|
||||||
var d = haversineKm(p0.lat, p0.lon, p1.lat, p1.lon);
|
if (dEle > 1) totalEleGain += dEle - 1;
|
||||||
distance += d;
|
if (dEle < -1) totalEleLoss += (-dEle) - 1;
|
||||||
if (!isNaN(p0.ele) && !isNaN(p1.ele)) {
|
if (isNaN(globalHighest) || p1.ele > globalHighest) globalHighest = p1.ele;
|
||||||
var dEle = p1.ele - p0.ele;
|
if (isNaN(globalLowest) || p1.ele < globalLowest) globalLowest = p1.ele;
|
||||||
if (dEle > 1) eleGain += dEle - 1;
|
}
|
||||||
else if (dEle < -1) eleLoss += (-dEle) - 1;
|
|
||||||
if (isNaN(highest) || p1.ele > highest) highest = p1.ele;
|
if (p0.time && p1.time) {
|
||||||
if (isNaN(lowest) || p1.ele < lowest) lowest = p1.ele;
|
var dtHrs = (Date.parse(p1.time) - Date.parse(p0.time)) / 3600000;
|
||||||
}
|
if (dtHrs > 0) {
|
||||||
if (p0.time && p1.time) {
|
var speed = d / dtHrs;
|
||||||
var dtHrs = (Date.parse(p1.time) - Date.parse(p0.time)) / 3600000;
|
if (speed >= 1) totalMovingTime += dtHrs;
|
||||||
if (dtHrs > 0) {
|
}
|
||||||
var speed = d / dtHrs;
|
|
||||||
if (speed >= 1) movingTime += dtHrs;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
// include first point of each file in elevation range
|
||||||
// include first point in elevation range
|
if (pts.length > 0 && !isNaN(pts[0].ele)) {
|
||||||
if (!isNaN(masterPts[0].ele)) {
|
if (isNaN(globalHighest) || pts[0].ele > globalHighest) globalHighest = pts[0].ele;
|
||||||
if (isNaN(highest) || masterPts[0].ele > highest) highest = masterPts[0].ele;
|
if (isNaN(globalLowest) || pts[0].ele < globalLowest) globalLowest = pts[0].ele;
|
||||||
if (isNaN(lowest) || masterPts[0].ele < lowest) lowest = masterPts[0].ele;
|
}
|
||||||
}
|
});
|
||||||
var avgSpeed = movingTime > 0 ? distance / movingTime : 0;
|
|
||||||
var movHours = Math.floor(movingTime);
|
var avgSpeed = totalMovingTime > 0 ? totalDistance / totalMovingTime : 0;
|
||||||
var movMins = Math.round((movingTime - movHours) * 60);
|
var movHours = Math.floor(totalMovingTime);
|
||||||
|
var movMins = Math.round((totalMovingTime - movHours) * 60);
|
||||||
if (movMins === 60) { movHours++; movMins = 0; }
|
if (movMins === 60) { movHours++; movMins = 0; }
|
||||||
|
|
||||||
callback({
|
callback({
|
||||||
distance: distance,
|
distance: totalDistance,
|
||||||
eleGain: eleGain,
|
eleGain: totalEleGain,
|
||||||
eleLoss: eleLoss,
|
eleLoss: totalEleLoss,
|
||||||
highest: highest,
|
highest: globalHighest,
|
||||||
lowest: lowest,
|
lowest: globalLowest,
|
||||||
movingTime: movHours + ':' + (movMins < 10 ? '0' : '') + movMins,
|
movingTime: movHours + ':' + (movMins < 10 ? '0' : '') + movMins,
|
||||||
avgSpeed: avgSpeed
|
avgSpeed: avgSpeed
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user