diff --git a/js/lib/togeojson.js b/js/lib/togeojson.js index 74f741c88..15d90a0f4 100644 --- a/js/lib/togeojson.js +++ b/js/lib/togeojson.js @@ -1,62 +1,124 @@ toGeoJSON = (function() { - var removeSpace = (/\s*/g), trimSpace = (/^\s*|\s*$/g), splitSpace = (/\s+/); + '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; } - function nodeVal(x) { return x && x.firstChild && x.firstChild.nodeValue; } - function coord1(v) { return numarray(v.replace(removeSpace, '').split(',')); } - function coord(v) { - var coords = v.replace(trimSpace, '').split(splitSpace), o = []; - for (var i = 0; i < coords.length; i++) o.push(coord1(coords[i])); + function clean(x) { + var o = {}; + for (var i in x) if (x[i]) o[i] = x[i]; return o; } - function fc() { return { type: 'FeatureCollection', features: [] }; } + // get the content of a text node, if any + function nodeVal(x) { if (x) {norm(x);} return x && x.firstChild && x.firstChild.nodeValue; } + // 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) { return [attrf(x, 'lon'), attrf(x, 'lat')]; } + + // create a new feature collection parent object + function fc() { + return { + type: 'FeatureCollection', + features: [] + }; + } + + var styleSupport = false; + if (typeof XMLSerializer !== 'undefined') { + var serializer = new XMLSerializer(); + styleSupport = true; + } + function xml2str(str) { return serializer.serializeToString(str); } + var t = { kml: function(doc, o) { o = o || {}; - var gj = fc(), styleIndex = {}, - geotypes = ['Polygon', 'LineString', 'Point'], - placemarks = get(doc, 'Placemark'), styles = get(doc, 'Style'); - if (o.styles) for (var k = 0; k < styles.length; k++) { - styleIndex['#' + styles[k].id] = okhash(styles[k].innerHTML).toString(16); + var gj = fc(), + // styleindex keeps track of hashed styles in order to match features + styleIndex = {}, + // atomic geospatial types supported by KML - MultiGeometry is + // handled separately + geotypes = ['Polygon', 'LineString', 'Point', 'Track'], + // all root placemarks in the file + placemarks = get(doc, 'Placemark'), + styles = get(doc, 'Style'); + + if (styleSupport) for (var k = 0; k < styles.length; k++) { + styleIndex['#' + attr(styles[k], 'id')] = okhash(xml2str(styles[k])).toString(16); } for (var j = 0; j < placemarks.length; j++) { gj.features = gj.features.concat(getPlacemark(placemarks[j])); } + function gxCoord(v) { return numarray(v.split(' ')); } + function gxCoords(root) { + var elems = get(root, 'coord', 'gx'), coords = []; + for (var i = 0; i < elems.length; i++) coords.push(gxCoord(nodeVal(elems[i]))); + return coords; + } 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')); 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') { - geoms.push({ type: 'Point', + geoms.push({ + type: 'Point', coordinates: coord1(nodeVal(get1(geomNode, 'coordinates'))) }); } else if (geotypes[i] == 'LineString') { - geoms.push({ type: 'LineString', + geoms.push({ + type: 'LineString', coordinates: coord(nodeVal(get1(geomNode, 'coordinates'))) }); } else if (geotypes[i] == 'Polygon') { - var rings = get(geomNode, 'LinearRing'), coords = []; + var rings = get(geomNode, 'LinearRing'), + coords = []; for (k = 0; k < rings.length; k++) { coords.push(coord(nodeVal(get1(rings[k], 'coordinates')))); } - geoms.push({ type: 'Polygon', coordinates: coords }); + geoms.push({ + type: 'Polygon', + coordinates: coords + }); + } else if (geotypes[i] == 'Track') { + geoms.push({ + type: 'LineString', + coordinates: gxCoords(geomNode) + }); } } } @@ -70,7 +132,7 @@ toGeoJSON = (function() { description = nodeVal(get1(root, 'description')), extendedData = get1(root, 'ExtendedData'); - if (!geoms.length) return false; + if (!geoms.length) return []; if (name) properties.name = name; if (styleUrl && styleIndex[styleUrl]) { properties.styleUrl = styleUrl; @@ -88,28 +150,69 @@ toGeoJSON = (function() { properties[simpleDatas[i].getAttribute('name')] = nodeVal(simpleDatas[i]); } } - return [{ type: 'Feature', geometry: (geoms.length === 1) ? geoms[0] : { - type: 'GeometryCollection', - geometries: geoms }, properties: properties }]; + return [{ + type: 'Feature', + geometry: (geoms.length === 1) ? geoms[0] : { + type: 'GeometryCollection', + geometries: geoms + }, + properties: properties + }]; } return gj; }, gpx: function(doc, o) { - var i, j, tracks = get(doc, 'trk'), track, pt, gj = fc(); + var i, + tracks = get(doc, 'trk'), + routes = get(doc, 'rte'), + waypoints = get(doc, 'wpt'), + // a feature collection + gj = fc(); for (i = 0; i < tracks.length; i++) { - track = tracks[i]; - var name = nodeVal(get1(track, 'name')); - var pts = get(track, 'trkpt'), line = []; + gj.features.push(getLinestring(tracks[i], 'trkpt')); + } + for (i = 0; i < routes.length; i++) { + gj.features.push(getLinestring(routes[i], 'rtept')); + } + 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([attrf(pts[j], 'lon'), attrf(pts[j], 'lat')]); + line.push(coordPair(pts[j])); } - gj.features.push({ + return { type: 'Feature', - properties: { - name: name || '' - }, - geometry: { type: 'LineString', coordinates: line } - }); + properties: getProperties(node), + geometry: { + type: 'LineString', + coordinates: line + } + }; + } + function getPoint(node) { + var prop = getProperties(node); + prop.ele = nodeVal(get1(node, 'ele')); + prop.sym = nodeVal(get1(node, 'sym')); + return { + type: 'Feature', + properties: prop, + geometry: { + type: 'Point', + coordinates: coordPair(node) + } + }; + } + 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); } return gj; }