F/filter elevations (#135)

* implement a gpx metrics computation, with filtered elevation gain and loss

* Merges with main

* renamed variable for clarity

---------

Co-authored-by: gri38 <>
This commit is contained in:
Nicolas Guillot
2025-02-10 19:14:07 +01:00
committed by GitHub
parent 550e3daa43
commit b816637e64
2 changed files with 80 additions and 17 deletions

View File

@@ -0,0 +1,72 @@
import { haversineDistance } from './utils';
class GpxMetricsComputation {
private readonly thresholdXY_m: number; // Distance threshold for filtering on the XY axis (latitude / longitude)
private readonly thresholdZ_m: number; // Distance threshold for filtering on the Z axis (elevation)
private lastFilteredPointXY: any | null = null;
private lastFilteredZ: number | null = null;
private lastZ: number | null = null;
totalElevationGain = 0;
totalElevationLoss = 0;
totalElevationGainSmoothed = 0;
totalElevationLossSmoothed = 0;
totalDistance = 0;
totalDistanceSmoothed = 0;
constructor(thresholdXY_m: number, thresholdZ_m: number) {
this.thresholdXY_m = thresholdXY_m;
this.thresholdZ_m = thresholdZ_m;
}
addAndFilter(point: any) {
if (!this.lastFilteredPointXY) {
// Firstly, we are not yet filtering
this.lastFilteredPointXY = point;
this.lastFilteredZ = point.ele ?? 0;
this.lastZ = point.ele ?? 0;
return;
}
const distance = haversineDistance(
this.lastFilteredPointXY.$.lat,
this.lastFilteredPointXY.$.lon,
point.$.lat,
point.$.lon
);
this.totalDistance += distance;
const elevation = point.ele ?? 0;
// @ts-ignore I know this.lastZ is not null
const elevationDiff = elevation - this.lastZ;
this.lastZ = elevation;
if (elevationDiff > 0) {
this.totalElevationGain += elevationDiff;
}
if (elevationDiff < 0) {
this.totalElevationLoss -= elevationDiff;
}
if (distance < this.thresholdXY_m) {
return;
}
this.totalDistanceSmoothed += distance;
this.lastFilteredPointXY = point;
// @ts-ignore: I know this.lastFilteredZ is not null
const elevationDiffSmoothed = elevation - this.lastFilteredZ;
if (Math.abs(elevationDiffSmoothed) < this.thresholdZ_m) {
return;
}
this.lastFilteredZ = elevation;
if (elevationDiffSmoothed > 0) {
this.totalElevationGainSmoothed += elevationDiffSmoothed;
} else {
this.totalElevationLossSmoothed -= elevationDiffSmoothed;
}
}
}
export default GpxMetricsComputation;

View File

@@ -4,6 +4,7 @@ import Route from './route';
import Track from './track';
import { allDatesToISOString, haversineDistance, removeEmpty } from './utils';
import Waypoint from './waypoint';
import GpxMetricsComputation from './gpx-metrics-computation';
//@ts-ignore
import geohash from "ngeohash"
@@ -96,6 +97,8 @@ export default class GPX {
let totalLat = 0
let totalLon = 0
const metrics = new GpxMetricsComputation(5, 10);
let minLat = Infinity, maxLat = -Infinity, minLon = Infinity, maxLon = -Infinity;
const allPoints: Waypoint[] = []
@@ -115,23 +118,8 @@ export default class GPX {
const pointLength = points.length
for (let i = 1; i < pointLength; i++) {
const prevPoint = points[i - 1];
const point = points[i];
const elevation = point.ele ?? 0
const previousElevation = prevPoint.ele ?? 0
const elevationDiff = elevation - previousElevation;
if (elevationDiff > 0) {
totalElevationGain += elevationDiff;
} else {
totalElevationLoss += Math.abs(elevationDiff)
}
const distance = haversineDistance(
prevPoint.$.lat ?? 0,
prevPoint.$.lon ?? 0,
point.$.lat ?? 0,
point.$.lon ?? 0,
);
metrics.addAndFilter(point)
totalLat += point.$.lat ?? 0;
totalLon += point.$.lon ?? 0;
@@ -140,11 +128,14 @@ export default class GPX {
maxLat = Math.max(maxLat, point.$.lat ?? -Infinity);
minLon = Math.min(minLon, point.$.lon ?? Infinity);
maxLon = Math.max(maxLon, point.$.lon ?? -Infinity);
totalDistance += distance;
}
}
}
totalElevationGain = metrics.totalElevationGainSmoothed;
totalElevationLoss = metrics.totalElevationLossSmoothed;
totalDistance = metrics.totalDistance;
const boundingBox = { minLat, maxLat, minLon, maxLon };
const centroid = { lat: totalLat / allPoints.length, lon: totalLon / allPoints.length };