UNPKG

gpx2geojson

Version:

Convert GPX to GeoJSON,applicable to various gpx data.

247 lines (245 loc) 10.1 kB
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;