You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

467 lines
15 KiB

(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = global || self, factory(global['leaflet-kmz'] = {}));
}(this, (function (exports) { 'use strict';
// import JSZip from 'jszip';
// import * as toGeoJSON from '@tmcw/togeojson';
function loadFile(url) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.responseType = "arraybuffer";
xhr.onload = () => {
if (xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 0)) {
resolve(xhr.response || xhr.responseText);
} else {
resolve('');
}
};
xhr.onerror = () => reject("Error " + xhr.status + " while fetching remote file: " + url);
xhr.send();
});
}
function getKmlDoc(files) {
return files["doc.kml"] ? "doc.kml" : getKmlFiles(Object.keys(files))[0];
}
function getKmlFiles(files) {
return files.filter((file) => isKmlFile(file));
}
function getImageFiles(files) {
return files.filter((file) => isImageFile(file));
}
function getFileExt(filename) {
return filename.split('.').pop().toLowerCase().replace('jpg', 'jpeg');
}
function getFileName(url) {
return url.split('/').pop();
}
function getMimeType(filename, ext) {
var mime = 'text/plain';
if (/\.(jpe?g|png|gif|bmp)$/i.test(filename)) {
mime = 'image/' + ext;
} else if (/\.kml$/i.test(filename)) {
mime = 'text/plain';
}
return mime;
}
function isImageFile(filename) {
return /\.(jpe?g|png|gif|bmp)$/i.test(filename);
}
function isKmlFile(filename) {
return /.*\.kml/.test(filename);
}
/**
* It checks if a given file begins with PK, if so it's zipped
*
* @link https://en.wikipedia.org/wiki/List_of_file_signatures
*/
function isZipped(file) {
return 'PK' === String.fromCharCode(new Uint8Array(file, 0, 1), new Uint8Array(file, 1, 1));
}
function lazyLoader(urls, promise) {
return promise instanceof Promise ? promise : Promise.all(urls.map(url => loadJS(url)))
}
function loadJS(url) {
return new Promise((resolve, reject) => {
let tag = document.createElement("script");
tag.addEventListener('load', resolve.bind(url), { once: true });
tag.src = url;
document.head.appendChild(tag);
});
}
function parseLatLonBox(xml) {
let box = L.latLngBounds([
xml.getElementsByTagName('south')[0].childNodes[0].nodeValue,
xml.getElementsByTagName('west')[0].childNodes[0].nodeValue
], [
xml.getElementsByTagName('north')[0].childNodes[0].nodeValue,
xml.getElementsByTagName('east')[0].childNodes[0].nodeValue
]);
let rotation = xml.getElementsByTagName('rotation')[0];
if (rotation !== undefined) {
rotation = parseFloat(rotation.childNodes[0].nodeValue);
}
return [box, rotation];
}
function parseGroundOverlay(xml, props) {
let [bounds, rotation] = parseLatLonBox(xml.getElementsByTagName('LatLonBox')[0]);
let href = xml.getElementsByTagName('href')[0];
let color = xml.getElementsByTagName('color')[0];
let icon = xml.getElementsByTagName('Icon')[0];
let options = {};
if (!href && icon) {
href = icon.getElementsByTagName('href')[0];
}
href = href.childNodes[0].nodeValue;
href = props.icons[href] || href;
if (color) {
color = color.childNodes[0].nodeValue;
options.opacity = parseInt(color.substring(0, 2), 16) / 255.0;
options.color = '#' + color.substring(6, 8) + color.substring(4, 6) + color.substring(2, 4);
}
if (rotation) {
options.rotation = rotation;
}
return new L.KMZImageOverlay(href, bounds, { opacity: options.opacity, angle: options.rotation });
}
function toGeoJSON(data, props) {
var xml = data instanceof XMLDocument ? data : toXML(data);
var json = window.toGeoJSON.kml(xml);
json.properties = L.extend({}, json.properties, props || {});
return json;
}
function toXML(data) {
var text = data;
if (data instanceof ArrayBuffer) {
text = new Uint8Array(data).reduce(function (data, byte) {
return data + String.fromCharCode(byte);
}, '');
var encoding = text.substring(0, text.indexOf("?>")).match(/encoding\s*=\s*["'](.*)["']/i);
if (encoding) {
text = new TextDecoder(encoding[1]).decode(data);
}
}
else {
text = text.substr(text.indexOf('<'));
}
return text ? (new DOMParser()).parseFromString(text, 'text/xml') : document.implementation.createDocument(null, "kml");}
function unzip(folder) {
return new Promise((resolve, reject) => {
window.JSZip.loadAsync(folder)
.then((zip) => {
// Parse KMZ files.
var files = Object.keys(zip.files)
.map((name) => {
var entry = zip.files[name];
if (isImageFile(name)) {
var ext = getFileExt(name);
var mime = getMimeType(name, ext);
return entry
.async("base64")
.then((value) => [name, 'data:' + mime + ';base64,' + value]);
}
return entry
.async("text")
.then((value) => [name, value]); // [ fileName, stringValue ]
});
// Return KMZ files.
Promise.all(files).then((list) =>
resolve(list.reduce((obj, item) => {
obj[item[0]] = item[1]; // { fileName: stringValue }
return obj;
}, {}))
);
});
});
}
const KMZLayer = L.KMZLayer = L.FeatureGroup.extend({
options: {
interactive: true,
ballon: true,
bindPopup: true,
bindTooltip: true,
preferCanvas: false,
},
initialize: function(kmzUrl, options) {
L.extend(this.options, options);
if (L.Browser.mobile) this.options.bindTooltip = false;
this._layers = {};
if (kmzUrl) this.load(kmzUrl);
},
add: function(kmzUrl) {
this.load(kmzUrl);
},
load: function(kmzUrl) {
L.KMZLayer._jsPromise = lazyLoader(this._requiredJSModules(), L.KMZLayer._jsPromise)
.then(() => loadFile(kmzUrl))
.then((data) => this.parse(data, { name: getFileName(kmzUrl), icons: {} }));
},
parse: function(data, props) {
return isZipped(data) ? this._parseKMZ(data, props) : this._parseKML(data, props);
},
_parseKMZ: function(data, props) {
unzip(data).then((kmzFiles) => {
var kmlDoc = getKmlDoc(kmzFiles);
var images = getImageFiles(Object.keys(kmzFiles));
var kmlString = kmzFiles[kmlDoc];
// cache all images with their base64 encoding
props.icons = images.reduce((obj, item) => {
obj[item] = kmzFiles[item];
return obj;
}, {});
this._parseKML(kmlString, props);
});
},
_parseKML: function(data, props) {
var xml = toXML(data);
var geojson = toGeoJSON(xml, props);
var layer = (this.options.geometryToLayer || this._geometryToLayer).call(this, geojson, xml);
this.addLayer(layer);
this.fire('load', { layer: layer, name: geojson.properties.name });
},
_geometryToLayer: function(data, xml) {
var preferCanvas = this._map ? this._map.options.preferCanvas : this.options.preferCanvas;
// var emptyIcon = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'/%3E";
// parse GeoJSON
//console.log("DATA",data)
var layer = L.geoJson(data, {
pointToLayer: (feature, latlng) => {
//console.log("FEAT",feature)
if (preferCanvas) {
return L.kmzMarker(latlng, {
iconUrl: data.properties.icons[feature.properties.icon] || feature.properties.icon,
iconSize: [28, 28],
iconAnchor: [14, 14],
interactive: this.options.interactive,
});
}
var kicon = L.icon({
iconUrl: data.properties.icons[feature.properties.icon] || feature.properties.icon,
iconSize: [28, 28],
iconAnchor: [14, 14],
})
if (feature.properties && feature.properties.SymbolSpecification) {
var mysymbol;
if (feature.properties.MilitaryEchelon && feature.properties.SymbolSpecification.split(':')[0] === "APP-6D") {
var a = feature.properties.SymbolSpecification.split(':')[1].substr(0,8);
var c = feature.properties.SymbolSpecification.split(':')[1].substr(10);
var b = parseInt(feature.properties.MilitaryEchelon) + 10;
mysymbol = new ms.Symbol("" + a + b + c);
}
else {
mysymbol = new ms.Symbol(feature.properties.SymbolSpecification.split(':')[1]);
}
mysymbol = mysymbol.setOptions({ size:20 });
if (feature.properties.name) { mysymbol = mysymbol.setOptions({ uniqueDesignation:feature.properties.name }); }
if (feature.properties.MilitaryParentId) { mysymbol = mysymbol.setOptions({ higherFormation:feature.properties.MilitaryParentId }); }
kicon = L.icon({
iconUrl: mysymbol.toDataURL(),
iconAnchor: [mysymbol.getAnchor().x, mysymbol.getAnchor().y],
});
//console.log("META",mysymbol.getMetadata())
}
if (kicon.options.iconUrl === undefined) { // No icon found so just use default marker.
return L.marker(latlng);
}
else {
return L.marker(latlng, {
icon: kicon,
interactive: this.options.interactive,
});
}
// TODO: handle L.svg renderer within the L.KMZMarker class?
},
style: (feature) => {
//console.log("FEATSTYLE",feature)
var styles = {};
var prop = feature.properties;
if (prop.stroke) {
styles.stroke = true;
styles.color = prop.stroke;
}
if (prop.fill) {
styles.fill = true;
styles.fillColor = prop.fill;
}
if (prop["stroke-opacity"]) {
styles.opacity = prop["stroke-opacity"];
}
if (prop["fill-opacity"]) {
styles.fillOpacity = prop["fill-opacity"];
}
if (prop["stroke-width"]) {
styles.weight = prop["stroke-width"] * 1.05;
}
return styles;
},
onEachFeature: (feature, layer) => {
//console.log("POP",feature.properties)
//if (!this.options.ballon) return;
var prop = feature.properties;
var name = (prop.name || "").trim();
// var desc = (prop.description || "").trim();
var desc = prop.description || "";
var p = '<div>';
if (name || desc) {
// if (this.options.bindPopup) {
p += '<b>' + name + '</b>' + '<br>' + desc + '</div>';
// }
if (this.options.bindTooltip) {
layer.bindTooltip('<b>' + name + '</b>', {
direction: 'auto',
sticky: true,
});
}
}
var u = {};
if (prop.MilitaryParentId) { u.group = prop.MilitaryParentId; }
if (prop.FeaturePlatformId) { u["platform id"] = prop.FeaturePlatformId; }
if (prop.FeatureAddress) { u.address = prop.FeatureAddress; }
if (prop.SymbolSpecification) { u[prop.SymbolSpecification.split(':')[0]] = prop.SymbolSpecification.split(':')[1]; }
if (prop.Speed && prop.Speed !== "0") { u.speed = prop.Speed; }
if (prop.FeatureLastModified) { u["last update"] = prop.FeatureLastModified; }
if (u["last update"]) { u["last update"] = (new Date(u["last update"]*1000)).toISOString(); }
p += '<table border="0">';
Object.entries(u).forEach(([key, value]) => {
p += '<tr><td>'+key+'</td><td>'+value+'</td></tr>';
});
p += '</table></div>';
if (p !== '<div><table border="0"></table></div>') {
layer.bindPopup(p);
}
},
interactive: this.options.interactive,
});
// parse GroundOverlays
let el = xml.getElementsByTagName('GroundOverlay');
for (let l, k = 0; k < el.length; k++) {
l = parseGroundOverlay(el[k], data.properties);
if (l) {
layer.addLayer(l);
}
}
return layer;
},
_requiredJSModules: function() {
var urls = [];
var host = 'https://unpkg.com/';
if (typeof window.JSZip !== 'function') {
urls.push(host + 'jszip@3.5.0/dist/jszip.min.js');
}
if (typeof window.toGeoJSON !== 'object') {
urls.push(host + '@tmcw/togeojson@4.1.0/dist/togeojson.umd.js');
}
return urls;
},
});
L.kmzLayer = function(url, options) {
return new L.KMZLayer(url, options);
};
/**
* Optimized leaflet canvas renderer to load numerous markers
*
* @link https://stackoverflow.com/a/51852641
* @link https://stackoverflow.com/a/43019740
*
*/
L.KMZMarker = L.CircleMarker.extend({
// initialize: function(latlng, options) {
// L.CircleMarker.prototype.initialize.call(this, latlng, options);
// },
_updatePath: function() {
var renderer = this._renderer;
var icon = this._icon;
var layer = this;
if (!renderer._drawing || layer._empty()) {
return;
}
// if (icon.complete)
if (icon) {
icon.drawImage();
} else {
icon = this._icon = new Image(this.options.iconSize[0], this.options.iconSize[1]);
icon.anchor = [icon.width / 2.0, icon.height / 2.0];
icon.onload = icon.drawImage = () => {
var p = layer._point.subtract(icon.anchor);
var ctx = renderer._ctx;
ctx.drawImage(icon, p.x, p.y, icon.width, icon.height);
// Removed in Leaflet 1.4.0
// if (renderer._drawnLayers) renderer._drawnLayers[layer._leaflet_id] = layer;
// else renderer._layers[layer._leaflet_id] = layer;
};
icon.src = this.options.iconUrl;
}
}
});
L.kmzMarker = function(ll, opts) {
return new L.KMZMarker(ll, opts);
};
var KMZMarker = L.KMZMarker;
/**
* Copyright (c) 2011-2015, Pavel Shramov, Bruno Bergot - MIT licence
*
* adapted from: https://github.com/windycom/leaflet-kml/L.KML.js
*/
L.KMZImageOverlay = 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;
}
});
exports.KMZLayer = KMZLayer;
exports.KMZMarker = KMZMarker;
Object.defineProperty(exports, '__esModule', { value: true });
})));