gpxparser
Version:
GPX file parser
466 lines (388 loc) • 14.3 kB
JavaScript
/**
* GPX file parser
*
* @constructor
*/
let gpxParser = function () {
this.xmlSource = "";
this.metadata = {};
this.waypoints = [];
this.tracks = [];
this.routes = [];
};
/**
* Parse a gpx formatted string to a GPXParser Object
*
* @param {string} gpxstring - A GPX formatted String
*
* @return {gpxParser} A GPXParser object
*/
gpxParser.prototype.parse = function (gpxstring) {
let keepThis = this;
let domParser = new window.DOMParser();
this.xmlSource = domParser.parseFromString(gpxstring, 'text/xml');
let metadata = this.xmlSource.querySelector('metadata');
if(metadata != null){
this.metadata.name = this.getElementValue(metadata, "name");
this.metadata.desc = this.getElementValue(metadata, "desc");
this.metadata.time = this.getElementValue(metadata, "time");
let author = {};
let authorElem = metadata.querySelector('author');
if(authorElem != null){
author.name = this.getElementValue(authorElem, "name");
author.email = {};
let emailElem = authorElem.querySelector('email');
if(emailElem != null){
author.email.id = emailElem.getAttribute("id");
author.email.domain = emailElem.getAttribute("domain");
}
let link = {};
let linkElem = authorElem.querySelector('link');
if(linkElem != null){
link.href = linkElem.getAttribute('href');
link.text = this.getElementValue(linkElem, "text");
link.type = this.getElementValue(linkElem, "type");
}
author.link = link;
}
this.metadata.author = author;
let link = {};
let linkElem = this.queryDirectSelector(metadata, 'link');
if(linkElem != null){
link.href = linkElem.getAttribute('href');
link.text = this.getElementValue(linkElem, "text");
link.type = this.getElementValue(linkElem, "type");
this.metadata.link = link;
}
}
var wpts = [].slice.call(this.xmlSource.querySelectorAll('wpt'));
for (let idx in wpts){
var wpt = wpts[idx];
let pt = {};
pt.name = keepThis.getElementValue(wpt, "name");
pt.sym = keepThis.getElementValue(wpt, "sym");
pt.lat = parseFloat(wpt.getAttribute("lat"));
pt.lon = parseFloat(wpt.getAttribute("lon"));
let floatValue = parseFloat(keepThis.getElementValue(wpt, "ele"));
pt.ele = isNaN(floatValue) ? null : floatValue;
pt.cmt = keepThis.getElementValue(wpt, "cmt");
pt.desc = keepThis.getElementValue(wpt, "desc");
let time = keepThis.getElementValue(wpt, "time");
pt.time = time == null ? null : new Date(time);
keepThis.waypoints.push(pt);
}
var rtes = [].slice.call(this.xmlSource.querySelectorAll('rte'));
for (let idx in rtes){
let rte = rtes[idx];
let route = {};
route.name = keepThis.getElementValue(rte, "name");
route.cmt = keepThis.getElementValue(rte, "cmt");
route.desc = keepThis.getElementValue(rte, "desc");
route.src = keepThis.getElementValue(rte, "src");
route.number = keepThis.getElementValue(rte, "number");
let type = keepThis.queryDirectSelector(rte, "type");
route.type = type != null ? type.innerHTML : null;
let link = {};
let linkElem = rte.querySelector('link');
if(linkElem != null){
link.href = linkElem.getAttribute('href');
link.text = keepThis.getElementValue(linkElem, "text");
link.type = keepThis.getElementValue(linkElem, "type");
}
route.link = link;
let routepoints = [];
var rtepts = [].slice.call(rte.querySelectorAll('rtept'));
for (let idxIn in rtepts){
let rtept = rtepts[idxIn];
let pt = {};
pt.lat = parseFloat(rtept.getAttribute("lat"));
pt.lon = parseFloat(rtept.getAttribute("lon"));
let floatValue = parseFloat(keepThis.getElementValue(rtept, "ele"));
pt.ele = isNaN(floatValue) ? null : floatValue;
let time = keepThis.getElementValue(rtept, "time");
pt.time = time == null ? null : new Date(time);
routepoints.push(pt);
}
route.distance = keepThis.calculDistance(routepoints);
route.elevation = keepThis.calcElevation(routepoints);
route.slopes = keepThis.calculSlope(routepoints, route.distance.cumul);
route.points = routepoints;
keepThis.routes.push(route);
}
var trks = [].slice.call(this.xmlSource.querySelectorAll('trk'));
for (let idx in trks){
let trk = trks[idx];
let track = {};
track.name = keepThis.getElementValue(trk, "name");
track.cmt = keepThis.getElementValue(trk, "cmt");
track.desc = keepThis.getElementValue(trk, "desc");
track.src = keepThis.getElementValue(trk, "src");
track.number = keepThis.getElementValue(trk, "number");
let type = keepThis.queryDirectSelector(trk, "type");
track.type = type != null ? type.innerHTML : null;
let link = {};
let linkElem = trk.querySelector('link');
if(linkElem != null){
link.href = linkElem.getAttribute('href');
link.text = keepThis.getElementValue(linkElem, "text");
link.type = keepThis.getElementValue(linkElem, "type");
}
track.link = link;
let trackpoints = [];
let trkpts = [].slice.call(trk.querySelectorAll('trkpt'));
for (let idxIn in trkpts){
var trkpt = trkpts[idxIn];
let pt = {};
pt.lat = parseFloat(trkpt.getAttribute("lat"));
pt.lon = parseFloat(trkpt.getAttribute("lon"));
let floatValue = parseFloat(keepThis.getElementValue(trkpt, "ele"));
pt.ele = isNaN(floatValue) ? null : floatValue;
let time = keepThis.getElementValue(trkpt, "time");
pt.time = time == null ? null : new Date(time);
trackpoints.push(pt);
}
track.distance = keepThis.calculDistance(trackpoints);
track.elevation = keepThis.calcElevation(trackpoints);
track.slopes = keepThis.calculSlope(trackpoints, track.distance.cumul);
track.points = trackpoints;
keepThis.tracks.push(track);
}
};
/**
* Get value from a XML DOM element
*
* @param {Element} parent - Parent DOM Element
* @param {string} needle - Name of the searched element
*
* @return {} The element value
*/
gpxParser.prototype.getElementValue = function(parent, needle){
let elem = parent.querySelector(needle);
if(elem != null){
return elem.innerHTML != undefined ? elem.innerHTML : elem.childNodes[0].data;
}
return elem;
};
/**
* Search the value of a direct child XML DOM element
*
* @param {Element} parent - Parent DOM Element
* @param {string} needle - Name of the searched element
*
* @return {} The element value
*/
gpxParser.prototype.queryDirectSelector = function(parent, needle) {
let elements = parent.querySelectorAll(needle);
let finalElem = elements[0];
if(elements.length > 1) {
let directChilds = parent.childNodes;
for(idx in directChilds) {
elem = directChilds[idx];
if(elem.tagName === needle) {
finalElem = elem;
}
}
}
return finalElem;
};
/**
* Calcul the Distance Object from an array of points
*
* @param {} points - An array of points with lat and lon properties
*
* @return {DistanceObject} An object with total distance and Cumulative distances
*/
gpxParser.prototype.calculDistance = function(points) {
let distance = {};
let totalDistance = 0;
let cumulDistance = [];
for (var i = 0; i < points.length - 1; i++) {
totalDistance += this.calcDistanceBetween(points[i],points[i+1]);
cumulDistance[i] = totalDistance;
}
cumulDistance[points.length - 1] = totalDistance;
distance.total = totalDistance;
distance.cumul = cumulDistance;
return distance;
};
/**
* Calcul Distance between two points with lat and lon
*
* @param {} wpt1 - A geographic point with lat and lon properties
* @param {} wpt2 - A geographic point with lat and lon properties
*
* @returns {float} The distance between the two points
*/
gpxParser.prototype.calcDistanceBetween = function (wpt1, wpt2) {
let latlng1 = {};
latlng1.lat = wpt1.lat;
latlng1.lon = wpt1.lon;
let latlng2 = {};
latlng2.lat = wpt2.lat;
latlng2.lon = wpt2.lon;
var rad = Math.PI / 180,
lat1 = latlng1.lat * rad,
lat2 = latlng2.lat * rad,
sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2),
sinDLon = Math.sin((latlng2.lon - latlng1.lon) * rad / 2),
a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon,
c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return 6371000 * c;
};
/**
* Generate Elevation Object from an array of points
*
* @param {} points - An array of points with ele property
*
* @returns {ElevationObject} An object with negative and positive height difference and average, max and min altitude data
*/
gpxParser.prototype.calcElevation = function (points) {
var dp = 0,
dm = 0,
ret = {};
for (var i = 0; i < points.length - 1; i++) {
let rawNextElevation = points[i + 1].ele;
let rawElevation = points[i].ele;
if(rawNextElevation !== null && rawElevation !== null) {
let diff = parseFloat(rawNextElevation) - parseFloat(rawElevation);
if (diff < 0) {
dm += diff;
} else if (diff > 0) {
dp += diff;
}
}
}
var elevation = [];
var sum = 0;
for (var i = 0, len = points.length; i < len; i++) {
let rawElevation = points[i].ele;
if(rawElevation !== null) {
var ele = parseFloat(points[i].ele);
elevation.push(ele);
sum += ele;
}
}
ret.max = Math.max.apply(null, elevation) || null;
ret.min = Math.min.apply(null, elevation) || null;
ret.pos = Math.abs(dp) || null;
ret.neg = Math.abs(dm) || null;
ret.avg = sum / elevation.length || null;
return ret;
};
/**
* Generate slopes Object from an array of Points and an array of Cumulative distance
*
* @param {} points - An array of points with ele property
* @param {} cumul - An array of cumulative distance
*
* @returns {SlopeObject} An array of slopes
*/
gpxParser.prototype.calculSlope = function(points, cumul) {
let slopes = [];
for (var i = 0; i < points.length - 1; i++) {
let point = points[i];
let nextPoint = points[i+1];
let elevationDiff = nextPoint.ele - point.ele;
let distance = cumul[i+1] - cumul[i];
let slope = (elevationDiff * 100) / distance;
slopes.push(slope);
}
return slopes;
};
/**
* Export the GPX object to a GeoJSON formatted Object
*
* @returns {} a GeoJSON formatted Object
*/
gpxParser.prototype.toGeoJSON = function () {
var GeoJSON = {
"type": "FeatureCollection",
"features": [],
"properties": {
"name": this.metadata.name,
"desc": this.metadata.desc,
"time": this.metadata.time,
"author": this.metadata.author,
"link": this.metadata.link,
},
};
for(idx in this.tracks) {
let track = this.tracks[idx];
var feature = {
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": []
},
"properties": {
}
};
feature.properties.name = track.name;
feature.properties.cmt = track.cmt;
feature.properties.desc = track.desc;
feature.properties.src = track.src;
feature.properties.number = track.number;
feature.properties.link = track.link;
feature.properties.type = track.type;
for(idx in track.points) {
let pt = track.points[idx];
var geoPt = [];
geoPt.push(pt.lon);
geoPt.push(pt.lat);
geoPt.push(pt.ele);
feature.geometry.coordinates.push(geoPt);
}
GeoJSON.features.push(feature);
}
for(idx in this.routes) {
let track = this.routes[idx];
var feature = {
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": []
},
"properties": {
}
};
feature.properties.name = track.name;
feature.properties.cmt = track.cmt;
feature.properties.desc = track.desc;
feature.properties.src = track.src;
feature.properties.number = track.number;
feature.properties.link = track.link;
feature.properties.type = track.type;
for(idx in track.points) {
let pt = track.points[idx];
var geoPt = [];
geoPt.push(pt.lon);
geoPt.push(pt.lat);
geoPt.push(pt.ele);
feature.geometry.coordinates.push(geoPt);
}
GeoJSON.features.push(feature);
}
for(idx in this.waypoints) {
let pt = this.waypoints[idx];
var feature = {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": []
},
"properties": {
}
};
feature.properties.name = pt.name;
feature.properties.sym = pt.sym;
feature.properties.cmt = pt.cmt;
feature.properties.desc = pt.desc;
feature.geometry.coordinates = [pt.lon, pt.lat, pt.ele];
GeoJSON.features.push(feature);
}
return GeoJSON;
};
if(typeof module !== 'undefined'){
require('jsdom-global')();
module.exports = gpxParser;
}