commit ec7b6cfe2a32a1ee8639337c0ba7c6418508a171
parent abf2683238e3d9759acd34fa7eaeacee8da78d46
Author: Sergej Orlov <wladimirych@gmail.com>
Date: Thu, 9 Feb 2017 11:59:12 +0300
[print control] map render; save pdf; hash state; tileLayer, Yandex. Bing
Diffstat:
11 files changed, 683 insertions(+), 57 deletions(-)
diff --git a/src/App.js b/src/App.js
@@ -70,7 +70,9 @@ function setUp() {
/////////// controls bottom-left corner
- new L.Control.PrintPages({position: 'bottomleft'}).addTo(map);
+ new L.Control.PrintPages({position: 'bottomleft'})
+ .addTo(map)
+ .enableHashState('p');
/////////// controls bottom-right corner
diff --git a/src/lib/leaflet.control.printPages/control.js b/src/lib/leaflet.control.printPages/control.js
@@ -5,8 +5,13 @@ import 'lib/controls-styles/controls-styles.css';
import './control.css';
import PageFeature from './pageFeature';
import Contextmenu from 'lib/contextmenu';
-import {renderMap} from './map-render'
+import {renderPages} from './map-render'
import formHtml from './form.html';
+import {notify, notifyXhrError} from 'lib/notifications';
+import {makePdf} from './pdf';
+import {saveAs} from 'browser-filesaver';
+import {blobFromString} from 'lib/binary-strings';
+import 'lib/leaflet.hashState/leaflet.hashState';
ko.extenders.checkNumberRange = function(target, range) {
return ko.pureComputed({
@@ -23,9 +28,23 @@ ko.extenders.checkNumberRange = function(target, range) {
).extend({notify: 'always'});
};
+function savePagesPdf(imagesInfo, resolution) {
+ let pdf = makePdf(imagesInfo, resolution);
+ pdf = blobFromString(pdf);
+ saveAs(pdf, 'map.pdf');
+}
+
+function savePageJpg(page) {
+ saveAs(blobFromString(page.data), 'map.jpg');
+}
+
L.Control.PrintPages = L.Control.extend({
options: {position: 'bottomleft'},
+ includes: [L.Mixin.Events, L.Mixin.HashState],
+
+ stateChangeEvents: ['change'],
+
pageSizes: [
{'name': 'A1', width: 594, height: 841},
{'name': 'A2', width: 420, height: 594},
@@ -56,6 +75,12 @@ L.Control.PrintPages = L.Control.extend({
this.scale.subscribe(this.onPageSizeChanged, this);
this.resolution.subscribe(this.onPageSizeChanged, this);
this.pageSizeDescription = ko.pureComputed(this._displayPageSize, this);
+
+ //hash state notifications
+ this.scale.subscribe(this.notifyChange, this);
+ this.printSize.subscribe(this.notifyChange, this);
+ this.resolution.subscribe(this.notifyChange, this);
+ this.zoomLevel.subscribe(this.notifyChange, this);
},
onAdd: function(map) {
@@ -82,27 +107,32 @@ L.Control.PrintPages = L.Control.extend({
L.DomUtil.addClass(this._container, 'minimized');
},
- addPage: function(isLandsacape) {
+ addPage: function(isLandsacape, center) {
let [pageWidth, pageHeight] = this.printSize();
if (isLandsacape) {
[pageWidth, pageHeight] = [pageHeight, pageWidth];
}
- const page = new PageFeature(this._map.getCenter(), [pageWidth, pageHeight],
+ if (!center) {
+ center = this._map.getCenter();
+ }
+ const page = new PageFeature(center, [pageWidth, pageHeight],
this.scale(), (this.pages.length + 1).toString()
);
+ page._rotated = isLandsacape;
page.addTo(this._map);
this.pages.push(page);
let cm = new Contextmenu(this.makePageContexmenuItems.bind(this, page));
page.on('contextmenu', cm.show, cm);
page.on('click', this.rotatePage.bind(this, page));
page.on('move', this.updateFormZooms, this);
+ page.on('moveend', this.notifyChange, this);
this.updateFormZooms();
+ this.notifyChange();
return page
},
addLandscapePage: function() {
const page = this.addPage(true);
- page._rotated = true;
},
addPortraitPage: function() {
@@ -116,20 +146,101 @@ L.Control.PrintPages = L.Control.extend({
for (; i < this.pages.length; i++) {
this.pages[i].setLabel((i + 1).toString());
}
+ this.notifyChange();
this.updateFormZooms()
},
removePages: function() {
this.pages.forEach((page) => page.removeFrom(this._map));
this.pages = [];
+ this.notifyChange();
this.updateFormZooms();
},
- savePdf: function(data) {
+ onSavePdfClicked: function() {
+ if (!this.pages.length) {
+ notify('Add some pages to print');
+ return;
+ }
+ this.savePdf();
+ },
+
+ zoomForPrint: function() {
+ let zoom = this.zoomLevel();
+ if (zoom === 'auto') {
+ zoom = this.suggestZooms()
+ } else {
+ zoom = {mapZoom: zoom, satZoom: zoom}
+ }
+ return zoom;
+ },
+
+ incrementProgress: function(inc, range) {
+ this.downloadProgressRange(range);
+ this.downloadProgressDone((this.downloadProgressDone() || 0) + inc);
+ },
+
+ savePdf: function() {
if (!this._map) {
return;
}
- renderMap(this._map);
+ this.downloadProgressDone(0);
+ this.makingPdf(true);
+ const pages = this.pages.map((page) => {
+ return {
+ latLngBounds: page.getLatLngBounds(),
+ printSize: page.getPrintSize()
+ }
+ }
+ );
+ const resolution = this.resolution();
+ renderPages({
+ map: this._map,
+ pages,
+ zooms: this.zoomForPrint(),
+ resolution,
+ progressCallback: this.incrementProgress.bind(this)
+ }
+ ).then((images) => {
+ if (images) {
+ savePagesPdf(images, resolution)
+ }
+ }
+ ).catch((e) => {
+ if (e.status !== undefined) {
+ notifyXhrError(e, 'map');
+ } else {
+ notify(e);
+ }
+ }
+ ).then(() => this.makingPdf(false));
+ },
+
+ savePageJpg: function(page) {
+ const pages = [{
+ latLngBounds: page.getLatLngBounds(),
+ printSize: page.getPrintSize()
+ }];
+ this.downloadProgressDone(0);
+ this.makingPdf(true);
+ renderPages({
+ map: this._map,
+ pages,
+ zooms: this.zoomForPrint(),
+ resolution: this.resolution(),
+ progressCallback: this.incrementProgress.bind(this)
+ }
+ )
+ .then((images) => savePageJpg(images[0]))
+ .catch((e) => {
+ // throw e;
+ if (e.status !== undefined) {
+ notifyXhrError(e, 'map');
+ } else {
+ notify(e);
+ }
+ }
+ ).then(() => this.makingPdf(false));
},
onPageSizeChanged: function() {
@@ -151,7 +262,7 @@ L.Control.PrintPages = L.Control.extend({
'-',
{text: 'Delete', callback: this.removePage.bind(this, page)},
'-',
- {text: 'Save image', callback: this.savePageJpg.bind(this, page), disabled: true}
+ {text: 'Save image', callback: this.savePageJpg.bind(this, page)}
];
if (this.pages.length > 1) {
items.push({text: 'Change order', separator: true});
@@ -172,10 +283,7 @@ L.Control.PrintPages = L.Control.extend({
rotatePage: function(page) {
page._rotated = !page._rotated;
page.rotate();
- },
-
- savePageJpg: function(page) {
-
+ this.notifyChange();
},
renumberPage: function(page, newIndex) {
@@ -185,6 +293,7 @@ L.Control.PrintPages = L.Control.extend({
for (let i = Math.min(oldIndex, newIndex); i < this.pages.length; i++) {
this.pages[i].setLabel((i + 1).toString());
}
+ this.notifyChange();
},
_printSize: function() {
@@ -231,6 +340,64 @@ L.Control.PrintPages = L.Control.extend({
}
}
return `${width} x ${height} mm`;
+ },
+
+ notifyChange: function() {
+ this.fire('change');
+ },
+
+ serializeState: function() {
+ const pages = this.pages;
+ let state = null;
+ if (pages.length) {
+ state = [];
+ state.push(this.scale().toString());
+ state.push(this.resolution().toString());
+ state.push(this.zoomLevel().toString());
+ state.push(this.pageWidth().toString());
+ state.push(this.pageHeight().toString());
+ state.push(this.marginLeft().toString());
+ state.push(this.marginRight().toString());
+ state.push(this.marginTop().toString());
+ state.push(this.marginBottom().toString());
+ for (let page of pages) {
+ let latLng = page.getLatLng();
+ state.push(latLng.lat.toFixed(5));
+ state.push(latLng.lng.toFixed(5));
+ state.push(page._rotated ? '1' : '0');
+ }
+ }
+ return state;
+ },
+
+ unserializeState: function(state) {
+ if (!state || !state.length) {
+ return false;
+ }
+ this.removePages();
+ state = [...state];
+ this.scale(state.shift());
+ this.resolution(state.shift());
+ this.zoomLevel(state.shift());
+ this.pageWidth(state.shift());
+ this.pageHeight(state.shift());
+ this.marginLeft(state.shift());
+ this.marginRight(state.shift());
+ this.marginTop(state.shift());
+ this.marginBottom(state.shift());
+ let lat, lng, rotated;
+ while (state.length >= 3) {
+ lat = parseFloat(state.shift());
+ lng = parseFloat(state.shift());
+ rotated = parseInt(state.shift(), 10);
+ if (isNaN(lat) || isNaN(lng) || lat < -85 || lat > 85 || lng < -180 || lng > 180) {
+ break;
+ }
+ this.addPage(!!rotated, L.latLng(lat, lng));
+ }
+ return true;
+
+
}
}
-);
-\ No newline at end of file
+);
diff --git a/src/lib/leaflet.control.printPages/form.html b/src/lib/leaflet.control.printPages/form.html
@@ -78,7 +78,7 @@
<div class="download-button-row">
<div class="button-minimize" data-bind="click: setMinimized"></div>
<a class="text-button button-save" data-bind="
- click: savePdf,
+ click: onSavePdfClicked,
visible: !makingPdf()">Save PDF</a>
<div data-bind="
component: {
diff --git a/src/lib/leaflet.control.printPages/map-render.js b/src/lib/leaflet.control.printPages/map-render.js
@@ -1,46 +1,51 @@
+import L from 'leaflet';
+import 'lib/leaflet.layer.rasterize';
+import {XHRQueue} from 'lib/xhr-promise';
-function getZIndex(el) {
- return parseInt(window.getComputedStyle(el).zIndex, 10) || 0;
-}
-function compare(i1, i2) {
- if (i1 < i2) {
- return -1;
- } else if (i1 > i2) {
- return 1;
+function getLayersForPrint(map, xhrQueue) {
+ function getZIndex(el) {
+ return parseInt(window.getComputedStyle(el).zIndex, 10) || 0;
}
- return 0;
-}
-function compareArrays(ar1, ar2) {
- const len = Math.min(ar1.length, ar2.length);
- for (let i = 0; i < len; i++) {
- let c = compare(ar1[i], ar2[i]);
- if (c) {
- return c;
+ function compare(i1, i2) {
+ if (i1 < i2) {
+ return -1;
+ } else if (i1 > i2) {
+ return 1;
}
+ return 0;
}
- return compare(ar1.length, ar2.length);
-}
-function getLayerZOrder(layer) {
- let el = layer._container || layer._path;
- if (!el) {
- throw TypeError('Unsupported layer type');
+ function compareLayersOrder(layer1, layer2) {
+ return compareArrays(getLayerZOrder(layer1), getLayerZOrder(layer2));
}
- const order = [];
- while (el !== layer._map._container) {
- order.push(getZIndex(el));
- el = el.parentElement;
+
+ function getLayerZOrder(layer) {
+ let el = layer._container || layer._path;
+ if (!el) {
+ throw TypeError('Unsupported layer type');
+ }
+ const order = [];
+ while (el !== layer._map._container) {
+ order.push(getZIndex(el));
+ el = el.parentElement;
+ }
+ return order.reverse()
}
- return order.reverse()
-}
-function compareLayersOrder(layer1, layer2) {
- return compareArrays(getLayerZOrder(layer1), getLayerZOrder(layer2));
-}
-function getLayersForPrint(map) {
+ function compareArrays(ar1, ar2) {
+ const len = Math.min(ar1.length, ar2.length);
+ for (let i = 0; i < len; i++) {
+ let c = compare(ar1[i], ar2[i]);
+ if (c) {
+ return c;
+ }
+ }
+ return compare(ar1.length, ar2.length);
+ }
+
let layers = [];
map.eachLayer((layer) => {
if (layer.options.print) {
@@ -49,20 +54,202 @@ function getLayersForPrint(map) {
}
);
layers.sort(compareLayersOrder);
- layers = layers.map((l) => {l.clone()});
+ layers = layers.map((l) => l.cloneForPrint({xhrQueue}));
return layers;
}
-function renderPage(layers, bounds, zoom, resolution) {
- // const canvas =
+class PageComposer {
+ constructor(destSize, pixelBoundsAtZoom24) {
+ this.destSize = destSize;
+ this.projectedBounds = pixelBoundsAtZoom24;
+ this.currentCanvas = null;
+ this.currentZoom = null;
+ this.targetCanvas = this.createCanvas(destSize);
+ }
+
+ createCanvas(size) {
+ const canvas = L.DomUtil.create('canvas');
+ canvas.width = size.x;
+ canvas.height = size.y;
+ return canvas;
+ }
+
+ putTile({image, tilePos, tileSize, zoom}) {
+ if (image === null) {
+ return;
+ }
+ if (zoom !== this.currentZoom) {
+ this.mergeCurrentCanvas();
+ this.setupCurrentCanvas(zoom);
+ }
+ this.currentCanvas.getContext('2d').drawImage(image, tilePos.x, tilePos.y, tileSize.x, tileSize.y);
+ }
+
+ setupCurrentCanvas(zoom) {
+ const q = 1 << (24 - zoom);
+ const
+ topLeft = this.projectedBounds.min.divideBy(q).round(),
+ bottomRight = this.projectedBounds.max.divideBy(q).round(),
+ size = bottomRight.subtract(topLeft);
+ this.currentCanvas = this.createCanvas(size);
+ this.currentZoom = zoom;
+ // this.currentOffset = topLeft;
+ }
+
+ mergeCurrentCanvas() {
+ if (!this.currentCanvas) {
+ return;
+ }
+ this.targetCanvas.getContext('2d').drawImage(this.currentCanvas, 0, 0,
+ this.destSize.x, this.destSize.y
+ );
+ this.currentCanvas = null;
+ }
+
+ getDataUrl() {
+ this.mergeCurrentCanvas();
+ const dataUrl = this.targetCanvas.toDataURL("image/jpeg");
+ this.targetCanvas = null;
+ return dataUrl;
+ }
+}
+
+function getTempMap(zoom) {
+ const container = L.DomUtil.create('div', '', document.body);
+ Object.assign(container.style, {
+ width: '100px',
+ height: '100px',
+ position: 'absolute',
+ left: '0',
+ top: '0',
+ // visibility: 'hidden'
+ }
+ );
+ const map = L.map(container, {fadeAnimation: false, zoomAnimation: false, inertia: false});
+ map.setView(L.latLng(0, 0), zoom);
+ return map;
+}
+
+function disposeMap(map) {
+ const container = map._container;
+ map.remove();
+ L.DomUtil.remove(container);
+}
+
+async function* iterateLayersTiles(layers, latLngBounds, zooms) {
+ const defaultXHROptions = {
+ responseType: 'blob',
+ timeout: 10000,
+ isResponseSuccess: (xhr) => xhr.status === 200 || xhr.status === 404
+ };
+ let doStop;
+ for (let layer of layers) {
+ let zoom;
+ if (layer.options.scaleDependent) {
+ zoom = zooms.mapZoom;
+ } else {
+ zoom = zooms.satZoom;
+ }
+ let map = getTempMap(zoom);
+ let pixelBounds = L.bounds(
+ map.project(latLngBounds.getNorthWest()).round(),
+ map.project(latLngBounds.getSouthEast()).round()
+ );
+ map.addLayer(layer);
+ let {iterateTilePromises, count} = await layer.getTilesInfo({xhrOptions: defaultXHROptions, pixelBounds});
+ for (let tilePromise of iterateTilePromises()) {
+ tilePromise.tilePromise =
+ tilePromise.tilePromise.then((tileInfo) => Object.assign(tileInfo, {zoom, progressInc: 1 / count}));
+ doStop = yield tilePromise;
+ if (doStop) {
+ tilePromise.abortLoading();
+ console.log('DO STOP3');
+ break;
+ }
+ }
+ disposeMap(map);
+ if (doStop) {
+ break;
+ }
+ }
}
-function savePageJpg(map, bounds, zoom, resolution) {
- const layers = getLayersForPrint(map);
+async function* promiseQueueBuffer(source, maxActive) {
+ const queue = [];
+ while (queue.length < maxActive) {
+ let {value, done} = await source.next();
+ if (done) {
+ break;
+ }
+ queue.push(value);
+ }
+
+ while (queue.length) {
+ let doStop = yield queue.shift();
+ if (doStop) {
+ console.log('DO STOP2');
+ let {value, done} = await source.next(true);
+ if (!done) {
+ queue.push(value);
+ }
+ for (let {abortLoading} of queue) {
+ abortLoading();
+ }
+
+ return;
+ }
+ let {value, done} = await source.next();
+ if (!done) {
+ queue.push(value);
+ }
+ }
}
-function savePagesPdf(map, boundsList, zoom, resolution) {
- const layers = getLayersForPrint(map);
+
+async function renderPages({map, pages, zooms, resolution, progressCallback}) {
+ const xhrQueue = new XHRQueue();
+ const layers = getLayersForPrint(map, xhrQueue);
+ const progressRange = pages.length * layers.length;
+ const pageImagesInfo = [];
+ for (let page of pages) {
+ let destPixelSize = page.printSize.multiplyBy(resolution / 25.4).round();
+ let pixelBounds = L.bounds(
+ map.project(page.latLngBounds.getNorthWest(), 24).round(),
+ map.project(page.latLngBounds.getSouthEast(), 24).round()
+ );
+
+ const composer = new PageComposer(destPixelSize, pixelBounds);
+ let tilesIterator = await iterateLayersTiles(layers, page.latLngBounds, zooms);
+ let queuedTilesIterator = promiseQueueBuffer(tilesIterator, 20);
+ while (true) {
+ let {value: tilePromise, done} = await queuedTilesIterator.next();
+ iterateLayersTiles(layers, page.latLngBounds, zooms);
+ if (done) {
+ break;
+ }
+ let tileInfo;
+ try {
+ tileInfo = await tilePromise.tilePromise;
+ } catch (e) {
+ console.log('DO STOP1');
+ queuedTilesIterator.next(true);
+ throw e;
+ }
+ progressCallback(tileInfo.progressInc, progressRange);
+ composer.putTile(tileInfo);
+ }
+ const dataUrl = composer.getDataUrl();
+ let data = dataUrl.substring(dataUrl.indexOf(',') + 1);
+ data = atob(data);
+ pageImagesInfo.push({
+ data,
+ width: destPixelSize.x,
+ height: destPixelSize.y
+ }
+ );
+ }
+ return pageImagesInfo;
}
-export {savePageJpg, savePagesPdf};
-\ No newline at end of file
+
+export {renderPages};
+\ No newline at end of file
diff --git a/src/lib/leaflet.control.printPages/pageFeature.js b/src/lib/leaflet.control.printPages/pageFeature.js
@@ -75,10 +75,13 @@ const PageFeature = L.Marker.extend({
setSize: function(paperSize, scale) {
this.paperSize = paperSize;
this.scale = scale;
- console.log(paperSize, scale);
this.updateView();
},
+ getPrintSize: function() {
+ return L.point(...this.paperSize);
+ },
+
rotate: function(e) {
this.paperSize = [this.paperSize[1], this.paperSize[0]];
this.updateView();
diff --git a/src/lib/leaflet.control.printPages/pdf.js b/src/lib/leaflet.control.printPages/pdf.js
@@ -0,0 +1,155 @@
+function header() {
+ return '%PDF-1.3';
+}
+
+
+function eof() {
+ return '%%EOF\n';
+}
+
+
+function recCatalog() {
+ return (
+`1 0 obj
+<< /Type /Catalog
+/Pages 2 0 R
+>>
+endobj`);
+}
+
+
+function recPages(count) {
+ let kidsIds = [];
+ for (let i = 0; i < count; i++) {
+ kidsIds.push(`${i * 3 + 3} 0 R`);
+ }
+ kidsIds = kidsIds.join(' ');
+ return (
+`2 0 obj
+<< /Type /Pages
+/Kids [ ${kidsIds} ]
+/Count ${count}
+>>
+endobj`);
+}
+
+
+function recPage(serialNum, width, height) {
+ const pageRecId = serialNum * 3 + 3;
+ const contentRecId = pageRecId + 1;
+ const imageRecId = pageRecId + 2;
+ return (
+`${pageRecId} 0 obj
+<< /Type /Page
+/Parent 2 0 R
+/MediaBox [0 0 ${width} ${height}]
+/Contents ${contentRecId} 0 R
+/Resources <<
+ /XObject << /Im${serialNum} ${imageRecId} 0 R >>
+ /ProcSet [ /PDF /Text /ImageC ]
+ >>
+>>
+endobj`);
+}
+
+
+function recContent(serialNum, width, height) {
+ const pageRecId = serialNum * 3 + 3;
+ const contentRecId = pageRecId + 1;
+ const contents = (
+`q
+${width} 0 0 ${height} 0 0 cm
+/Im${serialNum} Do
+Q `);
+ return (
+`${contentRecId} 0 obj
+<<
+/Length ${contents.length + 1}
+>>
+stream
+${contents}
+endstream
+endobj`);
+}
+
+
+function recImage(serialNum, widthPixels, heightPixels, data) {
+ const pageRecId = serialNum * 3 + 3;
+ const imageRecId = pageRecId + 2;
+
+ return (
+`${imageRecId} 0 obj
+<< /Type /XObject
+/Subtype /Image
+/Name /Im${serialNum}
+/Filter [ /DCTDecode ]
+/Width ${widthPixels}
+/Height ${heightPixels}
+/ColorSpace /DeviceRGB
+/BitsPerComponent 8
+/Length ${data.length}
+>>
+stream
+${data}
+endstream
+endobj`);
+}
+
+
+function recTrailer(recOffsets, currentOffset) {
+ const trailer = [];
+ trailer.push(
+`xref
+0 ${recOffsets.length + 1}
+0000000000 65535 f `);
+ const zeroes = '0000000000';
+ for (let offset of recOffsets) {
+ offset = offset.toString();
+ let padding = zeroes.substr(offset.length);
+ trailer.push(`${padding}${offset} 00000 n `);
+ }
+
+ trailer.push(
+`trailer
+<< /Root 1 0 R
+/Size ${recOffsets.length + 1}
+>>
+startxref
+${currentOffset}`);
+ return trailer.join('\n');
+}
+
+
+function makePdf(imagesInfo, resolution) {
+ let offset = 0;
+ const offsets = [];
+ let pdf = [];
+
+ function addRec(s, skipOffset) {
+ pdf.push(s);
+ offset += s.length + 1;
+ if (!skipOffset) {
+ offsets.push(offset);
+ }
+ }
+
+ addRec(header(), true);
+ addRec(recCatalog());
+ addRec(recPages(imagesInfo.length));
+ for (let [i, {data, width, height}] of imagesInfo.entries()) {
+ let widthPoints = width / resolution * 72,
+ heightPoints = height / resolution * 72;
+ addRec(recPage(i, widthPoints, heightPoints));
+ addRec(recContent(i, widthPoints, heightPoints));
+ addRec(recImage(i, width, height, data));
+ }
+ addRec(recTrailer(offsets, offset), true);
+ addRec(eof(), true);
+
+ pdf = pdf.join('\n');
+ return pdf;
+}
+
+
+export {makePdf};
+
diff --git a/src/lib/leaflet.layer.rasterize/Bing.js b/src/lib/leaflet.layer.rasterize/Bing.js
@@ -0,0 +1,30 @@
+import L from 'leaflet';
+import 'lib/leaflet.layer.bing';
+
+L.BingLayer.include({
+ whenReady: function() {
+ if (this._url) {
+ return Promise.resolve();
+ } else {
+ return new Promise((resolve) => {
+ let i = setInterval(() => {
+ if (this._url) {
+ clearInterval(i);
+ resolve();
+ }
+ }, 50
+ )
+ }
+ );
+ }
+ },
+
+ cloneForPrint: function(options) {
+ return new L.BingLayer(this._key, L.Util.extend({}, this.options, options));
+ },
+
+ getTilesInfo: function(printOptions) {
+ return this.whenReady().then(() => L.TileLayer.prototype.getTilesInfo.call(this, printOptions));
+ }
+ }
+);
diff --git a/src/lib/leaflet.layer.rasterize/TileLayer.js b/src/lib/leaflet.layer.rasterize/TileLayer.js
@@ -0,0 +1,52 @@
+import L from 'leaflet';
+import urlViaCorsProxy from 'lib/CORSProxy';
+import {imgFromDataString} from './imgFromDataString';
+
+L.TileLayer.include({
+ cloneForPrint: function(options) {
+ return L.tileLayer(this._url, L.Util.extend({}, this.options, options));
+ },
+
+ getTilesInfo: function(printOptions) {
+ const {pixelBounds, xhrOptions} = printOptions;
+ const tileRange = this._pxBoundsToTileRange(pixelBounds);
+ const topLeft = pixelBounds.min;
+ const tilePromiseIterator = (function*() {
+ for (let j = tileRange.min.y; j <= tileRange.max.y; j++) {
+ for (let i = tileRange.min.x; i <= tileRange.max.x; i++) {
+ let coords = new L.Point(i, j);
+ coords.z = this._tileZoom;
+
+ if (!this._isValidTile(coords)) {
+ continue;
+ }
+
+ let url = this.getTileUrl(coords);
+ if (this.options.noCors) {
+ url = urlViaCorsProxy(url);
+ }
+ let tilePos = this._getTilePos(coords);
+ const coordsPlusOne = coords.add(L.point(1, 1));
+ coordsPlusOne.z = coords.z;
+ const tileSize = this._getTilePos(coordsPlusOne).subtract(tilePos);
+ tilePos = tilePos.add(this._level.origin).subtract(topLeft);
+ let promise = this.options.xhrQueue.put(url, xhrOptions);
+ yield {
+ tilePromise: promise.then(imgFromDataString).then((image) => {
+ return {image, tilePos, tileSize};
+ }
+ ),
+ abortLoading: () => promise.abort()
+ };
+ }
+ }
+ }).bind(this);
+ return Promise.resolve({
+ iterateTilePromises: tilePromiseIterator,
+ count: (tileRange.max.x - tileRange.min.x + 1) * (tileRange.max.y - tileRange.min.y + 1)
+ }
+ );
+ }
+ }
+);
+
diff --git a/src/lib/leaflet.layer.rasterize/Yandex.js b/src/lib/leaflet.layer.rasterize/Yandex.js
@@ -0,0 +1,8 @@
+import L from 'leaflet';
+import 'lib/leaflet.layer.yandex';
+
+L.Layer.Yandex.include({
+ cloneForPrint: function(options) {
+ return new L.Layer.Yandex(this._mapType, L.Util.extend({}, this.options, options));
+ },
+});
+\ No newline at end of file
diff --git a/src/lib/leaflet.layer.rasterize/imgFromDataString.js b/src/lib/leaflet.layer.rasterize/imgFromDataString.js
@@ -0,0 +1,19 @@
+function imgFromDataString(xhr) {
+ if (xhr.status === 200 && xhr.response.size) {
+ const image = new Image();
+ let blobUrl = window.URL.createObjectURL(xhr.response);
+ const promise = new Promise((resolve) => {
+ image.onload = () => {
+ resolve(image);
+ window.URL.revokeObjectURL(blobUrl);
+ };
+ }
+ );
+ image.src = blobUrl;
+ return promise;
+ } else {
+ return null;
+ }
+}
+
+export {imgFromDataString}
diff --git a/src/lib/leaflet.layer.rasterize/index.js b/src/lib/leaflet.layer.rasterize/index.js
@@ -0,0 +1,3 @@
+import './TileLayer'
+import './Bing'
+import './Yandex'