User:Evad37/kmlToJson.js

// Derived from https://github.com/mapbox/togeojson/blob/master/togeojson.js (BSD-2-Clause licence)

// $( function($) {

var _toGeoJSON = (function fnToGeoJSON {   '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;   } // 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;    } // 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 ],[ 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) { 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 { return { type: 'FeatureCollection', features: [] };   }

var serializer; if (typeof XMLSerializer !== 'undefined') { /* istanbul ignore next */ serializer = new 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); }

var t = { kml: function(doc) {

var gj = fc, // styleindex keeps track of hashed styles in order to match features 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', 'gx:Track'], // all root placemarks in the file placemarks = get(doc, 'Placemark'), styles = get(doc, 'Style'), styleMaps = get(doc, 'StyleMap');

for (var k = 0; k < styles.length; k++) { 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 = [], times = []; if (elems.length === 0) elems = get(root, 'gx:coord'); for (var i = 0; i < elems.length; i++) coords.push(gxCoord(nodeVal(elems[i]))); 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 = [], 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') { geoms.push({                                   type: 'Point',                                    coordinates: coord1(nodeVal(get1(geomNode, 'coordinates')))                                }); } else if (geotypes[i] === 'LineString') { geoms.push({                                   type: 'LineString',                                    coordinates: coord(nodeVal(get1(geomNode, 'coordinates')))                                }); } else if (geotypes[i] === 'Polygon') { 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                                }); } else if (geotypes[i] === 'Track' ||                               geotypes[i] === 'gx:Track') { var track = gxCoords(geomNode); geoms.push({                                   type: 'LineString',                                    coordinates: track.coords                                }); if (track.times.length) coordTimes.push(track.times); }                       }                    }                }                return { geoms: geoms, coordTimes: coordTimes };           }            function getPlacemark(root) { 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')), 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 (!geomsAndTimes.geoms.length) return []; if (name) properties.name = name; if (address) properties.address = address; if (styleUrl) { if (styleUrl[0] !== '#') { styleUrl = '#' + styleUrl; }

properties.styleUrl = 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');

for (i = 0; i < datas.length; i++) { properties[datas[i].getAttribute('name')] = nodeVal(get1(datas[i], 'value')); }                   for (i = 0; i < simpleDatas.length; i++) { properties[simpleDatas[i].getAttribute('name')] = nodeVal(simpleDatas[i]); }               }                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: (geomsAndTimes.geoms.length === 1) ? geomsAndTimes.geoms[0] : { type: 'GeometryCollection', geometries: geomsAndTimes.geoms },                   properties: properties };               if (attr(root, 'id')) feature.id = attr(root, 'id'); return [feature]; }           return gj; },       gpx: function(doc) { var i,               tracks = get(doc, 'trk'), routes = get(doc, 'rte'), waypoints = get(doc, 'wpt'), // a feature collection gj = fc, feature; for (i = 0; i < tracks.length; i++) { feature = getTrack(tracks[i]); if (feature) gj.features.push(feature); }           for (i = 0; i < routes.length; i++) { feature = getRoute(routes[i]); if (feature) gj.features.push(feature); }           for (i = 0; i < waypoints.length; i++) { gj.features.push(getPoint(waypoints[i])); }           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: 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.line }               };                return routeObj; }           function getPoint(node) { var prop = getProperties(node); extend(prop, getMulti(node, ['sym'])); return { type: 'Feature', properties: prop, geometry: { type: 'Point', 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 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; }   };    return t; });

var toGeoJson = _toGeoJSON.kml;

var getKML = function fnGetKML { var url = 'https:' + mw.config.get('wgServer') + mw.util.getUrl(null, {action: 'raw'}); return $.ajax(url); };

var toDOM = function fnToDOM(xmlStr) { return (new DOMParser).parseFromString(xmlStr, 'text/xml'); };

var parseOutput = function(geoJSON) { return JSON.stringify(geoJSON); };

var showOutput = function fnShowOutput(output) { mw.util.$content.empty; $(' ')		.attr('disabled', 'true') .css({'background':'#ddd', 'height':'350px'}) .val(output) .appendTo(mw.util.$content); };

var doConverion = function fnConvert(pagename) { // Clear current content mw.util.$content.empty.append('Working...'); getKML(pagename) .then(toDOM) .then(toGeoJson) .then(parseOutput) .then(showOutput); };

var setup = function fnSetup { var config = mw.config.get(['wgPageName', 'wgServer']); if ( config.wgPageName.indexOf('Template:Attached_KML/') == -1 ) { return; }

var portletLink = mw.util.addPortletLink(		'p-cactions',		'#',		'GeoJSON',		'ca-tojson',		'Convert to geoJSON',		'5'	); $('#ca-tojson').click(function(e) {		e.preventDefault;		doConverion(config.wgServer);	}); };

mw.loader.using( ['mediawiki.util'], setup);

}); //