UNPKG

19.2 kBJavaScriptView Raw
1var toGeoJSON = (function() {
2 'use strict';
3
4 var removeSpace = /\s*/g,
5 trimSpace = /^\s*|\s*$/g,
6 splitSpace = /\s+/;
7 // generate a short, numeric hash of a string
8 function okhash(x) {
9 if (!x || !x.length) return 0;
10 for (var i = 0, h = 0; i < x.length; i++) {
11 h = ((h << 5) - h) + x.charCodeAt(i) | 0;
12 } return h;
13 }
14 // all Y children of X
15 function get(x, y) { return x.getElementsByTagName(y); }
16 function attr(x, y) { return x.getAttribute(y); }
17 function attrf(x, y) { return parseFloat(attr(x, y)); }
18 // one Y child of X, if any, otherwise null
19 function get1(x, y) { var n = get(x, y); return n.length ? n[0] : null; }
20 // https://developer.mozilla.org/en-US/docs/Web/API/Node.normalize
21 function norm(el) { if (el.normalize) { el.normalize(); } return el; }
22 // cast array x into numbers
23 function numarray(x) {
24 for (var j = 0, o = []; j < x.length; j++) { o[j] = parseFloat(x[j]); }
25 return o;
26 }
27 // get the content of a text node, if any
28 function nodeVal(x) {
29 if (x) { norm(x); }
30 return (x && x.textContent) || '';
31 }
32 // get the contents of multiple text nodes, if present
33 function getMulti(x, ys) {
34 var o = {}, n, k;
35 for (k = 0; k < ys.length; k++) {
36 n = get1(x, ys[k]);
37 if (n) o[ys[k]] = nodeVal(n);
38 }
39 return o;
40 }
41 // add properties of Y to X, overwriting if present in both
42 function extend(x, y) { for (var k in y) x[k] = y[k]; }
43 // get one coordinate from a coordinate array, if any
44 function coord1(v) { return numarray(v.replace(removeSpace, '').split(',')); }
45 // get all coordinates from a coordinate array as [[],[]]
46 function coord(v) {
47 var coords = v.replace(trimSpace, '').split(splitSpace),
48 o = [];
49 for (var i = 0; i < coords.length; i++) {
50 o.push(coord1(coords[i]));
51 }
52 return o;
53 }
54 function coordPair(x) {
55 var ll = [attrf(x, 'lon'), attrf(x, 'lat')],
56 ele = get1(x, 'ele'),
57 // handle namespaced attribute in browser
58 heartRate = get1(x, 'gpxtpx:hr') || get1(x, 'hr'),
59 time = get1(x, 'time'),
60 e;
61 if (ele) {
62 e = parseFloat(nodeVal(ele));
63 if (!isNaN(e)) {
64 ll.push(e);
65 }
66 }
67 return {
68 coordinates: ll,
69 time: time ? nodeVal(time) : null,
70 heartRate: heartRate ? parseFloat(nodeVal(heartRate)) : null
71 };
72 }
73
74 // create a new feature collection parent object
75 function fc() {
76 return {
77 type: 'FeatureCollection',
78 features: []
79 };
80 }
81
82 var serializer;
83 if (typeof XMLSerializer !== 'undefined') {
84 /* istanbul ignore next */
85 serializer = new XMLSerializer();
86 // only require xmldom in a node environment
87 } else if (typeof exports === 'object' && typeof process === 'object' && !process.browser) {
88 serializer = new (require('xmldom').XMLSerializer)();
89 }
90 function xml2str(str) {
91 // IE9 will create a new XMLSerializer but it'll crash immediately.
92 // This line is ignored because we don't run coverage tests in IE9
93 /* istanbul ignore next */
94 if (str.xml !== undefined) return str.xml;
95 return serializer.serializeToString(str);
96 }
97
98 var t = {
99 kml: function(doc) {
100
101 var gj = fc(),
102 // styleindex keeps track of hashed styles in order to match features
103 styleIndex = {}, styleByHash = {},
104 // stylemapindex keeps track of style maps to expose in properties
105 styleMapIndex = {},
106 // atomic geospatial types supported by KML - MultiGeometry is
107 // handled separately
108 geotypes = ['Polygon', 'LineString', 'Point', 'Track', 'gx:Track'],
109 // all root placemarks in the file
110 placemarks = get(doc, 'Placemark'),
111 styles = get(doc, 'Style'),
112 styleMaps = get(doc, 'StyleMap');
113
114 for (var k = 0; k < styles.length; k++) {
115 var hash = okhash(xml2str(styles[k])).toString(16);
116 styleIndex['#' + attr(styles[k], 'id')] = hash;
117 styleByHash[hash] = styles[k];
118 }
119 for (var l = 0; l < styleMaps.length; l++) {
120 styleIndex['#' + attr(styleMaps[l], 'id')] = okhash(xml2str(styleMaps[l])).toString(16);
121 var pairs = get(styleMaps[l], 'Pair');
122 var pairsMap = {};
123 for (var m = 0; m < pairs.length; m++) {
124 pairsMap[nodeVal(get1(pairs[m], 'key'))] = nodeVal(get1(pairs[m], 'styleUrl'));
125 }
126 styleMapIndex['#' + attr(styleMaps[l], 'id')] = pairsMap;
127
128 }
129 for (var j = 0; j < placemarks.length; j++) {
130 gj.features = gj.features.concat(getPlacemark(placemarks[j]));
131 }
132 function kmlColor(v) {
133 var color, opacity;
134 v = v || '';
135 if (v.substr(0, 1) === '#') { v = v.substr(1); }
136 if (v.length === 6 || v.length === 3) { color = v; }
137 if (v.length === 8) {
138 opacity = parseInt(v.substr(0, 2), 16) / 255;
139 color = '#' + v.substr(6, 2) +
140 v.substr(4, 2) +
141 v.substr(2, 2);
142 }
143 return [color, isNaN(opacity) ? undefined : opacity];
144 }
145 function gxCoord(v) { return numarray(v.split(' ')); }
146 function gxCoords(root) {
147 var elems = get(root, 'coord', 'gx'), coords = [], times = [];
148 if (elems.length === 0) elems = get(root, 'gx:coord');
149 for (var i = 0; i < elems.length; i++) coords.push(gxCoord(nodeVal(elems[i])));
150 var timeElems = get(root, 'when');
151 for (var j = 0; j < timeElems.length; j++) times.push(nodeVal(timeElems[j]));
152 return {
153 coords: coords,
154 times: times
155 };
156 }
157 function getGeometry(root) {
158 var geomNode, geomNodes, i, j, k, geoms = [], coordTimes = [];
159 if (get1(root, 'MultiGeometry')) { return getGeometry(get1(root, 'MultiGeometry')); }
160 if (get1(root, 'MultiTrack')) { return getGeometry(get1(root, 'MultiTrack')); }
161 if (get1(root, 'gx:MultiTrack')) { return getGeometry(get1(root, 'gx:MultiTrack')); }
162 for (i = 0; i < geotypes.length; i++) {
163 geomNodes = get(root, geotypes[i]);
164 if (geomNodes) {
165 for (j = 0; j < geomNodes.length; j++) {
166 geomNode = geomNodes[j];
167 if (geotypes[i] === 'Point') {
168 geoms.push({
169 type: 'Point',
170 coordinates: coord1(nodeVal(get1(geomNode, 'coordinates')))
171 });
172 } else if (geotypes[i] === 'LineString') {
173 geoms.push({
174 type: 'LineString',
175 coordinates: coord(nodeVal(get1(geomNode, 'coordinates')))
176 });
177 } else if (geotypes[i] === 'Polygon') {
178 var rings = get(geomNode, 'LinearRing'),
179 coords = [];
180 for (k = 0; k < rings.length; k++) {
181 coords.push(coord(nodeVal(get1(rings[k], 'coordinates'))));
182 }
183 geoms.push({
184 type: 'Polygon',
185 coordinates: coords
186 });
187 } else if (geotypes[i] === 'Track' ||
188 geotypes[i] === 'gx:Track') {
189 var track = gxCoords(geomNode);
190 geoms.push({
191 type: 'LineString',
192 coordinates: track.coords
193 });
194 if (track.times.length) coordTimes.push(track.times);
195 }
196 }
197 }
198 }
199 return {
200 geoms: geoms,
201 coordTimes: coordTimes
202 };
203 }
204 function getPlacemark(root) {
205 var geomsAndTimes = getGeometry(root), i, properties = {},
206 name = nodeVal(get1(root, 'name')),
207 address = nodeVal(get1(root, 'address')),
208 styleUrl = nodeVal(get1(root, 'styleUrl')),
209 description = nodeVal(get1(root, 'description')),
210 timeSpan = get1(root, 'TimeSpan'),
211 timeStamp = get1(root, 'TimeStamp'),
212 extendedData = get1(root, 'ExtendedData'),
213 lineStyle = get1(root, 'LineStyle'),
214 polyStyle = get1(root, 'PolyStyle'),
215 visibility = get1(root, 'visibility');
216
217 if (!geomsAndTimes.geoms.length) return [];
218 if (name) properties.name = name;
219 if (address) properties.address = address;
220 if (styleUrl) {
221 if (styleUrl[0] !== '#') {
222 styleUrl = '#' + styleUrl;
223 }
224
225 properties.styleUrl = styleUrl;
226 if (styleIndex[styleUrl]) {
227 properties.styleHash = styleIndex[styleUrl];
228 }
229 if (styleMapIndex[styleUrl]) {
230 properties.styleMapHash = styleMapIndex[styleUrl];
231 properties.styleHash = styleIndex[styleMapIndex[styleUrl].normal];
232 }
233 // Try to populate the lineStyle or polyStyle since we got the style hash
234 var style = styleByHash[properties.styleHash];
235 if (style) {
236 if (!lineStyle) lineStyle = get1(style, 'LineStyle');
237 if (!polyStyle) polyStyle = get1(style, 'PolyStyle');
238 }
239 }
240 if (description) properties.description = description;
241 if (timeSpan) {
242 var begin = nodeVal(get1(timeSpan, 'begin'));
243 var end = nodeVal(get1(timeSpan, 'end'));
244 properties.timespan = { begin: begin, end: end };
245 }
246 if (timeStamp) {
247 properties.timestamp = nodeVal(get1(timeStamp, 'when'));
248 }
249 if (lineStyle) {
250 var linestyles = kmlColor(nodeVal(get1(lineStyle, 'color'))),
251 color = linestyles[0],
252 opacity = linestyles[1],
253 width = parseFloat(nodeVal(get1(lineStyle, 'width')));
254 if (color) properties.stroke = color;
255 if (!isNaN(opacity)) properties['stroke-opacity'] = opacity;
256 if (!isNaN(width)) properties['stroke-width'] = width;
257 }
258 if (polyStyle) {
259 var polystyles = kmlColor(nodeVal(get1(polyStyle, 'color'))),
260 pcolor = polystyles[0],
261 popacity = polystyles[1],
262 fill = nodeVal(get1(polyStyle, 'fill')),
263 outline = nodeVal(get1(polyStyle, 'outline'));
264 if (pcolor) properties.fill = pcolor;
265 if (!isNaN(popacity)) properties['fill-opacity'] = popacity;
266 if (fill) properties['fill-opacity'] = fill === '1' ? properties['fill-opacity'] || 1 : 0;
267 if (outline) properties['stroke-opacity'] = outline === '1' ? properties['stroke-opacity'] || 1 : 0;
268 }
269 if (extendedData) {
270 var datas = get(extendedData, 'Data'),
271 simpleDatas = get(extendedData, 'SimpleData');
272
273 for (i = 0; i < datas.length; i++) {
274 properties[datas[i].getAttribute('name')] = nodeVal(get1(datas[i], 'value'));
275 }
276 for (i = 0; i < simpleDatas.length; i++) {
277 properties[simpleDatas[i].getAttribute('name')] = nodeVal(simpleDatas[i]);
278 }
279 }
280 if (visibility) {
281 properties.visibility = nodeVal(visibility);
282 }
283 if (geomsAndTimes.coordTimes.length) {
284 properties.coordTimes = (geomsAndTimes.coordTimes.length === 1) ?
285 geomsAndTimes.coordTimes[0] : geomsAndTimes.coordTimes;
286 }
287 var feature = {
288 type: 'Feature',
289 geometry: (geomsAndTimes.geoms.length === 1) ? geomsAndTimes.geoms[0] : {
290 type: 'GeometryCollection',
291 geometries: geomsAndTimes.geoms
292 },
293 properties: properties
294 };
295 if (attr(root, 'id')) feature.id = attr(root, 'id');
296 return [feature];
297 }
298 return gj;
299 },
300 gpx: function(doc) {
301 var i,
302 tracks = get(doc, 'trk'),
303 routes = get(doc, 'rte'),
304 waypoints = get(doc, 'wpt'),
305 // a feature collection
306 gj = fc(),
307 feature;
308 for (i = 0; i < tracks.length; i++) {
309 feature = getTrack(tracks[i]);
310 if (feature) gj.features.push(feature);
311 }
312 for (i = 0; i < routes.length; i++) {
313 feature = getRoute(routes[i]);
314 if (feature) gj.features.push(feature);
315 }
316 for (i = 0; i < waypoints.length; i++) {
317 gj.features.push(getPoint(waypoints[i]));
318 }
319 function getPoints(node, pointname) {
320 var pts = get(node, pointname),
321 line = [],
322 times = [],
323 heartRates = [],
324 l = pts.length;
325 if (l < 2) return {}; // Invalid line in GeoJSON
326 for (var i = 0; i < l; i++) {
327 var c = coordPair(pts[i]);
328 line.push(c.coordinates);
329 if (c.time) times.push(c.time);
330 if (c.heartRate) heartRates.push(c.heartRate);
331 }
332 return {
333 line: line,
334 times: times,
335 heartRates: heartRates
336 };
337 }
338 function getTrack(node) {
339 var segments = get(node, 'trkseg'),
340 track = [],
341 times = [],
342 heartRates = [],
343 line;
344 for (var i = 0; i < segments.length; i++) {
345 line = getPoints(segments[i], 'trkpt');
346 if (line) {
347 if (line.line) track.push(line.line);
348 if (line.times && line.times.length) times.push(line.times);
349 if (line.heartRates && line.heartRates.length) heartRates.push(line.heartRates);
350 }
351 }
352 if (track.length === 0) return;
353 var properties = getProperties(node);
354 extend(properties, getLineStyle(get1(node, 'extensions')));
355 if (times.length) properties.coordTimes = track.length === 1 ? times[0] : times;
356 if (heartRates.length) properties.heartRates = track.length === 1 ? heartRates[0] : heartRates;
357 return {
358 type: 'Feature',
359 properties: properties,
360 geometry: {
361 type: track.length === 1 ? 'LineString' : 'MultiLineString',
362 coordinates: track.length === 1 ? track[0] : track
363 }
364 };
365 }
366 function getRoute(node) {
367 var line = getPoints(node, 'rtept');
368 if (!line.line) return;
369 var prop = getProperties(node);
370 extend(prop, getLineStyle(get1(node, 'extensions')));
371 var routeObj = {
372 type: 'Feature',
373 properties: prop,
374 geometry: {
375 type: 'LineString',
376 coordinates: line.line
377 }
378 };
379 return routeObj;
380 }
381 function getPoint(node) {
382 var prop = getProperties(node);
383 extend(prop, getMulti(node, ['sym']));
384 return {
385 type: 'Feature',
386 properties: prop,
387 geometry: {
388 type: 'Point',
389 coordinates: coordPair(node).coordinates
390 }
391 };
392 }
393 function getLineStyle(extensions) {
394 var style = {};
395 if (extensions) {
396 var lineStyle = get1(extensions, 'line');
397 if (lineStyle) {
398 var color = nodeVal(get1(lineStyle, 'color')),
399 opacity = parseFloat(nodeVal(get1(lineStyle, 'opacity'))),
400 width = parseFloat(nodeVal(get1(lineStyle, 'width')));
401 if (color) style.stroke = color;
402 if (!isNaN(opacity)) style['stroke-opacity'] = opacity;
403 // GPX width is in mm, convert to px with 96 px per inch
404 if (!isNaN(width)) style['stroke-width'] = width * 96 / 25.4;
405 }
406 }
407 return style;
408 }
409 function getProperties(node) {
410 var prop = getMulti(node, ['name', 'cmt', 'desc', 'type', 'time', 'keywords']),
411 links = get(node, 'link');
412 if (links.length) prop.links = [];
413 for (var i = 0, link; i < links.length; i++) {
414 link = { href: attr(links[i], 'href') };
415 extend(link, getMulti(links[i], ['text', 'type']));
416 prop.links.push(link);
417 }
418 return prop;
419 }
420 return gj;
421 }
422 };
423 return t;
424})();
425
426if (typeof module !== 'undefined') module.exports = toGeoJSON;
\No newline at end of file