// 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 ? n0 : 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++) { oj = parseFloat(xj]); }
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, ysk]);
if (n) oysk]] = nodeVal(n);
}
return o;
}
// add properties of Y to X, overwriting if present in both
function extend(x, y) { for (var k in y) xk = yk]; }
// 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(coordsi]));
}
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(stylesk])).toString(16);
styleIndex'#' + attr(stylesk], 'id')] = hash;
styleByHashhash = stylesk];
}
for (var l = 0; l < styleMaps.length; l++) {
styleIndex'#' + attr(styleMapsl], 'id')] = okhash(xml2str(styleMapsl])).toString(16);
var pairs = get(styleMapsl], 'Pair');
var pairsMap = {};
for (var m = 0; m < pairs.length; m++) {
pairsMapnodeVal(get1(pairsm], 'key'))] = nodeVal(get1(pairsm], 'styleUrl'));
}
styleMapIndex'#' + attr(styleMapsl], 'id')] = pairsMap;
}
for (var j = 0; j < placemarks.length; j++) {
gj.features = gj.features.concat(getPlacemark(placemarksj]));
}
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(elemsi])));
var timeElems = get(root, 'when');
for (var j = 0; j < timeElems.length; j++) times.push(nodeVal(timeElemsj]));
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, geotypesi]);
if (geomNodes) {
for (j = 0; j < geomNodes.length; j++) {
geomNode = geomNodesj];
if (geotypesi === 'Point') {
geoms.push({
type: 'Point',
coordinates: coord1(nodeVal(get1(geomNode, 'coordinates')))
});
} else if (geotypesi === 'LineString') {
geoms.push({
type: 'LineString',
coordinates: coord(nodeVal(get1(geomNode, 'coordinates')))
});
} else if (geotypesi === 'Polygon') {
var rings = get(geomNode, 'LinearRing'),
coords = [];
for (k = 0; k < rings.length; k++) {
coords.push(coord(nodeVal(get1(ringsk], 'coordinates'))));
}
geoms.push({
type: 'Polygon',
coordinates: coords
});
} else if (geotypesi === 'Track' ||
geotypesi === '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 (styleUrl0 !== '#') {
styleUrl = '#' + styleUrl;
}
properties.styleUrl = styleUrl;
if (styleIndexstyleUrl]) {
properties.styleHash = styleIndexstyleUrl];
}
if (styleMapIndexstyleUrl]) {
properties.styleMapHash = styleMapIndexstyleUrl];
properties.styleHash = styleIndexstyleMapIndexstyleUrl].normal];
}
// Try to populate the lineStyle or polyStyle since we got the style hash
var style = styleByHashproperties.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 = linestyles0],
opacity = linestyles1],
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 = polystyles0],
popacity = polystyles1],
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++) {
propertiesdatasi].getAttribute('name')] = nodeVal(get1(datasi], 'value'));
}
for (i = 0; i < simpleDatas.length; i++) {
propertiessimpleDatasi].getAttribute('name')] = nodeVal(simpleDatasi]);
}
}
if (visibility) {
properties.visibility = nodeVal(visibility);
}
if (geomsAndTimes.coordTimes.length) {
properties.coordTimes = (geomsAndTimes.coordTimes.length === 1) ?
geomsAndTimes.coordTimes0 : geomsAndTimes.coordTimes;
}
var feature = {
type: 'Feature',
geometry: (geomsAndTimes.geoms.length === 1) ? geomsAndTimes.geoms0 : {
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(tracksi]);
if (feature) gj.features.push(feature);
}
for (i = 0; i < routes.length; i++) {
feature = getRoute(routesi]);
if (feature) gj.features.push(feature);
}
for (i = 0; i < waypoints.length; i++) {
gj.features.push(getPoint(waypointsi]));
}
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(ptsi]);
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(segmentsi], '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([], tracks].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 ? times0 : times;
if (heartRates.length) properties.heartRates = track.length === 1 ? heartRates0 : heartRates;
return {
type: 'Feature',
properties: properties,
geometry: {
type: track.length === 1 ? 'LineString' : 'MultiLineString',
coordinates: track.length === 1 ? track0 : 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(linksi], 'href') };
extend(link, getMulti(linksi], '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>