1 | var toGeoJSON = (function() {
|
2 | 'use strict';
|
3 |
|
4 | var removeSpace = /\s*/g,
|
5 | trimSpace = /^\s*|\s*$/g,
|
6 | splitSpace = /\s+/;
|
7 |
|
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 |
|
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 |
|
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 |
|
23 | function numarray(x) {
|
24 | for (var j = 0, o = []; j < x.length; j++) { o[j] = parseFloat(x[j]); }
|
25 | return o;
|
26 | }
|
27 |
|
28 | function nodeVal(x) {
|
29 | if (x) { norm(x); }
|
30 | return (x && x.textContent) || '';
|
31 | }
|
32 |
|
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 |
|
42 | function extend(x, y) { for (var k in y) x[k] = y[k]; }
|
43 |
|
44 | function coord1(v) { return numarray(v.replace(removeSpace, '').split(',')); }
|
45 |
|
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 |
|
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 |
|
75 | function fc() {
|
76 | return {
|
77 | type: 'FeatureCollection',
|
78 | features: []
|
79 | };
|
80 | }
|
81 |
|
82 | var serializer;
|
83 | if (typeof XMLSerializer !== 'undefined') {
|
84 |
|
85 | serializer = new XMLSerializer();
|
86 |
|
87 | } else if (typeof exports === 'object' && typeof process === 'object' && !process.browser) {
|
88 | serializer = new (require('xmldom').XMLSerializer)();
|
89 | }
|
90 | function xml2str(str) {
|
91 |
|
92 |
|
93 |
|
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 |
|
103 | styleIndex = {}, styleByHash = {},
|
104 |
|
105 | styleMapIndex = {},
|
106 |
|
107 |
|
108 | geotypes = ['Polygon', 'LineString', 'Point', 'Track', 'gx:Track'],
|
109 |
|
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 |
|
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 {};
|
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 |
|
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 |
|
426 | if (typeof module !== 'undefined') module.exports = toGeoJSON; |
\ | No newline at end of file |