Skip to content

Instantly share code, notes, and snippets.

@JosePedroDias
Last active December 27, 2020 19:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JosePedroDias/8c4c6a796fe6d45a7caff57b28ab44c0 to your computer and use it in GitHub Desktop.
Save JosePedroDias/8c4c6a796fe6d45a7caff57b28ab44c0 to your computer and use it in GitHub Desktop.
Fix Strava GPX

Your GPS device has messed up and the recorded route has a faulty segment?

This code iterates over recorded points, finding biggest distance discrepancy and allowing you to remove it and save an altered XML version leaving out those measurements.

const fs = require('fs').promises;
const convert = require('xml-js'); // https://github.com/nashwaan/xml-js
const π = Math.PI;
const DEG2RAD = π / 180;
const RK = 6373; // mean radius of the earth (km) at 39 degrees from the equator
function distance(a, b) {
// convert coordinates to radians
const lat1 = DEG2RAD * a.lat;
const lon1 = DEG2RAD * a.lon;
const lat2 = DEG2RAD * b.lat;
const lon2 = DEG2RAD * b.lon;
// find the differences between the coordinates
const dlat = lat2 - lat1;
const dlon = lon2 - lon1;
// here's the heavy lifting
const aa =
Math.pow(Math.sin(dlat / 2), 2) +
Math.pow(Math.sin(dlon / 2) * Math.cos(lat1) * Math.cos(lat2), 2);
const c = 2 * Math.atan2(Math.sqrt(aa), Math.sqrt(1 - aa)); // great circle distance in radians
return c * RK; // great circle distance in km
}
function pairConsecutives(arr) {
return arr.slice(0, arr.length - 1).map((el, idx) => [el, arr[idx + 1]]);
}
async function go(gpxFile, removals) {
const xml = await fs.readFile(gpxFile);
const o = convert.xml2js(xml);
const rootEl = o.elements[0];
const trkEl = rootEl.elements.find((el) => el.name === 'trk');
const trkSegEl = trkEl.elements.find((el) => el.name === 'trkseg');
// remove outliers (sort dec):
removals.sort((a, b) => b - a);
removals.forEach((idx) => {
trkSegEl.elements.splice(idx, 1);
});
const data = trkSegEl.elements
.map((el) => {
const lat = parseFloat(el.attributes.lat);
const lon = parseFloat(el.attributes.lon);
const timeEl = el.elements.find((el) => el.name === 'time');
const time = new Date(timeEl.elements[0].text);
return { lat, lon, time };
});
let maxI;
let maxDist = 0;
let maxDt = 0;
pairConsecutives(data).forEach(([a, b], i) => {
const dist = distance(a, b); // in km
const dt = (b.time.valueOf() - a.time.valueOf()) / 1000; // in secs
const vel = (dist * 3600) / dt; // in km/h
console.log(
`#${i} | dist: ${Math.round(
dist * 1000
)} m | dt: ${dt} secs | vel: ${vel.toFixed(2)} km/h`
);
if (dist > maxDist) {
maxDist = dist;
maxDt = dt;
maxI = i;
}
});
console.log(
`to #${maxI + 1} ~> maxDist: ${Math.round(
maxDist * 1000
)} m, with dt of ${maxDt} secs`
);
const xml2 = convert.js2xml(o, { spaces: 1 });
const gpxFile2 = gpxFile.replace('.gpx', '_fixed.gpx');
await fs.writeFile(gpxFile2, xml2);
console.log(`saved ${gpxFile2}`);
}
go(
// file to read
'/Users/jdi14/Downloads/Afternoon_Run.gpx',
[
// readings to leave out
348,
349,
350,
]
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment