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, | |
] | |
); |