User:Evad37/kmlToJson.js

// Derived from https://github.com/mapbox/togeojson/blob/master/togeojson.js (BSD-2-Clause licence)// <nowiki>$( 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();$('<textarea>').attr('disabled', 'true').css({'background':'#ddd', 'height':'350px'}).val(output).appendTo(mw.util.$content);};var doConverion = function fnConvert(pagename) {// Clear current contentmw.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);});// </nowiki>