leaflet dist
This commit is contained in:
parent
ceb5dd0c92
commit
f374443ba5
|
@ -0,0 +1,124 @@
|
||||||
|
.bigimage-unicode-icon {
|
||||||
|
font-size: 19px;
|
||||||
|
font-weight: bold;
|
||||||
|
display: block;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bigimage-unicode-icon-disable {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
#print-params {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#print-params > h6 {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-family: inherit;
|
||||||
|
font-weight: 500;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
#print-params > input[type="number"] {
|
||||||
|
border: 1px solid rgb(153, 153, 153);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close:hover,
|
||||||
|
.close:focus {
|
||||||
|
color: #000000;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operation-btn {
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline !important;
|
||||||
|
padding-right: 3px;
|
||||||
|
vertical-align: middle;
|
||||||
|
font-size: 20px;
|
||||||
|
margin: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.see-btn:before {
|
||||||
|
content: '\1F441';
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-btn:before {
|
||||||
|
content: '\2B07';
|
||||||
|
}
|
||||||
|
|
||||||
|
#print-loading {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
height: 30px;
|
||||||
|
width: 30px;
|
||||||
|
margin: -15px 0 0 -15px;
|
||||||
|
border: 4px rgb(167, 164, 171) solid;
|
||||||
|
border-top: 4px #52006d solid;
|
||||||
|
border-radius: 50%;
|
||||||
|
-webkit-animation: spin 1s infinite linear;
|
||||||
|
animation: spin 1s infinite linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes spin {
|
||||||
|
from {
|
||||||
|
-webkit-transform: rotate(0deg);
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
-webkit-transform: rotate(359deg);
|
||||||
|
transform: rotate(359deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes spin {
|
||||||
|
from {
|
||||||
|
-webkit-transform: rotate(0deg);
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
-webkit-transform: rotate(359deg);
|
||||||
|
transform: rotate(359deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-button {
|
||||||
|
font-family: sans-serif;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 1em;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0.3em 0.8em;
|
||||||
|
background: linear-gradient(45deg, #2b71ad, #0c7162);
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: 0 0.4em 0.4em rgba(0, 0, 0, 0.25);
|
||||||
|
border-radius: 0.4em;
|
||||||
|
transition: box-shadow 0.2s ease-in-out;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-button:hover {
|
||||||
|
box-shadow: 0 0.1em 0.25em rgba(0,0,0,0.25);
|
||||||
|
}
|
|
@ -0,0 +1,417 @@
|
||||||
|
/*
|
||||||
|
Leaflet.BigImage (https://github.com/pasichnykvasyl/Leaflet.BigImage).
|
||||||
|
(c) 2020, Vasyl Pasichnyk, pasichnykvasyl (Oswald)
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function (factory, window) {
|
||||||
|
|
||||||
|
// define an AMD module that relies on 'leaflet'
|
||||||
|
if (typeof define === 'function' && define.amd) {
|
||||||
|
define(['leaflet'], factory);
|
||||||
|
|
||||||
|
// define a Common JS module that relies on 'leaflet'
|
||||||
|
} else if (typeof exports === 'object') {
|
||||||
|
module.exports = factory(require('leaflet'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// attach your plugin to the global 'L' variable
|
||||||
|
if (typeof window !== 'undefined' && window.L) {
|
||||||
|
window.L.YourPlugin = factory(L);
|
||||||
|
}
|
||||||
|
}(function (L) {
|
||||||
|
|
||||||
|
L.Control.BigImage = L.Control.extend({
|
||||||
|
options: {
|
||||||
|
position: 'topright',
|
||||||
|
title: 'Get image',
|
||||||
|
printControlLabel: '🖶',
|
||||||
|
printControlClasses: [],
|
||||||
|
printControlTitle: 'Get image',
|
||||||
|
_unicodeClass: 'bigimage-unicode-icon',
|
||||||
|
maxScale: 10,
|
||||||
|
minScale: 1,
|
||||||
|
inputTitle: 'Choose scale:',
|
||||||
|
downloadTitle: 'Download'
|
||||||
|
},
|
||||||
|
|
||||||
|
onAdd: function (map) {
|
||||||
|
this._map = map;
|
||||||
|
|
||||||
|
const title = this.options.printControlTitle;
|
||||||
|
const label = this.options.printControlLabel;
|
||||||
|
let classes = this.options.printControlClasses;
|
||||||
|
|
||||||
|
if (label.indexOf('&') != -1) classes.push(this.options._unicodeClass);
|
||||||
|
|
||||||
|
return this._createControl(label, title, classes, this._click, this);
|
||||||
|
},
|
||||||
|
|
||||||
|
_click: function (e) {
|
||||||
|
this._container.classList.add('leaflet-control-layers-expanded');
|
||||||
|
this._containerParams.style.display = '';
|
||||||
|
this._controlPanel.classList.add('bigimage-unicode-icon-disable');
|
||||||
|
},
|
||||||
|
|
||||||
|
_createControl: function (label, title, classesToAdd, fn, context) {
|
||||||
|
|
||||||
|
this._container = document.createElement('div');
|
||||||
|
this._container.id = 'print-container';
|
||||||
|
this._container.classList.add('leaflet-bar');
|
||||||
|
|
||||||
|
this._containerParams = document.createElement('div');
|
||||||
|
this._containerParams.id = 'print-params';
|
||||||
|
this._containerParams.style.display = 'none';
|
||||||
|
|
||||||
|
this._createCloseButton();
|
||||||
|
|
||||||
|
let containerTitle = document.createElement('h6');
|
||||||
|
containerTitle.style.width = '100%';
|
||||||
|
containerTitle.innerHTML = this.options.inputTitle;
|
||||||
|
this._containerParams.appendChild(containerTitle);
|
||||||
|
|
||||||
|
this._createScaleInput();
|
||||||
|
this._createDownloadButton();
|
||||||
|
this._container.appendChild(this._containerParams);
|
||||||
|
|
||||||
|
this._createControlPanel(classesToAdd, context, label, title, fn);
|
||||||
|
|
||||||
|
L.DomEvent.disableScrollPropagation(this._container);
|
||||||
|
L.DomEvent.disableClickPropagation(this._container);
|
||||||
|
|
||||||
|
return this._container;
|
||||||
|
},
|
||||||
|
|
||||||
|
_createDownloadButton: function () {
|
||||||
|
this._downloadBtn = document.createElement('div');
|
||||||
|
this._downloadBtn.classList.add('download-button');
|
||||||
|
|
||||||
|
this._downloadBtn = document.createElement('div');
|
||||||
|
this._downloadBtn.classList.add('download-button');
|
||||||
|
this._downloadBtn.innerHTML = this.options.downloadTitle;
|
||||||
|
|
||||||
|
this._downloadBtn.addEventListener('click', () => {
|
||||||
|
let scale_value = this._scaleInput.value;
|
||||||
|
if (!scale_value || scale_value < this.options.minScale || scale_value > this.options.maxScale) {
|
||||||
|
this._scaleInput.value = this.options.minScale;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._containerParams.classList.add('print-disabled');
|
||||||
|
this._loader.style.display = 'block';
|
||||||
|
this._print();
|
||||||
|
});
|
||||||
|
this._containerParams.appendChild(this._downloadBtn);
|
||||||
|
},
|
||||||
|
|
||||||
|
_createScaleInput: function () {
|
||||||
|
this._scaleInput = document.createElement('input');
|
||||||
|
this._scaleInput.style.width = '100%';
|
||||||
|
this._scaleInput.type = 'number';
|
||||||
|
this._scaleInput.value = this.options.minScale;
|
||||||
|
this._scaleInput.min = this.options.minScale;
|
||||||
|
this._scaleInput.max = this.options.maxScale;
|
||||||
|
this._scaleInput.id = 'scale';
|
||||||
|
this._containerParams.appendChild(this._scaleInput);
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
_createCloseButton: function () {
|
||||||
|
let span = document.createElement('div');
|
||||||
|
span.classList.add('close');
|
||||||
|
span.innerHTML = '×';
|
||||||
|
|
||||||
|
span.addEventListener('click', () => {
|
||||||
|
this._container.classList.remove('leaflet-control-layers-expanded');
|
||||||
|
this._containerParams.style.display = 'none';
|
||||||
|
this._controlPanel.classList.remove('bigimage-unicode-icon-disable');
|
||||||
|
});
|
||||||
|
|
||||||
|
this._containerParams.appendChild(span);
|
||||||
|
},
|
||||||
|
|
||||||
|
_createControlPanel: function (classesToAdd, context, label, title, fn) {
|
||||||
|
let controlPanel = document.createElement('a');
|
||||||
|
controlPanel.innerHTML = label;
|
||||||
|
controlPanel.id = 'print-btn';
|
||||||
|
controlPanel.setAttribute('title', title);
|
||||||
|
classesToAdd.forEach(function (c) {
|
||||||
|
controlPanel.classList.add(c);
|
||||||
|
});
|
||||||
|
L.DomEvent.on(controlPanel, 'click', fn, context);
|
||||||
|
this._container.appendChild(controlPanel);
|
||||||
|
this._controlPanel = controlPanel;
|
||||||
|
|
||||||
|
this._loader = document.createElement('div');
|
||||||
|
this._loader.id = 'print-loading';
|
||||||
|
this._container.appendChild(this._loader);
|
||||||
|
},
|
||||||
|
|
||||||
|
_getLayers: function (resolve) {
|
||||||
|
let self = this;
|
||||||
|
let promises = [];
|
||||||
|
self._map.eachLayer(function (layer) {
|
||||||
|
promises.push(new Promise((new_resolve) => {
|
||||||
|
try {
|
||||||
|
if (layer instanceof L.Marker && layer._icon && layer._icon.src) {
|
||||||
|
self._getMarkerLayer(layer, new_resolve)
|
||||||
|
} else if (layer instanceof L.TileLayer) {
|
||||||
|
self._getTileLayer(layer, new_resolve);
|
||||||
|
} else if (layer instanceof L.Circle) {
|
||||||
|
if (!self.circles[layer._leaflet_id]) {
|
||||||
|
self.circles[layer._leaflet_id] = layer;
|
||||||
|
}
|
||||||
|
new_resolve();
|
||||||
|
} else if (layer instanceof L.Path) {
|
||||||
|
self._getPathLayer(layer, new_resolve);
|
||||||
|
} else {
|
||||||
|
new_resolve();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
new_resolve();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
Promise.all(promises).then(() => {
|
||||||
|
resolve()
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_getTileLayer: function (layer, resolve) {
|
||||||
|
let self = this;
|
||||||
|
|
||||||
|
self.tiles = [];
|
||||||
|
self.tileSize = layer._tileSize.x;
|
||||||
|
self.tileBounds = L.bounds(self.bounds.min.divideBy(self.tileSize)._floor(), self.bounds.max.divideBy(self.tileSize)._floor());
|
||||||
|
|
||||||
|
for (let j = self.tileBounds.min.y; j <= self.tileBounds.max.y; j++)
|
||||||
|
for (let i = self.tileBounds.min.x; i <= self.tileBounds.max.x; i++)
|
||||||
|
self.tiles.push(new L.Point(i, j));
|
||||||
|
|
||||||
|
let promiseArray = [];
|
||||||
|
self.tiles.forEach(tilePoint => {
|
||||||
|
let originalTilePoint = tilePoint.clone();
|
||||||
|
if (layer._adjustTilePoint) layer._adjustTilePoint(tilePoint);
|
||||||
|
|
||||||
|
let tilePos = originalTilePoint.scaleBy(new L.Point(self.tileSize, self.tileSize)).subtract(self.bounds.min);
|
||||||
|
|
||||||
|
if (tilePoint.y < 0) return;
|
||||||
|
|
||||||
|
promiseArray.push(new Promise(resolve => {
|
||||||
|
self._loadTile(tilePoint, tilePos, layer, resolve);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
Promise.all(promiseArray).then(() => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_loadTile: function (tilePoint, tilePos, layer, resolve) {
|
||||||
|
let self = this;
|
||||||
|
let imgIndex = tilePoint.x + ':' + tilePoint.y + ':' + self.zoom;
|
||||||
|
let image = new Image();
|
||||||
|
image.crossOrigin = 'Anonymous';
|
||||||
|
image.onload = function () {
|
||||||
|
if (!self.tilesImgs[imgIndex]) self.tilesImgs[imgIndex] = {img: image, x: tilePos.x, y: tilePos.y};
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
image.src = layer.getTileUrl(tilePoint);
|
||||||
|
},
|
||||||
|
|
||||||
|
_getMarkerLayer: function (layer, resolve) {
|
||||||
|
let self = this;
|
||||||
|
|
||||||
|
if (self.markers[layer._leaflet_id]) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pixelPoint = self._map.project(layer._latlng);
|
||||||
|
pixelPoint = pixelPoint.subtract(new L.Point(self.bounds.min.x, self.bounds.min.y));
|
||||||
|
|
||||||
|
if (layer.options.icon && layer.options.icon.options && layer.options.icon.options.iconAnchor) {
|
||||||
|
pixelPoint.x -= layer.options.icon.options.iconAnchor[0];
|
||||||
|
pixelPoint.y -= layer.options.icon.options.iconAnchor[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!self._pointPositionIsNotCorrect(pixelPoint)) {
|
||||||
|
let image = new Image();
|
||||||
|
image.crossOrigin = 'Anonymous';
|
||||||
|
image.onload = function () {
|
||||||
|
self.markers[layer._leaflet_id] = {img: image, x: pixelPoint.x, y: pixelPoint.y};
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
image.src = layer._icon.src;
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_pointPositionIsNotCorrect: function (point) {
|
||||||
|
return (point.x < 0 || point.y < 0 || point.x > this.canvas.width || point.y > this.canvas.height);
|
||||||
|
},
|
||||||
|
|
||||||
|
_getPathLayer: function (layer, resolve) {
|
||||||
|
let self = this;
|
||||||
|
|
||||||
|
let correct = 0;
|
||||||
|
let parts = [];
|
||||||
|
|
||||||
|
if (layer._mRadius || !layer._latlngs) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let latlngs = layer.options.fill ? layer._latlngs[0] : layer._latlngs;
|
||||||
|
latlngs.forEach((latLng) => {
|
||||||
|
let pixelPoint = self._map.project(latLng);
|
||||||
|
pixelPoint = pixelPoint.subtract(new L.Point(self.bounds.min.x, self.bounds.min.y));
|
||||||
|
parts.push(pixelPoint);
|
||||||
|
if (pixelPoint.x < self.canvas.width && pixelPoint.y < self.canvas.height) correct = 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (correct) self.path[layer._leaflet_id] = {
|
||||||
|
parts: parts,
|
||||||
|
closed: layer.options.fill,
|
||||||
|
options: layer.options
|
||||||
|
};
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
|
||||||
|
_changeScale: function (scale) {
|
||||||
|
if (!scale || scale <= 1) return 0;
|
||||||
|
|
||||||
|
let addX = (this.bounds.max.x - this.bounds.min.x) / 2 * (scale - 1);
|
||||||
|
let addY = (this.bounds.max.y - this.bounds.min.y) / 2 * (scale - 1);
|
||||||
|
|
||||||
|
this.bounds.min.x -= addX;
|
||||||
|
this.bounds.min.y -= addY;
|
||||||
|
this.bounds.max.x += addX;
|
||||||
|
this.bounds.max.y += addY;
|
||||||
|
|
||||||
|
this.canvas.width *= scale;
|
||||||
|
this.canvas.height *= scale;
|
||||||
|
},
|
||||||
|
|
||||||
|
_drawPath: function (value) {
|
||||||
|
let self = this;
|
||||||
|
|
||||||
|
self.ctx.beginPath();
|
||||||
|
let count = 0;
|
||||||
|
let options = value.options;
|
||||||
|
value.parts.forEach((point) => {
|
||||||
|
self.ctx[count++ ? 'lineTo' : 'moveTo'](point.x, point.y);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (value.closed) self.ctx.closePath();
|
||||||
|
|
||||||
|
this._feelPath(options);
|
||||||
|
},
|
||||||
|
|
||||||
|
_drawCircle: function (layer, resolve) {
|
||||||
|
|
||||||
|
if (layer._empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let point = this._map.project(layer._latlng);
|
||||||
|
point = point.subtract(new L.Point(this.bounds.min.x, this.bounds.min.y));
|
||||||
|
|
||||||
|
let r = Math.max(Math.round(layer._radius), 1),
|
||||||
|
s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
|
||||||
|
|
||||||
|
if (s !== 1) {
|
||||||
|
this.ctx.save();
|
||||||
|
this.scale(1, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.arc(point.x, point.y / s, r, 0, Math.PI * 2, false);
|
||||||
|
|
||||||
|
if (s !== 1) {
|
||||||
|
this.ctx.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._feelPath(layer.options);
|
||||||
|
},
|
||||||
|
|
||||||
|
_feelPath: function (options) {
|
||||||
|
|
||||||
|
if (options.fill) {
|
||||||
|
this.ctx.globalAlpha = options.fillOpacity;
|
||||||
|
this.ctx.fillStyle = options.fillColor || options.color;
|
||||||
|
this.ctx.fill(options.fillRule || 'evenodd');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.stroke && options.weight !== 0) {
|
||||||
|
if (this.ctx.setLineDash) {
|
||||||
|
this.ctx.setLineDash(options && options._dashArray || []);
|
||||||
|
}
|
||||||
|
this.ctx.globalAlpha = options.opacity;
|
||||||
|
this.ctx.lineWidth = options.weight;
|
||||||
|
this.ctx.strokeStyle = options.color;
|
||||||
|
this.ctx.lineCap = options.lineCap;
|
||||||
|
this.ctx.lineJoin = options.lineJoin;
|
||||||
|
this.ctx.stroke();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_print: function () {
|
||||||
|
let self = this;
|
||||||
|
|
||||||
|
self.tilesImgs = {};
|
||||||
|
self.markers = {};
|
||||||
|
self.path = {};
|
||||||
|
self.circles = {};
|
||||||
|
|
||||||
|
let dimensions = self._map.getSize();
|
||||||
|
|
||||||
|
self.zoom = self._map.getZoom();
|
||||||
|
self.bounds = self._map.getPixelBounds();
|
||||||
|
|
||||||
|
self.canvas = document.createElement('canvas');
|
||||||
|
self.canvas.width = dimensions.x;
|
||||||
|
self.canvas.height = dimensions.y;
|
||||||
|
self.ctx = self.canvas.getContext('2d');
|
||||||
|
|
||||||
|
this._changeScale(document.getElementById('scale').value);
|
||||||
|
|
||||||
|
let promise = new Promise(function (resolve, reject) {
|
||||||
|
self._getLayers(resolve);
|
||||||
|
});
|
||||||
|
|
||||||
|
promise.then(() => {
|
||||||
|
return new Promise(((resolve, reject) => {
|
||||||
|
for (const [key, value] of Object.entries(self.tilesImgs)) {
|
||||||
|
self.ctx.drawImage(value.img, value.x, value.y, self.tileSize, self.tileSize);
|
||||||
|
}
|
||||||
|
for (const [key, value] of Object.entries(self.path)) {
|
||||||
|
self._drawPath(value);
|
||||||
|
}
|
||||||
|
for (const [key, value] of Object.entries(self.markers)) {
|
||||||
|
self.ctx.drawImage(value.img, value.x, value.y);
|
||||||
|
}
|
||||||
|
for (const [key, value] of Object.entries(self.circles)) {
|
||||||
|
self._drawCircle(value);
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
}));
|
||||||
|
}).then(() => {
|
||||||
|
self.canvas.toBlob(function (blob) {
|
||||||
|
let link = document.createElement('a');
|
||||||
|
link.download = "bigImage.png";
|
||||||
|
link.href = URL.createObjectURL(blob);
|
||||||
|
link.click();
|
||||||
|
});
|
||||||
|
self._containerParams.classList.remove('print-disabled');
|
||||||
|
self._loader.style.display = 'none';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
L.control.bigImage = function (options) {
|
||||||
|
return new L.Control.BigImage(options);
|
||||||
|
};
|
||||||
|
}, window));
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,135 @@
|
||||||
|
(function () {
|
||||||
|
var r = new RegExp("(^|(.*?\\/))(include-leaflet\.js)(\\?|$)"),
|
||||||
|
s = document.getElementsByTagName('script'), targetScript;
|
||||||
|
for (var i = 0; i < s.length; i++) {
|
||||||
|
var src = s[i].getAttribute('src');
|
||||||
|
if (src) {
|
||||||
|
var m = src.match(r);
|
||||||
|
if (m) {
|
||||||
|
targetScript = s[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function inputScript(url) {
|
||||||
|
var script = '<script type="text/javascript" src="' + url + '"><' + '/script>';
|
||||||
|
document.writeln(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
function inputCSS(url) {
|
||||||
|
var css = '<link rel="stylesheet" href="' + url + '">';
|
||||||
|
document.writeln(css);
|
||||||
|
}
|
||||||
|
|
||||||
|
function inArray(arr, item) {
|
||||||
|
for (i in arr) {
|
||||||
|
if (arr[i] == item) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function supportES6() {
|
||||||
|
var code = "'use strict'; class Foo {}; class Bar extends Foo {};";
|
||||||
|
try {
|
||||||
|
(new Function(code))();
|
||||||
|
} catch (err) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!Array.from) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//加载类库资源文件
|
||||||
|
function load() {
|
||||||
|
var includes = (targetScript.getAttribute('include') || "").split(",");
|
||||||
|
var excludes = (targetScript.getAttribute('exclude') || "").split(",");
|
||||||
|
if (!inArray(excludes, 'leaflet')) {
|
||||||
|
inputCSS("../../web/libs/leaflet/1.3.1/leaflet.css");
|
||||||
|
inputScript("../../web/libs/leaflet/1.3.1/leaflet.js");
|
||||||
|
}
|
||||||
|
if (inArray(includes, 'mapv')) {
|
||||||
|
inputScript("../../web/libs/mapv/2.0.20/mapv.min.js");
|
||||||
|
}
|
||||||
|
if (inArray(includes, 'turf')) {
|
||||||
|
inputScript("../../web/libs/turf/5.1.6/turf.min.js");
|
||||||
|
}
|
||||||
|
if (inArray(includes, 'echarts')) {
|
||||||
|
inputScript("../../web/libs/echarts/4.1.0/echarts.min.js");
|
||||||
|
}
|
||||||
|
if (inArray(includes, 'd3')) {
|
||||||
|
inputScript("../../web/libs/d3/5.5.0/d3.min.js");
|
||||||
|
}
|
||||||
|
if (inArray(includes, 'd3-hexbin')) {
|
||||||
|
inputScript("../../web/libs/d3/d3-hexbin.v0.2.min.js");
|
||||||
|
}
|
||||||
|
if (inArray(includes, 'd3Layer')) {
|
||||||
|
inputScript("../../web/libs/leaflet/plugins/leaflet.d3Layer/leaflet-d3Layer.min.js");
|
||||||
|
}
|
||||||
|
if (inArray(includes, 'elasticsearch')) {
|
||||||
|
inputScript("../../web/libs/elasticsearch/15.0.0/elasticsearch.min.js");
|
||||||
|
}
|
||||||
|
if (inArray(includes, 'deck')) {
|
||||||
|
inputScript("../../web/libs/deck.gl/5.1.3/deck.gl.js");
|
||||||
|
}
|
||||||
|
if (inArray(includes, 'xlsx')) {
|
||||||
|
inputScript("../../web/libs/xlsx/0.12.13/xlsx.core.min.js");
|
||||||
|
}
|
||||||
|
if (!inArray(excludes, 'iclient9-leaflet')) {
|
||||||
|
if (supportES6()) {
|
||||||
|
inputScript("../../dist/leaflet/iclient9-leaflet-es6.min.js");
|
||||||
|
} else {
|
||||||
|
inputScript("../../dist/leaflet/iclient9-leaflet.min.js");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (inArray(includes, 'iclient9-leaflet-css')) {
|
||||||
|
inputCSS("../../dist/leaflet/iclient9-leaflet.min.css");
|
||||||
|
}
|
||||||
|
if (inArray(includes, 'iclient9-plot-leaflet')) {
|
||||||
|
inputCSS("../../web/libs/plotting/leaflet/9.1.1/iclient9-plot-leaflet.css");
|
||||||
|
if (supportES6()) {
|
||||||
|
inputScript("../../web/libs/plotting/leaflet/9.1.1/iclient9-plot-leaflet-es6.min.js");
|
||||||
|
} else {
|
||||||
|
inputScript("../../web/libs/plotting/leaflet/9.1.1/iclient9-plot-leaflet.min.js");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (inArray(includes, 'leaflet.heat')) {
|
||||||
|
inputScript("../../web/libs/leaflet/plugins/leaflet.heat/leaflet-heat.js");
|
||||||
|
}
|
||||||
|
if (inArray(includes, 'osmbuildings')) {
|
||||||
|
inputScript("../../web/libs/osmbuildings/OSMBuildings-Leaflet.js");
|
||||||
|
}
|
||||||
|
if (inArray(includes, 'leaflet.markercluster')) {
|
||||||
|
inputCSS("../../web/libs/leaflet/plugins/leaflet.markercluster/1.3.0/MarkerCluster.Default.css");
|
||||||
|
inputCSS("../../web/libs/leaflet/plugins/leaflet.markercluster/1.3.0/MarkerCluster.css");
|
||||||
|
inputScript("../../web/libs/leaflet/plugins/leaflet.markercluster/1.3.0/leaflet.markercluster.js");
|
||||||
|
}
|
||||||
|
if (inArray(includes, 'leaflet-icon-pulse')) {
|
||||||
|
inputCSS("../../web/libs/leaflet/plugins/leaflet-icon-pulse/L.Icon.Pulse.css");
|
||||||
|
inputScript("../../web/libs/leaflet/plugins/leaflet-icon-pulse/L.Icon.Pulse.js");
|
||||||
|
}
|
||||||
|
if (inArray(includes, 'leaflet.draw')) {
|
||||||
|
inputCSS("../../web/libs/leaflet/plugins/leaflet.draw/1.0.2/leaflet.draw.css");
|
||||||
|
inputScript("../../web/libs/leaflet/plugins/leaflet.draw/1.0.2/leaflet.draw.js");
|
||||||
|
}
|
||||||
|
if (inArray(includes, 'leaflet.pm')) {
|
||||||
|
inputCSS("../../web/libs/leaflet/plugins/leaflet.pm/0.25.0/leaflet.pm.css");
|
||||||
|
inputScript("../../web/libs/leaflet/plugins/leaflet.pm/0.25.0/leaflet.pm.min.js");
|
||||||
|
}
|
||||||
|
if (inArray(includes, 'leaflet.miniMap')) {
|
||||||
|
inputCSS("../../web/libs/leaflet/plugins/leaflet-miniMap/3.6.1/dist/Control.MiniMap.min.css");
|
||||||
|
inputScript("../../web/libs/leaflet/plugins/leaflet-miniMap/3.6.1/dist/Control.MiniMap.min.js");
|
||||||
|
}
|
||||||
|
if (inArray(includes, 'leaflet.sidebyside')) {
|
||||||
|
inputScript("../../web/libs/leaflet/plugins/leaflet-side-by-side/leaflet-side-by-side.min.js");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
load();
|
||||||
|
window.isLocal = true;
|
||||||
|
window.server = document.location.toString().match(/file:\/\//) ? "http://localhost:8090" : document.location.protocol + "//" + document.location.host;
|
||||||
|
})();
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,478 @@
|
||||||
|
(function(global, factory) {
|
||||||
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('leaflet'))
|
||||||
|
: typeof define === 'function' && define.amd ? define(['leaflet'], factory)
|
||||||
|
: (factory(global.L));
|
||||||
|
}(this, function(L$1) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
L$1 = L$1 && L$1.hasOwnProperty('default') ? L$1['default'] : L$1;
|
||||||
|
|
||||||
|
// functional re-impl of L.Point.distanceTo,
|
||||||
|
// with no dependency on Leaflet for easier testing
|
||||||
|
function pointDistance(ptA, ptB) {
|
||||||
|
var x = ptB.x - ptA.x;
|
||||||
|
var y = ptB.y - ptA.y;
|
||||||
|
return Math.sqrt(x * x + y * y);
|
||||||
|
}
|
||||||
|
|
||||||
|
var computeSegmentHeading = function computeSegmentHeading(a, b) {
|
||||||
|
return (Math.atan2(b.y - a.y, b.x - a.x) * 180 / Math.PI + 90 + 360) % 360;
|
||||||
|
};
|
||||||
|
|
||||||
|
var asRatioToPathLength = function asRatioToPathLength(_ref, totalPathLength) {
|
||||||
|
var value = _ref.value;
|
||||||
|
var isInPixels = _ref.isInPixels;
|
||||||
|
return isInPixels ? value / totalPathLength : value;
|
||||||
|
};
|
||||||
|
|
||||||
|
function parseRelativeOrAbsoluteValue(value) {
|
||||||
|
if (typeof value === 'string' && value.indexOf('%') !== -1) {
|
||||||
|
return {
|
||||||
|
value: parseFloat(value) / 100,
|
||||||
|
isInPixels: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
var parsedValue = value ? parseFloat(value) : 0;
|
||||||
|
return {
|
||||||
|
value: parsedValue,
|
||||||
|
isInPixels: parsedValue > 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var pointsEqual = function pointsEqual(a, b) {
|
||||||
|
return a.x === b.x && a.y === b.y;
|
||||||
|
};
|
||||||
|
|
||||||
|
function pointsToSegments(pts) {
|
||||||
|
return pts.reduce(function(segments, b, idx, points) {
|
||||||
|
// this test skips same adjacent points
|
||||||
|
if (idx > 0 && !pointsEqual(b, points[idx - 1])) {
|
||||||
|
var a = points[idx - 1];
|
||||||
|
var distA = segments.length > 0 ? segments[segments.length - 1].distB : 0;
|
||||||
|
var distAB = pointDistance(a, b);
|
||||||
|
segments.push({
|
||||||
|
a: a,
|
||||||
|
b: b,
|
||||||
|
distA: distA,
|
||||||
|
distB: distA + distAB,
|
||||||
|
heading: computeSegmentHeading(a, b)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return segments;
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
function projectPatternOnPointPath(pts, pattern) {
|
||||||
|
// 1. split the path into segment infos
|
||||||
|
var segments = pointsToSegments(pts);
|
||||||
|
var nbSegments = segments.length;
|
||||||
|
if (nbSegments === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalPathLength = segments[nbSegments - 1].distB;
|
||||||
|
|
||||||
|
var offset = asRatioToPathLength(pattern.offset, totalPathLength);
|
||||||
|
var endOffset = asRatioToPathLength(pattern.endOffset, totalPathLength);
|
||||||
|
var repeat = asRatioToPathLength(pattern.repeat, totalPathLength);
|
||||||
|
|
||||||
|
var repeatIntervalPixels = totalPathLength * repeat;
|
||||||
|
var startOffsetPixels = offset > 0 ? totalPathLength * offset : 0;
|
||||||
|
var endOffsetPixels = endOffset > 0 ? totalPathLength * endOffset : 0;
|
||||||
|
|
||||||
|
// 2. generate the positions of the pattern as offsets from the path start
|
||||||
|
var positionOffsets = [];
|
||||||
|
var positionOffset = startOffsetPixels;
|
||||||
|
do {
|
||||||
|
positionOffsets.push(positionOffset);
|
||||||
|
positionOffset += repeatIntervalPixels;
|
||||||
|
} while (repeatIntervalPixels > 0 && positionOffset < totalPathLength - endOffsetPixels);
|
||||||
|
|
||||||
|
// 3. projects offsets to segments
|
||||||
|
var segmentIndex = 0;
|
||||||
|
var segment = segments[0];
|
||||||
|
return positionOffsets.map(function(positionOffset) {
|
||||||
|
// find the segment matching the offset,
|
||||||
|
// starting from the previous one as offsets are ordered
|
||||||
|
while (positionOffset > segment.distB && segmentIndex < nbSegments - 1) {
|
||||||
|
segmentIndex++;
|
||||||
|
segment = segments[segmentIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
var segmentRatio = (positionOffset - segment.distA) / (segment.distB - segment.distA);
|
||||||
|
return {
|
||||||
|
pt: interpolateBetweenPoints(segment.a, segment.b, segmentRatio),
|
||||||
|
heading: segment.heading
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the point which lies on the segment defined by points A and B,
|
||||||
|
* at the given ratio of the distance from A to B, by linear interpolation.
|
||||||
|
*/
|
||||||
|
function interpolateBetweenPoints(ptA, ptB, ratio) {
|
||||||
|
if (ptB.x !== ptA.x) {
|
||||||
|
return {
|
||||||
|
x: ptA.x + ratio * (ptB.x - ptA.x),
|
||||||
|
y: ptA.y + ratio * (ptB.y - ptA.y)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// special case where points lie on the same vertical axis
|
||||||
|
return {
|
||||||
|
x: ptA.x,
|
||||||
|
y: ptA.y + (ptB.y - ptA.y) * ratio
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
// save these original methods before they are overwritten
|
||||||
|
var proto_initIcon = L.Marker.prototype._initIcon;
|
||||||
|
var proto_setPos = L.Marker.prototype._setPos;
|
||||||
|
|
||||||
|
var oldIE = (L.DomUtil.TRANSFORM === 'msTransform');
|
||||||
|
|
||||||
|
L.Marker.addInitHook(function() {
|
||||||
|
var iconOptions = this.options.icon && this.options.icon.options;
|
||||||
|
var iconAnchor = iconOptions && this.options.icon.options.iconAnchor;
|
||||||
|
if (iconAnchor) {
|
||||||
|
iconAnchor = (iconAnchor[0] + 'px ' + iconAnchor[1] + 'px');
|
||||||
|
}
|
||||||
|
this.options.rotationOrigin = this.options.rotationOrigin || iconAnchor || 'center bottom';
|
||||||
|
this.options.rotationAngle = this.options.rotationAngle || 0;
|
||||||
|
|
||||||
|
// Ensure marker keeps rotated during dragging
|
||||||
|
this.on('drag', function(e) { e.target._applyRotation(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
L.Marker.include({
|
||||||
|
_initIcon: function() {
|
||||||
|
proto_initIcon.call(this);
|
||||||
|
},
|
||||||
|
|
||||||
|
_setPos: function(pos) {
|
||||||
|
proto_setPos.call(this, pos);
|
||||||
|
this._applyRotation();
|
||||||
|
},
|
||||||
|
|
||||||
|
_applyRotation: function() {
|
||||||
|
if (this.options.rotationAngle) {
|
||||||
|
this._icon.style[L.DomUtil.TRANSFORM + 'Origin'] = this.options.rotationOrigin;
|
||||||
|
|
||||||
|
if (oldIE) {
|
||||||
|
// for IE 9, use the 2D rotation
|
||||||
|
this._icon.style[L.DomUtil.TRANSFORM] = 'rotate(' + this.options.rotationAngle + 'deg)';
|
||||||
|
} else {
|
||||||
|
// for modern browsers, prefer the 3D accelerated version
|
||||||
|
this._icon.style[L.DomUtil.TRANSFORM] += ' rotateZ(' + this.options.rotationAngle + 'deg)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setRotationAngle: function(angle) {
|
||||||
|
this.options.rotationAngle = angle;
|
||||||
|
this.update();
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
setRotationOrigin: function(origin) {
|
||||||
|
this.options.rotationOrigin = origin;
|
||||||
|
this.update();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
L$1.Symbol = L$1.Symbol || {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple dash symbol, drawn as a Polyline.
|
||||||
|
* Can also be used for dots, if 'pixelSize' option is given the 0 value.
|
||||||
|
*/
|
||||||
|
L$1.Symbol.Dash = L$1.Class.extend({
|
||||||
|
options: {
|
||||||
|
pixelSize: 10,
|
||||||
|
pathOptions: {}
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize: function initialize(options) {
|
||||||
|
L$1.Util.setOptions(this, options);
|
||||||
|
this.options.pathOptions.clickable = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
buildSymbol: function buildSymbol(dirPoint, latLngs, map, index, total) {
|
||||||
|
var opts = this.options;
|
||||||
|
var d2r = Math.PI / 180;
|
||||||
|
|
||||||
|
// for a dot, nothing more to compute
|
||||||
|
if (opts.pixelSize <= 1) {
|
||||||
|
return L$1.polyline([dirPoint.latLng, dirPoint.latLng], opts.pathOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
var midPoint = map.project(dirPoint.latLng);
|
||||||
|
var angle = -(dirPoint.heading - 90) * d2r;
|
||||||
|
var a = L$1.point(midPoint.x + opts.pixelSize * Math.cos(angle + Math.PI) / 2, midPoint.y + opts.pixelSize * Math.sin(angle) / 2);
|
||||||
|
// compute second point by central symmetry to avoid unecessary cos/sin
|
||||||
|
var b = midPoint.add(midPoint.subtract(a));
|
||||||
|
return L$1.polyline([map.unproject(a), map.unproject(b)], opts.pathOptions);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
L$1.Symbol.dash = function(options) {
|
||||||
|
return new L$1.Symbol.Dash(options);
|
||||||
|
};
|
||||||
|
|
||||||
|
L$1.Symbol.ArrowHead = L$1.Class.extend({
|
||||||
|
options: {
|
||||||
|
polygon: true,
|
||||||
|
pixelSize: 10,
|
||||||
|
headAngle: 60,
|
||||||
|
pathOptions: {
|
||||||
|
stroke: false,
|
||||||
|
weight: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize: function initialize(options) {
|
||||||
|
L$1.Util.setOptions(this, options);
|
||||||
|
this.options.pathOptions.clickable = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
buildSymbol: function buildSymbol(dirPoint, latLngs, map, index, total) {
|
||||||
|
return this.options.polygon ? L$1.polygon(this._buildArrowPath(dirPoint, map), this.options.pathOptions) : L$1.polyline(this._buildArrowPath(dirPoint, map), this.options.pathOptions);
|
||||||
|
},
|
||||||
|
|
||||||
|
_buildArrowPath: function _buildArrowPath(dirPoint, map) {
|
||||||
|
var d2r = Math.PI / 180;
|
||||||
|
var tipPoint = map.project(dirPoint.latLng);
|
||||||
|
var direction = -(dirPoint.heading - 90) * d2r;
|
||||||
|
var radianArrowAngle = this.options.headAngle / 2 * d2r;
|
||||||
|
|
||||||
|
var headAngle1 = direction + radianArrowAngle;
|
||||||
|
var headAngle2 = direction - radianArrowAngle;
|
||||||
|
var arrowHead1 = L$1.point(tipPoint.x - this.options.pixelSize * Math.cos(headAngle1), tipPoint.y + this.options.pixelSize * Math.sin(headAngle1));
|
||||||
|
var arrowHead2 = L$1.point(tipPoint.x - this.options.pixelSize * Math.cos(headAngle2), tipPoint.y + this.options.pixelSize * Math.sin(headAngle2));
|
||||||
|
|
||||||
|
return [map.unproject(arrowHead1), dirPoint.latLng, map.unproject(arrowHead2)];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
L$1.Symbol.arrowHead = function(options) {
|
||||||
|
return new L$1.Symbol.ArrowHead(options);
|
||||||
|
};
|
||||||
|
|
||||||
|
L$1.Symbol.Marker = L$1.Class.extend({
|
||||||
|
options: {
|
||||||
|
markerOptions: {},
|
||||||
|
rotate: false
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize: function initialize(options) {
|
||||||
|
L$1.Util.setOptions(this, options);
|
||||||
|
this.options.markerOptions.clickable = false;
|
||||||
|
this.options.markerOptions.draggable = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
buildSymbol: function buildSymbol(directionPoint, latLngs, map, index, total) {
|
||||||
|
if (this.options.rotate) {
|
||||||
|
this.options.markerOptions.rotationAngle = directionPoint.heading + (this.options.angleCorrection || 0);
|
||||||
|
}
|
||||||
|
return L$1.marker(directionPoint.latLng, this.options.markerOptions);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
L$1.Symbol.marker = function(options) {
|
||||||
|
return new L$1.Symbol.Marker(options);
|
||||||
|
};
|
||||||
|
|
||||||
|
var isCoord = function isCoord(c) {
|
||||||
|
return c instanceof L$1.LatLng || Array.isArray(c) && c.length === 2 && typeof c[0] === 'number';
|
||||||
|
};
|
||||||
|
|
||||||
|
var isCoordArray = function isCoordArray(ll) {
|
||||||
|
return Array.isArray(ll) && isCoord(ll[0]);
|
||||||
|
};
|
||||||
|
|
||||||
|
L$1.PolylineDecorator = L$1.FeatureGroup.extend({
|
||||||
|
options: {
|
||||||
|
patterns: []
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize: function initialize(paths, options) {
|
||||||
|
L$1.FeatureGroup.prototype.initialize.call(this);
|
||||||
|
L$1.Util.setOptions(this, options);
|
||||||
|
this._map = null;
|
||||||
|
this._paths = this._initPaths(paths);
|
||||||
|
this._bounds = this._initBounds();
|
||||||
|
this._patterns = this._initPatterns(this.options.patterns);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deals with all the different cases. input can be one of these types:
|
||||||
|
* array of LatLng, array of 2-number arrays, Polyline, Polygon,
|
||||||
|
* array of one of the previous.
|
||||||
|
*/
|
||||||
|
_initPaths: function _initPaths(input, isPolygon) {
|
||||||
|
var _this = this;
|
||||||
|
|
||||||
|
if (isCoordArray(input)) {
|
||||||
|
// Leaflet Polygons don't need the first point to be repeated, but we do
|
||||||
|
var coords = isPolygon ? input.concat([input[0]]) : input;
|
||||||
|
return [coords];
|
||||||
|
}
|
||||||
|
if (input instanceof L$1.Polyline) {
|
||||||
|
// we need some recursivity to support multi-poly*
|
||||||
|
return this._initPaths(input.getLatLngs(), input instanceof L$1.Polygon);
|
||||||
|
}
|
||||||
|
if (Array.isArray(input)) {
|
||||||
|
// flatten everything, we just need coordinate lists to apply patterns
|
||||||
|
return input.reduce(function(flatArray, p) {
|
||||||
|
return flatArray.concat(_this._initPaths(p, isPolygon));
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
|
||||||
|
// parse pattern definitions and precompute some values
|
||||||
|
_initPatterns: function _initPatterns(patternDefs) {
|
||||||
|
return patternDefs.map(this._parsePatternDef);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the patterns used by this decorator
|
||||||
|
* and redraws the new one.
|
||||||
|
*/
|
||||||
|
setPatterns: function setPatterns(patterns) {
|
||||||
|
this.options.patterns = patterns;
|
||||||
|
this._patterns = this._initPatterns(this.options.patterns);
|
||||||
|
this.redraw();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the patterns used by this decorator
|
||||||
|
* and redraws the new one.
|
||||||
|
*/
|
||||||
|
setPaths: function setPaths(paths) {
|
||||||
|
this._paths = this._initPaths(paths);
|
||||||
|
this._bounds = this._initBounds();
|
||||||
|
this.redraw();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the pattern definition
|
||||||
|
*/
|
||||||
|
_parsePatternDef: function _parsePatternDef(patternDef, latLngs) {
|
||||||
|
return {
|
||||||
|
symbolFactory: patternDef.symbol,
|
||||||
|
// Parse offset and repeat values, managing the two cases:
|
||||||
|
// absolute (in pixels) or relative (in percentage of the polyline length)
|
||||||
|
offset: parseRelativeOrAbsoluteValue(patternDef.offset),
|
||||||
|
endOffset: parseRelativeOrAbsoluteValue(patternDef.endOffset),
|
||||||
|
repeat: parseRelativeOrAbsoluteValue(patternDef.repeat)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
onAdd: function onAdd(map) {
|
||||||
|
this._map = map;
|
||||||
|
this._draw();
|
||||||
|
this._map.on('moveend', this.redraw, this);
|
||||||
|
},
|
||||||
|
|
||||||
|
onRemove: function onRemove(map) {
|
||||||
|
this._map.off('moveend', this.redraw, this);
|
||||||
|
this._map = null;
|
||||||
|
L$1.FeatureGroup.prototype.onRemove.call(this, map);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* As real pattern bounds depends on map zoom and bounds,
|
||||||
|
* we just compute the total bounds of all paths decorated by this instance.
|
||||||
|
*/
|
||||||
|
_initBounds: function _initBounds() {
|
||||||
|
var allPathCoords = this._paths.reduce(function(acc, path) {
|
||||||
|
return acc.concat(path);
|
||||||
|
}, []);
|
||||||
|
return L$1.latLngBounds(allPathCoords);
|
||||||
|
},
|
||||||
|
|
||||||
|
getBounds: function getBounds() {
|
||||||
|
return this._bounds;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of ILayers object
|
||||||
|
*/
|
||||||
|
_buildSymbols: function _buildSymbols(latLngs, symbolFactory, directionPoints) {
|
||||||
|
var _this2 = this;
|
||||||
|
|
||||||
|
return directionPoints.map(function(directionPoint, i) {
|
||||||
|
return symbolFactory.buildSymbol(directionPoint, latLngs, _this2._map, i, directionPoints.length);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute pairs of LatLng and heading angle,
|
||||||
|
* that define positions and directions of the symbols on the path
|
||||||
|
*/
|
||||||
|
_getDirectionPoints: function _getDirectionPoints(latLngs, pattern) {
|
||||||
|
var _this3 = this;
|
||||||
|
|
||||||
|
if (latLngs.length < 2) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
var pathAsPoints = latLngs.map(function(latLng) {
|
||||||
|
return _this3._map.project(latLng);
|
||||||
|
});
|
||||||
|
return projectPatternOnPointPath(pathAsPoints, pattern).map(function(point) {
|
||||||
|
return {
|
||||||
|
latLng: _this3._map.unproject(L$1.point(point.pt)),
|
||||||
|
heading: point.heading
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
redraw: function redraw() {
|
||||||
|
if (!this._map) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.clearLayers();
|
||||||
|
this._draw();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all symbols for a given pattern as an array of FeatureGroup
|
||||||
|
*/
|
||||||
|
_getPatternLayers: function _getPatternLayers(pattern) {
|
||||||
|
var _this4 = this;
|
||||||
|
|
||||||
|
var mapBounds = this._map.getBounds().pad(0.1);
|
||||||
|
return this._paths.map(function(path) {
|
||||||
|
var directionPoints = _this4._getDirectionPoints(path, pattern)
|
||||||
|
// filter out invisible points
|
||||||
|
.filter(function(point) {
|
||||||
|
return mapBounds.contains(point.latLng);
|
||||||
|
});
|
||||||
|
return L$1.featureGroup(_this4._buildSymbols(path, pattern.symbolFactory, directionPoints));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw all patterns
|
||||||
|
*/
|
||||||
|
_draw: function _draw() {
|
||||||
|
var _this5 = this;
|
||||||
|
|
||||||
|
this._patterns.map(function(pattern) {
|
||||||
|
return _this5._getPatternLayers(pattern);
|
||||||
|
}).forEach(function(layers) {
|
||||||
|
_this5.addLayer(L$1.featureGroup(layers));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
/*
|
||||||
|
* Allows compact syntax to be used
|
||||||
|
*/
|
||||||
|
L$1.polylineDecorator = function(paths, options) {
|
||||||
|
return new L$1.PolylineDecorator(paths, options);
|
||||||
|
};
|
||||||
|
}));
|
|
@ -0,0 +1,227 @@
|
||||||
|
(function (factory, window) {
|
||||||
|
if (typeof define === 'function' && define.amd) {
|
||||||
|
define(['leaflet'], factory);
|
||||||
|
} else if (typeof exports === 'object') {
|
||||||
|
module.exports = factory(require('leaflet'));
|
||||||
|
}
|
||||||
|
if (typeof window !== 'undefined' && window.L) {
|
||||||
|
window.L.PolylineOffset = factory(L);
|
||||||
|
}
|
||||||
|
}(function (L) {
|
||||||
|
|
||||||
|
function forEachPair(list, callback) {
|
||||||
|
if (!list || list.length < 1) { return; }
|
||||||
|
for (var i = 1, l = list.length; i < l; i++) {
|
||||||
|
callback(list[i-1], list[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Find the coefficients (a,b) of a line of equation y = a.x + b,
|
||||||
|
or the constant x for vertical lines
|
||||||
|
Return null if there's no equation possible
|
||||||
|
*/
|
||||||
|
function lineEquation(pt1, pt2) {
|
||||||
|
if (pt1.x === pt2.x) {
|
||||||
|
return pt1.y === pt2.y ? null : { x: pt1.x };
|
||||||
|
}
|
||||||
|
|
||||||
|
var a = (pt2.y - pt1.y) / (pt2.x - pt1.x);
|
||||||
|
return {
|
||||||
|
a: a,
|
||||||
|
b: pt1.y - a * pt1.x,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Return the intersection point of two lines defined by two points each
|
||||||
|
Return null when there's no unique intersection
|
||||||
|
*/
|
||||||
|
function intersection(l1a, l1b, l2a, l2b) {
|
||||||
|
var line1 = lineEquation(l1a, l1b);
|
||||||
|
var line2 = lineEquation(l2a, l2b);
|
||||||
|
|
||||||
|
if (line1 === null || line2 === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line1.hasOwnProperty('x')) {
|
||||||
|
return line2.hasOwnProperty('x')
|
||||||
|
? null
|
||||||
|
: {
|
||||||
|
x: line1.x,
|
||||||
|
y: line2.a * line1.x + line2.b,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (line2.hasOwnProperty('x')) {
|
||||||
|
return {
|
||||||
|
x: line2.x,
|
||||||
|
y: line1.a * line2.x + line1.b,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line1.a === line2.a) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var x = (line2.b - line1.b) / (line1.a - line2.a);
|
||||||
|
return {
|
||||||
|
x: x,
|
||||||
|
y: line1.a * x + line1.b,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function translatePoint(pt, dist, heading) {
|
||||||
|
return {
|
||||||
|
x: pt.x + dist * Math.cos(heading),
|
||||||
|
y: pt.y + dist * Math.sin(heading),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var PolylineOffset = {
|
||||||
|
offsetPointLine: function(points, distance) {
|
||||||
|
var offsetSegments = [];
|
||||||
|
|
||||||
|
forEachPair(points, L.bind(function(a, b) {
|
||||||
|
if (a.x === b.x && a.y === b.y) { return; }
|
||||||
|
|
||||||
|
// angles in (-PI, PI]
|
||||||
|
var segmentAngle = Math.atan2(a.y - b.y, a.x - b.x);
|
||||||
|
var offsetAngle = segmentAngle - Math.PI/2;
|
||||||
|
|
||||||
|
offsetSegments.push({
|
||||||
|
offsetAngle: offsetAngle,
|
||||||
|
original: [a, b],
|
||||||
|
offset: [
|
||||||
|
translatePoint(a, distance, offsetAngle),
|
||||||
|
translatePoint(b, distance, offsetAngle)
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}, this));
|
||||||
|
|
||||||
|
return offsetSegments;
|
||||||
|
},
|
||||||
|
|
||||||
|
offsetPoints: function(pts, options) {
|
||||||
|
var offsetSegments = this.offsetPointLine(L.LineUtil.simplify(pts, options.smoothFactor), options.offset);
|
||||||
|
return this.joinLineSegments(offsetSegments, options.offset);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Join 2 line segments defined by 2 points each with a circular arc
|
||||||
|
*/
|
||||||
|
joinSegments: function(s1, s2, offset) {
|
||||||
|
// TODO: different join styles
|
||||||
|
return this.circularArc(s1, s2, offset)
|
||||||
|
.filter(function(x) { return x; })
|
||||||
|
},
|
||||||
|
|
||||||
|
joinLineSegments: function(segments, offset) {
|
||||||
|
var joinedPoints = [];
|
||||||
|
var first = segments[0];
|
||||||
|
var last = segments[segments.length - 1];
|
||||||
|
|
||||||
|
if (first && last) {
|
||||||
|
joinedPoints.push(first.offset[0]);
|
||||||
|
forEachPair(segments, L.bind(function(s1, s2) {
|
||||||
|
joinedPoints = joinedPoints.concat(this.joinSegments(s1, s2, offset));
|
||||||
|
}, this));
|
||||||
|
joinedPoints.push(last.offset[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return joinedPoints;
|
||||||
|
},
|
||||||
|
|
||||||
|
segmentAsVector: function(s) {
|
||||||
|
return {
|
||||||
|
x: s[1].x - s[0].x,
|
||||||
|
y: s[1].y - s[0].y,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
getSignedAngle: function(s1, s2) {
|
||||||
|
const a = this.segmentAsVector(s1);
|
||||||
|
const b = this.segmentAsVector(s2);
|
||||||
|
return Math.atan2(a.x * b.y - a.y * b.x, a.x * b.x + a.y * b.y);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Interpolates points between two offset segments in a circular form
|
||||||
|
*/
|
||||||
|
circularArc: function(s1, s2, distance) {
|
||||||
|
// if the segments are the same angle,
|
||||||
|
// there should be a single join point
|
||||||
|
if (s1.offsetAngle === s2.offsetAngle) {
|
||||||
|
return [s1.offset[1]];
|
||||||
|
}
|
||||||
|
|
||||||
|
const signedAngle = this.getSignedAngle(s1.offset, s2.offset);
|
||||||
|
// for inner angles, just find the offset segments intersection
|
||||||
|
if ((signedAngle * distance > 0) &&
|
||||||
|
(signedAngle * this.getSignedAngle(s1.offset, [s1.offset[0], s2.offset[1]]) > 0)) {
|
||||||
|
return [intersection(s1.offset[0], s1.offset[1], s2.offset[0], s2.offset[1])];
|
||||||
|
}
|
||||||
|
|
||||||
|
// draws a circular arc with R = offset distance, C = original meeting point
|
||||||
|
var points = [];
|
||||||
|
var center = s1.original[1];
|
||||||
|
// ensure angles go in the anti-clockwise direction
|
||||||
|
var rightOffset = distance > 0;
|
||||||
|
var startAngle = rightOffset ? s2.offsetAngle : s1.offsetAngle;
|
||||||
|
var endAngle = rightOffset ? s1.offsetAngle : s2.offsetAngle;
|
||||||
|
// and that the end angle is bigger than the start angle
|
||||||
|
if (endAngle < startAngle) {
|
||||||
|
endAngle += Math.PI * 2;
|
||||||
|
}
|
||||||
|
var step = Math.PI / 8;
|
||||||
|
for (var alpha = startAngle; alpha < endAngle; alpha += step) {
|
||||||
|
points.push(translatePoint(center, distance, alpha));
|
||||||
|
}
|
||||||
|
points.push(translatePoint(center, distance, endAngle));
|
||||||
|
|
||||||
|
return rightOffset ? points.reverse() : points;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modify the L.Polyline class by overwriting the projection function
|
||||||
|
L.Polyline.include({
|
||||||
|
_projectLatlngs: function (latlngs, result, projectedBounds) {
|
||||||
|
var isFlat = latlngs.length > 0 && latlngs[0] instanceof L.LatLng;
|
||||||
|
|
||||||
|
if (isFlat) {
|
||||||
|
var ring = latlngs.map(L.bind(function(ll) {
|
||||||
|
var point = this._map.latLngToLayerPoint(ll);
|
||||||
|
if (projectedBounds) {
|
||||||
|
projectedBounds.extend(point);
|
||||||
|
}
|
||||||
|
return point;
|
||||||
|
}, this));
|
||||||
|
|
||||||
|
// Offset management hack ---
|
||||||
|
if (this.options.offset) {
|
||||||
|
ring = L.PolylineOffset.offsetPoints(ring, this.options);
|
||||||
|
}
|
||||||
|
// Offset management hack END ---
|
||||||
|
|
||||||
|
result.push(ring.map(function (xy) {
|
||||||
|
return L.point(xy.x, xy.y);
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
latlngs.forEach(L.bind(function(ll) {
|
||||||
|
this._projectLatlngs(ll, result, projectedBounds);
|
||||||
|
}, this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
L.Polyline.include({
|
||||||
|
setOffset: function(offset) {
|
||||||
|
this.options.offset = offset;
|
||||||
|
this.redraw();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return PolylineOffset;
|
||||||
|
|
||||||
|
}, window));
|
|
@ -0,0 +1,166 @@
|
||||||
|
/*
|
||||||
|
* Leaflet.TextPath - Shows text along a polyline
|
||||||
|
* Inspired by Tom Mac Wright article :
|
||||||
|
* http://mapbox.com/osmdev/2012/11/20/getting-serious-about-svg/
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
var __onAdd = L.Polyline.prototype.onAdd;
|
||||||
|
var __onRemove = L.Polyline.prototype.onRemove;
|
||||||
|
var __updatePath = L.Polyline.prototype._updatePath;
|
||||||
|
var __bringToFront = L.Polyline.prototype.bringToFront;
|
||||||
|
|
||||||
|
var PolylineTextPath = {
|
||||||
|
|
||||||
|
onAdd: function(map) {
|
||||||
|
__onAdd.call(this, map);
|
||||||
|
this._textRedraw();
|
||||||
|
},
|
||||||
|
|
||||||
|
onRemove: function(map) {
|
||||||
|
map = map || this._map;
|
||||||
|
if (map && this._textNode && map._renderer._container) { map._renderer._container.removeChild(this._textNode); }
|
||||||
|
__onRemove.call(this, map);
|
||||||
|
},
|
||||||
|
|
||||||
|
bringToFront: function() {
|
||||||
|
__bringToFront.call(this);
|
||||||
|
this._textRedraw();
|
||||||
|
},
|
||||||
|
|
||||||
|
_updatePath: function() {
|
||||||
|
__updatePath.call(this);
|
||||||
|
this._textRedraw();
|
||||||
|
},
|
||||||
|
|
||||||
|
_textRedraw: function() {
|
||||||
|
var text = this._text;
|
||||||
|
var options = this._textOptions;
|
||||||
|
if (text) {
|
||||||
|
this.setText(null).setText(text, options);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setText: function(text, options) {
|
||||||
|
this._text = text;
|
||||||
|
this._textOptions = options;
|
||||||
|
|
||||||
|
/* If not in SVG mode or Polyline not added to map yet return */
|
||||||
|
/* setText will be called by onAdd, using value stored in this._text */
|
||||||
|
if (!L.Browser.svg || typeof this._map === 'undefined') {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaults = {
|
||||||
|
repeat: false,
|
||||||
|
fillColor: 'black',
|
||||||
|
attributes: {},
|
||||||
|
below: false,
|
||||||
|
};
|
||||||
|
options = L.Util.extend(defaults, options);
|
||||||
|
|
||||||
|
/* If empty text, hide */
|
||||||
|
if (!text) {
|
||||||
|
if (this._textNode && this._textNode.parentNode) {
|
||||||
|
this._map._renderer._container.removeChild(this._textNode);
|
||||||
|
|
||||||
|
/* delete the node, so it will not be removed a 2nd time if the layer is later removed from the map */
|
||||||
|
delete this._textNode;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
text = text.replace(/ /g, '\u00A0'); // Non breakable spaces
|
||||||
|
var id = 'pathdef-' + L.Util.stamp(this);
|
||||||
|
var svg = this._map._renderer._container;
|
||||||
|
this._path.setAttribute('id', id);
|
||||||
|
|
||||||
|
if (options.repeat) {
|
||||||
|
/* Compute single pattern length */
|
||||||
|
var pattern = L.SVG.create('text');
|
||||||
|
for (var attr in options.attributes) { pattern.setAttribute(attr, options.attributes[attr]); }
|
||||||
|
pattern.appendChild(document.createTextNode(text));
|
||||||
|
svg.appendChild(pattern);
|
||||||
|
var alength = pattern.getComputedTextLength();
|
||||||
|
svg.removeChild(pattern);
|
||||||
|
|
||||||
|
/* Create string as long as path */
|
||||||
|
text = new Array(Math.ceil(isNaN(this._path.getTotalLength() / alength) ? 0 : this._path.getTotalLength() / alength)).join(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Put it along the path using textPath */
|
||||||
|
var textNode = L.SVG.create('text');
|
||||||
|
var textPath = L.SVG.create('textPath');
|
||||||
|
|
||||||
|
var dy = options.offset || this._path.getAttribute('stroke-width');
|
||||||
|
|
||||||
|
textPath.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', '#' + id);
|
||||||
|
textNode.setAttribute('dy', dy);
|
||||||
|
for (var attr in options.attributes) { textNode.setAttribute(attr, options.attributes[attr]); }
|
||||||
|
textPath.appendChild(document.createTextNode(text));
|
||||||
|
textNode.appendChild(textPath);
|
||||||
|
this._textNode = textNode;
|
||||||
|
|
||||||
|
if (options.below) {
|
||||||
|
svg.insertBefore(textNode, svg.firstChild);
|
||||||
|
} else {
|
||||||
|
svg.appendChild(textNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Center text according to the path's bounding box */
|
||||||
|
if (options.center) {
|
||||||
|
var textLength = textNode.getComputedTextLength();
|
||||||
|
var pathLength = this._path.getTotalLength();
|
||||||
|
/* Set the position for the left side of the textNode */
|
||||||
|
textNode.setAttribute('dx', ((pathLength / 2) - (textLength / 2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Change label rotation (if required) */
|
||||||
|
if (options.orientation) {
|
||||||
|
var rotateAngle = 0;
|
||||||
|
switch (options.orientation) {
|
||||||
|
case 'flip':
|
||||||
|
rotateAngle = 180;
|
||||||
|
break;
|
||||||
|
case 'perpendicular':
|
||||||
|
rotateAngle = 90;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
rotateAngle = options.orientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
var rotatecenterX = (textNode.getBBox().x + textNode.getBBox().width / 2);
|
||||||
|
var rotatecenterY = (textNode.getBBox().y + textNode.getBBox().height / 2);
|
||||||
|
textNode.setAttribute('transform', 'rotate(' + rotateAngle + ' ' + rotatecenterX + ' ' + rotatecenterY + ')');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Initialize mouse events for the additional nodes */
|
||||||
|
if (this.options.interactive) {
|
||||||
|
if (L.Browser.svg || !L.Browser.vml) {
|
||||||
|
textPath.setAttribute('class', 'leaflet-interactive');
|
||||||
|
}
|
||||||
|
|
||||||
|
var events = ['click', 'dblclick', 'mousedown', 'mouseover',
|
||||||
|
'mouseout', 'mousemove', 'contextmenu'];
|
||||||
|
for (var i = 0; i < events.length; i++) {
|
||||||
|
L.DomEvent.on(textNode, events[i], this.fire, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
L.Polyline.include(PolylineTextPath);
|
||||||
|
|
||||||
|
L.LayerGroup.include({
|
||||||
|
setText: function(text, options) {
|
||||||
|
for (var layer in this._layers) {
|
||||||
|
if (typeof this._layers[layer].setText === 'function') {
|
||||||
|
this._layers[layer].setText(text, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
|
@ -0,0 +1,337 @@
|
||||||
|
/* Copyright© 2000 - 2018 SuperMap Software Co.Ltd. All rights reserved.
|
||||||
|
* This program are made available under the terms of the Apache License, Version 2.0
|
||||||
|
* which accompanies this distribution and is available at http://www.apache.org/licenses/LICENSE-2.0.html.*/
|
||||||
|
|
||||||
|
importScripts('https://cdnjs.cloudflare.com/ajax/libs/Turf.js/5.1.6/turf.js')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 空间分析所需工具类
|
||||||
|
*/
|
||||||
|
onmessage = function (event) {
|
||||||
|
if (event.data) {
|
||||||
|
let params = event.data;
|
||||||
|
postMessage(analysis(params))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//IDW 插值
|
||||||
|
function interpolate(points, cellSize, options) {
|
||||||
|
options = options || {};
|
||||||
|
if (typeof options !== 'object') {
|
||||||
|
throw new Error('options is invalid');
|
||||||
|
}
|
||||||
|
var gridType = options.gridType;
|
||||||
|
var property = options.property;
|
||||||
|
var weight = options.weight;
|
||||||
|
|
||||||
|
if (!points) {
|
||||||
|
throw new Error('points is required');
|
||||||
|
}
|
||||||
|
turf.collectionOf(points, 'Point', 'input must contain Points');
|
||||||
|
if (!cellSize) {
|
||||||
|
throw new Error('cellSize is required');
|
||||||
|
}
|
||||||
|
if (weight !== undefined && typeof weight !== 'number') {
|
||||||
|
throw new Error('weight must be a number');
|
||||||
|
}
|
||||||
|
property = property || 'elevation';
|
||||||
|
gridType = gridType || 'square';
|
||||||
|
weight = weight || 1;
|
||||||
|
|
||||||
|
var box = options.bbox || turf.bbox(points);
|
||||||
|
|
||||||
|
var grid;
|
||||||
|
switch (gridType) {
|
||||||
|
case 'point':
|
||||||
|
case 'points':
|
||||||
|
grid = squareGrid(box, cellSize, options, gridType);
|
||||||
|
break;
|
||||||
|
case 'square':
|
||||||
|
case 'squares':
|
||||||
|
grid = squareGrid(box, cellSize, options, gridType);
|
||||||
|
break;
|
||||||
|
case 'hex':
|
||||||
|
case 'hexes':
|
||||||
|
grid = turf.hexGrid(box, cellSize, options);
|
||||||
|
break;
|
||||||
|
case 'triangle':
|
||||||
|
case 'triangles':
|
||||||
|
grid = turf.triangleGrid(box, cellSize, options);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error('invalid gridType');
|
||||||
|
}
|
||||||
|
var results = [];
|
||||||
|
var gridFeatures = grid.features;
|
||||||
|
var pointFeatures = points.features;
|
||||||
|
for (var i = 0, len = gridFeatures.length; i < len; i++) {
|
||||||
|
var zw = 0;
|
||||||
|
var sw = 0;
|
||||||
|
var gridFeature = gridFeatures[i];
|
||||||
|
for (var j = 0, leng = pointFeatures.length; j < leng; j++) {
|
||||||
|
var point = pointFeatures[j];
|
||||||
|
var gridPoint = (gridType === 'point') ? gridFeature : turf.centroid(gridFeature);
|
||||||
|
var d = turf.distance(gridPoint, point, options);
|
||||||
|
var zValue;
|
||||||
|
if (point.properties[property]) {
|
||||||
|
zValue = point.properties[property];
|
||||||
|
}
|
||||||
|
if (zValue === undefined) {
|
||||||
|
zValue = point.geometry.coordinates[2];
|
||||||
|
}
|
||||||
|
if (zValue === undefined) {
|
||||||
|
throw new Error('zValue is missing');
|
||||||
|
}
|
||||||
|
if (d === 0) {
|
||||||
|
zw = zValue;
|
||||||
|
}
|
||||||
|
var w = 1.0 / Math.pow(d, weight);
|
||||||
|
sw += w;
|
||||||
|
zw += w * zValue;
|
||||||
|
}
|
||||||
|
var newFeature = turf.clone(gridFeature);
|
||||||
|
newFeature.properties[property] = zw / sw;
|
||||||
|
results.push(newFeature);
|
||||||
|
}
|
||||||
|
return turf.featureCollection(results);
|
||||||
|
}
|
||||||
|
|
||||||
|
function squareGrid(bbox, cellSide, options, gridType) {
|
||||||
|
options = options || {};
|
||||||
|
if (!turf.isObject(options)) {
|
||||||
|
throw new Error('options is invalid');
|
||||||
|
}
|
||||||
|
var properties = options.properties;
|
||||||
|
var mask = options.mask;
|
||||||
|
|
||||||
|
var results = [];
|
||||||
|
|
||||||
|
if (cellSide === null || cellSide === undefined) {
|
||||||
|
throw new Error('cellSide is required');
|
||||||
|
}
|
||||||
|
if (!turf.isNumber(cellSide)) {
|
||||||
|
throw new Error('cellSide is invalid');
|
||||||
|
}
|
||||||
|
if (!bbox) {
|
||||||
|
throw new Error('bbox is required');
|
||||||
|
}
|
||||||
|
if (!Array.isArray(bbox)) {
|
||||||
|
throw new Error('bbox must be array');
|
||||||
|
}
|
||||||
|
if (bbox.length !== 4) {
|
||||||
|
throw new Error('bbox must contain 4 numbers');
|
||||||
|
}
|
||||||
|
if (mask && ['Polygon', 'MultiPolygon'].indexOf(turf.getType(mask)) === -1) {
|
||||||
|
throw new Error('options.mask must be a (Multi)Polygon');
|
||||||
|
}
|
||||||
|
|
||||||
|
var west = bbox[0];
|
||||||
|
var south = bbox[1];
|
||||||
|
var east = bbox[2];
|
||||||
|
var north = bbox[3];
|
||||||
|
|
||||||
|
var bboxWidth = (east - west);
|
||||||
|
var bboxHeight = (north - south);
|
||||||
|
|
||||||
|
var cellWidth = cellSide / 111.94;
|
||||||
|
var cellHeight = cellSide / 111.94;
|
||||||
|
|
||||||
|
var columns = Math.floor(bboxWidth / cellWidth);
|
||||||
|
var rows = Math.floor(bboxHeight / cellHeight);
|
||||||
|
|
||||||
|
var deltaX = (bboxWidth - columns * cellWidth) / 2;
|
||||||
|
var deltaY = (bboxHeight - rows * cellHeight) / 2;
|
||||||
|
|
||||||
|
var currentX = west + deltaX;
|
||||||
|
if (gridType === "square" || gridType === "squares") {
|
||||||
|
// for (var column = 0; column < columns; column++) {
|
||||||
|
// var currentY = south + deltaY;
|
||||||
|
// for (var row = 0; row < rows; row++) {
|
||||||
|
// var cellPoly = turf.polygon([[
|
||||||
|
// [currentX, currentY],
|
||||||
|
// [currentX, currentY + cellHeight],
|
||||||
|
// [currentX + cellWidth, currentY + cellHeight],
|
||||||
|
// [currentX + cellWidth, currentY],
|
||||||
|
// [currentX, currentY]
|
||||||
|
// ]], properties);
|
||||||
|
// if (mask) {
|
||||||
|
// if (intersect(mask, cellPoly)) results.push(cellPoly);
|
||||||
|
// } else {
|
||||||
|
// results.push(cellPoly);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// currentY += cellHeight;
|
||||||
|
// }
|
||||||
|
// currentX += cellWidth;
|
||||||
|
// }
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
while (currentX <= east) {
|
||||||
|
var currentY1 = south + deltaY;
|
||||||
|
while (currentY1 <= north) {
|
||||||
|
var cellPt = turf.point([currentX, currentY1], properties);
|
||||||
|
if (mask) {
|
||||||
|
if (turf.booleanWithin(cellPt, mask)) {
|
||||||
|
results.push(cellPt);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
results.push(cellPt);
|
||||||
|
}
|
||||||
|
currentY1 += cellHeight;
|
||||||
|
}
|
||||||
|
currentX += cellWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return turf.featureCollection(results);
|
||||||
|
}
|
||||||
|
|
||||||
|
function processBuffer(buffer, saveAttrs) {
|
||||||
|
var featureCollection = [];
|
||||||
|
//var maxBounds = [-180, -90, 180, 90];
|
||||||
|
//获取缓冲区异常feature, 并从原featureCollection中删除
|
||||||
|
for (var i = 0, len = buffer.features.length; i < len; i++) {
|
||||||
|
var feature = buffer.features[i];
|
||||||
|
//不保留原有的属性值
|
||||||
|
if (!saveAttrs) {
|
||||||
|
//对字段属性进行for循环
|
||||||
|
for (var pro in feature.properties) {
|
||||||
|
var shouldDeleted = true;
|
||||||
|
//序号和参考地点保留
|
||||||
|
if (pro === 'ID') {
|
||||||
|
shouldDeleted = false;
|
||||||
|
}
|
||||||
|
//删除除了位置和序号字段的其他字段属性
|
||||||
|
if (shouldDeleted) delete feature.properties[pro];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var bbox = turf.bbox(buffer.features[i]);
|
||||||
|
//bounds的x轴距离作为判断依据,看数据误差<=1,这里写大一点防止意外
|
||||||
|
if (parseInt(bbox[2] - bbox[0]) > 5) {
|
||||||
|
//if(util.isRectOverlap(maxBounds, turf.bbox(buffer.features[i]))) {
|
||||||
|
|
||||||
|
//删除功能,第一个参数为第一项位置,第二个参数为要删除几个。 array.splice(index, num) ,返回值为删除内容,array为结果值。
|
||||||
|
//删除位置为i的一个要素
|
||||||
|
featureCollection.push(buffer.features.splice(i, 1)[0]);
|
||||||
|
i--;
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//将异常feature分割为两个正常feature,重新添加到featureCollextion中
|
||||||
|
var newCod;
|
||||||
|
for (var k = 0; k < featureCollection.length; k++) {
|
||||||
|
newCod = [];
|
||||||
|
//获取不正常features
|
||||||
|
var abnormalFeature = featureCollection[k];
|
||||||
|
//获取不正常features的坐标
|
||||||
|
var coordinates = turf.getCoords(abnormalFeature)[0];
|
||||||
|
//对坐标进行循环
|
||||||
|
for (var j = 0, leng = coordinates.length; j < leng; j++) {
|
||||||
|
if (coordinates[j][0] < 0) {
|
||||||
|
newCod.push(coordinates.splice(j, 1)[0]);
|
||||||
|
j--;
|
||||||
|
leng--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//闭合polygon
|
||||||
|
if (newCod.length > 3) {
|
||||||
|
if (newCod[newCod.length - 1][0] !== newCod[0][0] || newCod[newCod.length - 1][1] !== newCod[0][1]) {
|
||||||
|
newCod.push(newCod[0]);
|
||||||
|
}
|
||||||
|
//两个feature属性相同
|
||||||
|
buffer.features.push(turf.polygon([newCod], abnormalFeature.properties));
|
||||||
|
}
|
||||||
|
//闭合polygon
|
||||||
|
if (coordinates.length > 3) {
|
||||||
|
if (coordinates[coordinates.length - 1][0] !== coordinates[0][0] || coordinates[coordinates.length - 1][1] !== coordinates[0][1]) {
|
||||||
|
coordinates.push(coordinates[0]);
|
||||||
|
}
|
||||||
|
buffer.features.push(abnormalFeature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//feature合并
|
||||||
|
function unionFeature(featureCollection, isFirst) {
|
||||||
|
var results = [];
|
||||||
|
var features = featureCollection.features;
|
||||||
|
var featureLength = features.length;
|
||||||
|
var oneceTotal = 2; //两两合并 直到最后剩一个多面对象为止(分网格 合并)
|
||||||
|
var total = Math.round(featureLength / oneceTotal);
|
||||||
|
//数组顺序打乱
|
||||||
|
if (!isFirst) this.random(features);
|
||||||
|
for (var i = 0; i <= total; i++) {
|
||||||
|
var start = i * oneceTotal;
|
||||||
|
var result = featureCollection.features[start];
|
||||||
|
for (var j = 1; j < oneceTotal; j++) {
|
||||||
|
var index = start + j;
|
||||||
|
if (featureCollection.features[index]) {
|
||||||
|
try {
|
||||||
|
result = this.union(result, featureCollection.features[index]);
|
||||||
|
} catch (e) {
|
||||||
|
results.push(featureCollection.features[index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result) results.push(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (results && results.length > 1) {
|
||||||
|
//结果还是多个 继续合并
|
||||||
|
return this.unionFeature(turf.featureCollection(results), false);
|
||||||
|
} else {
|
||||||
|
return results[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//数组顺序打乱
|
||||||
|
function random(arr) {
|
||||||
|
arr.sort(function () { return 0.5 - Math.random() });
|
||||||
|
}
|
||||||
|
|
||||||
|
function union(features, polys) {
|
||||||
|
let result = turf.union(features, polys);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//多面转单面
|
||||||
|
function toPolygons(multiPolygon) {
|
||||||
|
var polygons = [];
|
||||||
|
if (turf.getType(multiPolygon) === "Polygon") {
|
||||||
|
polygons = [multiPolygon];
|
||||||
|
} else {
|
||||||
|
var coords = turf.getCoords(multiPolygon);
|
||||||
|
polygons = coords.map(function (coord) {
|
||||||
|
var poly = turf.polygon(coord);
|
||||||
|
poly.bbox = turf.bbox(poly);
|
||||||
|
poly.properties = multiPolygon.properties;
|
||||||
|
return poly;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return polygons;
|
||||||
|
}
|
||||||
|
|
||||||
|
function analysis(params) {
|
||||||
|
let analysisMethod = params.analysisMethod;
|
||||||
|
if (analysisMethod === "isolines") {
|
||||||
|
|
||||||
|
let grid = interpolate(params.pointGrid, params.analysisCellSize, params.options);
|
||||||
|
return turf.isolines(grid, params.breaks, { zProperty: params.zProperty });
|
||||||
|
|
||||||
|
} else if (analysisMethod === "buffer") {
|
||||||
|
|
||||||
|
let buffer = turf.buffer(params.geoJson, params.radius, { unit: params.unit });
|
||||||
|
this.processBuffer(buffer, params.isSave);
|
||||||
|
if (params.isUnion) {
|
||||||
|
if (buffer.features.length > 0) {
|
||||||
|
var unied = this.unionFeature(buffer, true);
|
||||||
|
var result = this.toPolygons(unied);
|
||||||
|
return turf.featureCollection(result);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue