gpx2geojson
Version:
Convert GPX to GeoJSON,applicable to various gpx data.
247 lines (245 loc) • 10.1 kB
JavaScript
var gpx2GeoJSON = (function() {
'use strict';
var removeSpace = /\s*/g,
trimSpace = /^\s*|\s*$/g,
splitSpace = /\s+/;
// generate a short, numeric hash of a string
function okhash(x) {
if (!x || !x.length) return 0;
for (var i = 0, h = 0; i < x.length; i++) {
h = ((h << 5) - h) + x.charCodeAt(i) | 0;
} return h;
}
// all Y children of X
function get(x, y) { return x.getElementsByTagName(y); }
function attr(x, y) { return x.getAttribute(y); }
function attrf(x, y) { return parseFloat(attr(x, y)); }
// one Y child of X, if any, otherwise null
function get1(x, y) { var n = get(x, y); return n.length ? n[0] : null; }
// https://developer.mozilla.org/en-US/docs/Web/API/Node.normalize
function norm(el) { if (el.normalize) { el.normalize(); } return el; }
// cast array x into numbers
function numarray(x) {
for (var j = 0, o = []; j < x.length; j++) { o[j] = parseFloat(x[j]); }
return o;
}
// get the content of a text node, if any
function nodeVal(x) {
if (x) { norm(x); }
return (x && x.textContent) || '';
}
// get the contents of multiple text nodes, if present
function getMulti(x, ys) {
var o = {}, n, k;
for (k = 0; k < ys.length; k++) {
n = get1(x, ys[k]);
if (n) o[ys[k]] = nodeVal(n);
}
return o;
}
// add properties of Y to X, overwriting if present in both
function extend(x, y) { for (var k in y) x[k] = y[k]; }
// get one coordinate from a coordinate array, if any
function coord1(v) { return numarray(v.replace(removeSpace, '').split(',')); }
// get all coordinates from a coordinate array as [[],[]]
function coord(v) {
var coords = v.replace(trimSpace, '').split(splitSpace),
o = [];
for (var i = 0; i < coords.length; i++) {
o.push(coord1(coords[i]));
}
return o;
}
function coordPair(x) {
var ll = [attrf(x, 'lon'), attrf(x, 'lat')],
ele = get1(x, 'ele'),
// handle namespaced attribute in browser
heartRate = get1(x, 'gpxtpx:hr') || get1(x, 'hr'),
time = get1(x, 'time'),
e;
if (ele) {
e = parseFloat(nodeVal(ele));
if (!isNaN(e)) {
ll.push(e);
}
}
return {
coordinates: ll,
time: time ? nodeVal(time) : null,
heartRate: heartRate ? parseFloat(nodeVal(heartRate)) : null
};
}
function FCollection() { // create a new feature collection parent object
return {
type: 'FeatureCollection',
features: []
};
}
var serializer;
if (typeof XMLSerializer !== 'undefined') {
serializer = new XMLSerializer();
} else {
var isNodeEnv = (typeof process === 'object' && !process.browser);
var isTitaniumEnv = (typeof Titanium === 'object');
if (typeof exports === 'object' && (isNodeEnv || isTitaniumEnv)) {
serializer = new (require('xmldom').XMLSerializer)();
} else {
throw new Error('Unable to initialize serializer');
}
}
function xml2str(str) {
// IE9 will create a new XMLSerializer but it'll crash immediately.
// This line is ignored because we don't run coverage tests in IE9
if (str.xml !== undefined) return str.xml;
return serializer.serializeToString(str);
}
var functionObj = {
gpx: function(doc) {
var i,
tracks = get(doc, 'trk'),
routes = get(doc, 'rte'),
waypoints = get(doc, 'wpt'),
trkpts = get(doc, 'trkpt'),
fc = FCollection(), // feature collection object
feature;
for (i = 0; i < tracks.length; i++) {
feature = getTrack(tracks[i]);
if (feature) fc.features.push(feature);
}
for (i = 0; i < routes.length; i++) {
feature = getRoute(routes[i]);
if (feature) fc.features.push(feature);
}
for (i = 0; i < waypoints.length; i++) {
fc.features.push(getPoint(waypoints[i]));
}
for (i = 0; i < trkpts.length; i++) {
fc.features.push(getPoint(trkpts[i]));
}
function initializeArray(arr, size) {
for (var h = 0; h < size; h++) {
arr.push(null);
}
return arr;
}
function getPoints(node, pointname) {
var pts = get(node, pointname),
line = [],
times = [],
heartRates = [],
l = pts.length;
if (l < 2) return {}; // Invalid line in GeoJSON
for (var i = 0; i < l; i++) {
var c = coordPair(pts[i]);
line.push(c.coordinates);
if (c.time) times.push(c.time);
if (c.heartRate || heartRates.length) {
if (!heartRates.length) initializeArray(heartRates, i);
heartRates.push(c.heartRate || null);
}
}
return {
line: line,
times: times,
heartRates: heartRates
};
}
function getTrack(node) {
var segments = get(node, 'trkseg'),
track = [],
times = [],
heartRates = [],
line;
for (var i = 0; i < segments.length; i++) {
line = getPoints(segments[i], 'trkpt');
if (line) {
if (line.line) track.push(line.line);
if (line.times && line.times.length) times.push(line.times);
if (heartRates.length || (line.heartRates && line.heartRates.length)) {
if (!heartRates.length) {
for (var s = 0; s < i; s++) {
heartRates.push(initializeArray([], track[s].length));
}
}
if (line.heartRates && line.heartRates.length) {
heartRates.push(line.heartRates);
} else {
heartRates.push(initializeArray([], line.line.length || 0));
}
}
}
}
if (track.length === 0) return;
var properties = getProperties(node);
extend(properties, getLineStyle(get1(node, 'extensions')));
if (times.length) properties.coordTimes = track.length === 1 ? times[0] : times;
if (heartRates.length) properties.heartRates = track.length === 1 ? heartRates[0] : heartRates;
return {
type: 'Feature',
properties: properties,
geometry: {
type: track.length === 1 ? 'LineString' : 'MultiLineString',
coordinates: track.length === 1 ? track[0] : track
}
};
}
function getRoute(node) {
var line = getPoints(node, 'rtept');
if (!line.line) return;
var prop = getProperties(node);
extend(prop, getLineStyle(get1(node, 'extensions')));
var routeObj = {
type: 'Feature',
properties: prop,
geometry: {
type: 'LineString',
coordinates: line.line
}
};
return routeObj;
}
function getPoint(node) {
var prop = getProperties(node);
extend(prop, getMulti(node, ['sym']));
return {
type: 'Feature',
properties: prop,
geometry: {
type: 'Point',
coordinates: coordPair(node).coordinates
}
};
}
function getLineStyle(extensions) {
var style = {};
if (extensions) {
var lineStyle = get1(extensions, 'line');
if (lineStyle) {
var color = nodeVal(get1(lineStyle, 'color')),
opacity = parseFloat(nodeVal(get1(lineStyle, 'opacity'))),
width = parseFloat(nodeVal(get1(lineStyle, 'width')));
if (color) style.stroke = color;
if (!isNaN(opacity)) style['stroke-opacity'] = opacity;
// GPX width is in mm, convert to px with 96 px per inch
if (!isNaN(width)) style['stroke-width'] = width * 96 / 25.4;
}
}
return style;
}
function getProperties(node) {
var prop = getMulti(node, ['keywords','cellular','accuracy','speed','direction','ele','name', 'cmt', 'desc', 'type', 'time']),
links = get(node, 'link');
if (links.length) prop.links = [];
for (var i = 0, link; i < links.length; i++) {
link = { href: attr(links[i], 'href') };
extend(link, getMulti(links[i], ['text', 'type']));
prop.links.push(link);
}
return prop;
}
return fc;
}
};
return functionObj;
})();
if (typeof module !== 'undefined') module.exports = gpx2GeoJSON;