commit f697888955988fe099ce257232bdf4574c2c847f
parent c52ab44a55d3cc1e0c101204208b02be7f459795
Author: Sergej Orlov <wladimirych@gmail.com>
Date: Wed, 22 Feb 2017 01:44:46 +0300
[print] added printing for tracks
Diffstat:
7 files changed, 216 insertions(+), 43 deletions(-)
diff --git a/src/lib/leaflet.control.printPages/control.js b/src/lib/leaflet.control.printPages/control.js
@@ -197,6 +197,7 @@ L.Control.PrintPages = L.Control.extend({
pages,
zooms: this.zoomForPrint(),
resolution,
+ scale: this.scale(),
progressCallback: this.incrementProgress.bind(this)
}
).then((images) => {
@@ -226,6 +227,7 @@ L.Control.PrintPages = L.Control.extend({
pages,
zooms: this.zoomForPrint(),
resolution: this.resolution(),
+ scale: this.scale(),
progressCallback: this.incrementProgress.bind(this)
}
)
diff --git a/src/lib/leaflet.control.printPages/map-render.js b/src/lib/leaflet.control.printPages/map-render.js
@@ -58,6 +58,38 @@ function getLayersForPrint(map, xhrQueue) {
return layers;
}
+function blendMultiplyCanvas(src, dest) {
+ var s_data = src.getContext('2d').getImageData(0, 0, src.width, src.height).data;
+ var d_image_data = dest.getContext('2d').getImageData(0, 0, src.width, src.height);
+ var d_data = d_image_data.data;
+ var data_length = s_data.length,
+ sr, sg, sb, sa,
+ dr, dg, db,
+ l;
+ for (var i = 0; i < data_length; i += 4) {
+ sa = s_data[i + 3];
+ if (sa) {
+ sr = s_data[i];
+ sg = s_data[i + 1];
+ sb = s_data[i + 2];
+ dr = d_data[i];
+ dg = d_data[i + 1];
+ db = d_data[i + 2];
+
+ l = (dr + dg + db) / 3;
+ l = l / 255 * 192 + 63;
+ dr = sr / 255 * l;
+ dg = sg / 255 * l;
+ db = sb / 255 * l;
+
+ d_data[i] = dr;
+ d_data[i + 1] = dg;
+ d_data[i + 2] = db;
+ }
+ }
+ dest.getContext('2d').putImageData(d_image_data, 0, 0);
+}
+
class PageComposer {
constructor(destSize, pixelBoundsAtZoom24) {
this.destSize = destSize;
@@ -74,36 +106,59 @@ class PageComposer {
return canvas;
}
- putTile({image, tilePos, tileSize, zoom}) {
- if (image === null) {
+ putTile(tileInfo) {
+ if (tileInfo.image === null) {
return;
}
+ let zoom;
+ if (tileInfo.isOverlay) {
+ zoom = 'overlay';
+ } else {
+ zoom = tileInfo.zoom;
+ }
if (zoom !== this.currentZoom) {
this.mergeCurrentCanvas();
this.setupCurrentCanvas(zoom);
}
- this.currentCanvas.getContext('2d').drawImage(image, tilePos.x, tilePos.y, tileSize.x, tileSize.y);
+ if (tileInfo.isOverlay) {
+ tileInfo.draw(this.currentCanvas);
+ } else {
+ const ctx = this.currentCanvas.getContext('2d');
+ const {tilePos, tileSize} = tileInfo;
+ ctx.drawImage(tileInfo.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(),
+ let size;
+ if (zoom === 'overlay') {
+ size = this.destSize;
+ } else {
+ 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() {
+ console.time('merge canvas');
if (!this.currentCanvas) {
return;
}
- this.targetCanvas.getContext('2d').drawImage(this.currentCanvas, 0, 0,
- this.destSize.x, this.destSize.y
- );
+ if (this.currentZoom === 'overlay') {
+ blendMultiplyCanvas(this.currentCanvas, this.targetCanvas);
+ } else {
+ this.targetCanvas.getContext('2d').drawImage(this.currentCanvas, 0, 0,
+ this.destSize.x, this.destSize.y
+ );
+ }
this.currentCanvas = null;
+ console.timeEnd('merge canvas');
}
getDataUrl() {
@@ -150,7 +205,7 @@ function disposeMap(map) {
L.DomUtil.remove(container);
}
-async function* iterateLayersTiles(layers, latLngBounds, zooms) {
+async function* iterateLayersTiles(layers, latLngBounds, destPixelSize, resolution, scale, zooms) {
const defaultXHROptions = {
responseType: 'blob',
timeout: 10000,
@@ -170,12 +225,21 @@ async function* iterateLayersTiles(layers, latLngBounds, zooms) {
);
let map = getTempMap(zoom, layer._rasterizeNeedsFullSizeMap, pixelBounds);
map.addLayer(layer);
- let {iterateTilePromises, count} = await layer.getTilesInfo({xhrOptions: defaultXHROptions, pixelBounds});
+ let {iterateTilePromises, count} = await layer.getTilesInfo({
+ xhrOptions: defaultXHROptions,
+ pixelBounds,
+ latLngBounds,
+ destPixelSize,
+ resolution,
+ scale,
+ zoom
+ }
+ );
let lastPromise;
for (let tilePromise of iterateTilePromises()) {
lastPromise = tilePromise.tilePromise;
tilePromise.tilePromise =
- tilePromise.tilePromise.then((tileInfo) => Object.assign(tileInfo, {zoom, progressInc: 1 / count}));
+ tilePromise.tilePromise.then((tileInfo) => Object.assign({zoom, progressInc: 1 / count}, tileInfo));
doStop = yield tilePromise;
if (doStop) {
tilePromise.abortLoading();
@@ -224,7 +288,7 @@ async function* promiseQueueBuffer(source, maxActive) {
}
-async function renderPages({map, pages, zooms, resolution, progressCallback}) {
+async function renderPages({map, pages, zooms, resolution, scale, progressCallback}) {
const xhrQueue = new XHRQueue();
const layers = getLayersForPrint(map, xhrQueue);
const progressRange = pages.length * layers.length;
@@ -237,11 +301,10 @@ async function renderPages({map, pages, zooms, resolution, progressCallback}) {
);
const composer = new PageComposer(destPixelSize, pixelBounds);
- let tilesIterator = await iterateLayersTiles(layers, page.latLngBounds, zooms);
+ let tilesIterator = await iterateLayersTiles(layers, page.latLngBounds, destPixelSize, resolution, scale, zooms);
let queuedTilesIterator = promiseQueueBuffer(tilesIterator, 20);
while (true) {
let {value: tilePromise, done} = await queuedTilesIterator.next();
- iterateLayersTiles(layers, page.latLngBounds, zooms);
if (done) {
break;
}
diff --git a/src/lib/leaflet.control.track-list/track-list.js b/src/lib/leaflet.control.track-list/track-list.js
@@ -10,8 +10,6 @@ import {fetch} from 'lib/xhr-promise';
import geoExporters from './lib/geo_file_exporters';
import copyToClipboard from 'lib/clipboardCopy';
import {saveAs} from 'browser-filesaver';
-import 'lib/leaflet.polyline-edit';
-import 'lib/leaflet.polyline-measure';
import 'lib/leaflet.layer.canvasMarkers';
import 'lib/leaflet.lineutil.simplifyLatLngs';
import iconFromBackgroundImage from 'lib/iconFromBackgroundImage';
@@ -19,9 +17,23 @@ import 'lib/controls-styles/controls-styles.css';
import 'lib/leaflet.control.elevation-profile';
import 'lib/leaflet.control.commons';
import {blobFromString} from 'lib/binary-strings';
+import 'lib/leaflet.polyline-edit';
+import 'lib/leaflet.polyline-measure';
+
+
+
+const TrackSegment = L.MeasuredLine.extend({
+ includes: L.Polyline.EditMixin,
+
+ options: {
+ weight: 6,
+ lineCap: 'butt',
+ opacity: 0.5,
+
+ }
+});
+TrackSegment.mergeOptions(L.Polyline.EditMixinOptions);
-var MeasuredEditableLine = L.MeasuredLine.extend({});
-MeasuredEditableLine.include(L.Polyline.EditMixin);
L.Control.TrackList = L.Control.extend({
options: {position: 'bottomright'},
@@ -604,12 +616,9 @@ L.Control.TrackList = L.Control.extend({
},
addTrackSegment: function(track, sourcePoints) {
- var polyline = new MeasuredEditableLine(sourcePoints || [], {
- weight: 6,
+ var polyline = new TrackSegment(sourcePoints || [], {
color: this.colors[track.color()],
- lineCap: 'butt',
- className: 'leaflet-editable-line',
- opacity: 0.5,
+ print: true
}
);
polyline._parentTrack = track;
diff --git a/src/lib/leaflet.layer.rasterize/MeasuredLine.js b/src/lib/leaflet.layer.rasterize/MeasuredLine.js
@@ -0,0 +1,100 @@
+import L from 'leaflet';
+import 'lib/leaflet.polyline-measure';
+
+L.Polyline.include({
+ printWidthMm: 1,
+
+ cloneForPrint: function(options) {
+ options = L.Util.extend({}, this.options, options);
+ const latlngs = this.getLatLngs().map((latlng) => latlng.clone());
+ return new L.Polyline(latlngs, options);
+ },
+
+ _makelatLngToCanvasPixelTransformer: function(printOptions) {
+ const projectedBounds = printOptions.pixelBounds;
+ const scale = projectedBounds.getSize().unscaleBy(printOptions.destPixelSize);
+ const origin = projectedBounds.min;
+ return function(latlng) {
+ return L.CRS.EPSG3857.latLngToPoint(latlng, printOptions.zoom).subtract(origin).unscaleBy(scale);
+ }
+ },
+
+
+ _drawRaster: function(canvas, printOptions) {
+ const latlngs = this.getLatLngs();
+ if (!latlngs.length || !this.getBounds().intersects(printOptions.latLngBounds)) {
+ return;
+ }
+
+ const ctx = canvas.getContext('2d');
+ ctx.lineWidth = this.printWidthMm / 25.4 * printOptions.resolution;
+ ctx.lineJoin = 'round';
+ ctx.strokeStyle = this.options.color;
+ const transform = this._makelatLngToCanvasPixelTransformer(printOptions);
+ let point;
+ ctx.beginPath();
+
+ point = transform(latlngs[0]);
+ ctx.moveTo(point.x, point.y);
+ for (let i = 1; i < latlngs.length; i++) {
+ point = transform(latlngs[i]);
+ ctx.lineTo(point.x, point.y);
+ }
+ ctx.stroke();
+ return true;
+ },
+
+
+ getTilesInfo: async function(printOptions) {
+ return {
+ iterateTilePromises: (function*() {
+ yield {
+ tilePromise: Promise.resolve({
+ draw: (canvas) => this._drawRaster(canvas, printOptions),
+ isOverlay: true
+ }
+ ),
+ abortLoading: () => {
+ }
+ }
+ }).bind(this),
+ count: 1
+ };
+ }
+ }
+);
+
+L.MeasuredLine.include({
+ tickFontSizeMm: 2.5,
+
+ cloneForPrint: function(options) {
+ options = L.Util.extend({}, this.options, options);
+ const latlngs = this.getLatLngs().map((latlng) => latlng.clone());
+ return new L.MeasuredLine(latlngs, options);
+ },
+
+ _drawRaster: function(canvas, printOptions) {
+ if (!L.Polyline.prototype._drawRaster.call(this, canvas, printOptions)) {
+ return;
+ }
+ if (!this.options.measureTicksShown) {
+ return;
+ }
+ const minTicksIntervalMeters = printOptions.scale * 1.5;
+ const ctx = canvas.getContext('2d');
+ const ticks = this.getTicksPositions(minTicksIntervalMeters);
+ const ticksPixelSize = this.tickFontSizeMm / 25.4 * printOptions.resolution;
+ const transform = this._makelatLngToCanvasPixelTransformer(printOptions);
+ ctx.font = ticksPixelSize + 'px verdana';
+ ctx.fillStyle = this.options.color;
+ for (let tick of ticks) {
+ let m = tick.transformMatrix;
+ let label = '\u2014 ' + Math.round((tick.distanceValue / 10)) / 100 + ' km';
+ let position = transform(tick.position);
+ ctx.setTransform(m[0], m[1], m[2], m[3], position.x, position.y);
+ ctx.fillText(label, 0, ticksPixelSize * 0.3);
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
+ }
+ }
+ }
+);
+\ No newline at end of file
diff --git a/src/lib/leaflet.layer.rasterize/index.js b/src/lib/leaflet.layer.rasterize/index.js
@@ -4,3 +4,4 @@ import './Yandex'
import './Google'
import './WestraPasses'
import './CanvasMarkers'
+import './MeasuredLine'
+\ No newline at end of file
diff --git a/src/lib/leaflet.polyline-edit/index.js b/src/lib/leaflet.polyline-edit/index.js
@@ -1,9 +1,9 @@
import L from 'leaflet';
import './edit_line.css';
-function cloneLatLng(ll) {
- return L.latLng(ll.lat, ll.lng);
-}
+L.Polyline.EditMixinOptions = {
+ className: 'leaflet-editable-line'
+};
L.Polyline.EditMixin = {
_nodeMarkersZOffset: 10000,
@@ -167,7 +167,7 @@ L.Polyline.EditMixin = {
makeNodeMarker: function(nodeIndex) {
var node = this.getLatLngs()[nodeIndex],
- marker = L.marker(cloneLatLng(node), {
+ marker = L.marker(node.clone(), {
icon: L.divIcon(
{className: 'line-editor-node-marker-halo', 'html': '<div class="line-editor-node-marker"></div>'}
),
@@ -252,7 +252,7 @@ L.Polyline.EditMixin = {
var nodes = this.getLatLngs(),
isAddingLeft = (index === 1 && this._drawingDirection === -1),
isAddingRight = (index === nodes.length - 1 && this._drawingDirection === 1);
- latlng = cloneLatLng(latlng);
+ latlng = latlng.clone();
this.spliceLatLngs(index, 0, latlng);
this.makeNodeMarker(index);
if (!isAddingLeft && (index >= 1)) {
@@ -300,7 +300,7 @@ L.Polyline.EditMixin = {
this._map.removeLayer(oldNode._nodeMarker);
delete oldNode._nodeMarker;
delete oldMarker._lineNode;
- latlng = cloneLatLng(latlng);
+ latlng = latlng.clone();
this.spliceLatLngs(index, 1, latlng);
this.makeNodeMarker(index);
if (oldNode._segmentOverlay) {
diff --git a/src/lib/leaflet.polyline-measure/index.js b/src/lib/leaflet.polyline-measure/index.js
@@ -10,11 +10,12 @@ function pointOnSegmentAtDistance(p1, p2, dist) {
}
-function sinCosFromSegment(segment) {
- var p1 = segment[0],
- p2 = segment[1],
+function sinCosFromLatLonSegment(segment) {
+ const
+ p1 = L.CRS.EPSG3857.project(segment[0]),
+ p2 = L.CRS.EPSG3857.project(segment[1]),
dx = p2.x - p1.x,
- dy = p2.y - p1.y,
+ dy = p1.y - p2.y,
len = Math.sqrt(dx * dx + dy * dy),
sin = dy / len,
cos = dx / len;
@@ -80,19 +81,14 @@ L.MeasuredLine = L.Polyline.extend({
},
getTicksPositions: function(minTicksIntervalMeters, bounds) {
- if (!this._map) {
- return [];
- }
var steps = [500, 1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000, 1000000];
- var ticks = [],
- self = this;
+ var ticks = [];
function addTick(position, segment, distanceValue) {
if (bounds && (!bounds.contains(position))) {
return;
}
- segment = [self._map.project(segment[0], 1), self._map.project(segment[1], 1)];
- var sinCos = sinCosFromSegment(segment),
+ var sinCos = sinCosFromLatLonSegment(segment),
sin = sinCos[0], cos = sinCos[1],
transformMatrix;