diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3df5d7f..554e5c6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,7 @@
### Change Log for Node-RED Worldmap
+ - v2.31.0 - Better handling of KML files. Issue #211
+
- v2.30.3 - Fix for iframe height. Issue #210
- v2.30.2 - Fix for bad handling of mapbox id. Issue #208
- v2.30.1 - Don't resend bounds if not changed. Issue #209
diff --git a/README.md b/README.md
index 5aec216..7b78ccd 100644
--- a/README.md
+++ b/README.md
@@ -11,6 +11,7 @@ map web page for plotting "things" on.
### Updates
+- v2.31.0 - Better handling of KML files. Issue #211
- v2.30.3 - Fix for iframe height. Issue #210
- v2.30.2 - Fix for bad handling of mapbox id. Issue #208
- v2.30.1 - Don't resend bounds if not changed. Issue #209
diff --git a/package.json b/package.json
index 1146730..3eeb0dd 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "node-red-contrib-web-worldmap",
- "version": "2.30.3",
+ "version": "2.31.0",
"description": "A Node-RED node to provide a web page of a world map for plotting things on.",
"dependencies": {
"@turf/bezier-spline": "~6.5.0",
diff --git a/worldmap/index.html b/worldmap/index.html
index c5fcbeb..de2b5f7 100644
--- a/worldmap/index.html
+++ b/worldmap/index.html
@@ -68,6 +68,7 @@
+
diff --git a/worldmap/index3d.html b/worldmap/index3d.html
index 45fa9e7..2445230 100644
--- a/worldmap/index3d.html
+++ b/worldmap/index3d.html
@@ -178,7 +178,7 @@ var do3dMap = function() {
// create the points for the marker and return the geojson
var getPoints = function(p) {
- var fac = 0.000007; // basic size for bock icon in degrees....
+ var fac = 0.000007; // basic size for block icon in degrees....
var thing = "";
if (p.hasOwnProperty("icon")) {
if (p.icon.indexOf("male") !== -1) { thing = "person"; }
diff --git a/worldmap/leaflet/L.KML.js b/worldmap/leaflet/L.KML.js
new file mode 100644
index 0000000..49acd87
--- /dev/null
+++ b/worldmap/leaflet/L.KML.js
@@ -0,0 +1,456 @@
+/*!
+ Copyright (c) 2011-2015, Pavel Shramov, Bruno Bergot - MIT licence
+*/
+
+L.KML = L.FeatureGroup.extend({
+
+ initialize: function (kml) {
+ this._kml = kml;
+ this._layers = {};
+
+ if (kml) {
+ this.addKML(kml);
+ }
+ },
+
+ addKML: function (xml) {
+ var layers = L.KML.parseKML(xml);
+ if (!layers || !layers.length) return;
+ for (var i = 0; i < layers.length; i++) {
+ this.fire('addlayer', {
+ layer: layers[i]
+ });
+ this.addLayer(layers[i]);
+ }
+ this.latLngs = L.KML.getLatLngs(xml);
+ this.fire('loaded');
+ },
+
+ latLngs: []
+});
+
+L.Util.extend(L.KML, {
+
+ parseKML: function (xml) {
+ var style = this.parseStyles(xml);
+ this.parseStyleMap(xml, style);
+ var el = xml.getElementsByTagName('Folder');
+ var layers = [], l;
+ for (var i = 0; i < el.length; i++) {
+ if (!this._check_folder(el[i])) { continue; }
+ l = this.parseFolder(el[i], style);
+ if (l) { layers.push(l); }
+ }
+ el = xml.getElementsByTagName('Placemark');
+ for (var j = 0; j < el.length; j++) {
+ if (!this._check_folder(el[j])) { continue; }
+ l = this.parsePlacemark(el[j], xml, style);
+ if (l) { layers.push(l); }
+ }
+ el = xml.getElementsByTagName('GroundOverlay');
+ for (var k = 0; k < el.length; k++) {
+ l = this.parseGroundOverlay(el[k]);
+ if (l) { layers.push(l); }
+ }
+ return layers;
+ },
+
+ // Return false if e's first parent Folder is not [folder]
+ // - returns true if no parent Folders
+ _check_folder: function (e, folder) {
+ e = e.parentNode;
+ while (e && e.tagName !== 'Folder')
+ {
+ e = e.parentNode;
+ }
+ return !e || e === folder;
+ },
+
+ parseStyles: function (xml) {
+ var styles = {};
+ var sl = xml.getElementsByTagName('Style');
+ for (var i=0, len=sl.length; i 1) {
+ layer = new L.FeatureGroup(layers);
+ }
+
+ var name, descr = '';
+ el = place.getElementsByTagName('name');
+ if (el.length && el[0].childNodes.length) {
+ name = el[0].childNodes[0].nodeValue;
+ }
+ el = place.getElementsByTagName('description');
+ for (i = 0; i < el.length; i++) {
+ for (j = 0; j < el[i].childNodes.length; j++) {
+ descr = descr + el[i].childNodes[j].nodeValue;
+ }
+ }
+
+ if (name) {
+ layer.on('add', function () {
+ layer.bindPopup('' + name + '
' + descr, { className: 'kml-popup'});
+ });
+ }
+
+ return layer;
+ },
+
+ parseCoords: function (xml) {
+ var el = xml.getElementsByTagName('coordinates');
+ return this._read_coords(el[0]);
+ },
+
+ parseLineString: function (line, xml, options) {
+ var coords = this.parseCoords(line);
+ if (!coords.length) { return; }
+ return new L.Polyline(coords, options);
+ },
+
+ parseTrack: function (line, xml, options) {
+ var el = xml.getElementsByTagName('gx:coord');
+ if (el.length === 0) { el = xml.getElementsByTagName('coord'); }
+ var coords = [];
+ for (var j = 0; j < el.length; j++) {
+ coords = coords.concat(this._read_gxcoords(el[j]));
+ }
+ if (!coords.length) { return; }
+ return new L.Polyline(coords, options);
+ },
+
+ parsePoint: function (line, xml, options) {
+ var el = line.getElementsByTagName('coordinates');
+ if (!el.length) {
+ return;
+ }
+ var ll = el[0].childNodes[0].nodeValue.split(',');
+ return new L.KMLMarker(new L.LatLng(ll[1], ll[0]), options);
+ },
+
+ parsePolygon: function (line, xml, options) {
+ var el, polys = [], inner = [], i, coords;
+ el = line.getElementsByTagName('outerBoundaryIs');
+ for (i = 0; i < el.length; i++) {
+ coords = this.parseCoords(el[i]);
+ if (coords) {
+ polys.push(coords);
+ }
+ }
+ el = line.getElementsByTagName('innerBoundaryIs');
+ for (i = 0; i < el.length; i++) {
+ coords = this.parseCoords(el[i]);
+ if (coords) {
+ inner.push(coords);
+ }
+ }
+ if (!polys.length) {
+ return;
+ }
+ if (options.fillColor) {
+ options.fill = true;
+ }
+ if (polys.length === 1) {
+ return new L.Polygon(polys.concat(inner), options);
+ }
+ return new L.MultiPolygon(polys, options);
+ },
+
+ getLatLngs: function (xml) {
+ var el = xml.getElementsByTagName('coordinates');
+ var coords = [];
+ for (var j = 0; j < el.length; j++) {
+ // text might span many childNodes
+ coords = coords.concat(this._read_coords(el[j]));
+ }
+ return coords;
+ },
+
+ _read_coords: function (el) {
+ var text = '', coords = [], i;
+ for (i = 0; i < el.childNodes.length; i++) {
+ text = text + el.childNodes[i].nodeValue;
+ }
+ text = text.split(/[\s\n]+/);
+ for (i = 0; i < text.length; i++) {
+ var ll = text[i].split(',');
+ if (ll.length < 2) {
+ continue;
+ }
+ coords.push(new L.LatLng(ll[1], ll[0]));
+ }
+ return coords;
+ },
+
+ _read_gxcoords: function (el) {
+ var text = '', coords = [];
+ text = el.firstChild.nodeValue.split(' ');
+ coords.push(new L.LatLng(text[1], text[0]));
+ return coords;
+ },
+
+ parseGroundOverlay: function (xml) {
+ var latlonbox = xml.getElementsByTagName('LatLonBox')[0];
+ var bounds = new L.LatLngBounds(
+ [
+ latlonbox.getElementsByTagName('south')[0].childNodes[0].nodeValue,
+ latlonbox.getElementsByTagName('west')[0].childNodes[0].nodeValue
+ ],
+ [
+ latlonbox.getElementsByTagName('north')[0].childNodes[0].nodeValue,
+ latlonbox.getElementsByTagName('east')[0].childNodes[0].nodeValue
+ ]
+ );
+ var attributes = {Icon: true, href: true, color: true};
+ function _parse (xml) {
+ var options = {}, ioptions = {};
+ for (var i = 0; i < xml.childNodes.length; i++) {
+ var e = xml.childNodes[i];
+ var key = e.tagName;
+ if (!attributes[key]) { continue; }
+ var value = e.childNodes[0].nodeValue;
+ if (key === 'Icon') {
+ ioptions = _parse(e);
+ if (ioptions.href) { options.href = ioptions.href; }
+ } else if (key === 'href') {
+ options.href = value;
+ } else if (key === 'color') {
+ options.opacity = parseInt(value.substring(0, 2), 16) / 255.0;
+ options.color = '#' + value.substring(6, 8) + value.substring(4, 6) + value.substring(2, 4);
+ }
+ }
+ return options;
+ }
+ var options = {};
+ options = _parse(xml);
+ if (latlonbox.getElementsByTagName('rotation')[0] !== undefined) {
+ var rotation = latlonbox.getElementsByTagName('rotation')[0].childNodes[0].nodeValue;
+ options.rotation = parseFloat(rotation);
+ }
+ return new L.RotatedImageOverlay(options.href, bounds, {opacity: options.opacity, angle: options.rotation});
+ }
+
+});
+
+L.KMLIcon = L.Icon.extend({
+ options: {
+ iconSize: [32, 32],
+ iconAnchor: [16, 16],
+ },
+ _setIconStyles: function (img, name) {
+ L.Icon.prototype._setIconStyles.apply(this, [img, name]);
+ if( img.complete ) {
+ this.applyCustomStyles( img )
+ } else {
+ img.onload = this.applyCustomStyles.bind(this,img)
+ }
+
+ },
+ applyCustomStyles: function(img) {
+ var options = this.options;
+ this.options.popupAnchor = [0,(-0.83*img.height)];
+ if (options.anchorType.x === 'fraction')
+ img.style.marginLeft = (-options.anchorRef.x * img.width) + 'px';
+ if (options.anchorType.y === 'fraction')
+ img.style.marginTop = ((-(1 - options.anchorRef.y) * img.height) + 1) + 'px';
+ if (options.anchorType.x === 'pixels')
+ img.style.marginLeft = (-options.anchorRef.x) + 'px';
+ if (options.anchorType.y === 'pixels')
+ img.style.marginTop = (options.anchorRef.y - img.height + 1) + 'px';
+ }
+});
+
+
+L.KMLMarker = L.Marker.extend({
+ options: {
+ icon: new L.KMLIcon.Default()
+ }
+});
+
+// Inspired by https://github.com/bbecquet/Leaflet.PolylineDecorator/tree/master/src
+L.RotatedImageOverlay = L.ImageOverlay.extend({
+ options: {
+ angle: 0
+ },
+ _reset: function () {
+ L.ImageOverlay.prototype._reset.call(this);
+ this._rotate();
+ },
+ _animateZoom: function (e) {
+ L.ImageOverlay.prototype._animateZoom.call(this, e);
+ this._rotate();
+ },
+ _rotate: function () {
+ if (L.DomUtil.TRANSFORM) {
+ // use the CSS transform rule if available
+ this._image.style[L.DomUtil.TRANSFORM] += ' rotate(' + this.options.angle + 'deg)';
+ } else if (L.Browser.ie) {
+ // fallback for IE6, IE7, IE8
+ var rad = this.options.angle * (Math.PI / 180),
+ costheta = Math.cos(rad),
+ sintheta = Math.sin(rad);
+ this._image.style.filter += ' progid:DXImageTransform.Microsoft.Matrix(sizingMethod=\'auto expand\', M11=' +
+ costheta + ', M12=' + (-sintheta) + ', M21=' + sintheta + ', M22=' + costheta + ')';
+ }
+ },
+ getBounds: function () {
+ return this._bounds;
+ }
+});
diff --git a/worldmap/worldmap.js b/worldmap/worldmap.js
index 1699909..7f6cd1a 100644
--- a/worldmap/worldmap.js
+++ b/worldmap/worldmap.js
@@ -2480,8 +2480,12 @@ function doCommand(cmd) {
overlays[cmd.map.overlay].removeFrom(map);
existsalready = true;
}
- //var opt = {async:true};
- overlays[cmd.map.overlay] = omnivore.kml.parse(cmd.map.kml, null, custIco());
+ try {
+ const parser = new DOMParser();
+ const kml = parser.parseFromString(cmd.map.kml, 'text/xml');
+ const track = new L.KML(kml);
+ overlays[cmd.map.overlay] = track;
+ } catch(e) { console.log("Failed to parse KML") }
if (!existsalready) {
layercontrol.addOverlay(overlays[cmd.map.overlay],cmd.map.overlay);
}