nakarte

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

commit 5905eab820153b375099e32510948144fa0e37ef
parent 68cbffcbeb3f6065ff5364e33735ad17f8889ab2
Author: Sergey Orlov <wladimirych@gmail.com>
Date:   Wed, 21 Oct 2020 09:50:42 +0200

use hi-res tiles for hi-dpi displays, printing and JNX

Diffstat:
Meslint_rules/imports_relax_legacy.js | 1+
Meslint_rules/legacy_files_list.js | 1+
Msrc/layers.js | 79++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Msrc/lib/leaflet.control.printPages/map-render.js | 29+++++++++++++++++++++--------
Asrc/lib/leaflet.layer.RetinaTileLayer/index.js | 24++++++++++++++++++++++++
Asrc/lib/leaflet.layer.rasterize/RetinaTileLayer.js | 17+++++++++++++++++
Msrc/lib/leaflet.layer.rasterize/Yandex.js | 8+++++++-
Msrc/lib/leaflet.layer.rasterize/index.js | 1+
8 files changed, 128 insertions(+), 32 deletions(-)

diff --git a/eslint_rules/imports_relax_legacy.js b/eslint_rules/imports_relax_legacy.js @@ -28,6 +28,7 @@ const filesMissingExport = [ 'src/lib/leaflet.layer.rasterize/TileLayer.js', 'src/lib/leaflet.layer.rasterize/WestraPasses.js', 'src/lib/leaflet.layer.rasterize/Yandex.js', + 'src/lib/leaflet.layer.rasterize/RetinaTileLayer.js', 'src/lib/leaflet.layer.soviet-topomaps-grid/index.js', 'src/lib/leaflet.layer.westraPasses/index.js', 'src/lib/leaflet.layer.wikimapia/index.js', diff --git a/eslint_rules/legacy_files_list.js b/eslint_rules/legacy_files_list.js @@ -127,6 +127,7 @@ module.exports = [ 'src/lib/leaflet.layer.rasterize/MeasuredLine.js', 'src/lib/leaflet.layer.rasterize/Bing.js', 'src/lib/leaflet.layer.rasterize/Yandex.js', + 'src/lib/leaflet.layer.rasterize/RetinaTileLayer.js', 'src/lib/leaflet.layer.bing/dates.js', 'src/lib/leaflet.layer.bing/index.js', 'src/lib/logging/index.js', diff --git a/src/layers.js b/src/layers.js @@ -8,6 +8,7 @@ import '~/lib/leaflet.layer.soviet-topomaps-grid'; import '~/lib/leaflet.layer.westraPasses'; import '~/lib/leaflet.layer.wikimapia'; import {GeocachingSu} from '~/lib/leaflet.layer.geocaching-su'; +import {RetinaTileLayer} from '~/lib/leaflet.layer.RetinaTileLayer'; import urlViaCorsProxy from '~/lib/CORSProxy'; const layersDefs = [ @@ -616,10 +617,15 @@ import urlViaCorsProxy from '~/lib/CORSProxy'; { title: 'Strava heatmap (all)', isDefault: false, - layer: L.tileLayer( - urlViaCorsProxy( - 'https://heatmap-external-a.strava.com/tiles-auth/all/hot/{z}/{x}/{y}.png?px=256' - ), + layer: new RetinaTileLayer( + [ + urlViaCorsProxy( + 'https://heatmap-external-a.strava.com/tiles-auth/all/hot/{z}/{x}/{y}.png?px=256' + ), + urlViaCorsProxy( + 'https://heatmap-external-a.strava.com/tiles-auth/all/hot/{z}/{x}/{y}.png?px=512' + ), + ], { code: 'Sa', isOverlay: true, @@ -628,19 +634,24 @@ import urlViaCorsProxy from '~/lib/CORSProxy'; print: true, jnx: false, subdomains: 'abc', - maxNativeZoom: 16, noCors: true, shortName: 'strava_all', + retinaOptionsOverrides: [{maxNativeZoom: 16}, {maxNativeZoom: 15}], } ) }, { title: 'Strava heatmap (run)', isDefault: false, - layer: L.tileLayer( - urlViaCorsProxy( - 'https://heatmap-external-a.strava.com/tiles-auth/run/hot/{z}/{x}/{y}.png?px=256' - ), + layer: new RetinaTileLayer( + [ + urlViaCorsProxy( + 'https://heatmap-external-a.strava.com/tiles-auth/run/hot/{z}/{x}/{y}.png?px=256' + ), + urlViaCorsProxy( + 'https://heatmap-external-a.strava.com/tiles-auth/run/hot/{z}/{x}/{y}.png?px=512' + ), + ], { code: 'Sr', isOverlay: true, @@ -649,19 +660,24 @@ import urlViaCorsProxy from '~/lib/CORSProxy'; print: true, jnx: false, subdomains: 'abc', - maxNativeZoom: 16, noCors: true, shortName: 'strava_run', + retinaOptionsOverrides: [{maxNativeZoom: 16}, {maxNativeZoom: 15}], } ) }, { title: 'Strava heatmap (ride)', isDefault: false, - layer: L.tileLayer( - urlViaCorsProxy( - 'https://heatmap-external-a.strava.com/tiles-auth/ride/hot/{z}/{x}/{y}.png?px=256' - ), + layer: new RetinaTileLayer( + [ + urlViaCorsProxy( + 'https://heatmap-external-a.strava.com/tiles-auth/ride/hot/{z}/{x}/{y}.png?px=256' + ), + urlViaCorsProxy( + 'https://heatmap-external-a.strava.com/tiles-auth/ride/hot/{z}/{x}/{y}.png?px=512' + ), + ], { code: 'Sb', isOverlay: true, @@ -670,19 +686,24 @@ import urlViaCorsProxy from '~/lib/CORSProxy'; print: true, jnx: false, subdomains: 'abc', - maxNativeZoom: 16, noCors: true, shortName: 'strava_ride', + retinaOptionsOverrides: [{maxNativeZoom: 16}, {maxNativeZoom: 15}], } ) }, { title: 'Strava heatmap (winter)', isDefault: false, - layer: L.tileLayer( - urlViaCorsProxy( - 'https://heatmap-external-a.strava.com/tiles-auth/winter/hot/{z}/{x}/{y}.png?px=256' - ), + layer: new RetinaTileLayer( + [ + urlViaCorsProxy( + 'https://heatmap-external-a.strava.com/tiles-auth/winter/hot/{z}/{x}/{y}.png?px=256' + ), + urlViaCorsProxy( + 'https://heatmap-external-a.strava.com/tiles-auth/winter/hot/{z}/{x}/{y}.png?px=512' + ), + ], { code: 'Sw', isOverlay: true, @@ -691,9 +712,9 @@ import urlViaCorsProxy from '~/lib/CORSProxy'; print: true, jnx: false, subdomains: 'abc', - maxNativeZoom: 16, noCors: true, shortName: 'strava_winter', + retinaOptionsOverrides: [{maxNativeZoom: 16}, {maxNativeZoom: 15}], } ) }, @@ -756,7 +777,11 @@ import urlViaCorsProxy from '~/lib/CORSProxy'; { title: 'Czech base', isDefault: false, - layer: L.tileLayer("https://m{s}.mapserver.mapy.cz/base-m/{z}-{x}-{y}", + layer: new RetinaTileLayer( + [ + 'https://m{s}.mapserver.mapy.cz/base-m/{z}-{x}-{y}', + 'https://m{s}.mapserver.mapy.cz/base-m/retina/{z}-{x}-{y}' + ], { code: 'Czb', isOverlay: false, @@ -772,7 +797,11 @@ import urlViaCorsProxy from '~/lib/CORSProxy'; { title: 'mapy.cz tourist', isDefault: true, - layer: L.tileLayer("https://m{s}.mapserver.mapy.cz/turist-m/{z}-{x}-{y}", + layer: new RetinaTileLayer( + [ + 'https://m{s}.mapserver.mapy.cz/turist-m/{z}-{x}-{y}', + 'https://m{s}.mapserver.mapy.cz/turist-m/retina/{z}-{x}-{y}', + ], { code: 'Czt', isOverlay: false, @@ -789,7 +818,11 @@ import urlViaCorsProxy from '~/lib/CORSProxy'; { title: 'Czech winter', isDefault: false, - layer: L.tileLayer("https://m{s}.mapserver.mapy.cz/winter-m/{z}-{x}-{y}", + layer: new RetinaTileLayer( + [ + 'https://m{s}.mapserver.mapy.cz/winter-m/{z}-{x}-{y}', + 'https://m{s}.mapserver.mapy.cz/winter-m/retina/{z}-{x}-{y}', + ], { code: 'Czw', isOverlay: false, diff --git a/src/lib/leaflet.control.printPages/map-render.js b/src/lib/leaflet.control.printPages/map-render.js @@ -105,6 +105,7 @@ class PageComposer { this.projectedBounds = pixelBoundsAtZoom24; this.currentCanvas = null; this.currentZoom = null; + this.currentTileScale = null; this.targetCanvas = this.createCanvas(destSize); } @@ -125,20 +126,26 @@ class PageComposer { } else { zoom = tileInfo.zoom; } - if (zoom !== this.currentZoom) { + if (zoom !== this.currentZoom || tileInfo.tileScale !== this.currentTileScale) { this.mergeCurrentCanvas(); - this.setupCurrentCanvas(zoom); + this.setupCurrentCanvas(zoom, tileInfo.tileScale); } 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); + ctx.drawImage( + tileInfo.image, + tilePos.x * tileInfo.tileScale, + tilePos.y * tileInfo.tileScale, + tileSize.x * tileInfo.tileScale, + tileSize.y * tileInfo.tileScale + ); } } - setupCurrentCanvas(zoom) { + setupCurrentCanvas(zoom, tileScale) { let size; if (zoom === 'overlay' || zoom === 'solidOverlay') { size = this.destSize; @@ -147,10 +154,11 @@ class PageComposer { const topLeft = this.projectedBounds.min.divideBy(q).round(), bottomRight = this.projectedBounds.max.divideBy(q).round(); - size = bottomRight.subtract(topLeft); + size = bottomRight.subtract(topLeft).multiplyBy(tileScale); } this.currentCanvas = this.createCanvas(size); this.currentZoom = zoom; + this.currentTileScale = tileScale; } mergeCurrentCanvas() { @@ -211,7 +219,7 @@ async function* iterateLayersTiles( map = getTempMap(zoom, layer._rasterizeNeedsFullSizeMap, pixelBounds); map.addLayer(layer); } - let {iterateTilePromises, count} = await layer.getTilesInfo({ + let {iterateTilePromises, count, tileScale = 1} = await layer.getTilesInfo({ xhrOptions: defaultXHROptions, pixelBounds, latLngBounds, @@ -227,8 +235,13 @@ async function* iterateLayersTiles( for (let tilePromise of iterateTilePromises()) { layerPromises.push(tilePromise.tilePromise); let progressInc = (layer._printProgressWeight || 1) / count; - tilePromise.tilePromise = - tilePromise.tilePromise.then((tileInfo) => ({zoom, progressInc, layer, ...tileInfo})); + tilePromise.tilePromise = tilePromise.tilePromise.then((tileInfo) => ({ + zoom, + progressInc, + layer, + tileScale, + ...tileInfo, + })); doStop = yield tilePromise; if (doStop) { tilePromise.abortLoading(); diff --git a/src/lib/leaflet.layer.RetinaTileLayer/index.js b/src/lib/leaflet.layer.RetinaTileLayer/index.js @@ -0,0 +1,24 @@ +import L from 'leaflet'; + +class RetinaTileLayer extends L.TileLayer { + constructor(urls, options, hiRes = 'auto') { + let url, tileSizeMultiplicator; + const useHiResTiles = hiRes === 'auto' ? L.Browser.retina : hiRes; + const newOptions = L.extend({}, options); + if (useHiResTiles) { + url = urls[1]; + tileSizeMultiplicator = 2; + } else { + tileSizeMultiplicator = 1; + url = urls[0]; + } + if (options.retinaOptionsOverrides) { + L.extend(newOptions, options.retinaOptionsOverrides[useHiResTiles ? 1 : 0]); + } + super(url, newOptions); + this.urls = urls; + this.tileSizeMultiplicator = tileSizeMultiplicator; + } +} + +export {RetinaTileLayer}; diff --git a/src/lib/leaflet.layer.rasterize/RetinaTileLayer.js b/src/lib/leaflet.layer.rasterize/RetinaTileLayer.js @@ -0,0 +1,17 @@ +import L from 'leaflet'; + +import {RetinaTileLayer} from '~/lib/leaflet.layer.RetinaTileLayer'; +import './TileLayer'; + +RetinaTileLayer.include({ + cloneForPrint: function(options) { + const newOptions = L.Util.extend({}, this.options, options); + return new RetinaTileLayer(this.urls, newOptions, newOptions.scaleDependent); + }, + + getTilesInfo: async function(printOptions) { + const tilesInfo = await L.TileLayer.prototype.getTilesInfo.call(this, printOptions); + tilesInfo.tileScale = this.tileSizeMultiplicator; + return tilesInfo; + }, +}); diff --git a/src/lib/leaflet.layer.rasterize/Yandex.js b/src/lib/leaflet.layer.rasterize/Yandex.js @@ -3,7 +3,13 @@ import '~/lib/leaflet.layer.yandex'; L.Layer.Yandex.Map.include({ cloneForPrint: function(options) { - return new L.Layer.Yandex.Map({...this.options, ...options}); + return new L.Layer.Yandex.Map({...this.options, ...options, yandexScale: 2}); + }, + + getTilesInfo: async function(printOptions) { + const tilesInfo = await L.TileLayer.prototype.getTilesInfo.call(this, printOptions); + tilesInfo.tileScale = 2; + return tilesInfo; }, }); diff --git a/src/lib/leaflet.layer.rasterize/index.js b/src/lib/leaflet.layer.rasterize/index.js @@ -6,6 +6,7 @@ import './Google'; import './WestraPasses'; import './CanvasMarkers'; import './MeasuredLine'; +import './RetinaTileLayer'; function getTempMap(zoom, fullSize, pixelBounds) { const container = L.DomUtil.create('div', '', document.body);