From 0f217392d703098bbf23a61cbe38ee93803136e0 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Tue, 12 Mar 2019 00:19:13 +0100 Subject: [PATCH 1/4] Adjust tile cache size when too small --- src/ol/renderer/canvas/TileLayer.js | 22 ++++++++++++++++++++++ src/ol/source/BingMaps.js | 2 +- src/ol/source/CartoDB.js | 2 +- src/ol/source/OSM.js | 2 +- src/ol/source/Stamen.js | 2 +- src/ol/source/TileArcGISRest.js | 2 +- src/ol/source/TileImage.js | 2 +- src/ol/source/TileJSON.js | 2 +- src/ol/source/TileWMS.js | 2 +- src/ol/source/WMTS.js | 2 +- src/ol/source/XYZ.js | 2 +- src/ol/source/Zoomify.js | 2 +- 12 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/ol/renderer/canvas/TileLayer.js b/src/ol/renderer/canvas/TileLayer.js index 8ada186603..3bf407520b 100644 --- a/src/ol/renderer/canvas/TileLayer.js +++ b/src/ol/renderer/canvas/TileLayer.js @@ -7,6 +7,7 @@ import TileState from '../../TileState.js'; import {createEmpty, getIntersection, getTopLeft} from '../../extent.js'; import CanvasLayerRenderer from './Layer.js'; import {apply as applyTransform, compose as composeTransform, makeInverse, toString as transformToString} from '../../transform.js'; +import {assign} from '../../obj.js'; /** * @classdesc @@ -304,6 +305,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { this.manageTilePyramid(frameState, tileSource, tileGrid, pixelRatio, projection, extent, z, tileLayer.getPreload()); + this.updateCacheSize_(frameState, tileSource); this.scheduleExpireCache(frameState, tileSource); this.postRender(context, frameState); @@ -383,6 +385,26 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { return /** @type {import("../../ImageTile.js").default} */ (tile).getImage(); } + /** + * Check if the cache is big enough, and increase its size if necessary. + * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. + * @param {import("../../source/Tile.js").default} tileSource Tile source. + * @private + */ + updateCacheSize_(frameState, tileSource) { + const tileSourceKey = getUid(tileSource); + const keys = assign({}, frameState.usedTiles[tileSourceKey]); + if (tileSourceKey in frameState.wantedTiles) { + assign(keys, frameState.wantedTiles[tileSourceKey]); + } + const size = Object.keys(keys).length; + const tileCache = tileSource.tileCache; + if (tileCache.highWaterMark < size) { + tileCache.highWaterMark = size; + } + } + + /** } diff --git a/src/ol/source/BingMaps.js b/src/ol/source/BingMaps.js index e2bed7b93b..3d4ea68ab3 100644 --- a/src/ol/source/BingMaps.js +++ b/src/ol/source/BingMaps.js @@ -50,7 +50,7 @@ const TOS_ATTRIBUTION = ' Date: Tue, 12 Mar 2019 00:19:51 +0100 Subject: [PATCH 2/4] Move tile specific methods to the tile layer renderer --- src/ol/renderer/Layer.js | 105 +--------------------------- src/ol/renderer/canvas/TileLayer.js | 102 +++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 104 deletions(-) diff --git a/src/ol/renderer/Layer.js b/src/ol/renderer/Layer.js index 0c56666522..b65c917e73 100644 --- a/src/ol/renderer/Layer.js +++ b/src/ol/renderer/Layer.js @@ -1,10 +1,9 @@ /** * @module ol/renderer/Layer */ -import {getUid, abstract} from '../util.js'; +import {abstract} from '../util.js'; import ImageState from '../ImageState.js'; import Observable from '../Observable.js'; -import TileState from '../TileState.js'; import {listen} from '../events.js'; import EventType from '../events/EventType.js'; import SourceState from '../source/State.js'; @@ -165,108 +164,6 @@ class LayerRenderer extends Observable { } } - /** - * @param {import("../PluggableMap.js").FrameState} frameState Frame state. - * @param {import("../source/Tile.js").default} tileSource Tile source. - * @protected - */ - scheduleExpireCache(frameState, tileSource) { - if (tileSource.canExpireCache()) { - /** - * @param {import("../source/Tile.js").default} tileSource Tile source. - * @param {import("../PluggableMap.js").default} map Map. - * @param {import("../PluggableMap.js").FrameState} frameState Frame state. - */ - const postRenderFunction = function(tileSource, map, frameState) { - const tileSourceKey = getUid(tileSource); - if (tileSourceKey in frameState.usedTiles) { - tileSource.expireCache(frameState.viewState.projection, - frameState.usedTiles[tileSourceKey]); - } - }.bind(null, tileSource); - - frameState.postRenderFunctions.push( - /** @type {import("../PluggableMap.js").PostRenderFunction} */ (postRenderFunction) - ); - } - } - - /** - * @param {!Object>} usedTiles Used tiles. - * @param {import("../source/Tile.js").default} tileSource Tile source. - * @param {import('../Tile.js').default} tile Tile. - * @protected - */ - updateUsedTiles(usedTiles, tileSource, tile) { - // FIXME should we use tilesToDrawByZ instead? - const tileSourceKey = getUid(tileSource); - if (!(tileSourceKey in usedTiles)) { - usedTiles[tileSourceKey] = {}; - } - usedTiles[tileSourceKey][tile.getKey()] = true; - } - - /** - * Manage tile pyramid. - * This function performs a number of functions related to the tiles at the - * current zoom and lower zoom levels: - * - registers idle tiles in frameState.wantedTiles so that they are not - * discarded by the tile queue - * - enqueues missing tiles - * @param {import("../PluggableMap.js").FrameState} frameState Frame state. - * @param {import("../source/Tile.js").default} tileSource Tile source. - * @param {import("../tilegrid/TileGrid.js").default} tileGrid Tile grid. - * @param {number} pixelRatio Pixel ratio. - * @param {import("../proj/Projection.js").default} projection Projection. - * @param {import("../extent.js").Extent} extent Extent. - * @param {number} currentZ Current Z. - * @param {number} preload Load low resolution tiles up to 'preload' levels. - * @param {function(import("../Tile.js").default)=} opt_tileCallback Tile callback. - * @protected - */ - manageTilePyramid( - frameState, - tileSource, - tileGrid, - pixelRatio, - projection, - extent, - currentZ, - preload, - opt_tileCallback - ) { - const tileSourceKey = getUid(tileSource); - if (!(tileSourceKey in frameState.wantedTiles)) { - frameState.wantedTiles[tileSourceKey] = {}; - } - const wantedTiles = frameState.wantedTiles[tileSourceKey]; - const tileQueue = frameState.tileQueue; - const minZoom = tileGrid.getMinZoom(); - let tile, tileRange, tileResolution, x, y, z; - for (z = minZoom; z <= currentZ; ++z) { - tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z, tileRange); - tileResolution = tileGrid.getResolution(z); - for (x = tileRange.minX; x <= tileRange.maxX; ++x) { - for (y = tileRange.minY; y <= tileRange.maxY; ++y) { - if (currentZ - z <= preload) { - tile = tileSource.getTile(z, x, y, pixelRatio, projection); - if (tile.getState() == TileState.IDLE) { - wantedTiles[tile.getKey()] = true; - if (!tileQueue.isKeyQueued(tile.getKey())) { - tileQueue.enqueue([tile, tileSourceKey, - tileGrid.getTileCoordCenter(tile.tileCoord), tileResolution]); - } - } - if (opt_tileCallback !== undefined) { - opt_tileCallback(tile); - } - } else { - tileSource.useTile(z, x, y, projection); - } - } - } - } - } } export default LayerRenderer; diff --git a/src/ol/renderer/canvas/TileLayer.js b/src/ol/renderer/canvas/TileLayer.js index 3bf407520b..7b5e4b1c82 100644 --- a/src/ol/renderer/canvas/TileLayer.js +++ b/src/ol/renderer/canvas/TileLayer.js @@ -385,6 +385,47 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { return /** @type {import("../../ImageTile.js").default} */ (tile).getImage(); } + /** + * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. + * @param {import("../../source/Tile.js").default} tileSource Tile source. + * @protected + */ + scheduleExpireCache(frameState, tileSource) { + if (tileSource.canExpireCache()) { + /** + * @param {import("../../source/Tile.js").default} tileSource Tile source. + * @param {import("../../PluggableMap.js").default} map Map. + * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. + */ + const postRenderFunction = function(tileSource, map, frameState) { + const tileSourceKey = getUid(tileSource); + if (tileSourceKey in frameState.usedTiles) { + tileSource.expireCache(frameState.viewState.projection, + frameState.usedTiles[tileSourceKey]); + } + }.bind(null, tileSource); + + frameState.postRenderFunctions.push( + /** @type {import("../../PluggableMap.js").PostRenderFunction} */ (postRenderFunction) + ); + } + } + + /** + * @param {!Object>} usedTiles Used tiles. + * @param {import("../../source/Tile.js").default} tileSource Tile source. + * @param {import('../../Tile.js').default} tile Tile. + * @protected + */ + updateUsedTiles(usedTiles, tileSource, tile) { + // FIXME should we use tilesToDrawByZ instead? + const tileSourceKey = getUid(tileSource); + if (!(tileSourceKey in usedTiles)) { + usedTiles[tileSourceKey] = {}; + } + usedTiles[tileSourceKey][tile.getKey()] = true; + } + /** * Check if the cache is big enough, and increase its size if necessary. * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. @@ -405,6 +446,67 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { } /** + * Manage tile pyramid. + * This function performs a number of functions related to the tiles at the + * current zoom and lower zoom levels: + * - registers idle tiles in frameState.wantedTiles so that they are not + * discarded by the tile queue + * - enqueues missing tiles + * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. + * @param {import("../../source/Tile.js").default} tileSource Tile source. + * @param {import("../../tilegrid/TileGrid.js").default} tileGrid Tile grid. + * @param {number} pixelRatio Pixel ratio. + * @param {import("../../proj/Projection.js").default} projection Projection. + * @param {import("../../extent.js").Extent} extent Extent. + * @param {number} currentZ Current Z. + * @param {number} preload Load low resolution tiles up to 'preload' levels. + * @param {function(import("../../Tile.js").default)=} opt_tileCallback Tile callback. + * @protected + */ + manageTilePyramid( + frameState, + tileSource, + tileGrid, + pixelRatio, + projection, + extent, + currentZ, + preload, + opt_tileCallback + ) { + const tileSourceKey = getUid(tileSource); + if (!(tileSourceKey in frameState.wantedTiles)) { + frameState.wantedTiles[tileSourceKey] = {}; + } + const wantedTiles = frameState.wantedTiles[tileSourceKey]; + const tileQueue = frameState.tileQueue; + const minZoom = tileGrid.getMinZoom(); + let tile, tileRange, tileResolution, x, y, z; + for (z = minZoom; z <= currentZ; ++z) { + tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z, tileRange); + tileResolution = tileGrid.getResolution(z); + for (x = tileRange.minX; x <= tileRange.maxX; ++x) { + for (y = tileRange.minY; y <= tileRange.maxY; ++y) { + if (currentZ - z <= preload) { + tile = tileSource.getTile(z, x, y, pixelRatio, projection); + if (tile.getState() == TileState.IDLE) { + wantedTiles[tile.getKey()] = true; + if (!tileQueue.isKeyQueued(tile.getKey())) { + tileQueue.enqueue([tile, tileSourceKey, + tileGrid.getTileCoordCenter(tile.tileCoord), tileResolution]); + } + } + if (opt_tileCallback !== undefined) { + opt_tileCallback(tile); + } + } else { + tileSource.useTile(z, x, y, projection); + } + } + } + } + } + } From cb2b57232c172d493b2d5b2e108f0e65cee3ce76 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Tue, 12 Mar 2019 08:11:17 +0100 Subject: [PATCH 3/4] Add tests --- .../spec/ol/renderer/canvas/tilelayer.test.js | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/test/spec/ol/renderer/canvas/tilelayer.test.js b/test/spec/ol/renderer/canvas/tilelayer.test.js index 4120f086a8..1f64fcab43 100644 --- a/test/spec/ol/renderer/canvas/tilelayer.test.js +++ b/test/spec/ol/renderer/canvas/tilelayer.test.js @@ -2,6 +2,8 @@ import Map from '../../../../../src/ol/Map.js'; import View from '../../../../../src/ol/View.js'; import TileLayer from '../../../../../src/ol/layer/Tile.js'; import TileWMS from '../../../../../src/ol/source/TileWMS.js'; +import XYZ from '../../../../../src/ol/source/XYZ.js'; +import {fromLonLat} from '../../../../../src/ol/proj.js'; describe('ol.renderer.canvas.TileLayer', function() { @@ -49,4 +51,36 @@ describe('ol.renderer.canvas.TileLayer', function() { }); }); + describe('#renderFrame', function() { + let map, layer; + beforeEach(function() { + layer = new TileLayer({ + source: new XYZ({ + cacheSize: 1, + url: 'rendering/ol/data/tiles/osm/{z}/{x}/{y}.png' + }) + }); + map = new Map({ + target: createMapDiv(100, 100), + layers: [layer], + view: new View({ + center: fromLonLat([-122.416667, 37.783333]), + zoom: 5 + }) + }); + }); + afterEach(function() { + disposeMap(map); + }); + + it('increases the cache size if necessary', function(done) { + const tileCache = layer.getSource().tileCache; + expect(tileCache.highWaterMark).to.be(1); + map.once('rendercomplete', function() { + expect(tileCache.highWaterMark).to.be(2); + done(); + }); + }); + }); + }); From b4fc2499911ac7ed7e2862894a3f6a60d19fbd95 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Tue, 12 Mar 2019 08:50:15 +0100 Subject: [PATCH 4/4] Faster calculation of target size Since usedTiles and wantedTiles will never have an overlap, we can use their key count directly. --- src/ol/renderer/canvas/TileLayer.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/ol/renderer/canvas/TileLayer.js b/src/ol/renderer/canvas/TileLayer.js index 7b5e4b1c82..988627c4ec 100644 --- a/src/ol/renderer/canvas/TileLayer.js +++ b/src/ol/renderer/canvas/TileLayer.js @@ -7,7 +7,6 @@ import TileState from '../../TileState.js'; import {createEmpty, getIntersection, getTopLeft} from '../../extent.js'; import CanvasLayerRenderer from './Layer.js'; import {apply as applyTransform, compose as composeTransform, makeInverse, toString as transformToString} from '../../transform.js'; -import {assign} from '../../obj.js'; /** * @classdesc @@ -434,11 +433,13 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { */ updateCacheSize_(frameState, tileSource) { const tileSourceKey = getUid(tileSource); - const keys = assign({}, frameState.usedTiles[tileSourceKey]); - if (tileSourceKey in frameState.wantedTiles) { - assign(keys, frameState.wantedTiles[tileSourceKey]); + let size = 0; + if (tileSourceKey in frameState.usedTiles) { + size += Object.keys(frameState.usedTiles[tileSourceKey]).length; + } + if (tileSourceKey in frameState.wantedTiles) { + size += Object.keys(frameState.wantedTiles[tileSourceKey]).length; } - const size = Object.keys(keys).length; const tileCache = tileSource.tileCache; if (tileCache.highWaterMark < size) { tileCache.highWaterMark = size;