togeojson

convert KML and GPX to GeoJSON, without the fuss

Αυτός ο κώδικας δεν πρέπει να εγκατασταθεί άμεσα. Είναι μια βιβλιοθήκη για άλλους κώδικες που περιλαμβάνεται μέσω της οδηγίας meta // @require https://update.greatest.deepsurf.us/scripts/520574/1502033/togeojson.js

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