From bb007689d487c61ae9ef14bc15d144f801827dc1 Mon Sep 17 00:00:00 2001 From: johndoe Date: Wed, 13 Mar 2019 10:52:11 +0200 Subject: [PATCH] external: togeojson.js update to 72957d69545ed1f798d56618694473b603a0ba6f (v0.16.0-27-g72957d6) --- external/togeojson.js | 359 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 296 insertions(+), 63 deletions(-) diff --git a/external/togeojson.js b/external/togeojson.js index 0c4f23efc..23ec6cc39 100755 --- a/external/togeojson.js +++ b/external/togeojson.js @@ -1,9 +1,9 @@ -toGeoJSON = (function() { +var toGeoJSON = (function() { 'use strict'; - var removeSpace = (/\s*/g), - trimSpace = (/^\s*|\s*$/g), - splitSpace = (/\s+/); + 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; @@ -21,16 +21,25 @@ toGeoJSON = (function() { 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]); + for (var j = 0, o = []; j < x.length; j++) { o[j] = parseFloat(x[j]); } return o; } - function clean(x) { - var o = {}; - for (var i in x) if (x[i]) o[i] = x[i]; + // 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; } - // get the content of a text node, if any - function nodeVal(x) { if (x) {norm(x);} return x && x.firstChild && x.firstChild.nodeValue; } + // 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 [[],[]] @@ -42,7 +51,25 @@ toGeoJSON = (function() { } return o; } - function coordPair(x) { return [attrf(x, 'lon'), attrf(x, 'lat')]; } + 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 + }; + } // create a new feature collection parent object function fc() { @@ -54,60 +81,105 @@ toGeoJSON = (function() { var serializer; if (typeof XMLSerializer !== 'undefined') { + /* istanbul ignore next */ serializer = new XMLSerializer(); - } else if (typeof require !== 'undefined') { - serializer = new (require('xmldom').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 + /* istanbul ignore next */ + if (str.xml !== undefined) return str.xml; + return serializer.serializeToString(str); } - function xml2str(str) { return serializer.serializeToString(str); } var t = { - kml: function(doc, o) { - o = o || {}; + kml: function(doc) { var gj = fc(), // styleindex keeps track of hashed styles in order to match features - styleIndex = {}, + styleIndex = {}, styleByHash = {}, + // stylemapindex keeps track of style maps to expose in properties + styleMapIndex = {}, // atomic geospatial types supported by KML - MultiGeometry is // handled separately - geotypes = ['Polygon', 'LineString', 'Point', 'Track'], + geotypes = ['Polygon', 'LineString', 'Point', 'Track', 'gx:Track'], // all root placemarks in the file placemarks = get(doc, 'Placemark'), - styles = get(doc, 'Style'); + styles = get(doc, 'Style'), + styleMaps = get(doc, 'StyleMap'); for (var k = 0; k < styles.length; k++) { - styleIndex['#' + attr(styles[k], 'id')] = okhash(xml2str(styles[k])).toString(16); + var hash = okhash(xml2str(styles[k])).toString(16); + styleIndex['#' + attr(styles[k], 'id')] = hash; + styleByHash[hash] = styles[k]; + } + for (var l = 0; l < styleMaps.length; l++) { + styleIndex['#' + attr(styleMaps[l], 'id')] = okhash(xml2str(styleMaps[l])).toString(16); + var pairs = get(styleMaps[l], 'Pair'); + var pairsMap = {}; + for (var m = 0; m < pairs.length; m++) { + pairsMap[nodeVal(get1(pairs[m], 'key'))] = nodeVal(get1(pairs[m], 'styleUrl')); + } + styleMapIndex['#' + attr(styleMaps[l], 'id')] = pairsMap; + } for (var j = 0; j < placemarks.length; j++) { gj.features = gj.features.concat(getPlacemark(placemarks[j])); } + function kmlColor(v) { + var color, opacity; + v = v || ''; + if (v.substr(0, 1) === '#') { v = v.substr(1); } + if (v.length === 6 || v.length === 3) { color = v; } + if (v.length === 8) { + opacity = parseInt(v.substr(0, 2), 16) / 255; + color = '#' + v.substr(6, 2) + + v.substr(4, 2) + + v.substr(2, 2); + } + return [color, isNaN(opacity) ? undefined : opacity]; + } function gxCoord(v) { return numarray(v.split(' ')); } function gxCoords(root) { - var elems = get(root, 'coord', 'gx'), coords = []; + var elems = get(root, 'coord', 'gx'), coords = [], times = []; + if (elems.length === 0) elems = get(root, 'gx:coord'); for (var i = 0; i < elems.length; i++) coords.push(gxCoord(nodeVal(elems[i]))); - return coords; + var timeElems = get(root, 'when'); + for (var j = 0; j < timeElems.length; j++) times.push(nodeVal(timeElems[j])); + return { + coords: coords, + times: times + }; } function getGeometry(root) { - var geomNode, geomNodes, i, j, k, geoms = []; - if (get1(root, 'MultiGeometry')) return getGeometry(get1(root, 'MultiGeometry')); - if (get1(root, 'MultiTrack')) return getGeometry(get1(root, 'MultiTrack')); + var geomNode, geomNodes, i, j, k, geoms = [], coordTimes = []; + if (get1(root, 'MultiGeometry')) { return getGeometry(get1(root, 'MultiGeometry')); } + if (get1(root, 'MultiTrack')) { return getGeometry(get1(root, 'MultiTrack')); } + if (get1(root, 'gx:MultiTrack')) { return getGeometry(get1(root, 'gx:MultiTrack')); } for (i = 0; i < geotypes.length; i++) { geomNodes = get(root, geotypes[i]); if (geomNodes) { for (j = 0; j < geomNodes.length; j++) { geomNode = geomNodes[j]; - if (geotypes[i] == 'Point') { - if(nodeVal(get1(geomNode, 'coordinates')) === null) - continue; + if (geotypes[i] === 'Point') { geoms.push({ type: 'Point', coordinates: coord1(nodeVal(get1(geomNode, 'coordinates'))) }); - } else if (geotypes[i] == 'LineString') { + } else if (geotypes[i] === 'LineString') { geoms.push({ type: 'LineString', coordinates: coord(nodeVal(get1(geomNode, 'coordinates'))) }); - } else if (geotypes[i] == 'Polygon') { + } else if (geotypes[i] === 'Polygon') { var rings = get(geomNode, 'LinearRing'), coords = []; for (k = 0; k < rings.length; k++) { @@ -117,31 +189,96 @@ toGeoJSON = (function() { type: 'Polygon', coordinates: coords }); - } else if (geotypes[i] == 'Track') { + } else if (geotypes[i] === 'Track' || + geotypes[i] === 'gx:Track') { + var track = gxCoords(geomNode); geoms.push({ type: 'LineString', - coordinates: gxCoords(geomNode) + coordinates: track.coords }); + if (track.times.length) coordTimes.push(track.times); } } } } - return geoms; + return { + geoms: geoms, + coordTimes: coordTimes + }; } function getPlacemark(root) { - var geoms = getGeometry(root), i, properties = {}, + var geomsAndTimes = getGeometry(root), i, properties = {}, name = nodeVal(get1(root, 'name')), + address = nodeVal(get1(root, 'address')), styleUrl = nodeVal(get1(root, 'styleUrl')), description = nodeVal(get1(root, 'description')), - extendedData = get1(root, 'ExtendedData'); + timeSpan = get1(root, 'TimeSpan'), + timeStamp = get1(root, 'TimeStamp'), + extendedData = get1(root, 'ExtendedData'), + lineStyle = get1(root, 'LineStyle'), + polyStyle = get1(root, 'PolyStyle'), + visibility = get1(root, 'visibility'); - if (!geoms.length) return []; + if (!geomsAndTimes.geoms.length) return []; if (name) properties.name = name; - if (styleUrl && styleIndex[styleUrl]) { + if (address) properties.address = address; + if (styleUrl) { + if (styleUrl[0] !== '#') { + styleUrl = '#' + styleUrl; + } + properties.styleUrl = styleUrl; - properties.styleHash = styleIndex[styleUrl]; + if (styleIndex[styleUrl]) { + properties.styleHash = styleIndex[styleUrl]; + } + if (styleMapIndex[styleUrl]) { + properties.styleMapHash = styleMapIndex[styleUrl]; + properties.styleHash = styleIndex[styleMapIndex[styleUrl].normal]; + } + // Try to populate the lineStyle or polyStyle since we got the style hash + var style = styleByHash[properties.styleHash]; + if (style) { + if (!lineStyle) lineStyle = get1(style, 'LineStyle'); + if (!polyStyle) polyStyle = get1(style, 'PolyStyle'); + var iconStyle = get1(style, 'IconStyle'); + if (iconStyle) { + var icon = get1(iconStyle, 'Icon'); + if (icon) { + var href = nodeVal(get1(icon, 'href')); + if (href) properties.icon = href; + } + } + } } if (description) properties.description = description; + if (timeSpan) { + var begin = nodeVal(get1(timeSpan, 'begin')); + var end = nodeVal(get1(timeSpan, 'end')); + properties.timespan = { begin: begin, end: end }; + } + if (timeStamp) { + properties.timestamp = nodeVal(get1(timeStamp, 'when')); + } + if (lineStyle) { + var linestyles = kmlColor(nodeVal(get1(lineStyle, 'color'))), + color = linestyles[0], + opacity = linestyles[1], + width = parseFloat(nodeVal(get1(lineStyle, 'width'))); + if (color) properties.stroke = color; + if (!isNaN(opacity)) properties['stroke-opacity'] = opacity; + if (!isNaN(width)) properties['stroke-width'] = width; + } + if (polyStyle) { + var polystyles = kmlColor(nodeVal(get1(polyStyle, 'color'))), + pcolor = polystyles[0], + popacity = polystyles[1], + fill = nodeVal(get1(polyStyle, 'fill')), + outline = nodeVal(get1(polyStyle, 'outline')); + if (pcolor) properties.fill = pcolor; + if (!isNaN(popacity)) properties['fill-opacity'] = popacity; + if (fill) properties['fill-opacity'] = fill === '1' ? properties['fill-opacity'] || 1 : 0; + if (outline) properties['stroke-opacity'] = outline === '1' ? properties['stroke-opacity'] || 1 : 0; + } if (extendedData) { var datas = get(extendedData, 'Data'), simpleDatas = get(extendedData, 'SimpleData'); @@ -153,69 +290,165 @@ toGeoJSON = (function() { properties[simpleDatas[i].getAttribute('name')] = nodeVal(simpleDatas[i]); } } - return [{ + if (visibility) { + properties.visibility = nodeVal(visibility); + } + if (geomsAndTimes.coordTimes.length) { + properties.coordTimes = (geomsAndTimes.coordTimes.length === 1) ? + geomsAndTimes.coordTimes[0] : geomsAndTimes.coordTimes; + } + var feature = { type: 'Feature', - geometry: (geoms.length === 1) ? geoms[0] : { + geometry: (geomsAndTimes.geoms.length === 1) ? geomsAndTimes.geoms[0] : { type: 'GeometryCollection', - geometries: geoms + geometries: geomsAndTimes.geoms }, properties: properties - }]; + }; + if (attr(root, 'id')) feature.id = attr(root, 'id'); + return [feature]; } return gj; }, - gpx: function(doc, o) { + gpx: function(doc) { var i, tracks = get(doc, 'trk'), routes = get(doc, 'rte'), waypoints = get(doc, 'wpt'), // a feature collection - gj = fc(); + gj = fc(), + feature; for (i = 0; i < tracks.length; i++) { - gj.features.push(getLinestring(tracks[i], 'trkpt')); + feature = getTrack(tracks[i]); + if (feature) gj.features.push(feature); } for (i = 0; i < routes.length; i++) { - gj.features.push(getLinestring(routes[i], 'rtept')); + feature = getRoute(routes[i]); + if (feature) gj.features.push(feature); } for (i = 0; i < waypoints.length; i++) { gj.features.push(getPoint(waypoints[i])); } - function getLinestring(node, pointname) { - var j, pts = get(node, pointname), line = []; - for (j = 0; j < pts.length; j++) { - line.push(coordPair(pts[j])); + 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: getProperties(node), + 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 + coordinates: line.line } }; + return routeObj; } function getPoint(node) { var prop = getProperties(node); - prop.ele = nodeVal(get1(node, 'ele')); - prop.sym = nodeVal(get1(node, 'sym')); + extend(prop, getMulti(node, ['sym'])); return { type: 'Feature', properties: prop, geometry: { type: 'Point', - coordinates: coordPair(node) + 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 meta = ['name', 'desc', 'author', 'copyright', 'link', - 'time', 'keywords'], - prop = {}, - k; - for (k = 0; k < meta.length; k++) { - prop[meta[k]] = nodeVal(get1(node, meta[k])); - } - return clean(prop); + var prop = getMulti(node, ['name', 'cmt', 'desc', 'type', 'time', 'keywords']), + 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 gj; }