nakarte

Source code of https://map.sikmir.ru (fork)
git clone git://git.sikmir.ru/nakarte
Log | Files | Refs | LICENSE

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:
Msrc/lib/leaflet.control.printPages/control.js | 2++
Msrc/lib/leaflet.control.printPages/map-render.js | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Msrc/lib/leaflet.control.track-list/track-list.js | 27++++++++++++++++++---------
Asrc/lib/leaflet.layer.rasterize/MeasuredLine.js | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/lib/leaflet.layer.rasterize/index.js | 2++
Msrc/lib/leaflet.polyline-edit/index.js | 12++++++------
Msrc/lib/leaflet.polyline-measure/index.js | 18+++++++-----------
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;