User:Evad37/kmlToJson.js
Appearance
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page. |
This user script seems to have a documentation page at User:Evad37/kmlToJson. |
// 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 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);
});
// </nowiki>