leaflet.bigimage
Version:
A leaflet plugin that allows users to receive a map as an Big image and donwload it.
415 lines (335 loc) • 15.8 kB
JavaScript
/*
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: 'polyline-measure-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('polyline-measure-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);
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('polyline-measure-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 = "my-image.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));