上山的時候我習慣會用手機的 GPS 記錄行程,下山後再把匯出的 GPX 檔丟到 Google Earth 上分析,看看整趟行程的距離、時間、海拔變化等,就像上圖,是前一陣子去水漾森林的記錄。
因為這樣的分析滿有趣又實用的,所以就想練習下用 JavaScript 做出類似的分析,最後再拿自己做出來的結果(距離、時間)和 Google Earth 來比較。
- GPX — GPS eXchange Format
- XML to JavaScript Object
- Time / Distance between Points
GPX — GPS eXchange Format
首先,根據 wiki,GPX 是用 XML 的格式來記錄 GPS 的,所以是很熟悉的 tag 形式的資料,如下圖。觀察後可以發現,<trkseg> 裡的 <trkpt> 記錄了行程中各點的經緯度、海拔和時間,這就是我們需要的資料啦。
XML to JavaScript Object
接著,為了方便使用 JavaScript 進行分析,所以利用 fast-xml-parser 的幫忙直接把 XML 格式的 GPX 檔轉成一個 JavaScript Object。
const fastXmlParser = require("fast-xml-parser");let parsedGPX = fastXmlParser.parse(
fs.readFileSync("./shui-yang-forest.gpx", "utf8"),
{
attrPrefix: "",
ignoreNonTextNodeAttr: false
}
);console.log(parsedGPX);
Time / Distance between Points
要算出一個 GPX 的總距離也是滿單純的,把所有點兩兩間的距離算出來再相加就好啦,在這裡,我使用了 node-geo-distance 這個 package 來處理兩點之間的距離計算。
值得一提的是,node-geo-distance 在計算兩點距離時所使用的公式有兩種:
1. 把地球當作球體的 Haversine formula
2. 把地球當作橢圓球體的 Vincenty formula
所以下面也會把兩種結果都算出來一起做個比較。
const geo = require("node-geo-distance");
const points = parsedGPX.gpx.trk.trkseg.trkpt;
let distanceHaversine = 0;
let distanceVincenty = 0;for (let i = 0; i < points.length - 1; i++) {
const coord1 = ...
const coord2 = ...
distanceHaversine += +geo.haversineSync(coord1, coord2);
distanceVincenty += +geo.vincentySync(coord1, coord2);
}
而總時間的計算就單純使用 Moment.js 啦。
const time = moment(_.last(points).time).diff(points[0].time);
從最後的結果可以發現,距離的部分,用 Haversine formula 和 Vincenty formula 算出來的結果在精確到小數點後 1 位的情況下並沒有差別,而和封面照片中 Google Earth 算出來的 11km 也沒有太大差別,算是OK!
時間的部分也沒有問題,和 Google Earth 的一模一樣, 6hr 33m 13s !
完整程式碼如下: