From b3903df156fb2fd4166de914b1df957bd0a2ba66 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 13 Nov 2018 12:42:43 +0100 Subject: [PATCH 01/73] Use the composite renderer --- examples/webpack/example-builder.js | 3 + .../cases/linestring-style-opacity/main.js | 2 +- .../cases/linestring-style-rotation/main.js | 2 +- rendering/cases/linestring-style/main.js | 2 +- src/ol/CompositeMap.js | 84 ------------------- src/ol/Map.js | 4 +- test/index_test.js | 2 +- 7 files changed, 9 insertions(+), 90 deletions(-) delete mode 100644 src/ol/CompositeMap.js diff --git a/examples/webpack/example-builder.js b/examples/webpack/example-builder.js index 5f8b3cccd8..82eb7560ae 100644 --- a/examples/webpack/example-builder.js +++ b/examples/webpack/example-builder.js @@ -152,6 +152,9 @@ ExampleBuilder.prototype.render = async function(dir, chunk) { // add in script tag const jsName = `${name}.js`; let jsSource = getJsSource(chunk, path.join('.', jsName)); + if (!jsSource) { + throw new Error(`No .js source for ${jsName}`); + } jsSource = jsSource.replace(/'\.\.\/src\//g, '\''); if (data.cloak) { for (const entry of data.cloak) { diff --git a/rendering/cases/linestring-style-opacity/main.js b/rendering/cases/linestring-style-opacity/main.js index e663640825..710ef11bec 100644 --- a/rendering/cases/linestring-style-opacity/main.js +++ b/rendering/cases/linestring-style-opacity/main.js @@ -1,4 +1,4 @@ -import Map from '../../../src/ol/CompositeMap.js'; +import Map from '../../../src/ol/Map.js'; import View from '../../../src/ol/View.js'; import Feature from '../../../src/ol/Feature.js'; import LineString from '../../../src/ol/geom/LineString.js'; diff --git a/rendering/cases/linestring-style-rotation/main.js b/rendering/cases/linestring-style-rotation/main.js index f69bd1cf74..3a4031e89f 100644 --- a/rendering/cases/linestring-style-rotation/main.js +++ b/rendering/cases/linestring-style-rotation/main.js @@ -1,4 +1,4 @@ -import Map from '../../../src/ol/CompositeMap.js'; +import Map from '../../../src/ol/Map.js'; import View from '../../../src/ol/View.js'; import Feature from '../../../src/ol/Feature.js'; import LineString from '../../../src/ol/geom/LineString.js'; diff --git a/rendering/cases/linestring-style/main.js b/rendering/cases/linestring-style/main.js index d5cd8e4236..800659c76f 100644 --- a/rendering/cases/linestring-style/main.js +++ b/rendering/cases/linestring-style/main.js @@ -1,4 +1,4 @@ -import Map from '../../../src/ol/CompositeMap.js'; +import Map from '../../../src/ol/Map.js'; import View from '../../../src/ol/View.js'; import Feature from '../../../src/ol/Feature.js'; import LineString from '../../../src/ol/geom/LineString.js'; diff --git a/src/ol/CompositeMap.js b/src/ol/CompositeMap.js deleted file mode 100644 index 7050fd96f6..0000000000 --- a/src/ol/CompositeMap.js +++ /dev/null @@ -1,84 +0,0 @@ -/** - * @module ol/CompositeMap - */ -import PluggableMap from './PluggableMap.js'; -import {defaults as defaultControls} from './control/util.js'; -import {defaults as defaultInteractions} from './interaction.js'; -import {assign} from './obj.js'; -import CompositeMapRenderer from './renderer/Composite.js'; - -/** - * @classdesc - * The map is the core component of OpenLayers. For a map to render, a view, - * one or more layers, and a target container are needed: - * - * import Map from 'ol/Map'; - * import View from 'ol/View'; - * import TileLayer from 'ol/layer/Tile'; - * import OSM from 'ol/source/OSM'; - * - * var map = new Map({ - * view: new View({ - * center: [0, 0], - * zoom: 1 - * }), - * layers: [ - * new TileLayer({ - * source: new OSM() - * }) - * ], - * target: 'map' - * }); - * - * The above snippet creates a map using a {@link module:ol/layer/Tile} to - * display {@link module:ol/source/OSM~OSM} OSM data and render it to a DOM - * element with the id `map`. - * - * The constructor places a viewport container (with CSS class name - * `ol-viewport`) in the target element (see `getViewport()`), and then two - * further elements within the viewport: one with CSS class name - * `ol-overlaycontainer-stopevent` for controls and some overlays, and one with - * CSS class name `ol-overlaycontainer` for other overlays (see the `stopEvent` - * option of {@link module:ol/Overlay~Overlay} for the difference). The map - * itself is placed in a further element within the viewport. - * - * Layers are stored as a {@link module:ol/Collection~Collection} in - * layerGroups. A top-level group is provided by the library. This is what is - * accessed by `getLayerGroup` and `setLayerGroup`. Layers entered in the - * options are added to this group, and `addLayer` and `removeLayer` change the - * layer collection in the group. `getLayers` is a convenience function for - * `getLayerGroup().getLayers()`. Note that {@link module:ol/layer/Group~Group} - * is a subclass of {@link module:ol/layer/Base}, so layers entered in the - * options or added with `addLayer` can be groups, which can contain further - * groups, and so on. - * - * @fires import("./MapBrowserEvent.js").MapBrowserEvent - * @fires import("./MapEvent.js").MapEvent - * @fires module:ol/render/Event~RenderEvent#postcompose - * @fires module:ol/render/Event~RenderEvent#precompose - * @api - */ -class CompositeMap extends PluggableMap { - - /** - * @param {import("./PluggableMap.js").MapOptions} options Map options. - */ - constructor(options) { - options = assign({}, options); - if (!options.controls) { - options.controls = defaultControls(); - } - if (!options.interactions) { - options.interactions = defaultInteractions(); - } - - super(options); - } - - createRenderer() { - return new CompositeMapRenderer(this); - } -} - - -export default CompositeMap; diff --git a/src/ol/Map.js b/src/ol/Map.js index 56a42f4a03..a5b6937a0f 100644 --- a/src/ol/Map.js +++ b/src/ol/Map.js @@ -5,7 +5,7 @@ import PluggableMap from './PluggableMap.js'; import {defaults as defaultControls} from './control/util.js'; import {defaults as defaultInteractions} from './interaction.js'; import {assign} from './obj.js'; -import CanvasMapRenderer from './renderer/canvas/Map.js'; +import CompositeMapRenderer from './renderer/Composite.js'; /** * @classdesc @@ -76,7 +76,7 @@ class Map extends PluggableMap { } createRenderer() { - return new CanvasMapRenderer(this); + return new CompositeMapRenderer(this); } } diff --git a/test/index_test.js b/test/index_test.js index 5203db1f25..504e1d8b51 100644 --- a/test/index_test.js +++ b/test/index_test.js @@ -1,5 +1,5 @@ // require all modules ending in ".test.js" from the // current directory and all subdirectories -const testsContext = require.context('.', true, /\.test\.js$/); +const testsContext = require.context('./spec', true, /\.test\.js$/); testsContext.keys().forEach(testsContext); From f2cab1fcbbfcd1490f3ecbcf83b7fcc4b0dd8a58 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 13 Nov 2018 15:45:38 +0100 Subject: [PATCH 02/73] Give the map some height for scale line tests --- test/spec/ol/control/scaleline.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/spec/ol/control/scaleline.test.js b/test/spec/ol/control/scaleline.test.js index ea3e2f75b3..81cb3495a4 100644 --- a/test/spec/ol/control/scaleline.test.js +++ b/test/spec/ol/control/scaleline.test.js @@ -8,6 +8,7 @@ describe('ol.control.ScaleLine', function() { let map; beforeEach(function() { const target = document.createElement('div'); + target.style.height = '256px'; document.body.appendChild(target); map = new Map({ target: target From c612cce59110efb7e34709fae593f30d82d53fec Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 13 Nov 2018 15:46:40 +0100 Subject: [PATCH 03/73] Give the map some height for the box tests --- test/spec/ol/render/box.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/spec/ol/render/box.test.js b/test/spec/ol/render/box.test.js index 8a6cfcaee8..644c84db00 100644 --- a/test/spec/ol/render/box.test.js +++ b/test/spec/ol/render/box.test.js @@ -13,6 +13,8 @@ describe('ol.render.Box', function() { box = new RenderBox('test-box'); target = document.createElement('div'); + target.style.height = '256px'; + document.body.appendChild(target); map = new Map({ From f416cf742d58717bb6b26167030ceb0f1d7ca017 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 13 Nov 2018 16:11:11 +0100 Subject: [PATCH 04/73] Workaround for raster source --- examples/sea-level.js | 7 ++++--- src/ol/source/Raster.js | 11 ++++++++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/examples/sea-level.js b/examples/sea-level.js index 81b4b1edfe..00e9e09de9 100644 --- a/examples/sea-level.js +++ b/examples/sea-level.js @@ -2,7 +2,7 @@ import Map from '../src/ol/Map.js'; import View from '../src/ol/View.js'; import {Image as ImageLayer, Tile as TileLayer} from '../src/ol/layer.js'; import {fromLonLat} from '../src/ol/proj.js'; -import RasterSource from '../src/ol/source/Raster.js'; +import {Raster as RasterSource, TileJSON} from '../src/ol/source.js'; import XYZ from '../src/ol/source/XYZ.js'; function flood(pixels, data) { @@ -37,8 +37,9 @@ const map = new Map({ target: 'map', layers: [ new TileLayer({ - source: new XYZ({ - url: 'https://api.mapbox.com/styles/v1/tschaub/ciutc102t00c62js5fqd47kqw/tiles/256/{z}/{x}/{y}?access_token=' + key + source: new TileJSON({ + url: 'https://api.tiles.mapbox.com/v3/mapbox.world-light.json?secure', + crossOrigin: 'anonymous' }) }), new ImageLayer({ diff --git a/src/ol/source/Raster.js b/src/ol/source/Raster.js index aa952010fb..3072182ae1 100644 --- a/src/ol/source/Raster.js +++ b/src/ol/source/Raster.js @@ -441,6 +441,15 @@ function getImageData(renderer, frameState, layerState) { } const width = frameState.size[0]; const height = frameState.size[1]; + const element = renderer.renderFrame(frameState, layerState); + if (!(element instanceof HTMLCanvasElement)) { + throw new Error('Unsupported rendered element: ' + element); + } + if (element.width === width && element.height === height) { + const context = element.getContext('2d'); + return context.getImageData(0, 0, width, height); + } + if (!sharedContext) { sharedContext = createCanvasContext2D(width, height); } else { @@ -451,7 +460,7 @@ function getImageData(renderer, frameState, layerState) { sharedContext.clearRect(0, 0, width, height); } } - renderer.composeFrame(frameState, layerState, sharedContext); + sharedContext.drawImage(element, 0, 0, width, height); return sharedContext.getImageData(0, 0, width, height); } From 433ab97d1cc47b8f389350601553cd66c692dbb8 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 13 Nov 2018 16:39:17 +0100 Subject: [PATCH 05/73] End of composeFrame --- src/ol/render/EventType.js | 35 ++- src/ol/renderer/canvas/IntermediateCanvas.js | 50 +--- src/ol/renderer/canvas/Layer.js | 37 +-- src/ol/renderer/canvas/Map.js | 205 ------------- src/ol/renderer/canvas/VectorLayer.js | 140 +-------- .../canvas/intermediatecanvas.test.js | 110 ------- test/spec/ol/renderer/canvas/map.test.js | 271 ------------------ .../spec/ol/renderer/canvas/tilelayer.test.js | 71 ----- 8 files changed, 39 insertions(+), 880 deletions(-) delete mode 100644 src/ol/renderer/canvas/Map.js delete mode 100644 test/spec/ol/renderer/canvas/intermediatecanvas.test.js delete mode 100644 test/spec/ol/renderer/canvas/map.test.js diff --git a/src/ol/render/EventType.js b/src/ol/render/EventType.js index cb33e22568..75ead01133 100644 --- a/src/ol/render/EventType.js +++ b/src/ol/render/EventType.js @@ -6,21 +6,41 @@ * @enum {string} */ export default { + /** - * @event module:ol/render/Event~RenderEvent#postcompose + * Triggered before a layer is rendered. + * @event module:ol/render/Event~RenderEvent#prerender * @api */ - POSTCOMPOSE: 'postcompose', - /** - * @event module:ol/render/Event~RenderEvent#precompose - * @api - */ - PRECOMPOSE: 'precompose', + PRERENDER: 'prerender', + /** * @event module:ol/render/Event~RenderEvent#render * @api */ RENDER: 'render', + + /** + * Triggered after a layer is rendered. + * @event module:ol/render/Event~RenderEvent#postrender + * @api + */ + POSTRENDER: 'postrender', + + /** + * Triggered before layers are rendered. + * @event module:ol/render/Event~RenderEvent#precompose + * @api + */ + PRECOMPOSE: 'precompose', + + /** + * Triggered after all layers are rendered. + * @event module:ol/render/Event~RenderEvent#postcompose + * @api + */ + POSTCOMPOSE: 'postcompose', + /** * Triggered when rendering is complete, i.e. all sources and tiles have * finished loading for the current viewport, and all tiles are faded in. @@ -28,4 +48,5 @@ export default { * @api */ RENDERCOMPLETE: 'rendercomplete' + }; diff --git a/src/ol/renderer/canvas/IntermediateCanvas.js b/src/ol/renderer/canvas/IntermediateCanvas.js index 3c27a015bf..5fddae6104 100644 --- a/src/ol/renderer/canvas/IntermediateCanvas.js +++ b/src/ol/renderer/canvas/IntermediateCanvas.js @@ -50,58 +50,12 @@ class IntermediateCanvasRenderer extends CanvasLayerRenderer { canvas.className = this.getLayer().getClassName(); } - /** - * @inheritDoc - */ - composeFrame(frameState, layerState, context) { - - this.preCompose(context, frameState); - - const image = this.getImage(); - if (image) { - - // clipped rendering if layer extent is set - const extent = layerState.extent; - const clipped = extent !== undefined && - !containsExtent(extent, frameState.extent) && - intersects(extent, frameState.extent); - if (clipped) { - this.clip(context, frameState, extent); - } - - const imageTransform = this.getImageTransform(); - // for performance reasons, context.save / context.restore is not used - // to save and restore the transformation matrix and the opacity. - // see http://jsperf.com/context-save-restore-versus-variable - const alpha = context.globalAlpha; - context.globalAlpha = layerState.opacity; - - // for performance reasons, context.setTransform is only used - // when the view is rotated. see http://jsperf.com/canvas-transform - const dx = imageTransform[4]; - const dy = imageTransform[5]; - const dw = image.width * imageTransform[0]; - const dh = image.height * imageTransform[3]; - if (dw >= 0.5 && dh >= 0.5) { - context.drawImage(image, 0, 0, +image.width, +image.height, - Math.round(dx), Math.round(dy), Math.round(dw), Math.round(dh)); - } - context.globalAlpha = alpha; - - if (clipped) { - context.restore(); - } - } - - this.postCompose(context, frameState, layerState); - } - /** * @inheritDoc */ renderFrame(frameState, layerState) { - this.preRender(frameState); + this.preRender(this.layerContext, frameState); const image = this.getImage(); if (image) { @@ -134,7 +88,7 @@ class IntermediateCanvasRenderer extends CanvasLayerRenderer { } } - this.postRender(frameState, layerState); + this.postRender(this.layerContext, frameState, layerState); return this.layerContext.canvas; } diff --git a/src/ol/renderer/canvas/Layer.js b/src/ol/renderer/canvas/Layer.js index 8677cde559..fc7f6bb333 100644 --- a/src/ol/renderer/canvas/Layer.js +++ b/src/ol/renderer/canvas/Layer.js @@ -1,7 +1,6 @@ /** * @module ol/renderer/canvas/Layer */ -import {abstract} from '../../util.js'; import {getBottomLeft, getBottomRight, getTopLeft, getTopRight} from '../../extent.js'; import {TRUE} from '../../functions.js'; import RenderEvent from '../../render/Event.js'; @@ -118,11 +117,10 @@ class CanvasLayerRenderer extends LayerRenderer { /** * @param {CanvasRenderingContext2D} context Context. * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. - * @param {import("../../layer/Layer.js").State} layerState Layer state. * @param {import("../../transform.js").Transform=} opt_transform Transform. * @protected */ - postCompose(context, frameState, layerState, opt_transform) { + preRender(context, frameState, opt_transform) { this.dispatchComposeEvent_(RenderEventType.POSTCOMPOSE, context, frameState, opt_transform); } @@ -132,27 +130,8 @@ class CanvasLayerRenderer extends LayerRenderer { * @param {import("../../transform.js").Transform=} opt_transform Transform. * @protected */ - preCompose(context, frameState, opt_transform) { - this.dispatchComposeEvent_(RenderEventType.PRECOMPOSE, context, frameState, opt_transform); - } - - /** - * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. - * @param {import("../../transform.js").Transform=} opt_transform Transform. - * @protected - */ - preRender(frameState, opt_transform) { - // TODO: pre-render event - } - - /** - * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. - * @param {import("../../layer/Layer.js").State} layerState Layer state. - * @param {import("../../transform.js").Transform=} opt_transform Transform. - * @protected - */ - postRender(frameState, layerState, opt_transform) { - // TODO: pre-render event + postRender(context, frameState, opt_transform) { + this.dispatchComposeEvent_(RenderEventType.POSTCOMPOSE, context, frameState, opt_transform); } /** @@ -205,16 +184,6 @@ class CanvasLayerRenderer extends LayerRenderer { return composeTransform(this.transform_, dx1, dy1, sx, sy, 0, dx2, dy2); } - /** - * @abstract - * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. - * @param {import("../../layer/Layer.js").State} layerState Layer state. - * @param {CanvasRenderingContext2D} context Context. - */ - composeFrame(frameState, layerState, context) { - abstract(); - } - } export default CanvasLayerRenderer; diff --git a/src/ol/renderer/canvas/Map.js b/src/ol/renderer/canvas/Map.js deleted file mode 100644 index d8aa1e3b7e..0000000000 --- a/src/ol/renderer/canvas/Map.js +++ /dev/null @@ -1,205 +0,0 @@ -/** - * @module ol/renderer/canvas/Map - */ -import {create as createTransform, apply as applyTransform, compose as composeTransform} from '../../transform.js'; -import {stableSort} from '../../array.js'; -import {CLASS_UNSELECTABLE} from '../../css.js'; -import {createCanvasContext2D} from '../../dom.js'; -import {visibleAtResolution} from '../../layer/Layer.js'; -import RenderEvent from '../../render/Event.js'; -import RenderEventType from '../../render/EventType.js'; -import {rotateAtOffset} from '../../render/canvas.js'; -import CanvasImmediateRenderer from '../../render/canvas/Immediate.js'; -import MapRenderer, {sortByZIndex} from '../Map.js'; -import SourceState from '../../source/State.js'; - - -/** - * @classdesc - * Canvas map renderer. - * @api - */ -class CanvasMapRenderer extends MapRenderer { - - /** - * @param {import("../../PluggableMap.js").default} map Map. - */ - constructor(map) { - super(map); - - const container = map.getViewport(); - - /** - * @private - * @type {CanvasRenderingContext2D} - */ - this.context_ = createCanvasContext2D(); - - /** - * @private - * @type {HTMLCanvasElement} - */ - this.canvas_ = this.context_.canvas; - - this.canvas_.style.width = '100%'; - this.canvas_.style.height = '100%'; - this.canvas_.style.display = 'block'; - this.canvas_.className = CLASS_UNSELECTABLE; - container.insertBefore(this.canvas_, container.childNodes[0] || null); - - /** - * @private - * @type {boolean} - */ - this.renderedVisible_ = true; - - /** - * @private - * @type {import("../../transform.js").Transform} - */ - this.transform_ = createTransform(); - - } - - /** - * @param {import("../../render/EventType.js").default} type Event type. - * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. - */ - dispatchRenderEvent(type, frameState) { - const map = this.getMap(); - const context = this.context_; - if (map.hasListener(type)) { - const extent = frameState.extent; - const pixelRatio = frameState.pixelRatio; - const viewState = frameState.viewState; - const rotation = viewState.rotation; - - const transform = this.getTransform(frameState); - - const vectorContext = new CanvasImmediateRenderer(context, pixelRatio, - extent, transform, rotation); - const composeEvent = new RenderEvent(type, vectorContext, - frameState, context, null); - map.dispatchEvent(composeEvent); - } - } - - /** - * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. - * @protected - * @return {!import("../../transform.js").Transform} Transform. - */ - getTransform(frameState) { - const viewState = frameState.viewState; - const dx1 = this.canvas_.width / 2; - const dy1 = this.canvas_.height / 2; - const sx = frameState.pixelRatio / viewState.resolution; - const sy = -sx; - const angle = -viewState.rotation; - const dx2 = -viewState.center[0]; - const dy2 = -viewState.center[1]; - return composeTransform(this.transform_, dx1, dy1, sx, sy, angle, dx2, dy2); - } - - /** - * @inheritDoc - */ - renderFrame(frameState) { - - if (!frameState) { - if (this.renderedVisible_) { - this.canvas_.style.display = 'none'; - this.renderedVisible_ = false; - } - return; - } - - const context = this.context_; - const pixelRatio = frameState.pixelRatio; - const width = Math.round(frameState.size[0] * pixelRatio); - const height = Math.round(frameState.size[1] * pixelRatio); - if (this.canvas_.width != width || this.canvas_.height != height) { - this.canvas_.width = width; - this.canvas_.height = height; - } else { - context.clearRect(0, 0, width, height); - } - - const rotation = frameState.viewState.rotation; - - this.calculateMatrices2D(frameState); - - this.dispatchRenderEvent(RenderEventType.PRECOMPOSE, frameState); - - const layerStatesArray = frameState.layerStatesArray; - stableSort(layerStatesArray, sortByZIndex); - - if (rotation) { - context.save(); - rotateAtOffset(context, rotation, width / 2, height / 2); - } - - const viewResolution = frameState.viewState.resolution; - let i, ii; - for (i = 0, ii = layerStatesArray.length; i < ii; ++i) { - const layerState = layerStatesArray[i]; - const layer = layerState.layer; - const layerRenderer = /** @type {import("./Layer.js").default} */ (this.getLayerRenderer(layer)); - if (!visibleAtResolution(layerState, viewResolution) || - layerState.sourceState != SourceState.READY) { - continue; - } - if (layerRenderer.prepareFrame(frameState, layerState)) { - layerRenderer.composeFrame(frameState, layerState, context); - } - } - - if (rotation) { - context.restore(); - } - - this.dispatchRenderEvent(RenderEventType.POSTCOMPOSE, frameState); - - if (!this.renderedVisible_) { - this.canvas_.style.display = ''; - this.renderedVisible_ = true; - } - - this.scheduleRemoveUnusedLayerRenderers(frameState); - this.scheduleExpireIconCache(frameState); - } - - /** - * @inheritDoc - */ - forEachLayerAtPixel(pixel, frameState, hitTolerance, callback, thisArg, layerFilter, thisArg2) { - let result; - const viewState = frameState.viewState; - const viewResolution = viewState.resolution; - - const layerStates = frameState.layerStatesArray; - const numLayers = layerStates.length; - - const coordinate = applyTransform( - frameState.pixelToCoordinateTransform, pixel.slice()); - - let i; - for (i = numLayers - 1; i >= 0; --i) { - const layerState = layerStates[i]; - const layer = layerState.layer; - if (visibleAtResolution(layerState, viewResolution) && layerFilter.call(thisArg2, layer)) { - const layerRenderer = /** @type {import("./Layer.js").default} */ (this.getLayerRenderer(layer)); - result = layerRenderer.forEachLayerAtCoordinate( - coordinate, frameState, hitTolerance, callback, thisArg); - if (result) { - return result; - } - } - } - return undefined; - } - -} - - -export default CanvasMapRenderer; diff --git a/src/ol/renderer/canvas/VectorLayer.js b/src/ol/renderer/canvas/VectorLayer.js index 295dcd31c5..2d030f60a5 100644 --- a/src/ol/renderer/canvas/VectorLayer.js +++ b/src/ol/renderer/canvas/VectorLayer.js @@ -9,7 +9,7 @@ import EventType from '../../events/EventType.js'; import rbush from 'rbush'; import {buffer, createEmpty, containsExtent, getWidth} from '../../extent.js'; import RenderEventType from '../../render/EventType.js'; -import {labelCache, rotateAtOffset} from '../../render/canvas.js'; +import {labelCache} from '../../render/canvas.js'; import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js'; import InstructionsGroupExecutor from '../../render/canvas/ExecutorGroup.js'; import CanvasLayerRenderer from './Layer.js'; @@ -97,136 +97,6 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { super.disposeInternal(); } - /** - * @param {CanvasRenderingContext2D} context Context. - * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. - * @param {import("../../layer/Layer.js").State} layerState Layer state. - */ - compose(context, frameState, layerState) { - const extent = frameState.extent; - const pixelRatio = frameState.pixelRatio; - const skippedFeatureUids = layerState.managed ? - frameState.skippedFeatureUids : {}; - const viewState = frameState.viewState; - const projection = viewState.projection; - const rotation = viewState.rotation; - const projectionExtent = projection.getExtent(); - const vectorSource = /** @type {import("../../source/Vector.js").default} */ (this.getLayer().getSource()); - - let transform = this.getTransform(frameState, 0); - - // clipped rendering if layer extent is set - const clipExtent = layerState.extent; - const clipped = clipExtent !== undefined; - if (clipped) { - this.clip(context, frameState, clipExtent); - } - const replayGroup = this.replayGroup_; - if (replayGroup && !replayGroup.isEmpty()) { - if (this.declutterTree_) { - this.declutterTree_.clear(); - } - const layer = /** @type {import("../../layer/Vector.js").default} */ (this.getLayer()); - let drawOffsetX = 0; - let drawOffsetY = 0; - let replayContext; - const transparentLayer = layerState.opacity !== 1; - const hasRenderListeners = layer.hasListener(RenderEventType.RENDER); - if (transparentLayer || hasRenderListeners) { - let drawWidth = context.canvas.width; - let drawHeight = context.canvas.height; - if (rotation) { - const drawSize = Math.round(Math.sqrt(drawWidth * drawWidth + drawHeight * drawHeight)); - drawOffsetX = (drawSize - drawWidth) / 2; - drawOffsetY = (drawSize - drawHeight) / 2; - drawWidth = drawHeight = drawSize; - } - // resize and clear - this.context.canvas.width = drawWidth; - this.context.canvas.height = drawHeight; - replayContext = this.context; - } else { - replayContext = context; - } - - const alpha = replayContext.globalAlpha; - if (!transparentLayer) { - // for performance reasons, context.save / context.restore is not used - // to save and restore the transformation matrix and the opacity. - // see http://jsperf.com/context-save-restore-versus-variable - replayContext.globalAlpha = layerState.opacity; - } - - if (replayContext != context) { - replayContext.translate(drawOffsetX, drawOffsetY); - } - - const viewHints = frameState.viewHints; - const snapToPixel = !(viewHints[ViewHint.ANIMATING] || viewHints[ViewHint.INTERACTING]); - const halfWidth = (frameState.size[0] * pixelRatio) / 2; - const halfHeight = (frameState.size[1] * pixelRatio) / 2; - rotateAtOffset(replayContext, -rotation, halfWidth, halfHeight); - replayGroup.execute(replayContext, transform, rotation, skippedFeatureUids, snapToPixel); - if (vectorSource.getWrapX() && projection.canWrapX() && - !containsExtent(projectionExtent, extent)) { - let startX = extent[0]; - const worldWidth = getWidth(projectionExtent); - let world = 0; - let offsetX; - while (startX < projectionExtent[0]) { - --world; - offsetX = worldWidth * world; - transform = this.getTransform(frameState, offsetX); - replayGroup.execute(replayContext, transform, rotation, skippedFeatureUids, snapToPixel); - startX += worldWidth; - } - world = 0; - startX = extent[2]; - while (startX > projectionExtent[2]) { - ++world; - offsetX = worldWidth * world; - transform = this.getTransform(frameState, offsetX); - replayGroup.execute(replayContext, transform, rotation, skippedFeatureUids, snapToPixel); - startX -= worldWidth; - } - } - rotateAtOffset(replayContext, rotation, halfWidth, halfHeight); - - if (hasRenderListeners) { - this.dispatchRenderEvent(replayContext, frameState, transform); - } - if (replayContext != context) { - if (transparentLayer) { - const mainContextAlpha = context.globalAlpha; - context.globalAlpha = layerState.opacity; - context.drawImage(replayContext.canvas, -drawOffsetX, -drawOffsetY); - context.globalAlpha = mainContextAlpha; - } else { - context.drawImage(replayContext.canvas, -drawOffsetX, -drawOffsetY); - } - replayContext.translate(-drawOffsetX, -drawOffsetY); - } - - if (!transparentLayer) { - replayContext.globalAlpha = alpha; - } - } - - if (clipped) { - context.restore(); - } - } - - /** - * @inheritDoc - */ - composeFrame(frameState, layerState, context) { - const transform = this.getTransform(frameState, 0); - this.preCompose(context, frameState, transform); - this.compose(context, frameState, layerState); - this.postCompose(context, frameState, layerState, transform); - } - /** * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. * @param {import("../../layer/Layer.js").State} layerState Layer state. @@ -318,10 +188,12 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { * @inheritDoc */ renderFrame(frameState, layerState) { - this.preRender(frameState); + const context = this.context; + this.preRender(context, frameState); this.render(frameState, layerState); - this.postRender(frameState, layerState); - const canvas = this.context.canvas; + this.postRender(context, frameState); + + const canvas = context.canvas; const opacity = layerState.opacity; if (opacity !== canvas.style.opacity) { diff --git a/test/spec/ol/renderer/canvas/intermediatecanvas.test.js b/test/spec/ol/renderer/canvas/intermediatecanvas.test.js deleted file mode 100644 index 37966bac88..0000000000 --- a/test/spec/ol/renderer/canvas/intermediatecanvas.test.js +++ /dev/null @@ -1,110 +0,0 @@ -import {create as createTransform, scale} from '../../../../../src/ol/transform.js'; -import ImageLayer from '../../../../../src/ol/layer/Image.js'; -import MapRenderer from '../../../../../src/ol/renderer/Map.js'; -import IntermediateCanvasRenderer from '../../../../../src/ol/renderer/canvas/IntermediateCanvas.js'; - - -describe('ol.renderer.canvas.IntermediateCanvas', function() { - - describe('#composeFrame()', function() { - let renderer, frameState, layerState, context; - beforeEach(function() { - const layer = new ImageLayer({ - extent: [1, 2, 3, 4] - }); - renderer = new IntermediateCanvasRenderer(layer); - const image = new Image(); - image.width = 3; - image.height = 3; - renderer.getImage = function() { - return image; - }; - frameState = { - viewState: { - center: [2, 3], - resolution: 1, - rotation: 0 - }, - size: [10, 10], - pixelRatio: 1, - coordinateToPixelTransform: createTransform(), - pixelToCoordinateTransform: createTransform() - }; - renderer.getImageTransform = function() { - return createTransform(); - }; - MapRenderer.prototype.calculateMatrices2D(frameState); - layerState = layer.getLayerState(); - context = { - save: sinon.spy(), - restore: sinon.spy(), - translate: sinon.spy(), - rotate: sinon.spy(), - beginPath: sinon.spy(), - moveTo: sinon.spy(), - lineTo: sinon.spy(), - clip: sinon.spy(), - drawImage: sinon.spy() - }; - }); - - it('clips to layer extent and draws image', function() { - frameState.extent = [0, 1, 4, 5]; - - renderer.composeFrame(frameState, layerState, context); - expect(context.save.callCount).to.be(1); - expect(context.translate.callCount).to.be(0); - expect(context.rotate.callCount).to.be(0); - expect(context.beginPath.callCount).to.be(1); - expect(context.moveTo.firstCall.args).to.eql([4, 4]); - expect(context.lineTo.firstCall.args).to.eql([6, 4]); - expect(context.lineTo.secondCall.args).to.eql([6, 6]); - expect(context.lineTo.thirdCall.args).to.eql([4, 6]); - expect(context.clip.callCount).to.be(1); - expect(context.drawImage.firstCall.args).to.eql( - [renderer.getImage(), 0, 0, 3, 3, 0, 0, 3, 3]); - expect(context.restore.callCount).to.be(1); - }); - - it('does not clip if frame extent does not intersect layer extent', function() { - frameState.extent = [1.1, 2.1, 2.9, 3.9]; - - renderer.composeFrame(frameState, layerState, context); - expect(context.save.callCount).to.be(0); - expect(context.translate.callCount).to.be(0); - expect(context.rotate.callCount).to.be(0); - expect(context.beginPath.callCount).to.be(0); - expect(context.clip.callCount).to.be(0); - expect(context.drawImage.firstCall.args).to.eql( - [renderer.getImage(), 0, 0, 3, 3, 0, 0, 3, 3]); - expect(context.restore.callCount).to.be(0); - }); - - it('does not clip if frame extent is outside of layer extent', function() { - frameState.extent = [10, 20, 30, 40]; - - renderer.composeFrame(frameState, layerState, context); - expect(context.save.callCount).to.be(0); - expect(context.translate.callCount).to.be(0); - expect(context.rotate.callCount).to.be(0); - expect(context.beginPath.callCount).to.be(0); - expect(context.clip.callCount).to.be(0); - expect(context.drawImage.firstCall.args).to.eql( - [renderer.getImage(), 0, 0, 3, 3, 0, 0, 3, 3]); - expect(context.restore.callCount).to.be(0); - }); - - it('does not draw image with width or height < 0.5', function() { - frameState.extent = [10, 20, 30, 40]; - renderer.getImageTransform = function() { - return scale(createTransform(), 0.1, 0.1); - }; - - renderer.composeFrame(frameState, layerState, context); - - expect(context.drawImage.notCalled).to.be(true); - }); - - }); - -}); diff --git a/test/spec/ol/renderer/canvas/map.test.js b/test/spec/ol/renderer/canvas/map.test.js deleted file mode 100644 index c3bdb637b8..0000000000 --- a/test/spec/ol/renderer/canvas/map.test.js +++ /dev/null @@ -1,271 +0,0 @@ -import {getUid} from '../../../../../src/ol/util.js'; -import Feature from '../../../../../src/ol/Feature.js'; -import Map from '../../../../../src/ol/Map.js'; -import View from '../../../../../src/ol/View.js'; -import Point from '../../../../../src/ol/geom/Point.js'; -import TileLayer from '../../../../../src/ol/layer/Tile.js'; -import VectorLayer from '../../../../../src/ol/layer/Vector.js'; -import CanvasLayerRenderer from '../../../../../src/ol/renderer/canvas/Layer.js'; -import CanvasMapRenderer from '../../../../../src/ol/renderer/canvas/Map.js'; -import VectorSource from '../../../../../src/ol/source/Vector.js'; -import Icon from '../../../../../src/ol/style/Icon.js'; -import Style from '../../../../../src/ol/style/Style.js'; - -describe('ol.renderer.canvas.Map', function() { - - describe('constructor', function() { - - it('creates a new instance', function() { - const map = new Map({ - target: document.createElement('div') - }); - const renderer = new CanvasMapRenderer(map); - expect(renderer).to.be.a(CanvasMapRenderer); - }); - - }); - - describe('#forEachFeatureAtCoordinate', function() { - - let layer, map, target; - - beforeEach(function(done) { - target = document.createElement('div'); - target.style.width = '100px'; - target.style.height = '100px'; - document.body.appendChild(target); - map = new Map({ - pixelRatio: 1, - target: target, - view: new View({ - center: [0, 0], - zoom: 0 - }) - }); - - // 1 x 1 pixel black icon - const img = document.createElement('img'); - img.onload = function() { - done(); - }; - img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGNiAAAABgADNjd8qAAAAABJRU5ErkJggg=='; - - layer = new VectorLayer({ - source: new VectorSource({ - features: [ - new Feature({ - geometry: new Point([0, 0]) - }) - ] - }), - style: new Style({ - image: new Icon({ - img: img, - imgSize: [1, 1] - }) - }) - }); - }); - - afterEach(function() { - map.setTarget(null); - document.body.removeChild(target); - }); - - it('calls callback with layer for managed layers', function() { - map.addLayer(layer); - map.renderSync(); - const cb = sinon.spy(); - map.forEachFeatureAtPixel(map.getPixelFromCoordinate([0, 0]), cb); - expect(cb).to.be.called(); - expect(cb.firstCall.args[1]).to.be(layer); - }); - - it('calls callback with null for unmanaged layers', function() { - layer.setMap(map); - map.renderSync(); - const cb = sinon.spy(); - map.forEachFeatureAtPixel(map.getPixelFromCoordinate([0, 0]), cb); - expect(cb).to.be.called(); - expect(cb.firstCall.args[1]).to.be(null); - }); - - it('calls callback with main layer when skipped feature on unmanaged layer', function() { - const feature = layer.getSource().getFeatures()[0]; - const managedLayer = new VectorLayer({ - source: new VectorSource({ - features: [feature] - }) - }); - map.addLayer(managedLayer); - map.skipFeature(feature); - layer.setMap(map); - map.renderSync(); - const cb = sinon.spy(); - map.forEachFeatureAtPixel(map.getPixelFromCoordinate([0, 0]), cb); - expect(cb.callCount).to.be(1); - expect(cb.firstCall.args[1]).to.be(managedLayer); - }); - - it('filters managed layers', function() { - map.addLayer(layer); - map.renderSync(); - const cb = sinon.spy(); - map.forEachFeatureAtPixel(map.getPixelFromCoordinate([0, 0]), cb, { - layerFilter: function() { - return false; - } - }); - expect(cb).to.not.be.called(); - }); - - it('doesn\'t fail with layer with no source', function() { - map.addLayer(new TileLayer()); - map.renderSync(); - expect(function() { - map.forEachFeatureAtPixel(map.getPixelFromCoordinate([0, 0]), - function() {}); - }).to.not.throwException(); - }); - - it('calls callback for clicks inside of the hitTolerance', function() { - map.addLayer(layer); - map.renderSync(); - const cb1 = sinon.spy(); - const cb2 = sinon.spy(); - - const pixel = map.getPixelFromCoordinate([0, 0]); - - const pixelsInside = [ - [pixel[0] + 9, pixel[1]], - [pixel[0] - 9, pixel[1]], - [pixel[0], pixel[1] + 9], - [pixel[0], pixel[1] - 9] - ]; - - const pixelsOutside = [ - [pixel[0] + 9, pixel[1] + 9], - [pixel[0] - 9, pixel[1] + 9], - [pixel[0] + 9, pixel[1] - 9], - [pixel[0] - 9, pixel[1] - 9] - ]; - - for (let i = 0; i < 4; i++) { - map.forEachFeatureAtPixel(pixelsInside[i], cb1, {hitTolerance: 10}); - } - expect(cb1.callCount).to.be(4); - expect(cb1.firstCall.args[1]).to.be(layer); - - for (let j = 0; j < 4; j++) { - map.forEachFeatureAtPixel(pixelsOutside[j], cb2, {hitTolerance: 10}); - } - expect(cb2).not.to.be.called(); - }); - }); - - describe('#forEachLayerAtCoordinate', function() { - - let layer, map, target; - - beforeEach(function(done) { - target = document.createElement('div'); - target.style.width = '100px'; - target.style.height = '100px'; - document.body.appendChild(target); - map = new Map({ - pixelRatio: 1, - target: target, - view: new View({ - center: [0, 0], - zoom: 0 - }) - }); - - // 1 x 1 pixel black icon - const img = document.createElement('img'); - img.onload = function() { - done(); - }; - img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGNiAAAABgADNjd8qAAAAABJRU5ErkJggg=='; - - layer = new VectorLayer({ - source: new VectorSource({ - features: [ - new Feature({ - geometry: new Point([0, 0]) - }) - ] - }), - style: new Style({ - image: new Icon({ - img: img, - imgSize: [1, 1] - }) - }) - }); - }); - - afterEach(function() { - map.setTarget(null); - document.body.removeChild(target); - }); - - it('calls callback for clicks inside of the hitTolerance', function() { - map.addLayer(layer); - map.renderSync(); - const cb1 = sinon.spy(); - const cb2 = sinon.spy(); - - const pixel = map.getPixelFromCoordinate([0, 0]); - - const pixelsInside = [ - [pixel[0] + 9, pixel[1]], - [pixel[0] - 9, pixel[1]], - [pixel[0], pixel[1] + 9], - [pixel[0], pixel[1] - 9] - ]; - - const pixelsOutside = [ - [pixel[0] + 9, pixel[1] + 9], - [pixel[0] - 9, pixel[1] + 9], - [pixel[0] + 9, pixel[1] - 9], - [pixel[0] - 9, pixel[1] - 9] - ]; - - for (let i = 0; i < 4; i++) { - map.forEachLayerAtPixel(pixelsInside[i], cb1, {hitTolerance: 10}); - } - expect(cb1.callCount).to.be(4); - expect(cb1.firstCall.args[0]).to.be(layer); - - for (let j = 0; j < 4; j++) { - map.forEachLayerAtPixel(pixelsOutside[j], cb2, {hitTolerance: 10}); - } - expect(cb2).not.to.be.called(); - }); - }); - - describe('#renderFrame()', function() { - let layer, map, renderer; - - beforeEach(function() { - map = new Map({}); - map.on('postcompose', function() {}); - layer = new VectorLayer({ - source: new VectorSource({wrapX: true}) - }); - renderer = map.getRenderer(); - renderer.layerRenderers_ = {}; - const layerRenderer = new CanvasLayerRenderer(layer); - layerRenderer.prepareFrame = function() { - return true; - }; - layerRenderer.getImage = function() { - return null; - }; - renderer.layerRenderers_[getUid(layer)] = layerRenderer; - }); - - }); - -}); diff --git a/test/spec/ol/renderer/canvas/tilelayer.test.js b/test/spec/ol/renderer/canvas/tilelayer.test.js index 21ba13e2f0..4120f086a8 100644 --- a/test/spec/ol/renderer/canvas/tilelayer.test.js +++ b/test/spec/ol/renderer/canvas/tilelayer.test.js @@ -1,12 +1,7 @@ import Map from '../../../../../src/ol/Map.js'; import View from '../../../../../src/ol/View.js'; import TileLayer from '../../../../../src/ol/layer/Tile.js'; -import {get as getProjection} from '../../../../../src/ol/proj.js'; -import MapRenderer from '../../../../../src/ol/renderer/Map.js'; -import CanvasTileLayerRenderer from '../../../../../src/ol/renderer/canvas/TileLayer.js'; import TileWMS from '../../../../../src/ol/source/TileWMS.js'; -import XYZ from '../../../../../src/ol/source/XYZ.js'; -import {create as createTransform} from '../../../../../src/ol/transform.js'; describe('ol.renderer.canvas.TileLayer', function() { @@ -54,70 +49,4 @@ describe('ol.renderer.canvas.TileLayer', function() { }); }); - describe('#composeFrame()', function() { - - let img = null; - beforeEach(function(done) { - img = new Image(1, 1); - img.onload = function() { - done(); - }; - img.src = 'data:image/gif;base64,' + - 'R0lGODlhAQABAPAAAP8AAP///yH5BAAAAAAALAAAAAABAAEAAAICRAEAOw=='; - }); - afterEach(function() { - img = null; - }); - - it('uses correct draw scale when rotating (HiDPI)', function() { - const layer = new TileLayer({ - source: new XYZ({ - tileSize: 1 - }) - }); - const renderer = new CanvasTileLayerRenderer(layer); - renderer.renderedTiles = []; - const frameState = { - viewHints: [], - time: Date.now(), - viewState: { - center: [10, 5], - projection: getProjection('EPSG:3857'), - resolution: 1, - rotation: Math.PI - }, - extent: [0, 0, 20, 10], - size: [20, 10], - pixelRatio: 2, - coordinateToPixelTransform: createTransform(), - pixelToCoordinateTransform: createTransform(), - usedTiles: {}, - wantedTiles: {} - }; - renderer.getImageTransform = function() { - return createTransform(); - }; - MapRenderer.prototype.calculateMatrices2D(frameState); - const layerState = layer.getLayerState(); - const canvas = document.createElement('canvas'); - canvas.width = 200; - canvas.height = 100; - const context = { - canvas: canvas, - drawImage: sinon.spy() - }; - renderer.renderedTiles = [{ - getTileCoord: function() { - return [0, 0, 0]; - }, - getImage: function() { - return img; - } - }]; - renderer.prepareFrame(frameState, layerState); - renderer.composeFrame(frameState, layerState, context); - expect(context.drawImage.firstCall.args[0].width).to.be(17); - }); - }); - }); From 5ba8795355077affc91b0b1e63a59a66dc637025 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Wed, 14 Nov 2018 15:45:03 +0100 Subject: [PATCH 06/73] Tile layer rendering with the composite renderer --- rendering/cases/multiple-layers/expected.png | Bin 117722 -> 103443 bytes rendering/cases/multiple-layers/main.js | 27 +-- rendering/data/countries.json | 181 +++++++++++++++++ src/ol/renderer/canvas/TileLayer.js | 193 ++++++++++++------- 4 files changed, 313 insertions(+), 88 deletions(-) create mode 100644 rendering/data/countries.json diff --git a/rendering/cases/multiple-layers/expected.png b/rendering/cases/multiple-layers/expected.png index e2e8c7dc0f0ebee0673fd8868f2af85c03ae79d4..92744a1d8f583b36a1601a8d9f920a4397ed0bd9 100644 GIT binary patch literal 103443 zcmV*1KzP52P)+b66o}TWWdwLEGFc@G6h#+_X1gTw;B1B0`iPY{&luZ1fHPcsq z*bh34CUI?L#@*G56qd^iJU|j4RwMxe#4!U9;0)$Q&(%HMT~%FYR-SRWyFNr_RaR9G zpfxGRqO&q0!^1to-+TY}fA9H-XyLL$SWa%vnG;7&|EAm|0K$P2Ae_XB)4viZgb>(M z=LAwpq?8CDz{yFo0wEOEIfM|n$#($j95{z_4k@R7lQ?H^&LJk}ru_o2wkVHHj?at~ zMcFAK%j-_=SckP1uw@?)pHbPj!@k4hcN`v{_QRdHuMnbqUP9o60_P!=CI}Qlq+GtR z$a@~Wms>ZkQuP%poq0O78b9_E|0#d*ncu(<6j~KjW9YPMAbke?l;@v&2~UTdKYN~9 zt;Y7&CcFE4gkiw)+6pT0**Vz90an2EEzQE>8M?g@zxxMs_SB1YqkMx(`WkRXJ@ z*B;|>N)SX0M5sYZ{2Bi1fA~qH>#~09WonfLzWe4T zfBa|P#0E{e;~`$;p^6lRz&eey3M>c#zE`lkROc7}!B27Z_2=1Kf1Or6V!qX8uF+wx zy~rpLZ!-+jd$6)eS_ZK7XRo!`){d56>i>o zi?4mgY4waul=7g<|d;^L+A{OxmJVt;?1 z2k(6kzxAo#W3M}6dw0MnAM+o7@;{`T>~S#KqS306Wf|+YZc~jL4E6^^+NTnF)MJk` z%WW#5U^E&q8t3#!LmG_+#uz%Cd6G2a_HLKV3bwZQ$gRW=LvmxNMHMPxOkr{kx(8&1 zqup-eD?xv-&&ruO=I5H^d5STH-Q8W1B*XVYE}Xy2laGIhfBT2OLATqbI~t(N*(q_H z6!`yln{cQ9o+^Xc$<9f+n`~N0mFdC=axM8?>-~D)D zjg75s3JWScN437lzxniUaQ*si;?^SdW`#h8Z0_vRt~W@M1e1<&4y;uKp$87Kq(Bov zE)mX6UV}s_kM)ghZoPAzz}qDDGRFN4E}T6_v-%1Lxgrc>##x_O8ibn|4ipZJ6M_Pd zjh!LC_Ur!_fBzFdM6ET(xDnfX19o=yxDrGleER(arDHzv@gL#w$FJ}|{pzo=dVURz zqgiXRwR?kVT%i`7Aq)b3`w#vFkq*&XWAlPH-*}tO`~uBJhcts{zWyz~^3`wCXe_h7 zzQxzR@gjHMy~N!Y7r1fjbw2cg|A3n}H_(1ar&WPM@xUV=U_2Vq?`

Mt^UEahecn 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

h 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 DB0@lZ literal 117722 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

>c#!{!vTI?~&A2 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 */ const tilesToDrawByZ = {}; tilesToDrawByZ[z] = {}; - const findLoadedTiles = this.createLoadedTileFinder( - tileSource, projection, tilesToDrawByZ); - - const hints = frameState.viewHints; - const animatingOrInteracting = hints[ViewHint.ANIMATING] || hints[ViewHint.INTERACTING]; + const findLoadedTiles = this.createLoadedTileFinder(tileSource, projection, tilesToDrawByZ); const tmpExtent = this.tmpExtent; const tmpTileRange = this.tmpTileRange_; this.newTiles_ = false; - let tile, x, y; - for (x = tileRange.minX; x <= tileRange.maxX; ++x) { - for (y = tileRange.minY; y <= tileRange.maxY; ++y) { - if (Date.now() - frameState.time > 16 && animatingOrInteracting) { - continue; - } - tile = this.getTile(z, x, y, pixelRatio, projection); + for (let x = tileRange.minX; x <= tileRange.maxX; ++x) { + for (let y = tileRange.minY; y <= tileRange.maxY; ++y) { + const tile = this.getTile(z, x, y, pixelRatio, projection); if (this.isDrawableTile_(tile)) { const uid = getUid(this); if (tile.getState() == TileState.LOADED) { @@ -192,45 +219,28 @@ class CanvasTileLayerRenderer extends IntermediateCanvasRenderer { } } - const childTileRange = tileGrid.getTileCoordChildTileRange( - tile.tileCoord, tmpTileRange, tmpExtent); + const childTileRange = tileGrid.getTileCoordChildTileRange(tile.tileCoord, tmpTileRange, tmpExtent); + let covered = false; if (childTileRange) { covered = findLoadedTiles(z + 1, childTileRange); } if (!covered) { - tileGrid.forEachTileCoordParentTileRange( - tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent); + tileGrid.forEachTileCoordParentTileRange(tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent); } } } - const renderedResolution = tileResolution * pixelRatio / tilePixelRatio * oversampling; - if (!(this.renderedResolution && Date.now() - frameState.time > 16 && animatingOrInteracting) && ( - this.newTiles_ || - !(this.renderedExtent_ && containsExtent(this.renderedExtent_, extent)) || - this.renderedRevision != sourceRevision || - oversampling != this.oversampling_ || - !animatingOrInteracting && renderedResolution != this.renderedResolution - )) { + if (this.newTiles_ || !this.renderedExtent_ || !equals(canvasExtent, this.renderedExtent_)) { const context = this.context; - if (context) { - const tilePixelSize = tileSource.getTilePixelSize(z, pixelRatio, projection); - const width = Math.round(tileRange.getWidth() * tilePixelSize[0] / oversampling); - const height = Math.round(tileRange.getHeight() * tilePixelSize[1] / oversampling); - const canvas = context.canvas; - if (canvas.width != width || canvas.height != height) { - this.oversampling_ = oversampling; - canvas.width = width; - canvas.height = height; - } else { - if (this.renderedExtent_ && !equals(imageExtent, this.renderedExtent_)) { - context.clearRect(0, 0, width, height); - } - oversampling = this.oversampling_; - } + const canvas = context.canvas; + if (canvas.width != width || canvas.height != height) { + canvas.width = width; + canvas.height = height; + } else { + context.clearRect(0, 0, width, height); } this.renderedTiles.length = 0; @@ -245,39 +255,43 @@ class CanvasTileLayerRenderer extends IntermediateCanvasRenderer { return a > b ? 1 : a < b ? -1 : 0; } }); - let currentResolution, currentScale, currentTilePixelSize, currentZ, i, ii; - let tileExtent, tileGutter, tilesToDraw, w, h; - for (i = 0, ii = zs.length; i < ii; ++i) { - currentZ = zs[i]; - currentTilePixelSize = tileSource.getTilePixelSize(currentZ, pixelRatio, projection); - currentResolution = tileGrid.getResolution(currentZ); - currentScale = currentResolution / tileResolution; - tileGutter = tilePixelRatio * tileSource.getGutterForProjection(projection); - tilesToDraw = tilesToDrawByZ[currentZ]; + + for (let i = 0, ii = zs.length; i < ii; ++i) { + const currentZ = zs[i]; + const currentTilePixelSize = tileSource.getTilePixelSize(currentZ, pixelRatio, projection); + const currentResolution = tileGrid.getResolution(currentZ); + const currentScale = currentResolution / tileResolution; + const tileGutter = tilePixelRatio * tileSource.getGutterForProjection(projection); + const tilesToDraw = tilesToDrawByZ[currentZ]; for (const tileCoordKey in tilesToDraw) { - tile = tilesToDraw[tileCoordKey]; - tileExtent = tileGrid.getTileCoordExtent(tile.getTileCoord(), tmpExtent); - x = (tileExtent[0] - imageExtent[0]) / tileResolution * tilePixelRatio / oversampling; - y = (imageExtent[3] - tileExtent[3]) / tileResolution * tilePixelRatio / oversampling; - w = currentTilePixelSize[0] * currentScale / oversampling; - h = currentTilePixelSize[1] * currentScale / oversampling; + const tile = tilesToDraw[tileCoordKey]; + const tileExtent = tileGrid.getTileCoordExtent(tile.getTileCoord(), tmpExtent); + const x = (tileExtent[0] - canvasExtent[0]) / tileResolution; + const y = (canvasExtent[3] - tileExtent[3]) / tileResolution; + const w = currentTilePixelSize[0] * currentScale; + const h = currentTilePixelSize[1] * currentScale; this.drawTileImage(tile, frameState, layerState, x, y, w, h, tileGutter, z === currentZ); this.renderedTiles.push(tile); } } this.renderedRevision = sourceRevision; - this.renderedResolution = tileResolution * pixelRatio / tilePixelRatio * oversampling; - this.renderedExtent_ = imageExtent; + this.renderedResolution = tileResolution; + this.renderedExtent_ = canvasExtent; } - const scale = this.renderedResolution / viewResolution; - const transform = composeTransform(this.imageTransform_, - pixelRatio * size[0] / 2, pixelRatio * size[1] / 2, + const scale = this.renderedResolution / frameState.viewState.resolution; + const halfWidth = width / 2; + const halfHeight = height / 2; + + const transform = composeTransform( + this.imageTransform_, + halfWidth, halfHeight, scale, scale, - 0, - (this.renderedExtent_[0] - viewCenter[0]) / this.renderedResolution * pixelRatio, - (viewCenter[1] - this.renderedExtent_[3]) / this.renderedResolution * pixelRatio); + rotation, + -halfWidth, -halfHeight + ); + composeTransform(this.coordinateToCanvasPixelTransform, pixelRatio * size[0] / 2 - transform[4], pixelRatio * size[1] / 2 - transform[5], pixelRatio / viewResolution, -pixelRatio / viewResolution, @@ -293,6 +307,35 @@ class CanvasTileLayerRenderer extends IntermediateCanvasRenderer { return this.renderedTiles.length > 0; } + /** + * @inheritDoc + */ + renderFrame(frameState, layerState) { + const context = this.context; + this.preRender(context, frameState); + + // consider moving work from prepareFrame to here + + this.postRender(context, frameState, layerState); + + const canvas = context.canvas; + + const opacity = layerState.opacity; + if (opacity !== canvas.style.opacity) { + canvas.style.opacity = opacity; + } + + const rotation = frameState.viewState.rotation; + const scale = this.renderedResolution / frameState.viewState.resolution; + + const transform = 'rotate(' + rotation + 'rad) scale(' + scale + ')'; + if (transform !== canvas.style.transform) { + canvas.style.transform = transform; + } + + return canvas; + } + /** * @param {import("../../Tile.js").default} tile Tile. * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. From 6234b695128d994dacfece861fef3682b136d9a5 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Wed, 14 Nov 2018 15:46:43 +0100 Subject: [PATCH 07/73] Handle opacity and rotation in canvas intermediate renderer --- src/ol/renderer/canvas/IntermediateCanvas.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/ol/renderer/canvas/IntermediateCanvas.js b/src/ol/renderer/canvas/IntermediateCanvas.js index 5fddae6104..27e325f132 100644 --- a/src/ol/renderer/canvas/IntermediateCanvas.js +++ b/src/ol/renderer/canvas/IntermediateCanvas.js @@ -89,7 +89,20 @@ class IntermediateCanvasRenderer extends CanvasLayerRenderer { } this.postRender(this.layerContext, frameState, layerState); - return this.layerContext.canvas; + + const canvas = this.layerContext.canvas; + const opacity = layerState.opacity; + if (opacity !== canvas.style.opacity) { + canvas.style.opacity = opacity; + } + + const rotation = frameState.viewState.rotation; + const transform = 'rotate(' + rotation + 'rad)'; + if (transform !== canvas.style.transform) { + canvas.style.transform = transform; + } + + return canvas; } /** From 39a4f42e3d673d9bcc4b322cf907d8672c8cb4f7 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Wed, 14 Nov 2018 15:47:26 +0100 Subject: [PATCH 08/73] Resize canvas to handle rotation --- src/ol/renderer/canvas/IntermediateCanvas.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/ol/renderer/canvas/IntermediateCanvas.js b/src/ol/renderer/canvas/IntermediateCanvas.js index 27e325f132..fe4c4ddc98 100644 --- a/src/ol/renderer/canvas/IntermediateCanvas.js +++ b/src/ol/renderer/canvas/IntermediateCanvas.js @@ -127,8 +127,14 @@ class IntermediateCanvasRenderer extends CanvasLayerRenderer { clear(frameState) { const pixelRatio = frameState.pixelRatio; const canvas = this.layerContext.canvas; - const width = Math.round(frameState.size[0] * pixelRatio); - const height = Math.round(frameState.size[1] * pixelRatio); + + let width = Math.round(frameState.size[0] * pixelRatio); + let height = Math.round(frameState.size[1] * pixelRatio); + const rotation = frameState.viewState.rotation; + if (rotation) { + const size = Math.round(Math.sqrt(width * width + height * height)); + width = height = size; + } if (canvas.width != width || canvas.height != height) { canvas.width = width; From 20e5841aed7a093684c719c275f4f357bce272bf Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Wed, 14 Nov 2018 15:51:18 +0100 Subject: [PATCH 09/73] Remove vector mode for vector tile rendering --- doc/errors/index.md | 2 +- src/ol/layer/VectorTile.js | 5 ++-- src/ol/layer/VectorTileRenderType.js | 3 +-- src/ol/renderer/canvas/VectorTileLayer.js | 9 +++---- .../renderer/canvas/vectortilelayer.test.js | 25 ------------------- 5 files changed, 7 insertions(+), 37 deletions(-) diff --git a/doc/errors/index.md b/doc/errors/index.md index c99831c307..d783685479 100644 --- a/doc/errors/index.md +++ b/doc/errors/index.md @@ -111,7 +111,7 @@ Features for `updates` must have an id set by the feature reader or `ol.Feature# ### 28 -`renderMode` must be `'image'`, `'hybrid'` or `'vector'`. +`renderMode` must be `'image'` or `'hybrid'`. ### 29 diff --git a/src/ol/layer/VectorTile.js b/src/ol/layer/VectorTile.js index 758e6f516e..9024553ea2 100644 --- a/src/ol/layer/VectorTile.js +++ b/src/ol/layer/VectorTile.js @@ -94,9 +94,8 @@ class VectorTileLayer extends BaseVectorLayer { let renderMode = options.renderMode || VectorTileRenderType.HYBRID; assert(renderMode == undefined || renderMode == VectorTileRenderType.IMAGE || - renderMode == VectorTileRenderType.HYBRID || - renderMode == VectorTileRenderType.VECTOR, - 28); // `renderMode` must be `'image'`, `'hybrid'` or `'vector'` + renderMode == VectorTileRenderType.HYBRID, + 28); // `renderMode` must be `'image'` or `'hybrid'` if (options.declutter && renderMode == VectorTileRenderType.IMAGE) { renderMode = VectorTileRenderType.HYBRID; diff --git a/src/ol/layer/VectorTileRenderType.js b/src/ol/layer/VectorTileRenderType.js index 330d16ccb2..8cf99dbf71 100644 --- a/src/ol/layer/VectorTileRenderType.js +++ b/src/ol/layer/VectorTileRenderType.js @@ -17,6 +17,5 @@ */ export default { IMAGE: 'image', - HYBRID: 'hybrid', - VECTOR: 'vector' + HYBRID: 'hybrid' }; diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index 62850f0974..62c777038f 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -58,9 +58,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { * @param {import("../../layer/VectorTile.js").default} layer VectorTile layer. */ constructor(layer) { - - const renderMode = layer.getRenderMode(); - super(layer, renderMode === VectorTileRenderType.VECTOR); + super(layer); /** * Declutter tree. @@ -86,9 +84,8 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { */ this.tmpTransform_ = createTransform(); - // Use lower resolution for pure vector rendering. Closest resolution otherwise. - this.zDirection = renderMode === VectorTileRenderType.VECTOR ? 1 : 0; - + // Use closest resolution. + this.zDirection = 0; listen(labelCache, EventType.CLEAR, this.handleFontsChanged_, this); diff --git a/test/spec/ol/renderer/canvas/vectortilelayer.test.js b/test/spec/ol/renderer/canvas/vectortilelayer.test.js index 4b3f6d7e1e..96f5ab22f6 100644 --- a/test/spec/ol/renderer/canvas/vectortilelayer.test.js +++ b/test/spec/ol/renderer/canvas/vectortilelayer.test.js @@ -97,31 +97,6 @@ describe('ol.renderer.canvas.VectorTileLayer', function() { expect(renderer.zDirection).to.be(0); }); - it('uses lower resolution for pure vector rendering', function() { - const testLayer = new VectorTileLayer({ - renderMode: VectorTileRenderType.VECTOR, - source: source, - style: layerStyle - }); - const renderer = new CanvasVectorTileLayerRenderer(testLayer); - expect(renderer.zDirection).to.be(1); - }); - - it('does not render images for pure vector rendering', function() { - const testLayer = new VectorTileLayer({ - renderMode: VectorTileRenderType.VECTOR, - source: source, - style: layerStyle - }); - map.removeLayer(layer); - map.addLayer(testLayer); - const spy = sinon.spy(CanvasVectorTileLayerRenderer.prototype, - 'renderTileImage_'); - map.renderSync(); - expect(spy.callCount).to.be(0); - spy.restore(); - }); - it('does not render replays for pure image rendering', function() { const testLayer = new VectorTileLayer({ renderMode: VectorTileRenderType.IMAGE, From 87e5bbac4d92713d5f456ad6d3bf632b8e742aeb Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Wed, 14 Nov 2018 16:51:08 +0100 Subject: [PATCH 10/73] Image layer renderer --- src/ol/renderer/canvas/ImageLayer.js | 83 ++++++++++++++++++++++++++-- src/ol/source/Image.js | 1 + 2 files changed, 80 insertions(+), 4 deletions(-) diff --git a/src/ol/renderer/canvas/ImageLayer.js b/src/ol/renderer/canvas/ImageLayer.js index 0bca5ddad6..1b2353270b 100644 --- a/src/ol/renderer/canvas/ImageLayer.js +++ b/src/ol/renderer/canvas/ImageLayer.js @@ -3,8 +3,10 @@ */ import {ENABLE_RASTER_REPROJECTION} from '../../reproj/common.js'; import ViewHint from '../../ViewHint.js'; +import {containsExtent, intersects} from '../../extent.js'; import {getIntersection, isEmpty} from '../../extent.js'; -import IntermediateCanvasRenderer from './IntermediateCanvas.js'; +import {createCanvasContext2D} from '../../dom.js'; +import CanvasLayerRenderer from './Layer.js'; import {create as createTransform, compose as composeTransform} from '../../transform.js'; /** @@ -12,7 +14,7 @@ import {create as createTransform, compose as composeTransform} from '../../tran * Canvas renderer for image layers. * @api */ -class CanvasImageLayerRenderer extends IntermediateCanvasRenderer { +class CanvasImageLayerRenderer extends CanvasLayerRenderer { /** * @param {import("../../layer/Image.js").default} imageLayer Image layer. @@ -20,6 +22,21 @@ class CanvasImageLayerRenderer extends IntermediateCanvasRenderer { constructor(imageLayer) { super(imageLayer); + /** + * @protected + * @type {CanvasRenderingContext2D} + */ + this.context = createCanvasContext2D(); + + const canvas = this.context.canvas; + canvas.style.position = 'absolute'; + + /** + * @protected + * @type {import("../../transform.js").Transform} + */ + this.coordinateToCanvasPixelTransform = createTransform(); + /** * @protected * @type {?import("../../ImageBase.js").default} @@ -52,8 +69,6 @@ class CanvasImageLayerRenderer extends IntermediateCanvasRenderer { * @inheritDoc */ prepareFrame(frameState, layerState) { - - this.clear(frameState); const pixelRatio = frameState.pixelRatio; const size = frameState.size; const viewState = frameState.viewState; @@ -93,12 +108,14 @@ class CanvasImageLayerRenderer extends IntermediateCanvasRenderer { const imagePixelRatio = image.getPixelRatio(); const scale = pixelRatio * imageResolution / (viewResolution * imagePixelRatio); + const transform = composeTransform(this.imageTransform_, pixelRatio * size[0] / 2, pixelRatio * size[1] / 2, scale, scale, 0, imagePixelRatio * (imageExtent[0] - viewCenter[0]) / imageResolution, imagePixelRatio * (viewCenter[1] - imageExtent[3]) / imageResolution); + composeTransform(this.coordinateToCanvasPixelTransform, pixelRatio * size[0] / 2 - transform[4], pixelRatio * size[1] / 2 - transform[5], pixelRatio / viewResolution, -pixelRatio / viewResolution, @@ -111,6 +128,64 @@ class CanvasImageLayerRenderer extends IntermediateCanvasRenderer { return !!this.image_; } + /** + * @inheritDoc + */ + renderFrame(frameState, layerState) { + const pixelRatio = frameState.pixelRatio; + const context = this.context; + const canvas = context.canvas; + + let width = Math.round(frameState.size[0] * pixelRatio); + let height = Math.round(frameState.size[1] * pixelRatio); + const rotation = frameState.viewState.rotation; + if (rotation) { + const size = Math.round(Math.sqrt(width * width + height * height)); + width = height = size; + } + + if (canvas.width != width || canvas.height != height) { + canvas.width = width; + canvas.height = height; + canvas.style.width = (width / pixelRatio) + 'px'; + canvas.style.height = (height / pixelRatio) + 'px'; + } else { + context.clearRect(0, 0, width, height); + } + + const image = this.image_.getImage(); + + // clipped rendering if layer extent is set + const extent = layerState.extent; + const clipped = extent !== undefined && + !containsExtent(extent, frameState.extent) && + intersects(extent, frameState.extent); + if (clipped) { + this.clip(context, frameState, extent); + } + + const imageTransform = this.getImageTransform(); + + // for performance reasons, context.setTransform is only used + // when the view is rotated. see http://jsperf.com/canvas-transform + const dx = imageTransform[4]; + const dy = imageTransform[5]; + const dw = image.width * imageTransform[0]; + const dh = image.height * imageTransform[3]; + + if (dw >= 0.5 && dh >= 0.5) { + this.context.drawImage(image, 0, 0, +image.width, +image.height, + Math.round(dx), Math.round(dy), Math.round(dw), Math.round(dh)); + } + + if (clipped) { + context.restore(); + } + + return canvas; + + } + } diff --git a/src/ol/source/Image.js b/src/ol/source/Image.js index 8f2164c0f2..4aed75f417 100644 --- a/src/ol/source/Image.js +++ b/src/ol/source/Image.js @@ -212,6 +212,7 @@ class ImageSource extends Source { this.dispatchEvent( new ImageSourceEvent(ImageSourceEventType.IMAGELOADEND, image)); + this.changed(); break; case ImageState.ERROR: this.loading = false; From 489af4023d71f853354ca87c172c621667ee80f0 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Wed, 14 Nov 2018 18:21:43 +0100 Subject: [PATCH 11/73] Work with high dpi tiles --- src/ol/renderer/canvas/TileLayer.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/ol/renderer/canvas/TileLayer.js b/src/ol/renderer/canvas/TileLayer.js index 224efca03f..7cf15caaa8 100644 --- a/src/ol/renderer/canvas/TileLayer.js +++ b/src/ol/renderer/canvas/TileLayer.js @@ -49,6 +49,12 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { */ this.renderedExtent_ = null; + /** + * @private + * @type {number} + */ + this.renderedPixelRatio_ = 1; + /** * @protected * @type {number} @@ -179,8 +185,8 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { width = height = size; } - const dx = tileResolution * width / 2; - const dy = tileResolution * height / 2; + const dx = tileResolution * width / 2 / tilePixelRatio; + const dy = tileResolution * height / 2 / tilePixelRatio; const canvasExtent = [ viewCenter[0] - dx, viewCenter[1] - dy, @@ -266,8 +272,8 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { for (const tileCoordKey in tilesToDraw) { const tile = tilesToDraw[tileCoordKey]; const tileExtent = tileGrid.getTileCoordExtent(tile.getTileCoord(), tmpExtent); - const x = (tileExtent[0] - canvasExtent[0]) / tileResolution; - const y = (canvasExtent[3] - tileExtent[3]) / tileResolution; + const x = tilePixelRatio * (tileExtent[0] - canvasExtent[0]) / tileResolution; + const y = tilePixelRatio * (canvasExtent[3] - tileExtent[3]) / tileResolution; const w = currentTilePixelSize[0] * currentScale; const h = currentTilePixelSize[1] * currentScale; this.drawTileImage(tile, frameState, layerState, x, y, w, h, tileGutter, z === currentZ); @@ -277,6 +283,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { this.renderedRevision = sourceRevision; this.renderedResolution = tileResolution; + this.renderedPixelRatio_ = tilePixelRatio; this.renderedExtent_ = canvasExtent; } @@ -326,7 +333,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { } const rotation = frameState.viewState.rotation; - const scale = this.renderedResolution / frameState.viewState.resolution; + const scale = this.renderedResolution / frameState.viewState.resolution / this.renderedPixelRatio_; const transform = 'rotate(' + rotation + 'rad) scale(' + scale + ')'; if (transform !== canvas.style.transform) { From 610fcab79e613cacf5cc4453635e2495e8823afe Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Wed, 14 Nov 2018 18:22:10 +0100 Subject: [PATCH 12/73] Dispatch pre and post render events --- src/ol/renderer/canvas/Layer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ol/renderer/canvas/Layer.js b/src/ol/renderer/canvas/Layer.js index fc7f6bb333..d1b4bd01bd 100644 --- a/src/ol/renderer/canvas/Layer.js +++ b/src/ol/renderer/canvas/Layer.js @@ -121,7 +121,7 @@ class CanvasLayerRenderer extends LayerRenderer { * @protected */ preRender(context, frameState, opt_transform) { - this.dispatchComposeEvent_(RenderEventType.POSTCOMPOSE, context, frameState, opt_transform); + this.dispatchComposeEvent_(RenderEventType.PRERENDER, context, frameState, opt_transform); } /** @@ -131,7 +131,7 @@ class CanvasLayerRenderer extends LayerRenderer { * @protected */ postRender(context, frameState, opt_transform) { - this.dispatchComposeEvent_(RenderEventType.POSTCOMPOSE, context, frameState, opt_transform); + this.dispatchComposeEvent_(RenderEventType.POSTRENDER, context, frameState, opt_transform); } /** From c37b6202a0cae111d9333f7154a82c4d3c392692 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Wed, 14 Nov 2018 18:41:59 +0100 Subject: [PATCH 13/73] Use Map --- examples/mapbox-layer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/mapbox-layer.js b/examples/mapbox-layer.js index b0965a015d..aa2fbadcf4 100644 --- a/examples/mapbox-layer.js +++ b/examples/mapbox-layer.js @@ -1,4 +1,4 @@ -import Map from '../src/ol/CompositeMap.js'; +import Map from '../src/ol/Map.js'; import View from '../src/ol/View.js'; import Layer from '../src/ol/layer/Layer'; import {assign} from '../src/ol/obj'; From 5fffb67242b353325279fe85dd87aea47df83b09 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Wed, 14 Nov 2018 19:02:44 +0100 Subject: [PATCH 14/73] Clear canvas when nothing to replay --- src/ol/renderer/canvas/VectorLayer.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/ol/renderer/canvas/VectorLayer.js b/src/ol/renderer/canvas/VectorLayer.js index 2d030f60a5..ea80b70909 100644 --- a/src/ol/renderer/canvas/VectorLayer.js +++ b/src/ol/renderer/canvas/VectorLayer.js @@ -103,13 +103,16 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { */ render(frameState, layerState) { const replayGroup = this.replayGroup_; - if (!replayGroup || replayGroup.isEmpty()) { - return; - } - const context = this.context; const canvas = context.canvas; + if (!replayGroup || replayGroup.isEmpty()) { + if (canvas.width > 0) { + canvas.width = 0; + } + return; + } + const extent = frameState.extent; const pixelRatio = frameState.pixelRatio; const viewState = frameState.viewState; From 8eb48604e9b12ce8014dae5efea7f3dc7d8844ac Mon Sep 17 00:00:00 2001 From: ahocevar Date: Wed, 14 Nov 2018 19:28:09 +0100 Subject: [PATCH 15/73] Round tile x and y and reuse w and h --- src/ol/renderer/canvas/TileLayer.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ol/renderer/canvas/TileLayer.js b/src/ol/renderer/canvas/TileLayer.js index 7cf15caaa8..250f4fb632 100644 --- a/src/ol/renderer/canvas/TileLayer.js +++ b/src/ol/renderer/canvas/TileLayer.js @@ -267,15 +267,15 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { const currentTilePixelSize = tileSource.getTilePixelSize(currentZ, pixelRatio, projection); const currentResolution = tileGrid.getResolution(currentZ); const currentScale = currentResolution / tileResolution; + const w = currentTilePixelSize[0] * currentScale; + const h = currentTilePixelSize[1] * currentScale; const tileGutter = tilePixelRatio * tileSource.getGutterForProjection(projection); const tilesToDraw = tilesToDrawByZ[currentZ]; for (const tileCoordKey in tilesToDraw) { const tile = tilesToDraw[tileCoordKey]; const tileExtent = tileGrid.getTileCoordExtent(tile.getTileCoord(), tmpExtent); - const x = tilePixelRatio * (tileExtent[0] - canvasExtent[0]) / tileResolution; - const y = tilePixelRatio * (canvasExtent[3] - tileExtent[3]) / tileResolution; - const w = currentTilePixelSize[0] * currentScale; - const h = currentTilePixelSize[1] * currentScale; + const x = Math.round(tilePixelRatio * (tileExtent[0] - canvasExtent[0]) / tileResolution); + const y = Math.round(tilePixelRatio * (canvasExtent[3] - tileExtent[3]) / tileResolution); this.drawTileImage(tile, frameState, layerState, x, y, w, h, tileGutter, z === currentZ); this.renderedTiles.push(tile); } From c137b68938ede4de66aa54ddbb171ea0b45c53aa Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Wed, 14 Nov 2018 18:47:34 +0100 Subject: [PATCH 16/73] Dispatch pre-render before rendering --- src/ol/renderer/canvas/TileLayer.js | 137 ++++++++++++---------------- 1 file changed, 59 insertions(+), 78 deletions(-) diff --git a/src/ol/renderer/canvas/TileLayer.js b/src/ol/renderer/canvas/TileLayer.js index 250f4fb632..db48757050 100644 --- a/src/ol/renderer/canvas/TileLayer.js +++ b/src/ol/renderer/canvas/TileLayer.js @@ -4,7 +4,7 @@ import {getUid} from '../../util.js'; import TileRange from '../../TileRange.js'; import TileState from '../../TileState.js'; -import {createEmpty, equals, getIntersection, isEmpty} from '../../extent.js'; +import {createEmpty, getIntersection} from '../../extent.js'; import {createCanvasContext2D} from '../../dom.js'; import CanvasLayerRenderer from './Layer.js'; import {create as createTransform, compose as composeTransform} from '../../transform.js'; @@ -49,12 +49,6 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { */ this.renderedExtent_ = null; - /** - * @private - * @type {number} - */ - this.renderedPixelRatio_ = 1; - /** * @protected * @type {number} @@ -144,6 +138,14 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { * @inheritDoc */ prepareFrame(frameState, layerState) { + return true; + } + + /** + * @inheritDoc + */ + renderFrame(frameState, layerState) { + const context = this.context; const size = frameState.size; const viewState = frameState.viewState; const projection = viewState.projection; @@ -163,10 +165,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { if (layerState.extent !== undefined) { extent = getIntersection(extent, layerState.extent); } - if (isEmpty(extent)) { - // Return false to prevent the rendering of the layer. - return false; - } + // TODO: clip by layer extent const tilePixelRatio = tileSource.getTilePixelRatio(pixelRatio); @@ -238,59 +237,57 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { } } - if (this.newTiles_ || !this.renderedExtent_ || !equals(canvasExtent, this.renderedExtent_)) { - - const context = this.context; - const canvas = context.canvas; - if (canvas.width != width || canvas.height != height) { - canvas.width = width; - canvas.height = height; - } else { - context.clearRect(0, 0, width, height); - } - - this.renderedTiles.length = 0; - /** @type {Array} */ - const zs = Object.keys(tilesToDrawByZ).map(Number); - zs.sort(function(a, b) { - if (a === z) { - return 1; - } else if (b === z) { - return -1; - } else { - return a > b ? 1 : a < b ? -1 : 0; - } - }); - - for (let i = 0, ii = zs.length; i < ii; ++i) { - const currentZ = zs[i]; - const currentTilePixelSize = tileSource.getTilePixelSize(currentZ, pixelRatio, projection); - const currentResolution = tileGrid.getResolution(currentZ); - const currentScale = currentResolution / tileResolution; - const w = currentTilePixelSize[0] * currentScale; - const h = currentTilePixelSize[1] * currentScale; - const tileGutter = tilePixelRatio * tileSource.getGutterForProjection(projection); - const tilesToDraw = tilesToDrawByZ[currentZ]; - for (const tileCoordKey in tilesToDraw) { - const tile = tilesToDraw[tileCoordKey]; - const tileExtent = tileGrid.getTileCoordExtent(tile.getTileCoord(), tmpExtent); - const x = Math.round(tilePixelRatio * (tileExtent[0] - canvasExtent[0]) / tileResolution); - const y = Math.round(tilePixelRatio * (canvasExtent[3] - tileExtent[3]) / tileResolution); - this.drawTileImage(tile, frameState, layerState, x, y, w, h, tileGutter, z === currentZ); - this.renderedTiles.push(tile); - } - } - - this.renderedRevision = sourceRevision; - this.renderedResolution = tileResolution; - this.renderedPixelRatio_ = tilePixelRatio; - this.renderedExtent_ = canvasExtent; + const canvas = context.canvas; + if (canvas.width != width || canvas.height != height) { + canvas.width = width; + canvas.height = height; + } else { + context.clearRect(0, 0, width, height); } + this.preRender(context, frameState); + + this.renderedTiles.length = 0; + /** @type {Array} */ + const zs = Object.keys(tilesToDrawByZ).map(Number); + zs.sort(function(a, b) { + if (a === z) { + return 1; + } else if (b === z) { + return -1; + } else { + return a > b ? 1 : a < b ? -1 : 0; + } + }); + + for (let i = 0, ii = zs.length; i < ii; ++i) { + const currentZ = zs[i]; + const currentTilePixelSize = tileSource.getTilePixelSize(currentZ, pixelRatio, projection); + const currentResolution = tileGrid.getResolution(currentZ); + const currentScale = currentResolution / tileResolution; + const w = currentTilePixelSize[0] * currentScale; + const h = currentTilePixelSize[1] * currentScale; + const tileGutter = tilePixelRatio * tileSource.getGutterForProjection(projection); + const tilesToDraw = tilesToDrawByZ[currentZ]; + for (const tileCoordKey in tilesToDraw) { + const tile = tilesToDraw[tileCoordKey]; + const tileExtent = tileGrid.getTileCoordExtent(tile.getTileCoord(), tmpExtent); + const x = Math.round(tilePixelRatio * (tileExtent[0] - canvasExtent[0]) / tileResolution); + const y = Math.round(tilePixelRatio * (canvasExtent[3] - tileExtent[3]) / tileResolution); + this.drawTileImage(tile, frameState, layerState, x, y, w, h, tileGutter, z === currentZ); + this.renderedTiles.push(tile); + } + } + + + this.renderedRevision = sourceRevision; + this.renderedResolution = tileResolution; + this.renderedExtent_ = canvasExtent; const scale = this.renderedResolution / frameState.viewState.resolution; const halfWidth = width / 2; const halfHeight = height / 2; + // TODO: check where these are used and confirm they are correct const transform = composeTransform( this.imageTransform_, halfWidth, halfHeight, @@ -305,39 +302,23 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { 0, -viewCenter[0], -viewCenter[1]); - this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange); this.manageTilePyramid(frameState, tileSource, tileGrid, pixelRatio, projection, extent, z, tileLayer.getPreload()); this.scheduleExpireCache(frameState, tileSource); - return this.renderedTiles.length > 0; - } - - /** - * @inheritDoc - */ - renderFrame(frameState, layerState) { - const context = this.context; - this.preRender(context, frameState); - - // consider moving work from prepareFrame to here - this.postRender(context, frameState, layerState); - const canvas = context.canvas; - const opacity = layerState.opacity; if (opacity !== canvas.style.opacity) { canvas.style.opacity = opacity; } - const rotation = frameState.viewState.rotation; - const scale = this.renderedResolution / frameState.viewState.resolution / this.renderedPixelRatio_; + const canvasScale = this.renderedResolution / frameState.viewState.resolution / tilePixelRatio; - const transform = 'rotate(' + rotation + 'rad) scale(' + scale + ')'; - if (transform !== canvas.style.transform) { - canvas.style.transform = transform; + const canvasTransform = 'rotate(' + rotation + 'rad) scale(' + canvasScale + ')'; + if (canvasTransform !== canvas.style.transform) { + canvas.style.transform = canvasTransform; } return canvas; From f9ebb0c917dfe7961dacd9e359ce2d7d94376769 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Wed, 14 Nov 2018 19:36:13 +0100 Subject: [PATCH 17/73] Return two canvases from the vector tile layer renderer --- src/ol/renderer/canvas/VectorTileLayer.js | 220 +++++++++++++--------- 1 file changed, 136 insertions(+), 84 deletions(-) diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index 62c777038f..2e1a4afed8 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -2,6 +2,7 @@ * @module ol/renderer/canvas/VectorTileLayer */ import {getUid} from '../../util.js'; +import {createCanvasContext2D} from '../../dom.js'; import TileState from '../../TileState.js'; import ViewHint from '../../ViewHint.js'; import {listen, unlisten} from '../../events.js'; @@ -12,7 +13,7 @@ import VectorTileRenderType from '../../layer/VectorTileRenderType.js'; import {equivalent as equivalentProjection} from '../../proj.js'; import Units from '../../proj/Units.js'; import ReplayType from '../../render/ReplayType.js'; -import {labelCache, rotateAtOffset} from '../../render/canvas.js'; +import {labelCache} from '../../render/canvas.js'; import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js'; import {ORDER} from '../../render/replay.js'; import CanvasTileLayerRenderer from './TileLayer.js'; @@ -60,6 +61,33 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { constructor(layer) { super(layer); + const baseCanvas = this.context.canvas; + + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.overlayContext_ = createCanvasContext2D(); + + const overlayCanvas = this.overlayContext_.canvas; + overlayCanvas.style.position = 'absolute'; + + const container = document.createElement('div'); + const style = container.style; + style.position = 'absolute'; + style.display = 'flex'; + style.alignItems = 'center'; + style.justifyContent = 'center'; + + container.appendChild(baseCanvas); + container.appendChild(overlayCanvas); + + /** + * @private + * @type {HTMLElement} + */ + this.container_ = container; + /** * Declutter tree. * @private @@ -224,9 +252,9 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { render.call(this, feature); } } - const replayGroupInstructions = builderGroup.finish(); + const executorGroupInstructions = builderGroup.finish(); const renderingReplayGroup = new CanvasExecutorGroup(0, sharedExtent, resolution, - pixelRatio, source.getOverlaps(), this.declutterTree_, replayGroupInstructions, layer.getRenderBuffer()); + pixelRatio, source.getOverlaps(), this.declutterTree_, executorGroupInstructions, layer.getRenderBuffer()); sourceTile.setExecutorGroup(layer, tile.tileCoord.toString(), renderingReplayGroup); } builderState.renderedRevision = revision; @@ -329,101 +357,125 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { /** * @inheritDoc */ - postCompose(context, frameState, layerState) { + renderFrame(frameState, layerState) { + super.renderFrame(frameState, layerState); + const layer = /** @type {import("../../layer/VectorTile.js").default} */ (this.getLayer()); const renderMode = layer.getRenderMode(); - if (renderMode != VectorTileRenderType.IMAGE) { - const declutterReplays = layer.getDeclutter() ? {} : null; - const source = /** @type {import("../../source/VectorTile.js").default} */ (layer.getSource()); - const replayTypes = VECTOR_REPLAYS[renderMode]; - const pixelRatio = frameState.pixelRatio; - const rotation = frameState.viewState.rotation; - const size = frameState.size; - let offsetX, offsetY; - if (rotation) { - offsetX = Math.round(pixelRatio * size[0] / 2); - offsetY = Math.round(pixelRatio * size[1] / 2); - rotateAtOffset(context, -rotation, offsetX, offsetY); + if (renderMode === VectorTileRenderType.IMAGE) { + return this.container_; + } + + const context = this.overlayContext_; + const declutterReplays = layer.getDeclutter() ? {} : null; + const source = /** @type {import("../../source/VectorTile.js").default} */ (layer.getSource()); + const replayTypes = VECTOR_REPLAYS[renderMode]; + const pixelRatio = frameState.pixelRatio; + const rotation = frameState.viewState.rotation; + const size = frameState.size; + + // resize and clear + const canvas = context.canvas; + let width = Math.round(size[0] * pixelRatio); + let height = Math.round(size[1] * pixelRatio); + if (rotation) { + const size = Math.round(Math.sqrt(width * width + height * height)); + width = height = size; + } + if (canvas.width != width || canvas.height != height) { + canvas.width = width; + canvas.height = height; + canvas.style.width = (width / pixelRatio) + 'px'; + canvas.style.height = (height / pixelRatio) + 'px'; + } else { + context.clearRect(0, 0, width, height); + } + + if (declutterReplays) { + this.declutterTree_.clear(); + } + const viewHints = frameState.viewHints; + const snapToPixel = !(viewHints[ViewHint.ANIMATING] || viewHints[ViewHint.INTERACTING]); + const tiles = this.renderedTiles; + const tileGrid = source.getTileGridForProjection(frameState.viewState.projection); + const clips = []; + const zs = []; + for (let i = tiles.length - 1; i >= 0; --i) { + const tile = /** @type {import("../../VectorImageTile.js").default} */ (tiles[i]); + if (tile.getState() == TileState.ABORT) { + continue; } - if (declutterReplays) { - this.declutterTree_.clear(); - } - const viewHints = frameState.viewHints; - const snapToPixel = !(viewHints[ViewHint.ANIMATING] || viewHints[ViewHint.INTERACTING]); - const tiles = this.renderedTiles; - const tileGrid = source.getTileGridForProjection(frameState.viewState.projection); - const clips = []; - const zs = []; - for (let i = tiles.length - 1; i >= 0; --i) { - const tile = /** @type {import("../../VectorImageTile.js").default} */ (tiles[i]); - if (tile.getState() == TileState.ABORT) { + const tileCoord = tile.tileCoord; + const worldOffset = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent)[0] - tile.extent[0]; + let transform = undefined; + for (let t = 0, tt = tile.tileKeys.length; t < tt; ++t) { + const sourceTile = tile.getTile(tile.tileKeys[t]); + if (sourceTile.getState() != TileState.LOADED) { continue; } - const tileCoord = tile.tileCoord; - const worldOffset = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent)[0] - tile.extent[0]; - let transform = undefined; - for (let t = 0, tt = tile.tileKeys.length; t < tt; ++t) { - const sourceTile = tile.getTile(tile.tileKeys[t]); - if (sourceTile.getState() != TileState.LOADED) { - continue; - } - const executorGroup = /** @type {CanvasExecutorGroup} */ (sourceTile.getExecutorGroup(layer, tileCoord.toString())); - if (!executorGroup || !executorGroup.hasExecutors(replayTypes)) { - // sourceTile was not yet loaded when this.createReplayGroup_() was - // called, or it has no replays of the types we want to render - continue; - } - if (!transform) { - transform = this.getTransform(frameState, worldOffset); - } - const currentZ = sourceTile.tileCoord[0]; - const currentClip = executorGroup.getClipCoords(transform); - context.save(); - context.globalAlpha = layerState.opacity; - // Create a clip mask for regions in this low resolution tile that are - // already filled by a higher resolution tile - for (let j = 0, jj = clips.length; j < jj; ++j) { - const clip = clips[j]; - if (currentZ < zs[j]) { - context.beginPath(); - // counter-clockwise (outer ring) for current tile - context.moveTo(currentClip[0], currentClip[1]); - context.lineTo(currentClip[2], currentClip[3]); - context.lineTo(currentClip[4], currentClip[5]); - context.lineTo(currentClip[6], currentClip[7]); - // clockwise (inner ring) for higher resolution tile - context.moveTo(clip[6], clip[7]); - context.lineTo(clip[4], clip[5]); - context.lineTo(clip[2], clip[3]); - context.lineTo(clip[0], clip[1]); - context.clip(); - } - } - executorGroup.execute(context, transform, rotation, {}, snapToPixel, replayTypes, declutterReplays); - context.restore(); - clips.push(currentClip); - zs.push(currentZ); + const executorGroup = /** @type {CanvasExecutorGroup} */ (sourceTile.getExecutorGroup(layer, tileCoord.toString())); + if (!executorGroup || !executorGroup.hasExecutors(replayTypes)) { + // sourceTile was not yet loaded when this.createReplayGroup_() was + // called, or it has no replays of the types we want to render + continue; } - } - if (declutterReplays) { - replayDeclutter(declutterReplays, context, rotation, snapToPixel); - } - if (rotation) { - rotateAtOffset(context, rotation, - /** @type {number} */ (offsetX), /** @type {number} */ (offsetY)); + if (!transform) { + transform = this.getTransform(frameState, worldOffset); + } + const currentZ = sourceTile.tileCoord[0]; + const currentClip = executorGroup.getClipCoords(transform); + context.save(); + + // Create a clip mask for regions in this low resolution tile that are + // already filled by a higher resolution tile + for (let j = 0, jj = clips.length; j < jj; ++j) { + const clip = clips[j]; + if (currentZ < zs[j]) { + context.beginPath(); + // counter-clockwise (outer ring) for current tile + context.moveTo(currentClip[0], currentClip[1]); + context.lineTo(currentClip[2], currentClip[3]); + context.lineTo(currentClip[4], currentClip[5]); + context.lineTo(currentClip[6], currentClip[7]); + // clockwise (inner ring) for higher resolution tile + context.moveTo(clip[6], clip[7]); + context.lineTo(clip[4], clip[5]); + context.lineTo(clip[2], clip[3]); + context.lineTo(clip[0], clip[1]); + context.clip(); + } + } + executorGroup.execute(context, transform, rotation, {}, snapToPixel, replayTypes, declutterReplays); + context.restore(); + clips.push(currentClip); + zs.push(currentZ); } } - super.postCompose(context, frameState, layerState); + if (declutterReplays) { + replayDeclutter(declutterReplays, context, rotation, snapToPixel); + } + + const opacity = layerState.opacity; + if (opacity !== canvas.style.opacity) { + canvas.style.opacity = opacity; + } + + const canvasTransform = 'rotate(' + rotation + 'rad)'; + if (canvasTransform !== canvas.style.transform) { + canvas.style.transform = canvasTransform; + } + + return this.container_; } /** * @param {import("../../Feature.js").FeatureLike} feature Feature. * @param {number} squaredTolerance Squared tolerance. * @param {import("../../style/Style.js").default|Array} styles The style or array of styles. - * @param {import("../../render/canvas/BuilderGroup.js").default} replayGroup Replay group. + * @param {import("../../render/canvas/BuilderGroup.js").default} executorGroup Replay group. * @return {boolean} `true` if an image is loading. */ - renderFeature(feature, squaredTolerance, styles, replayGroup) { + renderFeature(feature, squaredTolerance, styles, executorGroup) { if (!styles) { return false; } @@ -431,12 +483,12 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { if (Array.isArray(styles)) { for (let i = 0, ii = styles.length; i < ii; ++i) { loading = renderFeature( - replayGroup, feature, styles[i], squaredTolerance, + executorGroup, feature, styles[i], squaredTolerance, this.handleStyleImageChange_, this) || loading; } } else { loading = renderFeature( - replayGroup, feature, styles, squaredTolerance, + executorGroup, feature, styles, squaredTolerance, this.handleStyleImageChange_, this); } return loading; From 6edac64b8159c2eeeac379d69d52c1b22a8f06dc Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Wed, 14 Nov 2018 20:36:30 +0100 Subject: [PATCH 18/73] Use the regular map in the rendering tests --- rendering/cases/linestring-style-css-filter/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rendering/cases/linestring-style-css-filter/main.js b/rendering/cases/linestring-style-css-filter/main.js index 925909548b..654a021118 100644 --- a/rendering/cases/linestring-style-css-filter/main.js +++ b/rendering/cases/linestring-style-css-filter/main.js @@ -1,4 +1,4 @@ -import Map from '../../../src/ol/CompositeMap.js'; +import Map from '../../../src/ol/Map.js'; import View from '../../../src/ol/View.js'; import Feature from '../../../src/ol/Feature.js'; import LineString from '../../../src/ol/geom/LineString.js'; From 9a4e665c3be8ee308e1afe18d7251613276a21c8 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Wed, 14 Nov 2018 20:53:28 +0100 Subject: [PATCH 19/73] Position tiles by offset instead of extent --- src/ol/renderer/canvas/TileLayer.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/ol/renderer/canvas/TileLayer.js b/src/ol/renderer/canvas/TileLayer.js index db48757050..15dcc0d46c 100644 --- a/src/ol/renderer/canvas/TileLayer.js +++ b/src/ol/renderer/canvas/TileLayer.js @@ -4,7 +4,7 @@ import {getUid} from '../../util.js'; import TileRange from '../../TileRange.js'; import TileState from '../../TileState.js'; -import {createEmpty, getIntersection} from '../../extent.js'; +import {createEmpty, getIntersection, getTopLeft} from '../../extent.js'; import {createCanvasContext2D} from '../../dom.js'; import CanvasLayerRenderer from './Layer.js'; import {create as createTransform, compose as composeTransform} from '../../transform.js'; @@ -266,13 +266,17 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { const currentScale = currentResolution / tileResolution; const w = currentTilePixelSize[0] * currentScale; const h = currentTilePixelSize[1] * currentScale; + const originTileCoord = tileGrid.getTileCoordForCoordAndZ(getTopLeft(canvasExtent), currentZ); + const originTileExtent = tileGrid.getTileCoordExtent(originTileCoord); + const originX = Math.round(tilePixelRatio * (originTileExtent[0] - canvasExtent[0]) / tileResolution); + const originY = Math.round(tilePixelRatio * (canvasExtent[3] - originTileExtent[3]) / tileResolution); const tileGutter = tilePixelRatio * tileSource.getGutterForProjection(projection); const tilesToDraw = tilesToDrawByZ[currentZ]; for (const tileCoordKey in tilesToDraw) { const tile = tilesToDraw[tileCoordKey]; - const tileExtent = tileGrid.getTileCoordExtent(tile.getTileCoord(), tmpExtent); - const x = Math.round(tilePixelRatio * (tileExtent[0] - canvasExtent[0]) / tileResolution); - const y = Math.round(tilePixelRatio * (canvasExtent[3] - tileExtent[3]) / tileResolution); + const tileCoord = tile.tileCoord; + const x = originX - (originTileCoord[1] - tileCoord[1]) * w; + const y = originY + (originTileCoord[2] - tileCoord[2]) * h; this.drawTileImage(tile, frameState, layerState, x, y, w, h, tileGutter, z === currentZ); this.renderedTiles.push(tile); } From 8822690cf4e9d43858c120b90f56c407413ee5d2 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Wed, 14 Nov 2018 21:04:04 +0100 Subject: [PATCH 20/73] Work with sources that have a max zoom --- src/ol/renderer/canvas/TileLayer.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ol/renderer/canvas/TileLayer.js b/src/ol/renderer/canvas/TileLayer.js index 15dcc0d46c..5ce5da9495 100644 --- a/src/ol/renderer/canvas/TileLayer.js +++ b/src/ol/renderer/canvas/TileLayer.js @@ -174,9 +174,9 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { let width = Math.round(frameState.size[0] * tilePixelRatio); let height = Math.round(frameState.size[1] * tilePixelRatio); if (tileResolution < viewResolution) { - const scale = tileResolution / tileGrid.getResolution(z + 1); - width *= scale; - height *= scale; + // scale canvas so it covers the viewport until new tiles come it + width *= 1.5; + height *= 1.5; } if (rotation) { From a9f98f2b1e61e39702bb9d4a8bb3b331127ccca0 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Wed, 14 Nov 2018 21:30:51 +0100 Subject: [PATCH 21/73] Rotation for image layers --- src/ol/renderer/canvas/ImageLayer.js | 102 ++++++++++++--------------- src/ol/renderer/canvas/TileLayer.js | 7 -- 2 files changed, 44 insertions(+), 65 deletions(-) diff --git a/src/ol/renderer/canvas/ImageLayer.js b/src/ol/renderer/canvas/ImageLayer.js index 1b2353270b..6f6fe12e4b 100644 --- a/src/ol/renderer/canvas/ImageLayer.js +++ b/src/ol/renderer/canvas/ImageLayer.js @@ -42,13 +42,6 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer { * @type {?import("../../ImageBase.js").default} */ this.image_ = null; - - /** - * @protected - * @type {import("../../transform.js").Transform} - */ - this.imageTransform_ = createTransform(); - } /** @@ -58,24 +51,14 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer { return !this.image_ ? null : this.image_.getImage(); } - /** - * @inheritDoc - */ - getImageTransform() { - return this.imageTransform_; - } - /** * @inheritDoc */ prepareFrame(frameState, layerState) { const pixelRatio = frameState.pixelRatio; - const size = frameState.size; const viewState = frameState.viewState; - const viewCenter = viewState.center; const viewResolution = viewState.resolution; - let image; const imageLayer = /** @type {import("../../layer/Image.js").default} */ (this.getLayer()); const imageSource = /** @type {import("../../source/Image.js").default} */ (imageLayer.getSource()); @@ -86,8 +69,7 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer { renderedExtent = getIntersection(renderedExtent, layerState.extent); } - if (!hints[ViewHint.ANIMATING] && !hints[ViewHint.INTERACTING] && - !isEmpty(renderedExtent)) { + if (!hints[ViewHint.ANIMATING] && !hints[ViewHint.INTERACTING] && !isEmpty(renderedExtent)) { let projection = viewState.projection; if (!ENABLE_RASTER_REPROJECTION) { const sourceProjection = imageSource.getProjection(); @@ -101,30 +83,6 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer { } } - if (this.image_) { - image = this.image_; - const imageExtent = image.getExtent(); - const imageResolution = image.getResolution(); - const imagePixelRatio = image.getPixelRatio(); - const scale = pixelRatio * imageResolution / - (viewResolution * imagePixelRatio); - - const transform = composeTransform(this.imageTransform_, - pixelRatio * size[0] / 2, pixelRatio * size[1] / 2, - scale, scale, - 0, - imagePixelRatio * (imageExtent[0] - viewCenter[0]) / imageResolution, - imagePixelRatio * (viewCenter[1] - imageExtent[3]) / imageResolution); - - composeTransform(this.coordinateToCanvasPixelTransform, - pixelRatio * size[0] / 2 - transform[4], pixelRatio * size[1] / 2 - transform[5], - pixelRatio / viewResolution, -pixelRatio / viewResolution, - 0, - -viewCenter[0], -viewCenter[1]); - - this.renderedResolution = imageResolution * pixelRatio / imagePixelRatio; - } - return !!this.image_; } @@ -132,18 +90,45 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer { * @inheritDoc */ renderFrame(frameState, layerState) { + const image = this.image_; + const imageExtent = image.getExtent(); + const imageResolution = image.getResolution(); + const imagePixelRatio = image.getPixelRatio(); const pixelRatio = frameState.pixelRatio; - const context = this.context; - const canvas = context.canvas; + const viewState = frameState.viewState; + const viewCenter = viewState.center; + const viewResolution = viewState.resolution; + const size = frameState.size; + const scale = pixelRatio * imageResolution / (viewResolution * imagePixelRatio); - let width = Math.round(frameState.size[0] * pixelRatio); - let height = Math.round(frameState.size[1] * pixelRatio); - const rotation = frameState.viewState.rotation; + let width = Math.round(size[0] * pixelRatio); + let height = Math.round(size[1] * pixelRatio); + const rotation = viewState.rotation; if (rotation) { const size = Math.round(Math.sqrt(width * width + height * height)); width = height = size; } + const transform = composeTransform(this.transform_, + width / 2, height / 2, + scale, scale, + 0, + imagePixelRatio * (imageExtent[0] - viewCenter[0]) / imageResolution, + imagePixelRatio * (viewCenter[1] - imageExtent[3]) / imageResolution); + + composeTransform(this.coordinateToCanvasPixelTransform, + pixelRatio * size[0] / 2 - transform[4], pixelRatio * size[1] / 2 - transform[5], + pixelRatio / viewResolution, -pixelRatio / viewResolution, + 0, + -viewCenter[0], -viewCenter[1]); + + this.renderedResolution = imageResolution * pixelRatio / imagePixelRatio; + + + const context = this.context; + const canvas = context.canvas; + + if (canvas.width != width || canvas.height != height) { canvas.width = width; canvas.height = height; @@ -153,8 +138,6 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer { context.clearRect(0, 0, width, height); } - const image = this.image_.getImage(); - // clipped rendering if layer extent is set const extent = layerState.extent; const clipped = extent !== undefined && @@ -164,17 +147,15 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer { this.clip(context, frameState, extent); } - const imageTransform = this.getImageTransform(); + const img = image.getImage(); - // for performance reasons, context.setTransform is only used - // when the view is rotated. see http://jsperf.com/canvas-transform - const dx = imageTransform[4]; - const dy = imageTransform[5]; - const dw = image.width * imageTransform[0]; - const dh = image.height * imageTransform[3]; + const dx = transform[4]; + const dy = transform[5]; + const dw = img.width * transform[0]; + const dh = img.height * transform[3]; if (dw >= 0.5 && dh >= 0.5) { - this.context.drawImage(image, 0, 0, +image.width, +image.height, + this.context.drawImage(img, 0, 0, +img.width, +img.height, Math.round(dx), Math.round(dy), Math.round(dw), Math.round(dh)); } @@ -182,6 +163,11 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer { context.restore(); } + const canvasTransform = 'rotate(' + rotation + 'rad)'; + if (canvasTransform !== canvas.style.transform) { + canvas.style.transform = canvasTransform; + } + return canvas; } diff --git a/src/ol/renderer/canvas/TileLayer.js b/src/ol/renderer/canvas/TileLayer.js index 5ce5da9495..795a9d64b5 100644 --- a/src/ol/renderer/canvas/TileLayer.js +++ b/src/ol/renderer/canvas/TileLayer.js @@ -377,13 +377,6 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { return context ? context.canvas : null; } - /** - * @inheritDoc - */ - getImageTransform() { - return this.imageTransform_; - } - /** * Get the image from a tile. * @param {import("../../Tile.js").default} tile Tile. From 33f6d6f110c29ff906fc75f9fb860857d5273a2a Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Wed, 14 Nov 2018 21:32:04 +0100 Subject: [PATCH 22/73] One fewer transform --- src/ol/renderer/canvas/TileLayer.js | 9 +-------- src/ol/renderer/canvas/VectorImageLayer.js | 4 +++- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/ol/renderer/canvas/TileLayer.js b/src/ol/renderer/canvas/TileLayer.js index 795a9d64b5..bb03bb1476 100644 --- a/src/ol/renderer/canvas/TileLayer.js +++ b/src/ol/renderer/canvas/TileLayer.js @@ -79,18 +79,11 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { */ this.tmpTileRange_ = new TileRange(0, 0, 0, 0); - /** - * @private - * @type {import("../../transform.js").Transform} - */ - this.imageTransform_ = createTransform(); - /** * @protected * @type {number} */ this.zDirection = 0; - } /** @@ -293,7 +286,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { // TODO: check where these are used and confirm they are correct const transform = composeTransform( - this.imageTransform_, + this.transform_, halfWidth, halfHeight, scale, scale, rotation, diff --git a/src/ol/renderer/canvas/VectorImageLayer.js b/src/ol/renderer/canvas/VectorImageLayer.js index 034018f793..5c39e8aa43 100644 --- a/src/ol/renderer/canvas/VectorImageLayer.js +++ b/src/ol/renderer/canvas/VectorImageLayer.js @@ -95,12 +95,14 @@ class CanvasVectorImageLayerRenderer extends CanvasImageLayerRenderer { const imagePixelRatio = image.getPixelRatio(); const scale = pixelRatio * imageResolution / (viewResolution * imagePixelRatio); - const transform = composeTransform(this.imageTransform_, + + const transform = composeTransform(this.transform_, pixelRatio * size[0] / 2, pixelRatio * size[1] / 2, scale, scale, 0, imagePixelRatio * (imageExtent[0] - viewCenter[0]) / imageResolution, imagePixelRatio * (viewCenter[1] - imageExtent[3]) / imageResolution); + composeTransform(this.coordinateToCanvasPixelTransform, pixelRatio * size[0] / 2 - transform[4], pixelRatio * size[1] / 2 - transform[5], pixelRatio / viewResolution, -pixelRatio / viewResolution, From b5378deb4570360f9821415eb770613f53b0c16b Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Wed, 14 Nov 2018 21:42:10 +0100 Subject: [PATCH 23/73] Create context in the canvas layer base class --- src/ol/renderer/canvas/ImageLayer.js | 9 --------- src/ol/renderer/canvas/Layer.js | 11 +++++++++++ src/ol/renderer/canvas/TileLayer.js | 9 --------- src/ol/renderer/canvas/VectorLayer.js | 10 ---------- 4 files changed, 11 insertions(+), 28 deletions(-) diff --git a/src/ol/renderer/canvas/ImageLayer.js b/src/ol/renderer/canvas/ImageLayer.js index 6f6fe12e4b..39fb886b7a 100644 --- a/src/ol/renderer/canvas/ImageLayer.js +++ b/src/ol/renderer/canvas/ImageLayer.js @@ -22,15 +22,6 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer { constructor(imageLayer) { super(imageLayer); - /** - * @protected - * @type {CanvasRenderingContext2D} - */ - this.context = createCanvasContext2D(); - - const canvas = this.context.canvas; - canvas.style.position = 'absolute'; - /** * @protected * @type {import("../../transform.js").Transform} diff --git a/src/ol/renderer/canvas/Layer.js b/src/ol/renderer/canvas/Layer.js index d1b4bd01bd..3dbb80ade1 100644 --- a/src/ol/renderer/canvas/Layer.js +++ b/src/ol/renderer/canvas/Layer.js @@ -2,6 +2,7 @@ * @module ol/renderer/canvas/Layer */ import {getBottomLeft, getBottomRight, getTopLeft, getTopRight} from '../../extent.js'; +import {createCanvasContext2D} from '../../dom.js'; import {TRUE} from '../../functions.js'; import RenderEvent from '../../render/Event.js'; import RenderEventType from '../../render/EventType.js'; @@ -34,6 +35,16 @@ class CanvasLayerRenderer extends LayerRenderer { */ this.transform_ = createTransform(); + /** + * @protected + * @type {CanvasRenderingContext2D} + */ + this.context = createCanvasContext2D(); + + const canvas = this.context.canvas; + canvas.style.position = 'absolute'; + canvas.className = this.getLayer().getClassName(); + } /** diff --git a/src/ol/renderer/canvas/TileLayer.js b/src/ol/renderer/canvas/TileLayer.js index bb03bb1476..9eb91e7ddb 100644 --- a/src/ol/renderer/canvas/TileLayer.js +++ b/src/ol/renderer/canvas/TileLayer.js @@ -22,15 +22,6 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { constructor(tileLayer) { super(tileLayer); - /** - * @protected - * @type {CanvasRenderingContext2D} - */ - this.context = createCanvasContext2D(); - - const canvas = this.context.canvas; - canvas.style.position = 'absolute'; - /** * @protected * @type {import("../../transform.js").Transform} diff --git a/src/ol/renderer/canvas/VectorLayer.js b/src/ol/renderer/canvas/VectorLayer.js index ea80b70909..d611d07aa6 100644 --- a/src/ol/renderer/canvas/VectorLayer.js +++ b/src/ol/renderer/canvas/VectorLayer.js @@ -3,7 +3,6 @@ */ import {getUid} from '../../util.js'; import ViewHint from '../../ViewHint.js'; -import {createCanvasContext2D} from '../../dom.js'; import {listen, unlisten} from '../../events.js'; import EventType from '../../events/EventType.js'; import rbush from 'rbush'; @@ -77,15 +76,6 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { */ this.replayGroupChanged = true; - /** - * @type {CanvasRenderingContext2D} - */ - this.context = createCanvasContext2D(); - - const canvas = this.context.canvas; - canvas.style.position = 'absolute'; - canvas.className = this.getLayer().getClassName(); - listen(labelCache, EventType.CLEAR, this.handleFontsChanged_, this); } From 8b077c66d8343cf880f68571fff1efa0cb9e5f52 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Wed, 14 Nov 2018 22:07:49 +0100 Subject: [PATCH 24/73] Rotate images --- src/ol/render/canvas/Executor.js | 4 ++-- src/ol/renderer/canvas/VectorTileLayer.js | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/ol/render/canvas/Executor.js b/src/ol/render/canvas/Executor.js index cb018d1e42..aa992258f1 100644 --- a/src/ol/render/canvas/Executor.js +++ b/src/ol/render/canvas/Executor.js @@ -700,8 +700,8 @@ class CanvasExecutor { backgroundFill = backgroundStroke = false; } - if (rotateWithView) { - rotation += viewRotation; + if (!rotateWithView) { + rotation -= viewRotation; } let widthIndex = 0; for (; d < dd; d += 2) { diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index 2e1a4afed8..c1447a7ab6 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -78,6 +78,8 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { style.display = 'flex'; style.alignItems = 'center'; style.justifyContent = 'center'; + style.width = '100%'; + style.height = '100%'; container.appendChild(baseCanvas); container.appendChild(overlayCanvas); @@ -420,7 +422,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { continue; } if (!transform) { - transform = this.getTransform(frameState, worldOffset); + transform = this.getRenderTransform(frameState, width, height, worldOffset); } const currentZ = sourceTile.tileCoord[0]; const currentClip = executorGroup.getClipCoords(transform); From ed7825e13a256117b62da0ac527322a2c51cfe87 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Thu, 15 Nov 2018 10:27:31 +0100 Subject: [PATCH 25/73] Render vector images --- src/ol/renderer/canvas/VectorImageLayer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ol/renderer/canvas/VectorImageLayer.js b/src/ol/renderer/canvas/VectorImageLayer.js index 5c39e8aa43..73446597bf 100644 --- a/src/ol/renderer/canvas/VectorImageLayer.js +++ b/src/ol/renderer/canvas/VectorImageLayer.js @@ -77,7 +77,7 @@ class CanvasVectorImageLayerRenderer extends CanvasImageLayerRenderer { !equals(skippedFeatures, newSkippedFeatures))) { context.canvas.width = imageFrameState.size[0] * pixelRatio; context.canvas.height = imageFrameState.size[1] * pixelRatio; - vectorRenderer.compose(context, imageFrameState, layerState); + vectorRenderer.render(imageFrameState, layerState); skippedFeatures = newSkippedFeatures; callback(); } From 26de43de0cadcd81de12ca7b1420cb2c7a362d8a Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Thu, 15 Nov 2018 10:31:31 +0100 Subject: [PATCH 26/73] Unused imports --- src/ol/renderer/canvas/ImageLayer.js | 1 - src/ol/renderer/canvas/TileLayer.js | 1 - 2 files changed, 2 deletions(-) diff --git a/src/ol/renderer/canvas/ImageLayer.js b/src/ol/renderer/canvas/ImageLayer.js index 39fb886b7a..2b8fc3becf 100644 --- a/src/ol/renderer/canvas/ImageLayer.js +++ b/src/ol/renderer/canvas/ImageLayer.js @@ -5,7 +5,6 @@ import {ENABLE_RASTER_REPROJECTION} from '../../reproj/common.js'; import ViewHint from '../../ViewHint.js'; import {containsExtent, intersects} from '../../extent.js'; import {getIntersection, isEmpty} from '../../extent.js'; -import {createCanvasContext2D} from '../../dom.js'; import CanvasLayerRenderer from './Layer.js'; import {create as createTransform, compose as composeTransform} from '../../transform.js'; diff --git a/src/ol/renderer/canvas/TileLayer.js b/src/ol/renderer/canvas/TileLayer.js index 9eb91e7ddb..69865e07c5 100644 --- a/src/ol/renderer/canvas/TileLayer.js +++ b/src/ol/renderer/canvas/TileLayer.js @@ -5,7 +5,6 @@ import {getUid} from '../../util.js'; import TileRange from '../../TileRange.js'; import TileState from '../../TileState.js'; import {createEmpty, getIntersection, getTopLeft} from '../../extent.js'; -import {createCanvasContext2D} from '../../dom.js'; import CanvasLayerRenderer from './Layer.js'; import {create as createTransform, compose as composeTransform} from '../../transform.js'; From f90efac131db3bf297f57a89a6cbd136a7b05020 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Thu, 15 Nov 2018 11:37:45 +0100 Subject: [PATCH 27/73] Smaller canvas when rotating vector layers --- src/ol/render/canvas/Executor.js | 4 ++-- src/ol/renderer/canvas/Layer.js | 2 +- src/ol/renderer/canvas/VectorLayer.js | 14 ++------------ src/ol/renderer/canvas/VectorTileLayer.js | 13 ++----------- 4 files changed, 7 insertions(+), 26 deletions(-) diff --git a/src/ol/render/canvas/Executor.js b/src/ol/render/canvas/Executor.js index aa992258f1..cb018d1e42 100644 --- a/src/ol/render/canvas/Executor.js +++ b/src/ol/render/canvas/Executor.js @@ -700,8 +700,8 @@ class CanvasExecutor { backgroundFill = backgroundStroke = false; } - if (!rotateWithView) { - rotation -= viewRotation; + if (rotateWithView) { + rotation += viewRotation; } let widthIndex = 0; for (; d < dd; d += 2) { diff --git a/src/ol/renderer/canvas/Layer.js b/src/ol/renderer/canvas/Layer.js index 3dbb80ade1..6475a8366e 100644 --- a/src/ol/renderer/canvas/Layer.js +++ b/src/ol/renderer/canvas/Layer.js @@ -192,7 +192,7 @@ class CanvasLayerRenderer extends LayerRenderer { const sy = -sx; const dx2 = -viewState.center[0] + offsetX; const dy2 = -viewState.center[1]; - return composeTransform(this.transform_, dx1, dy1, sx, sy, 0, dx2, dy2); + return composeTransform(this.transform_, dx1, dy1, sx, sy, -viewState.rotation, dx2, dy2); } } diff --git a/src/ol/renderer/canvas/VectorLayer.js b/src/ol/renderer/canvas/VectorLayer.js index d611d07aa6..4fd36b7455 100644 --- a/src/ol/renderer/canvas/VectorLayer.js +++ b/src/ol/renderer/canvas/VectorLayer.js @@ -123,12 +123,8 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { } // resize and clear - let width = Math.round(frameState.size[0] * pixelRatio); - let height = Math.round(frameState.size[1] * pixelRatio); - if (rotation) { - const size = Math.round(Math.sqrt(width * width + height * height)); - width = height = size; - } + const width = Math.round(frameState.size[0] * pixelRatio); + const height = Math.round(frameState.size[1] * pixelRatio); if (canvas.width != width || canvas.height != height) { canvas.width = width; canvas.height = height; @@ -193,12 +189,6 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { canvas.style.opacity = opacity; } - const rotation = frameState.viewState.rotation; - const transform = 'rotate(' + rotation + 'rad)'; - if (transform !== canvas.style.transform) { - canvas.style.transform = transform; - } - return canvas; } diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index c1447a7ab6..f1bb8c7dd5 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -378,12 +378,8 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { // resize and clear const canvas = context.canvas; - let width = Math.round(size[0] * pixelRatio); - let height = Math.round(size[1] * pixelRatio); - if (rotation) { - const size = Math.round(Math.sqrt(width * width + height * height)); - width = height = size; - } + const width = Math.round(size[0] * pixelRatio); + const height = Math.round(size[1] * pixelRatio); if (canvas.width != width || canvas.height != height) { canvas.width = width; canvas.height = height; @@ -462,11 +458,6 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { canvas.style.opacity = opacity; } - const canvasTransform = 'rotate(' + rotation + 'rad)'; - if (canvasTransform !== canvas.style.transform) { - canvas.style.transform = canvasTransform; - } - return this.container_; } From 6c0b3f773bcf79c44ab579b9f2a897224fe923f7 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Thu, 15 Nov 2018 13:22:06 +0100 Subject: [PATCH 28/73] New render event API (non functional yet) --- changelog/upgrade-notes.md | 2 +- src/ol/VectorImageTile.js | 9 +++++++- src/ol/render.js | 26 ++++++++++++++++++++++- src/ol/render/Event.js | 11 +++++----- src/ol/renderer/canvas/Layer.js | 12 ++++------- src/ol/renderer/canvas/VectorTileLayer.js | 20 +++++++++++------ 6 files changed, 58 insertions(+), 22 deletions(-) diff --git a/changelog/upgrade-notes.md b/changelog/upgrade-notes.md index fff1a1231d..65d5662a11 100644 --- a/changelog/upgrade-notes.md +++ b/changelog/upgrade-notes.md @@ -42,7 +42,7 @@ The `getUid` function from the `ol/util` module now returns a string instead of When a map contains a layer from a `ol/source/OSM` source, the `ol/control/Attribution` control will be shown with the `collapsible: false` behavior. -To get the previous behavior, configure the `ol/control/Attribution` control with `collapsible: true`. +To get the previous behavior, configure the `ol/control/Attribution` control with `collapsible: true`. ### v5.2.0 diff --git a/src/ol/VectorImageTile.js b/src/ol/VectorImageTile.js index eaa36142c9..ac5fbd68bf 100644 --- a/src/ol/VectorImageTile.js +++ b/src/ol/VectorImageTile.js @@ -72,6 +72,12 @@ class VectorImageTile extends Tile { */ this.sourceTiles_ = sourceTiles; + /** + * @private + * @type {boolean} + */ + this.sourceTilesLoaded = false; + /** * Keys of source tiles used by this tile. Use with {@link #getTile}. * @type {Array} @@ -312,7 +318,8 @@ class VectorImageTile extends Tile { if (loaded == this.tileKeys.length) { this.loadListenerKeys_.forEach(unlistenByKey); this.loadListenerKeys_.length = 0; - this.setState(TileState.LOADED); + this.sourceTilesLoaded = true; + this.changed(); } else { this.setState(empty == this.tileKeys.length ? TileState.EMPTY : TileState.ERROR); } diff --git a/src/ol/render.js b/src/ol/render.js index 9364f79264..8494cb61c7 100644 --- a/src/ol/render.js +++ b/src/ol/render.js @@ -2,7 +2,7 @@ * @module ol/render */ import {DEVICE_PIXEL_RATIO} from './has.js'; -import {create as createTransform, scale as scaleTransform} from './transform.js'; +import {apply as applyTransform, create as createTransform, scale as scaleTransform} from './transform.js'; import CanvasImmediateRenderer from './render/canvas/Immediate.js'; @@ -77,3 +77,27 @@ export function toContext(context, opt_options) { const transform = scaleTransform(createTransform(), pixelRatio, pixelRatio); return new CanvasImmediateRenderer(context, pixelRatio, extent, transform, 0); } + +/** + * Gets a vector context for drawing to + * @param {import("./render/Event.js").default} event Render event. + */ +export function getVectorContext(event) { + const frameState = event.frameState; + return new CanvasImmediateRenderer( + event.context, frameState.pixelRatio, frameState.extent, + event.pixelTransform, frameState.viewState.rotation); +} + +/** + * Gets the pixel of the event's canvas context from the map viewport's css pixel + * @param {import("./render/Event.js").default} event Render event. + * @param {import("./pixel.js").Pixel} pixel Css pixel relative to the top-left + * corner of the map viewport. + * @api + */ +export function getPixelFromPixel(event, pixel) { + const result = pixel.slice(0); + applyTransform(event.pixelTransform, pixel); + return result; +} diff --git a/src/ol/render/Event.js b/src/ol/render/Event.js index 6d23e04f53..28caf9fdc2 100644 --- a/src/ol/render/Event.js +++ b/src/ol/render/Event.js @@ -8,21 +8,22 @@ class RenderEvent extends Event { /** * @param {import("./EventType.js").default} type Type. - * @param {import("./VectorContext.js").default=} opt_vectorContext Vector context. + * @param {import("../transform.js").Transform=} opt_pixelTransform Transform. * @param {import("../PluggableMap.js").FrameState=} opt_frameState Frame state. * @param {?CanvasRenderingContext2D=} opt_context Context. * @param {?import("../webgl/Helper.js").default=} opt_glContext WebGL Context. */ - constructor(type, opt_vectorContext, opt_frameState, opt_context, opt_glContext) { + constructor(type, opt_pixelTransform, opt_frameState, opt_context, opt_glContext) { super(type); /** - * For canvas, this is an instance of {@link module:ol/render/canvas/Immediate}. - * @type {import("./VectorContext.js").default|undefined} + * Transform from css pixels (relative to the top-left corner of the map viewport) + * to render pixel on this event's `context`. + * @type {import("../transform.js").Transform|undefined} * @api */ - this.vectorContext = opt_vectorContext; + this.pixelTransform = opt_pixelTransform; /** * An object representing the current render frame state. diff --git a/src/ol/renderer/canvas/Layer.js b/src/ol/renderer/canvas/Layer.js index 6475a8366e..055de92de4 100644 --- a/src/ol/renderer/canvas/Layer.js +++ b/src/ol/renderer/canvas/Layer.js @@ -83,25 +83,21 @@ class CanvasLayerRenderer extends LayerRenderer { * @param {import("../../render/EventType.js").default} type Event type. * @param {CanvasRenderingContext2D} context Context. * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. - * @param {import("../../transform.js").Transform=} opt_transform Transform. + * @param {import("../../transform.js").Transform} pixelTransform Transform. * @private */ - dispatchComposeEvent_(type, context, frameState, opt_transform) { + dispatchComposeEvent_(type, context, frameState, pixelTransform) { const layer = this.getLayer(); if (layer.hasListener(type)) { const halfWidth = (frameState.size[0] * frameState.pixelRatio) / 2; const halfHeight = (frameState.size[1] * frameState.pixelRatio) / 2; const rotation = frameState.viewState.rotation; - rotateAtOffset(context, -rotation, halfWidth, halfHeight); - const transform = opt_transform !== undefined ? - opt_transform : this.getTransform(frameState, 0); const render = new CanvasImmediateRenderer( - context, frameState.pixelRatio, frameState.extent, transform, + context, frameState.pixelRatio, frameState.extent, pixelTransform, frameState.viewState.rotation); - const composeEvent = new RenderEvent(type, render, frameState, + const composeEvent = new RenderEvent(type, pixelTransform, frameState, context, null); layer.dispatchEvent(composeEvent); - rotateAtOffset(context, rotation, halfWidth, halfHeight); } } diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index f1bb8c7dd5..94cbd6ea28 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -5,7 +5,7 @@ import {getUid} from '../../util.js'; import {createCanvasContext2D} from '../../dom.js'; import TileState from '../../TileState.js'; import ViewHint from '../../ViewHint.js'; -import {listen, unlisten} from '../../events.js'; +import {listen, unlisten, unlistenByKey} from '../../events.js'; import EventType from '../../events/EventType.js'; import rbush from 'rbush'; import {buffer, containsCoordinate, equals, getIntersection, getTopLeft, intersects} from '../../extent.js'; @@ -108,6 +108,12 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { */ this.renderedLayerRevision_; + /** + * @private + * @type {Array.} + */ + this.tilesToRender_ = []; + /** * @private * @type {import("../../transform.js").Transform} @@ -134,11 +140,13 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { */ getTile(z, x, y, pixelRatio, projection) { const tile = super.getTile(z, x, y, pixelRatio, projection); - if (tile.getState() === TileState.LOADED) { - this.createExecutorGroup_(/** @type {import("../../VectorImageTile.js").default} */ (tile), pixelRatio, projection); - if (this.context) { - this.renderTileImage_(/** @type {import("../../VectorImageTile.js").default} */ (tile), pixelRatio, projection); - } + if (tile.getState() === TileState.IDLE) { + const key = listen(tile, EventType.CHANGE, function() { + if (tile.getState() === TileState.LOADING && tile.sourceTilesLoaded) { + this.tilesToRender_.push(tile); + unlistenByKey(key); + } + }.bind(this)); } return tile; } From 65ceb9264e6df5505e42401e19be0fe4ad063368 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Thu, 15 Nov 2018 13:49:16 +0100 Subject: [PATCH 29/73] Remove accidently committed changes --- src/ol/VectorImageTile.js | 9 +-------- src/ol/renderer/canvas/VectorTileLayer.js | 18 +++++------------- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/src/ol/VectorImageTile.js b/src/ol/VectorImageTile.js index ac5fbd68bf..eaa36142c9 100644 --- a/src/ol/VectorImageTile.js +++ b/src/ol/VectorImageTile.js @@ -72,12 +72,6 @@ class VectorImageTile extends Tile { */ this.sourceTiles_ = sourceTiles; - /** - * @private - * @type {boolean} - */ - this.sourceTilesLoaded = false; - /** * Keys of source tiles used by this tile. Use with {@link #getTile}. * @type {Array} @@ -318,8 +312,7 @@ class VectorImageTile extends Tile { if (loaded == this.tileKeys.length) { this.loadListenerKeys_.forEach(unlistenByKey); this.loadListenerKeys_.length = 0; - this.sourceTilesLoaded = true; - this.changed(); + this.setState(TileState.LOADED); } else { this.setState(empty == this.tileKeys.length ? TileState.EMPTY : TileState.ERROR); } diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index 94cbd6ea28..b288731e14 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -108,12 +108,6 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { */ this.renderedLayerRevision_; - /** - * @private - * @type {Array.} - */ - this.tilesToRender_ = []; - /** * @private * @type {import("../../transform.js").Transform} @@ -140,13 +134,11 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { */ getTile(z, x, y, pixelRatio, projection) { const tile = super.getTile(z, x, y, pixelRatio, projection); - if (tile.getState() === TileState.IDLE) { - const key = listen(tile, EventType.CHANGE, function() { - if (tile.getState() === TileState.LOADING && tile.sourceTilesLoaded) { - this.tilesToRender_.push(tile); - unlistenByKey(key); - } - }.bind(this)); + if (tile.getState() === TileState.LOADED) { + this.createReplayGroup_(/** @type {import("../../VectorImageTile.js").default} */ (tile), pixelRatio, projection); + if (this.context) { + this.renderTileImage_(/** @type {import("../../VectorImageTile.js").default} */ (tile), pixelRatio, projection); + } } return tile; } From bc347e3eb03af3b4b0f7331f7cfb36ef0f171de6 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Thu, 15 Nov 2018 13:50:39 +0100 Subject: [PATCH 30/73] Provide a pixel transform to render events --- src/ol/renderer/canvas/VectorImageLayer.js | 2 +- src/ol/renderer/canvas/VectorLayer.js | 65 +++++++++++----------- src/ol/renderer/canvas/VectorTileLayer.js | 5 +- src/ol/transform.js | 4 ++ 4 files changed, 39 insertions(+), 37 deletions(-) diff --git a/src/ol/renderer/canvas/VectorImageLayer.js b/src/ol/renderer/canvas/VectorImageLayer.js index 73446597bf..91ca356ace 100644 --- a/src/ol/renderer/canvas/VectorImageLayer.js +++ b/src/ol/renderer/canvas/VectorImageLayer.js @@ -77,7 +77,7 @@ class CanvasVectorImageLayerRenderer extends CanvasImageLayerRenderer { !equals(skippedFeatures, newSkippedFeatures))) { context.canvas.width = imageFrameState.size[0] * pixelRatio; context.canvas.height = imageFrameState.size[1] * pixelRatio; - vectorRenderer.render(imageFrameState, layerState); + vectorRenderer.renderFrame(imageFrameState, layerState); skippedFeatures = newSkippedFeatures; callback(); } diff --git a/src/ol/renderer/canvas/VectorLayer.js b/src/ol/renderer/canvas/VectorLayer.js index 4fd36b7455..78ffb866d4 100644 --- a/src/ol/renderer/canvas/VectorLayer.js +++ b/src/ol/renderer/canvas/VectorLayer.js @@ -13,6 +13,7 @@ import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js'; import InstructionsGroupExecutor from '../../render/canvas/ExecutorGroup.js'; import CanvasLayerRenderer from './Layer.js'; import {defaultOrder as defaultRenderOrder, getTolerance as getRenderTolerance, getSquaredTolerance as getSquaredRenderTolerance, renderFeature} from '../vector.js'; +import {toString as transformToString} from '../../transform.js'; /** * @classdesc @@ -88,23 +89,42 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { } /** - * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. - * @param {import("../../layer/Layer.js").State} layerState Layer state. + * @inheritDoc */ - render(frameState, layerState) { - const replayGroup = this.replayGroup_; + renderFrame(frameState, layerState) { const context = this.context; const canvas = context.canvas; + const replayGroup = this.replayGroup_; if (!replayGroup || replayGroup.isEmpty()) { if (canvas.width > 0) { canvas.width = 0; } - return; + return canvas; } - const extent = frameState.extent; const pixelRatio = frameState.pixelRatio; + + // a scale transform (we could add a createScale function in ol/transform) + const pixelTransform = [1 / pixelRatio, 0, 0, 1 / pixelRatio, 0, 0]; + + // resize and clear + const width = Math.round(frameState.size[0] * pixelRatio); + const height = Math.round(frameState.size[1] * pixelRatio); + if (canvas.width != width || canvas.height != height) { + canvas.width = width; + canvas.height = height; + const canvasTransform = transformToString(pixelTransform); + if (canvas.style.transform !== canvasTransform) { + canvas.style.transform = canvasTransform; + } + } else { + context.clearRect(0, 0, width, height); + } + + this.preRender(context, frameState, pixelTransform); + + const extent = frameState.extent; const viewState = frameState.viewState; const projection = viewState.projection; const rotation = viewState.rotation; @@ -122,22 +142,11 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { this.declutterTree_.clear(); } - // resize and clear - const width = Math.round(frameState.size[0] * pixelRatio); - const height = Math.round(frameState.size[1] * pixelRatio); - if (canvas.width != width || canvas.height != height) { - canvas.width = width; - canvas.height = height; - canvas.style.width = (width / pixelRatio) + 'px'; - canvas.style.height = (height / pixelRatio) + 'px'; - } else { - context.clearRect(0, 0, width, height); - } const viewHints = frameState.viewHints; const snapToPixel = !(viewHints[ViewHint.ANIMATING] || viewHints[ViewHint.INTERACTING]); - let transform = this.getRenderTransform(frameState, width, height, 0); + const transform = this.getRenderTransform(frameState, width, height, 0); const skippedFeatureUids = layerState.managed ? frameState.skippedFeatureUids : {}; replayGroup.execute(context, transform, rotation, skippedFeatureUids, snapToPixel); @@ -149,7 +158,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { while (startX < projectionExtent[0]) { --world; offsetX = worldWidth * world; - transform = this.getRenderTransform(frameState, width, height, offsetX); + const transform = this.getRenderTransform(frameState, width, height, offsetX); replayGroup.execute(context, transform, rotation, skippedFeatureUids, snapToPixel); startX += worldWidth; } @@ -158,31 +167,21 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { while (startX > projectionExtent[2]) { ++world; offsetX = worldWidth * world; - transform = this.getRenderTransform(frameState, width, height, offsetX); + const transform = this.getRenderTransform(frameState, width, height, offsetX); replayGroup.execute(context, transform, rotation, skippedFeatureUids, snapToPixel); startX -= worldWidth; } } if (this.getLayer().hasListener(RenderEventType.RENDER)) { - this.dispatchRenderEvent(context, frameState, transform); + this.dispatchRenderEvent(context, frameState, pixelTransform); } if (clipped) { context.restore(); } - } - /** - * @inheritDoc - */ - renderFrame(frameState, layerState) { - const context = this.context; - this.preRender(context, frameState); - this.render(frameState, layerState); - this.postRender(context, frameState); - - const canvas = context.canvas; + this.postRender(context, frameState, pixelTransform); const opacity = layerState.opacity; if (opacity !== canvas.style.opacity) { @@ -302,7 +301,9 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { const replayGroup = new CanvasBuilderGroup( getRenderTolerance(resolution, pixelRatio), extent, resolution, pixelRatio, vectorSource.getOverlaps(), this.declutterTree_, vectorLayer.getRenderBuffer()); + vectorSource.loadFeatures(extent, resolution, projection); + /** * @param {import("../../Feature.js").default} feature Feature. * @this {CanvasVectorLayerRenderer} diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index b288731e14..c5ffec5768 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -405,7 +405,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { } const tileCoord = tile.tileCoord; const worldOffset = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent)[0] - tile.extent[0]; - let transform = undefined; + const transform = this.getRenderTransform(frameState, width, height, worldOffset); for (let t = 0, tt = tile.tileKeys.length; t < tt; ++t) { const sourceTile = tile.getTile(tile.tileKeys[t]); if (sourceTile.getState() != TileState.LOADED) { @@ -417,9 +417,6 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { // called, or it has no replays of the types we want to render continue; } - if (!transform) { - transform = this.getRenderTransform(frameState, width, height, worldOffset); - } const currentZ = sourceTile.tileCoord[0]; const currentClip = executorGroup.getClipCoords(transform); context.save(); diff --git a/src/ol/transform.js b/src/ol/transform.js index 36630dee84..65c3b4f2f1 100644 --- a/src/ol/transform.js +++ b/src/ol/transform.js @@ -236,3 +236,7 @@ export function invert(transform) { export function determinant(mat) { return mat[0] * mat[3] - mat[1] * mat[2]; } + +export function toString(mat) { + return 'matrix(' + mat.join(', ') + ')'; +} From ee536fb70d62d78ed43a5eefe3da03e38e5930b4 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Thu, 15 Nov 2018 15:00:01 +0100 Subject: [PATCH 31/73] Remove unused code and imports --- src/ol/renderer/canvas/Layer.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/ol/renderer/canvas/Layer.js b/src/ol/renderer/canvas/Layer.js index 055de92de4..e19ec5cfba 100644 --- a/src/ol/renderer/canvas/Layer.js +++ b/src/ol/renderer/canvas/Layer.js @@ -7,7 +7,6 @@ import {TRUE} from '../../functions.js'; import RenderEvent from '../../render/Event.js'; import RenderEventType from '../../render/EventType.js'; import {rotateAtOffset} from '../../render/canvas.js'; -import CanvasImmediateRenderer from '../../render/canvas/Immediate.js'; import LayerRenderer from '../Layer.js'; import {create as createTransform, apply as applyTransform, compose as composeTransform} from '../../transform.js'; @@ -89,12 +88,6 @@ class CanvasLayerRenderer extends LayerRenderer { dispatchComposeEvent_(type, context, frameState, pixelTransform) { const layer = this.getLayer(); if (layer.hasListener(type)) { - const halfWidth = (frameState.size[0] * frameState.pixelRatio) / 2; - const halfHeight = (frameState.size[1] * frameState.pixelRatio) / 2; - const rotation = frameState.viewState.rotation; - const render = new CanvasImmediateRenderer( - context, frameState.pixelRatio, frameState.extent, pixelTransform, - frameState.viewState.rotation); const composeEvent = new RenderEvent(type, pixelTransform, frameState, context, null); layer.dispatchEvent(composeEvent); From aa4237539f3749d793205b90e4a3415a41e849a4 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Thu, 15 Nov 2018 15:29:41 +0100 Subject: [PATCH 32/73] Provide a pixel transform from the tile renderer --- src/ol/renderer/canvas/TileLayer.js | 39 ++++++++++------------------- 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/src/ol/renderer/canvas/TileLayer.js b/src/ol/renderer/canvas/TileLayer.js index 69865e07c5..e74af29e8a 100644 --- a/src/ol/renderer/canvas/TileLayer.js +++ b/src/ol/renderer/canvas/TileLayer.js @@ -6,7 +6,7 @@ import TileRange from '../../TileRange.js'; import TileState from '../../TileState.js'; import {createEmpty, getIntersection, getTopLeft} from '../../extent.js'; import CanvasLayerRenderer from './Layer.js'; -import {create as createTransform, compose as composeTransform} from '../../transform.js'; +import {create as createTransform, compose as composeTransform, toString as transformToString} from '../../transform.js'; /** * @classdesc @@ -129,7 +129,6 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { */ renderFrame(frameState, layerState) { const context = this.context; - const size = frameState.size; const viewState = frameState.viewState; const projection = viewState.projection; const viewResolution = viewState.resolution; @@ -221,13 +220,23 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { } const canvas = context.canvas; + const canvasScale = tileResolution / frameState.viewState.resolution / tilePixelRatio; + const pixelTransform = composeTransform(this.transform_, + 0, 0, + canvasScale, canvasScale, + rotation, + 0, 0 + ); + const canvasTransform = transformToString(pixelTransform); + if (canvas.width != width || canvas.height != height) { canvas.width = width; canvas.height = height; } else { context.clearRect(0, 0, width, height); } - this.preRender(context, frameState); + + this.preRender(context, frameState, pixelTransform); this.renderedTiles.length = 0; /** @type {Array} */ @@ -270,40 +279,18 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { this.renderedResolution = tileResolution; this.renderedExtent_ = canvasExtent; - const scale = this.renderedResolution / frameState.viewState.resolution; - const halfWidth = width / 2; - const halfHeight = height / 2; - - // TODO: check where these are used and confirm they are correct - const transform = composeTransform( - this.transform_, - halfWidth, halfHeight, - scale, scale, - rotation, - -halfWidth, -halfHeight - ); - - composeTransform(this.coordinateToCanvasPixelTransform, - pixelRatio * size[0] / 2 - transform[4], pixelRatio * size[1] / 2 - transform[5], - pixelRatio / viewResolution, -pixelRatio / viewResolution, - 0, - -viewCenter[0], -viewCenter[1]); - this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange); this.manageTilePyramid(frameState, tileSource, tileGrid, pixelRatio, projection, extent, z, tileLayer.getPreload()); this.scheduleExpireCache(frameState, tileSource); - this.postRender(context, frameState, layerState); + this.postRender(context, frameState, pixelTransform); const opacity = layerState.opacity; if (opacity !== canvas.style.opacity) { canvas.style.opacity = opacity; } - const canvasScale = this.renderedResolution / frameState.viewState.resolution / tilePixelRatio; - - const canvasTransform = 'rotate(' + rotation + 'rad) scale(' + canvasScale + ')'; if (canvasTransform !== canvas.style.transform) { canvas.style.transform = canvasTransform; } From 5bb110f157b8c97cbaee57e5405cae20cce24ecf Mon Sep 17 00:00:00 2001 From: ahocevar Date: Thu, 15 Nov 2018 16:26:56 +0100 Subject: [PATCH 33/73] Fix render context utility functions and feature-move-animation example --- examples/feature-move-animation.html | 6 +++--- examples/feature-move-animation.js | 5 +++-- src/ol/render.js | 20 +++++++++++++++----- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/examples/feature-move-animation.html b/examples/feature-move-animation.html index 281571fdb6..354450cfa5 100644 --- a/examples/feature-move-animation.html +++ b/examples/feature-move-animation.html @@ -3,10 +3,10 @@ layout: example.html title: Marker Animation shortdesc: Demonstrates how to move a feature along a line. docs: > - This example shows how to use postcompose and vectorContext to - animate a (marker) feature along a line. In this example an encoded polyline + This example shows how to use postrender events and a vector context to + animate a marker feature along a line. In this example an encoded polyline is being used. -tags: "animation, feature, postcompose, polyline" +tags: "animation, feature, postrender, polyline" cloak: - key: As1HiMj1PvLPlqc_gtM7AqZfBL8ZL3VrjaS3zIb22Uvb9WKhuJObROC-qUpa81U5 value: Your Bing Maps Key from http://www.bingmapsportal.com/ here diff --git a/examples/feature-move-animation.js b/examples/feature-move-animation.js index 45b31ad280..f19b590b70 100644 --- a/examples/feature-move-animation.js +++ b/examples/feature-move-animation.js @@ -7,6 +7,7 @@ import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js'; import BingMaps from '../src/ol/source/BingMaps.js'; import VectorSource from '../src/ol/source/Vector.js'; import {Circle as CircleStyle, Fill, Icon, Stroke, Style} from '../src/ol/style.js'; +import {getVectorContext} from '../src/ol/render.js'; // This long string is placed here due to jsFiddle limitations. // It is usually loaded with AJAX. @@ -142,7 +143,7 @@ const map = new Map({ }); const moveFeature = function(event) { - const vectorContext = event.vectorContext; + const vectorContext = getVectorContext(event); const frameState = event.frameState; if (animating) { @@ -176,7 +177,7 @@ function startAnimation() { geoMarker.setStyle(null); // just in case you pan somewhere else map.getView().setCenter(center); - map.on('postcompose', moveFeature); + vectorLayer.on('postrender', moveFeature); map.render(); } } diff --git a/src/ol/render.js b/src/ol/render.js index 8494cb61c7..0f39bf8803 100644 --- a/src/ol/render.js +++ b/src/ol/render.js @@ -2,7 +2,13 @@ * @module ol/render */ import {DEVICE_PIXEL_RATIO} from './has.js'; -import {apply as applyTransform, create as createTransform, scale as scaleTransform} from './transform.js'; +import { + apply as applyTransform, + create as createTransform, + invert as invertTransform, + multiply as multiplyTransform, + scale as scaleTransform +} from './transform.js'; import CanvasImmediateRenderer from './render/canvas/Immediate.js'; @@ -79,14 +85,17 @@ export function toContext(context, opt_options) { } /** - * Gets a vector context for drawing to + * Gets a vector context for drawing to the event's canvas. * @param {import("./render/Event.js").default} event Render event. + * @returns {CanvasImmediateRenderer} Vector context. + * @api */ export function getVectorContext(event) { const frameState = event.frameState; + const transform = multiplyTransform(invertTransform(event.pixelTransform.slice()), frameState.coordinateToPixelTransform); return new CanvasImmediateRenderer( - event.context, frameState.pixelRatio, frameState.extent, - event.pixelTransform, frameState.viewState.rotation); + event.context, frameState.pixelRatio, frameState.extent, transform, + frameState.viewState.rotation); } /** @@ -94,10 +103,11 @@ export function getVectorContext(event) { * @param {import("./render/Event.js").default} event Render event. * @param {import("./pixel.js").Pixel} pixel Css pixel relative to the top-left * corner of the map viewport. + * @returns {import("./pixel.js").Pixel} Pixel on the event's canvas context. * @api */ export function getPixelFromPixel(event, pixel) { const result = pixel.slice(0); - applyTransform(event.pixelTransform, pixel); + applyTransform(invertTransform(event.pixelTransform.slice()), result); return result; } From 32495388b9a76cca3e008ae5685c46eb83f69698 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Thu, 15 Nov 2018 17:07:42 +0100 Subject: [PATCH 34/73] Transform origin top left --- src/ol/renderer/Composite.js | 3 -- src/ol/renderer/canvas/ImageLayer.js | 42 ++++++++++++----------- src/ol/renderer/canvas/Layer.js | 1 + src/ol/renderer/canvas/TileLayer.js | 4 +-- src/ol/renderer/canvas/VectorTileLayer.js | 5 +-- 5 files changed, 26 insertions(+), 29 deletions(-) diff --git a/src/ol/renderer/Composite.js b/src/ol/renderer/Composite.js index 269ee6e560..67bbb8a501 100644 --- a/src/ol/renderer/Composite.js +++ b/src/ol/renderer/Composite.js @@ -31,9 +31,6 @@ class CompositeMapRenderer extends MapRenderer { */ this.element_ = document.createElement('div'); const style = this.element_.style; - style.display = 'flex'; - style.alignItems = 'center'; - style.justifyContent = 'center'; style.width = '100%'; style.height = '100%'; diff --git a/src/ol/renderer/canvas/ImageLayer.js b/src/ol/renderer/canvas/ImageLayer.js index 2b8fc3becf..328244c0db 100644 --- a/src/ol/renderer/canvas/ImageLayer.js +++ b/src/ol/renderer/canvas/ImageLayer.js @@ -6,7 +6,7 @@ import ViewHint from '../../ViewHint.js'; import {containsExtent, intersects} from '../../extent.js'; import {getIntersection, isEmpty} from '../../extent.js'; import CanvasLayerRenderer from './Layer.js'; -import {create as createTransform, compose as composeTransform} from '../../transform.js'; +import {create as createTransform, compose as composeTransform, toString as transformToString} from '../../transform.js'; /** * @classdesc @@ -99,31 +99,20 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer { width = height = size; } - const transform = composeTransform(this.transform_, - width / 2, height / 2, - scale, scale, - 0, - imagePixelRatio * (imageExtent[0] - viewCenter[0]) / imageResolution, - imagePixelRatio * (viewCenter[1] - imageExtent[3]) / imageResolution); - - composeTransform(this.coordinateToCanvasPixelTransform, - pixelRatio * size[0] / 2 - transform[4], pixelRatio * size[1] / 2 - transform[5], - pixelRatio / viewResolution, -pixelRatio / viewResolution, - 0, - -viewCenter[0], -viewCenter[1]); - - this.renderedResolution = imageResolution * pixelRatio / imagePixelRatio; - - const context = this.context; const canvas = context.canvas; + const pixelTransform = composeTransform(this.transform_, + frameState.size[0] / 2, frameState.size[1] / 2, + 1 / pixelRatio, 1 / pixelRatio, + rotation, + -width / 2, -height / 2 + ).slice(); + const canvasTransform = transformToString(pixelTransform); if (canvas.width != width || canvas.height != height) { canvas.width = width; canvas.height = height; - canvas.style.width = (width / pixelRatio) + 'px'; - canvas.style.height = (height / pixelRatio) + 'px'; } else { context.clearRect(0, 0, width, height); } @@ -139,6 +128,15 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer { const img = image.getImage(); + const transform = composeTransform(this.transform_, + width / 2, height / 2, + scale, scale, + 0, + imagePixelRatio * (imageExtent[0] - viewCenter[0]) / imageResolution, + imagePixelRatio * (viewCenter[1] - imageExtent[3]) / imageResolution); + + this.renderedResolution = imageResolution * pixelRatio / imagePixelRatio; + const dx = transform[4]; const dy = transform[5]; const dw = img.width * transform[0]; @@ -153,7 +151,11 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer { context.restore(); } - const canvasTransform = 'rotate(' + rotation + 'rad)'; + const opacity = layerState.opacity; + if (opacity !== canvas.style.opacity) { + canvas.style.opacity = opacity; + } + if (canvasTransform !== canvas.style.transform) { canvas.style.transform = canvasTransform; } diff --git a/src/ol/renderer/canvas/Layer.js b/src/ol/renderer/canvas/Layer.js index e19ec5cfba..4ba65a8389 100644 --- a/src/ol/renderer/canvas/Layer.js +++ b/src/ol/renderer/canvas/Layer.js @@ -42,6 +42,7 @@ class CanvasLayerRenderer extends LayerRenderer { const canvas = this.context.canvas; canvas.style.position = 'absolute'; + canvas.style.transformOrigin = 'top left'; canvas.className = this.getLayer().getClassName(); } diff --git a/src/ol/renderer/canvas/TileLayer.js b/src/ol/renderer/canvas/TileLayer.js index e74af29e8a..aa4adc3917 100644 --- a/src/ol/renderer/canvas/TileLayer.js +++ b/src/ol/renderer/canvas/TileLayer.js @@ -222,10 +222,10 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { const canvas = context.canvas; const canvasScale = tileResolution / frameState.viewState.resolution / tilePixelRatio; const pixelTransform = composeTransform(this.transform_, - 0, 0, + frameState.size[0] / 2, frameState.size[1] / 2, canvasScale, canvasScale, rotation, - 0, 0 + -width / 2, -height / 2 ); const canvasTransform = transformToString(pixelTransform); diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index c5ffec5768..68fbb50ad5 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -5,7 +5,7 @@ import {getUid} from '../../util.js'; import {createCanvasContext2D} from '../../dom.js'; import TileState from '../../TileState.js'; import ViewHint from '../../ViewHint.js'; -import {listen, unlisten, unlistenByKey} from '../../events.js'; +import {listen, unlisten} from '../../events.js'; import EventType from '../../events/EventType.js'; import rbush from 'rbush'; import {buffer, containsCoordinate, equals, getIntersection, getTopLeft, intersects} from '../../extent.js'; @@ -75,9 +75,6 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { const container = document.createElement('div'); const style = container.style; style.position = 'absolute'; - style.display = 'flex'; - style.alignItems = 'center'; - style.justifyContent = 'center'; style.width = '100%'; style.height = '100%'; From 666f57bd4c2bcaf151bb016b94a493116028068c Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Thu, 15 Nov 2018 17:16:53 +0100 Subject: [PATCH 35/73] Unused transforms --- src/ol/renderer/canvas/ImageLayer.js | 8 +------- src/ol/renderer/canvas/TileLayer.js | 8 +------- src/ol/renderer/canvas/VectorImageLayer.js | 20 -------------------- 3 files changed, 2 insertions(+), 34 deletions(-) diff --git a/src/ol/renderer/canvas/ImageLayer.js b/src/ol/renderer/canvas/ImageLayer.js index 328244c0db..b6e38e6ec5 100644 --- a/src/ol/renderer/canvas/ImageLayer.js +++ b/src/ol/renderer/canvas/ImageLayer.js @@ -6,7 +6,7 @@ import ViewHint from '../../ViewHint.js'; import {containsExtent, intersects} from '../../extent.js'; import {getIntersection, isEmpty} from '../../extent.js'; import CanvasLayerRenderer from './Layer.js'; -import {create as createTransform, compose as composeTransform, toString as transformToString} from '../../transform.js'; +import {compose as composeTransform, toString as transformToString} from '../../transform.js'; /** * @classdesc @@ -21,12 +21,6 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer { constructor(imageLayer) { super(imageLayer); - /** - * @protected - * @type {import("../../transform.js").Transform} - */ - this.coordinateToCanvasPixelTransform = createTransform(); - /** * @protected * @type {?import("../../ImageBase.js").default} diff --git a/src/ol/renderer/canvas/TileLayer.js b/src/ol/renderer/canvas/TileLayer.js index aa4adc3917..9f03aafd86 100644 --- a/src/ol/renderer/canvas/TileLayer.js +++ b/src/ol/renderer/canvas/TileLayer.js @@ -6,7 +6,7 @@ import TileRange from '../../TileRange.js'; import TileState from '../../TileState.js'; import {createEmpty, getIntersection, getTopLeft} from '../../extent.js'; import CanvasLayerRenderer from './Layer.js'; -import {create as createTransform, compose as composeTransform, toString as transformToString} from '../../transform.js'; +import {compose as composeTransform, toString as transformToString} from '../../transform.js'; /** * @classdesc @@ -21,12 +21,6 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { constructor(tileLayer) { super(tileLayer); - /** - * @protected - * @type {import("../../transform.js").Transform} - */ - this.coordinateToCanvasPixelTransform = createTransform(); - /** * @private * @type {number} diff --git a/src/ol/renderer/canvas/VectorImageLayer.js b/src/ol/renderer/canvas/VectorImageLayer.js index 91ca356ace..0fb933b564 100644 --- a/src/ol/renderer/canvas/VectorImageLayer.js +++ b/src/ol/renderer/canvas/VectorImageLayer.js @@ -7,7 +7,6 @@ import {equals} from '../../array.js'; import {getHeight, getWidth, isEmpty} from '../../extent.js'; import {assign} from '../../obj.js'; import CanvasImageLayerRenderer from './ImageLayer.js'; -import {compose as composeTransform} from '../../transform.js'; import CanvasVectorLayerRenderer from './VectorLayer.js'; /** @@ -49,9 +48,7 @@ class CanvasVectorImageLayerRenderer extends CanvasImageLayerRenderer { */ prepareFrame(frameState, layerState) { const pixelRatio = frameState.pixelRatio; - const size = frameState.size; const viewState = frameState.viewState; - const viewCenter = viewState.center; const viewResolution = viewState.resolution; const hints = frameState.viewHints; @@ -90,25 +87,8 @@ class CanvasVectorImageLayerRenderer extends CanvasImageLayerRenderer { if (this.image_) { const image = this.image_; - const imageExtent = image.getExtent(); const imageResolution = image.getResolution(); const imagePixelRatio = image.getPixelRatio(); - const scale = pixelRatio * imageResolution / - (viewResolution * imagePixelRatio); - - const transform = composeTransform(this.transform_, - pixelRatio * size[0] / 2, pixelRatio * size[1] / 2, - scale, scale, - 0, - imagePixelRatio * (imageExtent[0] - viewCenter[0]) / imageResolution, - imagePixelRatio * (viewCenter[1] - imageExtent[3]) / imageResolution); - - composeTransform(this.coordinateToCanvasPixelTransform, - pixelRatio * size[0] / 2 - transform[4], pixelRatio * size[1] / 2 - transform[5], - pixelRatio / viewResolution, -pixelRatio / viewResolution, - 0, - -viewCenter[0], -viewCenter[1]); - this.renderedResolution = imageResolution * pixelRatio / imagePixelRatio; } From 7a82904a3b59ed700d705292cef2f2d062b32486 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Thu, 15 Nov 2018 17:17:07 +0100 Subject: [PATCH 36/73] Unused intermediate canvas renderer --- src/ol/renderer/canvas/IntermediateCanvas.js | 177 ------------------- test/spec/ol/map.test.js | 8 +- 2 files changed, 4 insertions(+), 181 deletions(-) delete mode 100644 src/ol/renderer/canvas/IntermediateCanvas.js diff --git a/src/ol/renderer/canvas/IntermediateCanvas.js b/src/ol/renderer/canvas/IntermediateCanvas.js deleted file mode 100644 index fe4c4ddc98..0000000000 --- a/src/ol/renderer/canvas/IntermediateCanvas.js +++ /dev/null @@ -1,177 +0,0 @@ -/** - * @module ol/renderer/canvas/IntermediateCanvas - */ -import {abstract} from '../../util.js'; -import {scale as scaleCoordinate} from '../../coordinate.js'; -import {createCanvasContext2D} from '../../dom.js'; -import {containsExtent, intersects} from '../../extent.js'; -import CanvasLayerRenderer from './Layer.js'; -import {create as createTransform, apply as applyTransform} from '../../transform.js'; - -/** - * @abstract - */ -class IntermediateCanvasRenderer extends CanvasLayerRenderer { - - /** - * @param {import("../../layer/Layer.js").default} layer Layer. - * @param {boolean=} opt_noContext Skip the context creation. - */ - constructor(layer, opt_noContext) { - - super(layer); - - /** - * @protected - * @type {CanvasRenderingContext2D} - */ - this.context = opt_noContext ? null : createCanvasContext2D(); - - /** - * @protected - * @type {import("../../transform.js").Transform} - */ - this.coordinateToCanvasPixelTransform = createTransform(); - - /** - * @private - * @type {CanvasRenderingContext2D} - */ - this.hitCanvasContext_ = null; - - /** - * @protected - * @type {CanvasRenderingContext2D} - */ - this.layerContext = createCanvasContext2D(); - - const canvas = this.layerContext.canvas; - canvas.style.position = 'absolute'; - canvas.className = this.getLayer().getClassName(); - } - - /** - * @inheritDoc - */ - renderFrame(frameState, layerState) { - - this.preRender(this.layerContext, frameState); - const image = this.getImage(); - if (image) { - - // clipped rendering if layer extent is set - const extent = layerState.extent; - const clipped = extent !== undefined && - !containsExtent(extent, frameState.extent) && - intersects(extent, frameState.extent); - if (clipped) { - this.clip(this.layerContext, frameState, extent); - } - - const imageTransform = this.getImageTransform(); - - // for performance reasons, context.setTransform is only used - // when the view is rotated. see http://jsperf.com/canvas-transform - const dx = imageTransform[4]; - const dy = imageTransform[5]; - const dw = image.width * imageTransform[0]; - const dh = image.height * imageTransform[3]; - - if (dw >= 0.5 && dh >= 0.5) { - this.clear(frameState); - this.layerContext.drawImage(image, 0, 0, +image.width, +image.height, - Math.round(dx), Math.round(dy), Math.round(dw), Math.round(dh)); - } - - if (clipped) { - this.layerContext.restore(); - } - } - - this.postRender(this.layerContext, frameState, layerState); - - const canvas = this.layerContext.canvas; - const opacity = layerState.opacity; - if (opacity !== canvas.style.opacity) { - canvas.style.opacity = opacity; - } - - const rotation = frameState.viewState.rotation; - const transform = 'rotate(' + rotation + 'rad)'; - if (transform !== canvas.style.transform) { - canvas.style.transform = transform; - } - - return canvas; - } - - /** - * @abstract - * @return {HTMLCanvasElement|HTMLVideoElement|HTMLImageElement} Canvas. - */ - getImage() { - return abstract(); - } - - /** - * @abstract - * @return {!import("../../transform.js").Transform} Image transform. - */ - getImageTransform() { - return abstract(); - } - - /** - * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. - */ - clear(frameState) { - const pixelRatio = frameState.pixelRatio; - const canvas = this.layerContext.canvas; - - let width = Math.round(frameState.size[0] * pixelRatio); - let height = Math.round(frameState.size[1] * pixelRatio); - const rotation = frameState.viewState.rotation; - if (rotation) { - const size = Math.round(Math.sqrt(width * width + height * height)); - width = height = size; - } - - if (canvas.width != width || canvas.height != height) { - canvas.width = width; - canvas.height = height; - canvas.style.width = (width / pixelRatio) + 'px'; - canvas.style.height = (height / pixelRatio) + 'px'; - } else { - this.layerContext.clearRect(0, 0, width, height); - } - } - - /** - * @inheritDoc - */ - forEachLayerAtCoordinate(coordinate, frameState, hitTolerance, callback, thisArg) { - if (!this.getImage()) { - return undefined; - } - - const pixel = applyTransform(this.coordinateToCanvasPixelTransform, coordinate.slice()); - scaleCoordinate(pixel, frameState.viewState.resolution / this.renderedResolution); - - if (!this.hitCanvasContext_) { - this.hitCanvasContext_ = createCanvasContext2D(1, 1); - } - - this.hitCanvasContext_.clearRect(0, 0, 1, 1); - this.hitCanvasContext_.drawImage(this.getImage(), pixel[0], pixel[1], 1, 1, 0, 0, 1, 1); - - const imageData = this.hitCanvasContext_.getImageData(0, 0, 1, 1).data; - if (imageData[3] > 0) { - return callback.call(thisArg, this.getLayer(), imageData); - } else { - return undefined; - } - } -} - - -export default IntermediateCanvasRenderer; diff --git a/test/spec/ol/map.test.js b/test/spec/ol/map.test.js index bf3862d522..7a228cc906 100644 --- a/test/spec/ol/map.test.js +++ b/test/spec/ol/map.test.js @@ -18,7 +18,7 @@ import PinchZoom from '../../../src/ol/interaction/PinchZoom.js'; import ImageLayer from '../../../src/ol/layer/Image.js'; import TileLayer from '../../../src/ol/layer/Tile.js'; import VectorLayer from '../../../src/ol/layer/Vector.js'; -import IntermediateCanvasRenderer from '../../../src/ol/renderer/canvas/IntermediateCanvas.js'; +import TileLayerRenderer from '../../../src/ol/renderer/canvas/TileLayer.js'; import ImageStatic from '../../../src/ol/source/ImageStatic.js'; import VectorSource from '../../../src/ol/source/Vector.js'; import XYZ from '../../../src/ol/source/XYZ.js'; @@ -325,8 +325,8 @@ describe('ol.Map', function() { beforeEach(function(done) { log = []; - original = IntermediateCanvasRenderer.prototype.forEachLayerAtCoordinate; - IntermediateCanvasRenderer.prototype.forEachLayerAtCoordinate = function(coordinate) { + original = TileLayerRenderer.prototype.forEachLayerAtCoordinate; + TileLayerRenderer.prototype.forEachLayerAtCoordinate = function(coordinate) { log.push(coordinate.slice()); }; @@ -364,7 +364,7 @@ describe('ol.Map', function() { }); afterEach(function() { - IntermediateCanvasRenderer.prototype.forEachLayerAtCoordinate = original; + TileLayerRenderer.prototype.forEachLayerAtCoordinate = original; map.dispose(); document.body.removeChild(target); log = null; From ac3a1fb953eea4498abf04100e1971a2a43eaf3b Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Thu, 15 Nov 2018 18:03:02 +0100 Subject: [PATCH 37/73] Dispatch layer changed event when layer needs a re-render --- src/ol/renderer/Layer.js | 2 +- src/ol/source/Image.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ol/renderer/Layer.js b/src/ol/renderer/Layer.js index 06de5216a5..9307a187c5 100644 --- a/src/ol/renderer/Layer.js +++ b/src/ol/renderer/Layer.js @@ -158,7 +158,7 @@ class LayerRenderer extends Observable { renderIfReadyAndVisible() { const layer = this.getLayer(); if (layer.getVisible() && layer.getSourceState() == SourceState.READY) { - this.changed(); + layer.changed(); } } diff --git a/src/ol/source/Image.js b/src/ol/source/Image.js index 4aed75f417..8f2164c0f2 100644 --- a/src/ol/source/Image.js +++ b/src/ol/source/Image.js @@ -212,7 +212,6 @@ class ImageSource extends Source { this.dispatchEvent( new ImageSourceEvent(ImageSourceEventType.IMAGELOADEND, image)); - this.changed(); break; case ImageState.ERROR: this.loading = false; From 832dadb3af98c5506adc121ee890cd83b1b1c9a3 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Thu, 15 Nov 2018 19:01:26 +0100 Subject: [PATCH 38/73] Dedicated transforms --- src/ol/renderer/canvas/ImageLayer.js | 6 +++--- src/ol/renderer/canvas/Layer.js | 32 +++++++++------------------- src/ol/renderer/canvas/TileLayer.js | 3 ++- 3 files changed, 15 insertions(+), 26 deletions(-) diff --git a/src/ol/renderer/canvas/ImageLayer.js b/src/ol/renderer/canvas/ImageLayer.js index b6e38e6ec5..405dd4ec05 100644 --- a/src/ol/renderer/canvas/ImageLayer.js +++ b/src/ol/renderer/canvas/ImageLayer.js @@ -96,12 +96,12 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer { const context = this.context; const canvas = context.canvas; - const pixelTransform = composeTransform(this.transform_, + const pixelTransform = composeTransform(this.pixelTransform_, frameState.size[0] / 2, frameState.size[1] / 2, 1 / pixelRatio, 1 / pixelRatio, rotation, -width / 2, -height / 2 - ).slice(); + ); const canvasTransform = transformToString(pixelTransform); if (canvas.width != width || canvas.height != height) { @@ -122,7 +122,7 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer { const img = image.getImage(); - const transform = composeTransform(this.transform_, + const transform = composeTransform(this.tempTransform_, width / 2, height / 2, scale, scale, 0, diff --git a/src/ol/renderer/canvas/Layer.js b/src/ol/renderer/canvas/Layer.js index 4ba65a8389..fc302d514d 100644 --- a/src/ol/renderer/canvas/Layer.js +++ b/src/ol/renderer/canvas/Layer.js @@ -29,10 +29,18 @@ class CanvasLayerRenderer extends LayerRenderer { this.renderedResolution; /** + * A temporary transform. * @private * @type {import("../../transform.js").Transform} */ - this.transform_ = createTransform(); + this.tempTransform_ = createTransform(); + + /** + * The transform for rendered pixels to viewport CSS pixels. + * @private + * @type {import("../../transform.js").Transform} + */ + this.pixelTransform_ = createTransform(); /** * @protected @@ -44,7 +52,6 @@ class CanvasLayerRenderer extends LayerRenderer { canvas.style.position = 'absolute'; canvas.style.transformOrigin = 'top left'; canvas.className = this.getLayer().getClassName(); - } /** @@ -145,25 +152,6 @@ class CanvasLayerRenderer extends LayerRenderer { this.dispatchComposeEvent_(RenderEventType.RENDER, context, frameState, opt_transform); } - /** - * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. - * @param {number} offsetX Offset on the x-axis in view coordinates. - * @protected - * @return {!import("../../transform.js").Transform} Transform. - */ - getTransform(frameState, offsetX) { - const viewState = frameState.viewState; - const pixelRatio = frameState.pixelRatio; - const dx1 = pixelRatio * frameState.size[0] / 2; - const dy1 = pixelRatio * frameState.size[1] / 2; - const sx = pixelRatio / viewState.resolution; - const sy = -sx; - const angle = -viewState.rotation; - const dx2 = -viewState.center[0] + offsetX; - const dy2 = -viewState.center[1]; - return composeTransform(this.transform_, dx1, dy1, sx, sy, angle, dx2, dy2); - } - /** * Creates a transform for rendering to an element that will be rotated after rendering. * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. @@ -182,7 +170,7 @@ class CanvasLayerRenderer extends LayerRenderer { const sy = -sx; const dx2 = -viewState.center[0] + offsetX; const dy2 = -viewState.center[1]; - return composeTransform(this.transform_, dx1, dy1, sx, sy, -viewState.rotation, dx2, dy2); + return composeTransform(this.tempTransform_, dx1, dy1, sx, sy, -viewState.rotation, dx2, dy2); } } diff --git a/src/ol/renderer/canvas/TileLayer.js b/src/ol/renderer/canvas/TileLayer.js index 9f03aafd86..5fed8bb541 100644 --- a/src/ol/renderer/canvas/TileLayer.js +++ b/src/ol/renderer/canvas/TileLayer.js @@ -215,7 +215,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { const canvas = context.canvas; const canvasScale = tileResolution / frameState.viewState.resolution / tilePixelRatio; - const pixelTransform = composeTransform(this.transform_, + const pixelTransform = composeTransform(this.pixelTransform_, frameState.size[0] / 2, frameState.size[1] / 2, canvasScale, canvasScale, rotation, @@ -350,6 +350,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { getTileImage(tile) { return /** @type {import("../../ImageTile.js").default} */ (tile).getImage(); } + } From 5ad73f8bbd12e28e392ed99d215cca02c944d8e3 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 16 Nov 2018 10:34:03 +0100 Subject: [PATCH 39/73] Implement getDataAtPixel for all layer renderers --- src/ol/PluggableMap.js | 3 +- src/ol/renderer/Composite.js | 18 ++++---- src/ol/renderer/Layer.js | 12 +++--- src/ol/renderer/Map.js | 4 +- src/ol/renderer/canvas/Layer.js | 52 +++++++++++++---------- src/ol/renderer/canvas/VectorTileLayer.js | 50 ++++++++++++++++++++-- 6 files changed, 92 insertions(+), 47 deletions(-) diff --git a/src/ol/PluggableMap.js b/src/ol/PluggableMap.js index f33d5bb238..38474eff8b 100644 --- a/src/ol/PluggableMap.js +++ b/src/ol/PluggableMap.js @@ -623,8 +623,7 @@ class PluggableMap extends BaseObject { const hitTolerance = options.hitTolerance !== undefined ? opt_options.hitTolerance * this.frameState_.pixelRatio : 0; const layerFilter = options.layerFilter || TRUE; - return this.renderer_.forEachLayerAtPixel( - pixel, this.frameState_, hitTolerance, callback, null, layerFilter, null); + return this.renderer_.forEachLayerAtPixel(pixel, this.frameState_, hitTolerance, callback, layerFilter); } /** diff --git a/src/ol/renderer/Composite.js b/src/ol/renderer/Composite.js index 67bbb8a501..8e6f00c2da 100644 --- a/src/ol/renderer/Composite.js +++ b/src/ol/renderer/Composite.js @@ -1,7 +1,6 @@ /** * @module ol/renderer/canvas/Map */ -import {apply as applyTransform} from '../transform.js'; import {stableSort} from '../array.js'; import {CLASS_UNSELECTABLE} from '../css.js'; import {visibleAtResolution} from '../layer/Layer.js'; @@ -111,28 +110,27 @@ class CompositeMapRenderer extends MapRenderer { /** * @inheritDoc */ - forEachLayerAtPixel(pixel, frameState, hitTolerance, callback, thisArg, layerFilter, thisArg2) { - let result; + forEachLayerAtPixel(pixel, frameState, hitTolerance, callback, layerFilter) { const viewState = frameState.viewState; const viewResolution = viewState.resolution; const layerStates = frameState.layerStatesArray; const numLayers = layerStates.length; - const coordinate = applyTransform( - frameState.pixelToCoordinateTransform, pixel.slice()); - for (let i = numLayers - 1; i >= 0; --i) { const layerState = layerStates[i]; const layer = layerState.layer; - if (visibleAtResolution(layerState, viewResolution) && layerFilter.call(thisArg2, layer)) { + if (visibleAtResolution(layerState, viewResolution) && layerFilter(layer)) { const layerRenderer = this.getLayerRenderer(layer); if (!layerRenderer) { continue; } - result = layerRenderer.forEachLayerAtCoordinate(coordinate, frameState, hitTolerance, callback, thisArg); - if (result) { - return result; + const data = layerRenderer.getDataAtPixel(pixel, frameState, hitTolerance); + if (data) { + const result = callback(layer, data); + if (result) { + return result; + } } } } diff --git a/src/ol/renderer/Layer.js b/src/ol/renderer/Layer.js index 9307a187c5..3d20b415ac 100644 --- a/src/ol/renderer/Layer.js +++ b/src/ol/renderer/Layer.js @@ -92,16 +92,14 @@ class LayerRenderer extends Observable { /** * @abstract - * @param {import("../coordinate.js").Coordinate} coordinate Coordinate. + * @param {import("../pixel.js").Pixel} pixel Pixel. * @param {import("../PluggableMap.js").FrameState} frameState FrameState. * @param {number} hitTolerance Hit tolerance in pixels. - * @param {function(this: S, import("../layer/Layer.js").default, (Uint8ClampedArray|Uint8Array)): T} callback Layer - * callback. - * @param {S} thisArg Value to use as `this` when executing `callback`. - * @return {T|undefined} Callback result. - * @template S,T + * @return {Uint8ClampedArray|Uint8Array} The result. If there is no data at the pixel + * location, null will be returned. If there is data, but pixel values cannot be + * returned, and empty array will be returned. */ - forEachLayerAtCoordinate(coordinate, frameState, hitTolerance, callback, thisArg) { + getDataAtPixel(pixel, frameState, hitTolerance) { return abstract(); } diff --git a/src/ol/renderer/Map.js b/src/ol/renderer/Map.js index a07f84354f..281d1eb070 100644 --- a/src/ol/renderer/Map.js +++ b/src/ol/renderer/Map.js @@ -161,16 +161,14 @@ class MapRenderer extends Disposable { * @param {number} hitTolerance Hit tolerance in pixels. * @param {function(this: S, import("../layer/Layer.js").default, (Uint8ClampedArray|Uint8Array)): T} callback Layer * callback. - * @param {S} thisArg Value to use as `this` when executing `callback`. * @param {function(this: U, import("../layer/Layer.js").default): boolean} layerFilter Layer filter * function, only layers which are visible and for which this function * returns `true` will be tested for features. By default, all visible * layers will be tested. - * @param {U} thisArg2 Value to use as `this` when executing `layerFilter`. * @return {T|undefined} Callback result. * @template S,T,U */ - forEachLayerAtPixel(pixel, frameState, hitTolerance, callback, thisArg, layerFilter, thisArg2) { + forEachLayerAtPixel(pixel, frameState, hitTolerance, callback, layerFilter) { return abstract(); } diff --git a/src/ol/renderer/canvas/Layer.js b/src/ol/renderer/canvas/Layer.js index fc302d514d..63b3c475ee 100644 --- a/src/ol/renderer/canvas/Layer.js +++ b/src/ol/renderer/canvas/Layer.js @@ -3,12 +3,11 @@ */ import {getBottomLeft, getBottomRight, getTopLeft, getTopRight} from '../../extent.js'; import {createCanvasContext2D} from '../../dom.js'; -import {TRUE} from '../../functions.js'; import RenderEvent from '../../render/Event.js'; import RenderEventType from '../../render/EventType.js'; import {rotateAtOffset} from '../../render/canvas.js'; import LayerRenderer from '../Layer.js'; -import {create as createTransform, apply as applyTransform, compose as composeTransform} from '../../transform.js'; +import {invert as invertTransform, create as createTransform, apply as applyTransform, compose as composeTransform} from '../../transform.js'; /** * @abstract @@ -102,26 +101,6 @@ class CanvasLayerRenderer extends LayerRenderer { } } - /** - * @param {import("../../coordinate.js").Coordinate} coordinate Coordinate. - * @param {import("../../PluggableMap.js").FrameState} frameState FrameState. - * @param {number} hitTolerance Hit tolerance in pixels. - * @param {function(this: S, import("../../layer/Layer.js").default, (Uint8ClampedArray|Uint8Array)): T} callback Layer - * callback. - * @param {S} thisArg Value to use as `this` when executing `callback`. - * @return {T|undefined} Callback result. - * @template S,T,U - */ - forEachLayerAtCoordinate(coordinate, frameState, hitTolerance, callback, thisArg) { - const hasFeature = this.forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, TRUE); - - if (hasFeature) { - return callback.call(thisArg, this.getLayer(), null); - } else { - return undefined; - } - } - /** * @param {CanvasRenderingContext2D} context Context. * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. @@ -173,6 +152,35 @@ class CanvasLayerRenderer extends LayerRenderer { return composeTransform(this.tempTransform_, dx1, dy1, sx, sy, -viewState.rotation, dx2, dy2); } + /** + * @param {import("../../pixel.js").Pixel} pixel Pixel. + * @param {import("../../PluggableMap.js").FrameState} frameState FrameState. + * @param {number} hitTolerance Hit tolerance in pixels. + * @return {Uint8ClampedArray|Uint8Array} The result. If there is no data at the pixel + * location, null will be returned. If there is data, but pixel values cannot be + * returned, and empty array will be returned. + */ + getDataAtPixel(pixel, frameState, hitTolerance) { + const renderPixel = applyTransform(invertTransform(this.pixelTransform_.slice()), pixel.slice()); + const context = this.context; + + let data; + try { + data = context.getImageData(Math.round(renderPixel[0]), Math.round(renderPixel[1]), 1, 1).data; + } catch (err) { + if (err.name === 'SecurityError') { + // tainted canvas, we assume there is data at the given pixel (although there might not be) + return new Uint8Array(); + } + return data; + } + + if (data[3] === 0) { + return null; + } + return data; + } + } export default CanvasLayerRenderer; diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index 68fbb50ad5..c066a522f0 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -19,11 +19,14 @@ import {ORDER} from '../../render/replay.js'; import CanvasTileLayerRenderer from './TileLayer.js'; import {getSquaredTolerance as getSquaredRenderTolerance, renderFeature} from '../vector.js'; import { + apply as applyTransform, create as createTransform, compose as composeTransform, + invert as invertTransform, reset as resetTransform, scale as scaleTransform, - translate as translateTransform + translate as translateTransform, + toString as transformToString } from '../../transform.js'; import CanvasExecutorGroup, {replayDeclutter} from '../../render/canvas/ExecutorGroup.js'; @@ -71,6 +74,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { const overlayCanvas = this.overlayContext_.canvas; overlayCanvas.style.position = 'absolute'; + overlayCanvas.style.transformOrigin = 'top left'; const container = document.createElement('div'); const style = container.style; @@ -87,6 +91,13 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { */ this.container_ = container; + /** + * The transform for rendered pixels to viewport CSS pixels for the overlay canvas. + * @private + * @type {import("../../transform.js").Transform} + */ + this.overlayPixelTransform_ = createTransform(); + /** * Declutter tree. * @private @@ -377,11 +388,15 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { const canvas = context.canvas; const width = Math.round(size[0] * pixelRatio); const height = Math.round(size[1] * pixelRatio); + this.overlayPixelTransform_[0] = 1 / pixelRatio; + this.overlayPixelTransform_[3] = 1 / pixelRatio; if (canvas.width != width || canvas.height != height) { canvas.width = width; canvas.height = height; - canvas.style.width = (width / pixelRatio) + 'px'; - canvas.style.height = (height / pixelRatio) + 'px'; + const canvasTransform = transformToString(this.overlayPixelTransform_); + if (canvas.style.transform !== canvasTransform) { + canvas.style.transform = canvasTransform; + } } else { context.clearRect(0, 0, width, height); } @@ -519,6 +534,35 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { } } } + + /** + * @inheritdoc + */ + getDataAtPixel(pixel, frameState, hitTolerance) { + let data = super.getDataAtPixel(pixel, frameState, hitTolerance); + if (data) { + return data; + } + + const renderPixel = applyTransform(invertTransform(this.overlayPixelTransform_.slice()), pixel.slice()); + const context = this.overlayContext_; + + try { + data = context.getImageData(Math.round(renderPixel[0]), Math.round(renderPixel[1]), 1, 1).data; + } catch (err) { + if (err.name === 'SecurityError') { + // tainted canvas, we assume there is data at the given pixel (although there might not be) + return new Uint8Array(); + } + return data; + } + + if (data[3] === 0) { + return null; + } + return data; + } + } From cc9b7b62590689be4c376e3f1b09ff2b44319121 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 16 Nov 2018 10:34:26 +0100 Subject: [PATCH 40/73] Work around a TypeScript issue with inheritdoc --- src/ol/renderer/canvas/TileLayer.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ol/renderer/canvas/TileLayer.js b/src/ol/renderer/canvas/TileLayer.js index 5fed8bb541..cc52886763 100644 --- a/src/ol/renderer/canvas/TileLayer.js +++ b/src/ol/renderer/canvas/TileLayer.js @@ -119,7 +119,12 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { } /** + * TODO: File a TypeScript issue about inheritDoc not being followed + * all the way. Without this explicit return type, the VectorTileLayer + * renderFrame function does not pass. + * * @inheritDoc + * @returns {HTMLElement} The rendered element. */ renderFrame(frameState, layerState) { const context = this.context; From 358f58ba3aa85855fa269d9492bb581efa2bcf98 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 16 Nov 2018 10:34:50 +0100 Subject: [PATCH 41/73] Doc for transform toString function --- src/ol/transform.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ol/transform.js b/src/ol/transform.js index 65c3b4f2f1..cbb19c3c39 100644 --- a/src/ol/transform.js +++ b/src/ol/transform.js @@ -237,6 +237,12 @@ export function determinant(mat) { return mat[0] * mat[3] - mat[1] * mat[2]; } +/** + * A string version of the transform. This can be used + * for CSS transforms. + * @param {!Transform} mat Matrix. + * @return {string} The transform as a string. + */ export function toString(mat) { return 'matrix(' + mat.join(', ') + ')'; } From 45cd573768ab1ecfcea48cbdddcf7429ccfea15c Mon Sep 17 00:00:00 2001 From: Florent gravin Date: Thu, 15 Nov 2018 19:47:45 +0100 Subject: [PATCH 42/73] Fix layer-spy example using right post/pre render events --- examples/layer-spy.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/layer-spy.js b/examples/layer-spy.js index 7020fb80f5..2048b46734 100644 --- a/examples/layer-spy.js +++ b/examples/layer-spy.js @@ -52,7 +52,7 @@ container.addEventListener('mouseout', function() { }); // before rendering the layer, do some clipping -imagery.on('precompose', function(event) { +imagery.on('prerender', function(event) { const ctx = event.context; const pixelRatio = event.frameState.pixelRatio; ctx.save(); @@ -69,7 +69,7 @@ imagery.on('precompose', function(event) { }); // after rendering the layer, restore the canvas context -imagery.on('postcompose', function(event) { +imagery.on('postrender', function(event) { const ctx = event.context; ctx.restore(); }); From 4d54549b5fb24e5edbb6e86fa2ceabfb8bf52b7b Mon Sep 17 00:00:00 2001 From: Florent gravin Date: Fri, 16 Nov 2018 09:47:22 +0100 Subject: [PATCH 43/73] Fix flight-animation example with postrender event --- examples/flight-animation.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/examples/flight-animation.js b/examples/flight-animation.js index e05aaf53ec..c0b549267e 100644 --- a/examples/flight-animation.js +++ b/examples/flight-animation.js @@ -6,14 +6,17 @@ import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js'; import Stamen from '../src/ol/source/Stamen.js'; import VectorSource from '../src/ol/source/Vector.js'; import {Stroke, Style} from '../src/ol/style.js'; +import {getVectorContext} from '../src/ol/render'; + +const tileLayer = new TileLayer({ + source: new Stamen({ + layer: 'toner' + }) +}); const map = new Map({ layers: [ - new TileLayer({ - source: new Stamen({ - layer: 'toner' - }) - }) + tileLayer ], target: 'map', view: new View({ @@ -63,7 +66,7 @@ const flightsSource = new VectorSource({ addLater(feature, i * 50); } } - map.on('postcompose', animateFlights); + tileLayer.on('postrender', animateFlights); }); } }); @@ -85,7 +88,7 @@ map.addLayer(flightsLayer); const pointsPerMs = 0.1; function animateFlights(event) { - const vectorContext = event.vectorContext; + const vectorContext = getVectorContext(event); const frameState = event.frameState; vectorContext.setStyle(style); From 3cba5ffbe25b7f6862f36034d03a9938bb7df8aa Mon Sep 17 00:00:00 2001 From: Florent gravin Date: Fri, 16 Nov 2018 09:50:50 +0100 Subject: [PATCH 44/73] Fix swipe example using right post/pre render events --- examples/layer-swipe.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/layer-swipe.js b/examples/layer-swipe.js index d991556473..278de07d70 100644 --- a/examples/layer-swipe.js +++ b/examples/layer-swipe.js @@ -25,7 +25,7 @@ const map = new Map({ const swipe = document.getElementById('swipe'); -bing.on('precompose', function(event) { +bing.on('prerender', function(event) { const ctx = event.context; const width = ctx.canvas.width * (swipe.value / 100); @@ -35,7 +35,7 @@ bing.on('precompose', function(event) { ctx.clip(); }); -bing.on('postcompose', function(event) { +bing.on('postrender', function(event) { const ctx = event.context; ctx.restore(); }); From c5b7c5febb3d43f8863b93cd6d1b861476da7e75 Mon Sep 17 00:00:00 2001 From: Florent gravin Date: Fri, 16 Nov 2018 09:56:42 +0100 Subject: [PATCH 45/73] Fix magnify example using right postrender events --- examples/magnify.html | 2 +- examples/magnify.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/magnify.html b/examples/magnify.html index 6f62e4bdac..c8fa04f13a 100644 --- a/examples/magnify.html +++ b/examples/magnify.html @@ -3,7 +3,7 @@ layout: example.html title: Magnify shortdesc: Show a magnified version of imager under the pointer docs: > -

This example makes use of the postcompose event listener to +

This example makes use of the postrender event listener to oversample imagery in a circle around the pointer location. Listeners for this event have access to the Canvas context and can manipulate image data.

Move around the map to see the effect. Use the ↑ up and ↓ down arrow keys to adjust the magnified circle size.

tags: "magnify, image manipulation" diff --git a/examples/magnify.js b/examples/magnify.js index 0cf052b68e..de64115baf 100644 --- a/examples/magnify.js +++ b/examples/magnify.js @@ -48,7 +48,7 @@ container.addEventListener('mouseout', function() { }); // after rendering the layer, show an oversampled version around the pointer -imagery.on('postcompose', function(event) { +imagery.on('postrender', function(event) { if (mousePosition) { const context = event.context; const pixelRatio = event.frameState.pixelRatio; From 160e9e8056ad0b09a942f123e0abee5c19094371 Mon Sep 17 00:00:00 2001 From: Florent gravin Date: Fri, 16 Nov 2018 09:59:23 +0100 Subject: [PATCH 46/73] Fix image-filter example using right postrender events --- examples/image-filter.html | 4 ++-- examples/image-filter.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/image-filter.html b/examples/image-filter.html index 37d308d21a..1d68e835d1 100644 --- a/examples/image-filter.html +++ b/examples/image-filter.html @@ -3,9 +3,9 @@ layout: example.html title: Image Filters shortdesc: Apply a filter to imagery docs: > -

Layer rendering can be manipulated in precompose and postcompose event listeners. +

Layer rendering can be manipulated in prerender and postrender event listeners. These listeners get an event with a reference to the Canvas rendering context. - In this example, the postcompose listener applies a filter to the image data.

+ In this example, the postrender listener applies a filter to the image data.

tags: "filter, image manipulation" cloak: - key: As1HiMj1PvLPlqc_gtM7AqZfBL8ZL3VrjaS3zIb22Uvb9WKhuJObROC-qUpa81U5 diff --git a/examples/image-filter.js b/examples/image-filter.js index a35636a2df..47d321e56a 100644 --- a/examples/image-filter.js +++ b/examples/image-filter.js @@ -90,9 +90,9 @@ select.onchange = function() { /** - * Apply a filter on "postcompose" events. + * Apply a filter on "postrender" events. */ -imagery.on('postcompose', function(event) { +imagery.on('postrender', function(event) { convolve(event.context, selectedKernel); }); From 91bd144f0ed9837a0c4fe005dd7fa9c214cfbdab Mon Sep 17 00:00:00 2001 From: Florent gravin Date: Fri, 16 Nov 2018 10:00:11 +0100 Subject: [PATCH 47/73] Update post/pre render event doc usage in examples --- examples/feature-animation.html | 2 +- examples/flight-animation.html | 6 +++--- examples/layer-spy.html | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/feature-animation.html b/examples/feature-animation.html index be37779a7c..7b5b8f368a 100644 --- a/examples/feature-animation.html +++ b/examples/feature-animation.html @@ -3,7 +3,7 @@ layout: example.html title: Custom Animation shortdesc: Demonstrates how to animate features. docs: > - This example shows how to use postcompose and vectorContext to + This example shows how to use postrender and vectorContext to animate features. Here we choose to do a flash animation each time a feature is added to the layer. tags: "animation, vector, feature, flash" diff --git a/examples/flight-animation.html b/examples/flight-animation.html index b0c54dc875..0b7952ce40 100644 --- a/examples/flight-animation.html +++ b/examples/flight-animation.html @@ -1,12 +1,12 @@ --- layout: example.html title: Flight Animation -shortdesc: Demonstrates how to animate flights with ´postcompose´. +shortdesc: Demonstrates how to animate flights with ´postrender´. docs: > - This example shows how to use postcompose and vectorContext to + This example shows how to use postrender and vectorContext to animate flights. A great circle arc between two airports is calculated using
arc.js and then the flight - paths are animated with postcompose. The flight data is provided by + paths are animated with postrender. The flight data is provided by OpenFlights (a simplified data set from the Mapbox.js documentation is used). diff --git a/examples/layer-spy.html b/examples/layer-spy.html index 84bd1c3b32..952e52cc71 100644 --- a/examples/layer-spy.html +++ b/examples/layer-spy.html @@ -3,7 +3,7 @@ layout: example.html title: Layer Spy shortdesc: View a portion of one layer over another docs: > -

Layer rendering can be manipulated in precompose and postcompose event listeners. +

Layer rendering can be manipulated in prerender and postrender event listeners. These listeners get an event with a reference to the Canvas rendering context. In this example, the precompose listener sets a clipping mask around the most recent mouse position, giving you a spyglass effect for viewing one layer over another.

From 3bf9a54ed5dd9343b1556b56ab098a5b43e3f22b Mon Sep 17 00:00:00 2001 From: Florent gravin Date: Fri, 16 Nov 2018 10:14:48 +0100 Subject: [PATCH 48/73] Fix dynamic-data example migrating map postcompose event --- examples/dynamic-data.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/dynamic-data.js b/examples/dynamic-data.js index 885d90202d..8ba33533a7 100644 --- a/examples/dynamic-data.js +++ b/examples/dynamic-data.js @@ -4,14 +4,14 @@ import {MultiPoint, Point} from '../src/ol/geom.js'; import TileLayer from '../src/ol/layer/Tile.js'; import OSM from '../src/ol/source/OSM.js'; import {Circle as CircleStyle, Fill, Stroke, Style} from '../src/ol/style.js'; +import {getVectorContext} from '../src/ol/render'; +const tileLayer = new TileLayer({ + source: new OSM() +}); const map = new Map({ - layers: [ - new TileLayer({ - source: new OSM() - }) - ], + layers: [tileLayer], target: 'map', view: new View({ center: [0, 0], @@ -46,8 +46,8 @@ const omegaTheta = 30000; // Rotation period in ms const R = 7e6; const r = 2e6; const p = 2e6; -map.on('postcompose', function(event) { - const vectorContext = event.vectorContext; +tileLayer.on('postrender', function(event) { + const vectorContext = getVectorContext(event); const frameState = event.frameState; const theta = 2 * Math.PI * frameState.time / omegaTheta; const coordinates = []; From a9248349204d1de01b7d9e55e7db0ee7a4a8123e Mon Sep 17 00:00:00 2001 From: Florent gravin Date: Fri, 16 Nov 2018 10:17:21 +0100 Subject: [PATCH 49/73] Fix feature-animation example migrating map postcompose event --- examples/feature-animation.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/feature-animation.js b/examples/feature-animation.js index 4c6cea20d4..9e61145499 100644 --- a/examples/feature-animation.js +++ b/examples/feature-animation.js @@ -8,16 +8,16 @@ import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js'; import {fromLonLat} from '../src/ol/proj.js'; import {OSM, Vector as VectorSource} from '../src/ol/source.js'; import {Circle as CircleStyle, Stroke, Style} from '../src/ol/style.js'; +import {getVectorContext} from '../src/ol/render'; +const tileLayer = new TileLayer({ + source: new OSM({ + wrapX: false + }) +}); const map = new Map({ - layers: [ - new TileLayer({ - source: new OSM({ - wrapX: false - }) - }) - ], + layers: [tileLayer], target: 'map', view: new View({ center: [0, 0], @@ -44,10 +44,10 @@ function addRandomFeature() { const duration = 3000; function flash(feature) { const start = new Date().getTime(); - const listenerKey = map.on('postcompose', animate); + const listenerKey = tileLayer.on('postrender', animate); function animate(event) { - const vectorContext = event.vectorContext; + const vectorContext = getVectorContext(event); const frameState = event.frameState; const flashGeom = feature.getGeometry().clone(); const elapsed = frameState.time - start; From 039af41af1cc360b47e0e13670565ba83e5871f2 Mon Sep 17 00:00:00 2001 From: Florent gravin Date: Fri, 16 Nov 2018 10:21:17 +0100 Subject: [PATCH 50/73] Unbind postrender event in feature-move-animation example --- examples/feature-move-animation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/feature-move-animation.js b/examples/feature-move-animation.js index f19b590b70..a5119d3e97 100644 --- a/examples/feature-move-animation.js +++ b/examples/feature-move-animation.js @@ -195,7 +195,7 @@ function stopAnimation(ended) { /** @type {module:ol/geom/Point~Point} */ (geoMarker.getGeometry()) .setCoordinates(coord); //remove listener - map.un('postcompose', moveFeature); + vectorLayer.un('postrender', moveFeature); } startButton.addEventListener('click', startAnimation, false); From eafe1bf8a360474a1cee2d6c930c8537d04f34c2 Mon Sep 17 00:00:00 2001 From: Florent gravin Date: Fri, 16 Nov 2018 10:30:57 +0100 Subject: [PATCH 51/73] Fix geolocation-orientation example migrating map postcompose event --- examples/geolocation-orientation.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/geolocation-orientation.js b/examples/geolocation-orientation.js index c05f1bb73a..2adb79d1eb 100644 --- a/examples/geolocation-orientation.js +++ b/examples/geolocation-orientation.js @@ -13,13 +13,13 @@ const view = new View({ zoom: 19 }); +const tileLayer = new TileLayer({ + source: new OSM() +}); + // creating the map const map = new Map({ - layers: [ - new TileLayer({ - source: new OSM() - }) - ], + layers: [tileLayer], target: 'map', view: view }); @@ -155,7 +155,7 @@ const geolocateBtn = document.getElementById('geolocate'); geolocateBtn.addEventListener('click', function() { geolocation.setTracking(true); // Start position tracking - map.on('postcompose', updateView); + tileLayer.on('postrender', updateView); map.render(); disableButtons(); @@ -197,7 +197,7 @@ simulateBtn.addEventListener('click', function() { } geolocate(); - map.on('postcompose', updateView); + tileLayer.on('postrender', updateView); map.render(); disableButtons(); From f73c6fab353e377b8b15eb74e77a3130a3999ce5 Mon Sep 17 00:00:00 2001 From: Florent gravin Date: Fri, 16 Nov 2018 10:33:34 +0100 Subject: [PATCH 52/73] Fix igc example migrating map postcompose event --- examples/igc.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/examples/igc.js b/examples/igc.js index 90c71c2cb3..b7cf70cbda 100644 --- a/examples/igc.js +++ b/examples/igc.js @@ -7,6 +7,7 @@ import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js'; import OSM, {ATTRIBUTION} from '../src/ol/source/OSM.js'; import VectorSource from '../src/ol/source/Vector.js'; import {Circle as CircleStyle, Fill, Stroke, Style} from '../src/ol/style.js'; +import {getVectorContext} from '../src/ol/render'; const colors = { @@ -73,6 +74,10 @@ vectorSource.on('addfeature', function(event) { time.duration = time.stop - time.start; }); +const vectorLayer = new VectorLayer({ + source: vectorSource, + style: styleFunction +}); const map = new Map({ layers: [ @@ -86,10 +91,7 @@ const map = new Map({ '?apikey=0e6fc415256d4fbb9b5166a718591d71' }) }), - new VectorLayer({ - source: vectorSource, - style: styleFunction - }) + vectorLayer ], target: 'map', view: new View({ @@ -153,8 +155,8 @@ const style = new Style({ stroke: stroke }) }); -map.on('postcompose', function(evt) { - const vectorContext = evt.vectorContext; +vectorLayer.on('postrender', function(evt) { + const vectorContext = getVectorContext(evt); vectorContext.setStyle(style); if (point !== null) { vectorContext.drawGeometry(point); From 1dbe52d738550e82a3d41f64debfcfb04bcd688b Mon Sep 17 00:00:00 2001 From: Florent gravin Date: Fri, 16 Nov 2018 10:37:42 +0100 Subject: [PATCH 53/73] Fix layer-clipping example using right postrender events --- examples/layer-clipping.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/layer-clipping.js b/examples/layer-clipping.js index fabe66d27f..dc7a892dae 100644 --- a/examples/layer-clipping.js +++ b/examples/layer-clipping.js @@ -16,7 +16,7 @@ const map = new Map({ }) }); -osm.on('precompose', function(event) { +osm.on('prerender', function(event) { const ctx = event.context; ctx.save(); const pixelRatio = event.frameState.pixelRatio; @@ -38,7 +38,7 @@ osm.on('precompose', function(event) { ctx.translate(-size[0] / 2 * pixelRatio, -size[1] / 2 * pixelRatio); }); -osm.on('postcompose', function(event) { +osm.on('postrender', function(event) { const ctx = event.context; ctx.restore(); }); From 7831a591f7120c2b10926e0e09089e975bf70b2d Mon Sep 17 00:00:00 2001 From: Florent gravin Date: Fri, 16 Nov 2018 10:39:35 +0100 Subject: [PATCH 54/73] Fix igc example migrating map postcompose event --- examples/feature-animation.js | 2 +- examples/feature-move-animation.js | 2 +- examples/layer-spy.html | 2 +- examples/synthetic-points.js | 5 +++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/feature-animation.js b/examples/feature-animation.js index 9e61145499..e0bba2e586 100644 --- a/examples/feature-animation.js +++ b/examples/feature-animation.js @@ -72,7 +72,7 @@ function flash(feature) { unByKey(listenerKey); return; } - // tell OpenLayers to continue postcompose animation + // tell OpenLayers to continue postrender animation map.render(); } } diff --git a/examples/feature-move-animation.js b/examples/feature-move-animation.js index a5119d3e97..50a38c7f8f 100644 --- a/examples/feature-move-animation.js +++ b/examples/feature-move-animation.js @@ -161,7 +161,7 @@ const moveFeature = function(event) { const feature = new Feature(currentPoint); vectorContext.drawFeature(feature, styles.geoMarker); } - // tell OpenLayers to continue the postcompose animation + // tell OpenLayers to continue the postrender animation map.render(); }; diff --git a/examples/layer-spy.html b/examples/layer-spy.html index 952e52cc71..098bd87ad2 100644 --- a/examples/layer-spy.html +++ b/examples/layer-spy.html @@ -5,7 +5,7 @@ shortdesc: View a portion of one layer over another docs: >

Layer rendering can be manipulated in prerender and postrender event listeners. These listeners get an event with a reference to the Canvas rendering context. - In this example, the precompose listener sets a clipping mask around the most + In this example, the prerender listener sets a clipping mask around the most recent mouse position, giving you a spyglass effect for viewing one layer over another.

Move around the map to see the effect. Use the ↑ up and ↓ down arrow keys to adjust the spyglass size.

tags: "spy, image manipulation" diff --git a/examples/synthetic-points.js b/examples/synthetic-points.js index 5f04f1ba46..2fb7c5f3ef 100644 --- a/examples/synthetic-points.js +++ b/examples/synthetic-points.js @@ -5,6 +5,7 @@ import {LineString, Point} from '../src/ol/geom.js'; import VectorLayer from '../src/ol/layer/Vector.js'; import VectorSource from '../src/ol/source/Vector.js'; import {Circle as CircleStyle, Fill, Stroke, Style} from '../src/ol/style.js'; +import {getVectorContext} from '../src/ol/render'; const count = 20000; @@ -104,8 +105,8 @@ const style = new Style({ }) }); -map.on('postcompose', function(evt) { - const vectorContext = evt.vectorContext; +vector.on('postrender', function(evt) { + const vectorContext = getVectorContext(evt); vectorContext.setStyle(style); if (point !== null) { vectorContext.drawGeometry(point); From 90d46cb5398758ec57e3b465b824bc854334e866 Mon Sep 17 00:00:00 2001 From: Florent gravin Date: Fri, 16 Nov 2018 10:49:34 +0100 Subject: [PATCH 55/73] Remove blend-modes example As we are not doing canvas composition anymore --- examples/blend-modes.css | 4 - examples/blend-modes.html | 68 --------------- examples/blend-modes.js | 173 -------------------------------------- 3 files changed, 245 deletions(-) delete mode 100644 examples/blend-modes.css delete mode 100644 examples/blend-modes.html delete mode 100644 examples/blend-modes.js diff --git a/examples/blend-modes.css b/examples/blend-modes.css deleted file mode 100644 index 91102040cf..0000000000 --- a/examples/blend-modes.css +++ /dev/null @@ -1,4 +0,0 @@ -.map{ - background-repeat: repeat; - background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAN1wAADdcBQiibeAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAApSURBVBiVY7x///5/BjSgqKjIiC7GhC6ACwygQgxHMzAwMGDz4FDwDAD5/wevjSk4mwAAAABJRU5ErkJggg==); -} diff --git a/examples/blend-modes.html b/examples/blend-modes.html deleted file mode 100644 index 5a4009bf53..0000000000 --- a/examples/blend-modes.html +++ /dev/null @@ -1,68 +0,0 @@ ---- -layout: example.html -title: Blend Modes -shortdesc: Shows how to change the canvas compositing / blending mode in post- and precompose eventhandlers. -docs: > -

This example shows how to change the canvas compositing / blending mode in - post- and precompose event handlers. The Canvas 2D API provides the property - globalCompositeOperation with which one can influence which - composition operation will be used when drawing on the canvas. The various - options are well described on the MDN - documentation page.

- -

In this example three circles on the corners of an equilateral triangle are - drawn with red, green or blue styles respectively. By setting the - globalCompositeOperation you can change how these colors turn out - when they are combined on the map.

- -

You can select an operation in the select-field and you can also control - which layers will be affected by the chosen operation through the layer - checkboxes.

-tags: "blendmode, blend-mode, blend mode, blendingmode, blending-mode, blending mode, composition, compositing, canvas, vector" ---- -
-
- - - - -
diff --git a/examples/blend-modes.js b/examples/blend-modes.js deleted file mode 100644 index a5391ece72..0000000000 --- a/examples/blend-modes.js +++ /dev/null @@ -1,173 +0,0 @@ -import Feature from '../src/ol/Feature.js'; -import Map from '../src/ol/Map.js'; -import View from '../src/ol/View.js'; -import Point from '../src/ol/geom/Point.js'; -import VectorLayer from '../src/ol/layer/Vector.js'; -import VectorSource from '../src/ol/source/Vector.js'; -import {Circle as CircleStyle, Fill, Stroke, Style} from '../src/ol/style.js'; - - -// Create separate layers for red, green an blue circles. -// -// Every layer has one feature that is styled with a circle, together the -// features form the corners of an equilateral triangle and their styles overlap -const redLayer = new VectorLayer({ - source: new VectorSource({ - features: [new Feature(new Point([0, 0]))] - }), - style: new Style({ - image: new CircleStyle({ - fill: new Fill({ - color: 'rgba(255,0,0,0.8)' - }), - stroke: new Stroke({ - color: 'rgb(255,0,0)', - width: 15 - }), - radius: 120 - }) - }) -}); -const greenLayer = new VectorLayer({ - source: new VectorSource({ - // 433.013 is roughly 250 * Math.sqrt(3) - features: [new Feature(new Point([250, 433.013]))] - }), - style: new Style({ - image: new CircleStyle({ - fill: new Fill({ - color: 'rgba(0,255,0,0.8)' - }), - stroke: new Stroke({ - color: 'rgb(0,255,0)', - width: 15 - }), - radius: 120 - }) - }) -}); -const blueLayer = new VectorLayer({ - source: new VectorSource({ - features: [new Feature(new Point([500, 0]))] - }), - style: new Style({ - image: new CircleStyle({ - fill: new Fill({ - color: 'rgba(0,0,255,0.8)' - }), - stroke: new Stroke({ - color: 'rgb(0,0,255)', - width: 15 - }), - radius: 120 - }) - }) -}); - -// Create the map, the view is centered on the triangle. Zooming and panning is -// restricted to a sane area -const map = new Map({ - layers: [ - redLayer, - greenLayer, - blueLayer - ], - target: 'map', - view: new View({ - center: [250, 220], - extent: [0, 0, 500, 500], - resolution: 4, - minResolution: 2, - maxResolution: 32 - }) -}); - -// Get the form elements and bind the listeners -const select = document.getElementById('blend-mode'); -const affectRed = document.getElementById('affect-red'); -const affectGreen = document.getElementById('affect-green'); -const affectBlue = document.getElementById('affect-blue'); - - -/** - * This method sets the globalCompositeOperation to the value of the select - * field and it is bound to the precompose event of the layers. - * - * @param {module:ol/render/Event~RenderEvent} evt The render event. - */ -const setBlendModeFromSelect = function(evt) { - evt.context.globalCompositeOperation = select.value; -}; - - -/** - * This method resets the globalCompositeOperation to the default value of - * 'source-over' and it is bound to the postcompose event of the layers. - * - * @param {module:ol/render/Event~RenderEvent} evt The render event. - */ -const resetBlendModeFromSelect = function(evt) { - evt.context.globalCompositeOperation = 'source-over'; -}; - - -/** - * Bind the pre- and postcompose handlers to the passed layer. - * - * @param {module:ol/layer/Vector} layer The layer to bind the handlers to. - */ -const bindLayerListeners = function(layer) { - layer.on('precompose', setBlendModeFromSelect); - layer.on('postcompose', resetBlendModeFromSelect); -}; - - -/** - * Unind the pre- and postcompose handlers to the passed layers. - * - * @param {module:ol/layer/Vector} layer The layer to unbind the handlers from. - */ -const unbindLayerListeners = function(layer) { - layer.un('precompose', setBlendModeFromSelect); - layer.un('postcompose', resetBlendModeFromSelect); -}; - - -/** - * Handler for the click event of the 'affect-XXX' checkboxes. - * - * @this {HTMLInputElement} - */ -const affectLayerClicked = function() { - let layer; - if (this.id == 'affect-red') { - layer = redLayer; - } else if (this.id == 'affect-green') { - layer = greenLayer; - } else { - layer = blueLayer; - } - if (this.checked) { - bindLayerListeners(layer); - } else { - unbindLayerListeners(layer); - } - map.render(); -}; - - -// Rerender map when blend mode changes -select.addEventListener('change', function() { - map.render(); -}); - -// Unbind / bind listeners depending on the checked state when the checkboxes -// are clicked -affectRed.addEventListener('click', affectLayerClicked); -affectGreen.addEventListener('click', affectLayerClicked); -affectBlue.addEventListener('click', affectLayerClicked); - -// Initially bind listeners -bindLayerListeners(redLayer); -bindLayerListeners(greenLayer); -bindLayerListeners(blueLayer); From 4a3dbb0e240fe5b86097ae713ba8889535517e2e Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 16 Nov 2018 10:55:16 +0100 Subject: [PATCH 56/73] Use the main map in rendering tests --- rendering/cases/layer-tile-2layers-extentclip/main.js | 2 +- rendering/cases/layer-tile-simple/main.js | 2 +- rendering/cases/layer-tile-transition/main.js | 2 +- rendering/cases/layer-vectortile-simple/main.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rendering/cases/layer-tile-2layers-extentclip/main.js b/rendering/cases/layer-tile-2layers-extentclip/main.js index b4c4fe3260..8a472d94fe 100644 --- a/rendering/cases/layer-tile-2layers-extentclip/main.js +++ b/rendering/cases/layer-tile-2layers-extentclip/main.js @@ -1,4 +1,4 @@ -import Map from '../../../src/ol/CompositeMap.js'; +import Map from '../../../src/ol/Map.js'; import View from '../../../src/ol/View.js'; import TileLayer from '../../../src/ol/layer/Tile.js'; import {fromLonLat} from '../../../src/ol/proj'; diff --git a/rendering/cases/layer-tile-simple/main.js b/rendering/cases/layer-tile-simple/main.js index 365b572850..16144d3888 100644 --- a/rendering/cases/layer-tile-simple/main.js +++ b/rendering/cases/layer-tile-simple/main.js @@ -1,4 +1,4 @@ -import Map from '../../../src/ol/CompositeMap.js'; +import Map from '../../../src/ol/Map.js'; import View from '../../../src/ol/View.js'; import TileLayer from '../../../src/ol/layer/Tile.js'; import {fromLonLat} from '../../../src/ol/proj'; diff --git a/rendering/cases/layer-tile-transition/main.js b/rendering/cases/layer-tile-transition/main.js index 78282f1bb6..6bf60e35f0 100644 --- a/rendering/cases/layer-tile-transition/main.js +++ b/rendering/cases/layer-tile-transition/main.js @@ -1,4 +1,4 @@ -import Map from '../../../src/ol/CompositeMap.js'; +import Map from '../../../src/ol/Map.js'; import View from '../../../src/ol/View.js'; import TileLayer from '../../../src/ol/layer/Tile.js'; import {fromLonLat} from '../../../src/ol/proj'; diff --git a/rendering/cases/layer-vectortile-simple/main.js b/rendering/cases/layer-vectortile-simple/main.js index 4146d12424..baa2382917 100644 --- a/rendering/cases/layer-vectortile-simple/main.js +++ b/rendering/cases/layer-vectortile-simple/main.js @@ -1,4 +1,4 @@ -import Map from '../../../src/ol/CompositeMap.js'; +import Map from '../../../src/ol/Map.js'; import View from '../../../src/ol/View.js'; import VectorTileSource from '../../../src/ol/source/VectorTile'; import MVT from '../../../src/ol/format/MVT'; From b3bcf7dac12223d99fb3d627734165f6100d777f Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 16 Nov 2018 11:29:42 +0100 Subject: [PATCH 57/73] Add 2% tolerance to vector rendering tests --- rendering/cases/layer-vectortile-simple/main.js | 5 ++++- .../cases/linestring-style-rotation/main.js | 2 +- rendering/test.js | 16 +++++++++++----- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/rendering/cases/layer-vectortile-simple/main.js b/rendering/cases/layer-vectortile-simple/main.js index baa2382917..2a149087e3 100644 --- a/rendering/cases/layer-vectortile-simple/main.js +++ b/rendering/cases/layer-vectortile-simple/main.js @@ -23,4 +23,7 @@ new Map({ }) }); -render({message: 'Vector tile layer renders'}); +render({ + message: 'Vector tile layer renders', + tolerance: 0.02 +}); diff --git a/rendering/cases/linestring-style-rotation/main.js b/rendering/cases/linestring-style-rotation/main.js index 3a4031e89f..98a8f76c57 100644 --- a/rendering/cases/linestring-style-rotation/main.js +++ b/rendering/cases/linestring-style-rotation/main.js @@ -67,4 +67,4 @@ new Map({ }) }); -render(); +render({tolerance: 0.02}); diff --git a/rendering/test.js b/rendering/test.js index d267bdc99a..85d5660493 100755 --- a/rendering/test.js +++ b/rendering/test.js @@ -182,24 +182,30 @@ async function copyActualToExpected(entry) { async function renderEach(page, entries, options) { let fail = false; for (const entry of entries) { - const config = await renderPage(page, entry, options); - const message = config.message !== undefined ? config.message : entry; - const tolerance = config.tolerance !== undefined ? config.tolerance : 0; + const {tolerance = 0, message = ''} = await renderPage(page, entry, options); + if (options.fix) { await copyActualToExpected(entry); continue; } + const {error, mismatch} = await getScreenshotsMismatch(entry); if (error) { options.log.error(error); fail = true; continue; } + + let detail = `case ${entry}`; + if (message) { + detail = `${detail} (${message})`; + } + if (mismatch > tolerance) { - options.log.error(`checking '${message}': mismatch ${mismatch.toFixed(3)}`); + options.log.error(`${detail}': mismatch ${mismatch.toFixed(3)}`); fail = true; } else { - options.log.info(`checking '${message}': ok`); + options.log.info(`${detail}': ok`); await touch(getPassFilePath(entry)); } } From 73ffda10db762a8337b9a1ea994fdf2d4ad4f53f Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 16 Nov 2018 12:19:29 +0100 Subject: [PATCH 58/73] Smarter interim tile creation --- src/ol/VectorImageTile.js | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/ol/VectorImageTile.js b/src/ol/VectorImageTile.js index eaa36142c9..a8531a7dbe 100644 --- a/src/ol/VectorImageTile.js +++ b/src/ol/VectorImageTile.js @@ -6,7 +6,7 @@ import Tile from './Tile.js'; import TileState from './TileState.js'; import {createCanvasContext2D} from './dom.js'; import {listen, unlistenByKey} from './events.js'; -import {getHeight, getIntersection, getWidth} from './extent.js'; +import {containsExtent, getHeight, getIntersection, getWidth} from './extent.js'; import EventType from './events/EventType.js'; import {loadFeaturesXhr} from './featureloader.js'; import {VOID} from './functions.js'; @@ -103,6 +103,8 @@ class VectorImageTile extends Tile { */ this.sourceTileListenerKeys_ = []; + this.sourceTilesLoaded = false; + /** * Use only source tiles that are loaded already * @type {boolean} @@ -148,16 +150,24 @@ class VectorImageTile extends Tile { this.finishLoading_(); } - if (zoom <= tileCoord[0] && this.state != TileState.LOADED) { - while (zoom > tileGrid.getMinZoom()) { + if (!this.sourceTilesLoaded && !useLoadedOnly) { + let bestZoom = -1; + for (const key in sourceTiles) { + const sourceTile = sourceTiles[key]; + if (sourceTile.getState() === TileState.LOADED) { + const sourceTileCoord = sourceTile.tileCoord; + const sourceTileExtent = sourceTileGrid.getTileCoordExtent(sourceTileCoord); + if (containsExtent(sourceTileExtent, extent) && sourceTileCoord[0] > bestZoom) { + bestZoom = sourceTileCoord[0]; + } + } + } + if (bestZoom !== -1) { const tile = new VectorImageTile(tileCoord, state, sourceRevision, format, tileLoadFunction, urlTileCoord, tileUrlFunction, sourceTileGrid, tileGrid, sourceTiles, pixelRatio, projection, - tileClass, VOID, --zoom); - if (tile.state == TileState.LOADED) { - this.interimTile = tile; - break; - } + tileClass, VOID, bestZoom); + this.interimTile = tile; } } } @@ -168,6 +178,7 @@ class VectorImageTile extends Tile { * @inheritDoc */ disposeInternal() { + this.getInterimTile = super.getInterimTile; this.state = TileState.ABORT; this.changed(); if (this.interimTile) { @@ -312,6 +323,7 @@ class VectorImageTile extends Tile { if (loaded == this.tileKeys.length) { this.loadListenerKeys_.forEach(unlistenByKey); this.loadListenerKeys_.length = 0; + this.sourceTilesLoaded = true; this.setState(TileState.LOADED); } else { this.setState(empty == this.tileKeys.length ? TileState.EMPTY : TileState.ERROR); From 038f122d11602fe14f6505f75edb464b8d1cf177 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 16 Nov 2018 12:34:09 +0100 Subject: [PATCH 59/73] Clip tile layers by extent --- .../expected.png | Bin 39242 -> 0 bytes .../layer-tile-2layers-extentclip/main.js | 47 ------------------ .../cases/layer-tile-extent/expected.png | Bin 0 -> 7264 bytes rendering/cases/layer-tile-extent/main.js | 40 +++++++++++++++ src/ol/renderer/canvas/Layer.js | 33 ++++++++++++ src/ol/renderer/canvas/TileLayer.js | 12 +++-- 6 files changed, 82 insertions(+), 50 deletions(-) delete mode 100644 rendering/cases/layer-tile-2layers-extentclip/expected.png delete mode 100644 rendering/cases/layer-tile-2layers-extentclip/main.js create mode 100644 rendering/cases/layer-tile-extent/expected.png create mode 100644 rendering/cases/layer-tile-extent/main.js diff --git a/rendering/cases/layer-tile-2layers-extentclip/expected.png b/rendering/cases/layer-tile-2layers-extentclip/expected.png deleted file mode 100644 index e1e007a8a1550d630bef282e02da2cef04728a08..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39242 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

Gwt+=9kJ@SGyT#_0hNE@lqX7{U*$H%~v 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 literal 0 HcmV?d00001 diff --git a/rendering/cases/layer-tile-extent/main.js b/rendering/cases/layer-tile-extent/main.js new file mode 100644 index 0000000000..a678217834 --- /dev/null +++ b/rendering/cases/layer-tile-extent/main.js @@ -0,0 +1,40 @@ +/** + * Tile layers get clipped to their extent. + */ + +import Map from '../../../src/ol/Map.js'; +import View from '../../../src/ol/View.js'; +import TileLayer from '../../../src/ol/layer/Tile.js'; +import {fromLonLat} from '../../../src/ol/proj'; +import {transformExtent} from '../../../src/ol/proj.js'; +import XYZ from '../../../src/ol/source/XYZ'; + +const center = fromLonLat([7, 50]); +const extent = transformExtent([2, 47, 10, 53], 'EPSG:4326', 'EPSG:3857'); + +new Map({ + target: 'map', + view: new View({ + center: center, + zoom: 3 + }), + layers: [ + new TileLayer({ + source: new XYZ({ + url: '/data/tiles/satellite/{z}/{x}/{y}.jpg', + maxZoom: 3 + }), + extent: extent + }), + new TileLayer({ + source: new XYZ({ + url: '/data/tiles/stamen-labels/{z}/{x}/{y}.png', + minZoom: 3, + maxZoom: 5 + }), + extent: extent + }) + ] +}); + +render(); diff --git a/src/ol/renderer/canvas/Layer.js b/src/ol/renderer/canvas/Layer.js index 63b3c475ee..212ee2d995 100644 --- a/src/ol/renderer/canvas/Layer.js +++ b/src/ol/renderer/canvas/Layer.js @@ -85,6 +85,39 @@ class CanvasLayerRenderer extends LayerRenderer { rotateAtOffset(context, rotation, halfWidth, halfHeight); } + /** + * @param {CanvasRenderingContext2D} context Context. + * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. + * @param {import("../../extent.js").Extent} extent Clip extent. + * @protected + */ + clipUnrotated(context, frameState, extent) { + const topLeft = getTopLeft(extent); + const topRight = getTopRight(extent); + const bottomRight = getBottomRight(extent); + const bottomLeft = getBottomLeft(extent); + + applyTransform(frameState.coordinateToPixelTransform, topLeft); + applyTransform(frameState.coordinateToPixelTransform, topRight); + applyTransform(frameState.coordinateToPixelTransform, bottomRight); + applyTransform(frameState.coordinateToPixelTransform, bottomLeft); + + const inverted = invertTransform(this.pixelTransform_.slice()); + + applyTransform(inverted, topLeft); + applyTransform(inverted, topRight); + applyTransform(inverted, bottomRight); + applyTransform(inverted, bottomLeft); + + context.save(); + context.beginPath(); + context.moveTo(Math.round(topLeft[0]), Math.round(topLeft[1])); + context.lineTo(Math.round(topRight[0]), Math.round(topRight[1])); + context.lineTo(Math.round(bottomRight[0]), Math.round(bottomRight[1])); + context.lineTo(Math.round(bottomLeft[0]), Math.round(bottomLeft[1])); + context.clip(); + } + /** * @param {import("../../render/EventType.js").default} type Event type. * @param {CanvasRenderingContext2D} context Context. diff --git a/src/ol/renderer/canvas/TileLayer.js b/src/ol/renderer/canvas/TileLayer.js index cc52886763..ca151222d3 100644 --- a/src/ol/renderer/canvas/TileLayer.js +++ b/src/ol/renderer/canvas/TileLayer.js @@ -143,12 +143,10 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { const tileResolution = tileGrid.getResolution(z); let extent = frameState.extent; - if (layerState.extent !== undefined) { + if (layerState.extent) { extent = getIntersection(extent, layerState.extent); } - // TODO: clip by layer extent - const tilePixelRatio = tileSource.getTilePixelRatio(pixelRatio); // desired dimensions of the canvas in pixels @@ -235,6 +233,10 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { context.clearRect(0, 0, width, height); } + if (layerState.extent) { + this.clipUnrotated(context, frameState, layerState.extent); + } + this.preRender(context, frameState, pixelTransform); this.renderedTiles.length = 0; @@ -285,6 +287,10 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { this.postRender(context, frameState, pixelTransform); + if (layerState.extent) { + context.restore(); + } + const opacity = layerState.opacity; if (opacity !== canvas.style.opacity) { canvas.style.opacity = opacity; From 98a780e5f974d0e8cc58f55b8a9a2b86f62a8dc2 Mon Sep 17 00:00:00 2001 From: Frederic Junod Date: Fri, 16 Nov 2018 11:57:47 +0100 Subject: [PATCH 60/73] Add missing '.js' in import --- examples/dynamic-data.js | 2 +- examples/feature-animation.js | 2 +- examples/flight-animation.js | 2 +- examples/igc.js | 2 +- examples/synthetic-points.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/dynamic-data.js b/examples/dynamic-data.js index 8ba33533a7..e65a0d21f7 100644 --- a/examples/dynamic-data.js +++ b/examples/dynamic-data.js @@ -4,7 +4,7 @@ import {MultiPoint, Point} from '../src/ol/geom.js'; import TileLayer from '../src/ol/layer/Tile.js'; import OSM from '../src/ol/source/OSM.js'; import {Circle as CircleStyle, Fill, Stroke, Style} from '../src/ol/style.js'; -import {getVectorContext} from '../src/ol/render'; +import {getVectorContext} from '../src/ol/render.js'; const tileLayer = new TileLayer({ source: new OSM() diff --git a/examples/feature-animation.js b/examples/feature-animation.js index e0bba2e586..bd15602f7a 100644 --- a/examples/feature-animation.js +++ b/examples/feature-animation.js @@ -8,7 +8,7 @@ import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js'; import {fromLonLat} from '../src/ol/proj.js'; import {OSM, Vector as VectorSource} from '../src/ol/source.js'; import {Circle as CircleStyle, Stroke, Style} from '../src/ol/style.js'; -import {getVectorContext} from '../src/ol/render'; +import {getVectorContext} from '../src/ol/render.js'; const tileLayer = new TileLayer({ source: new OSM({ diff --git a/examples/flight-animation.js b/examples/flight-animation.js index c0b549267e..9445a8d14b 100644 --- a/examples/flight-animation.js +++ b/examples/flight-animation.js @@ -6,7 +6,7 @@ import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js'; import Stamen from '../src/ol/source/Stamen.js'; import VectorSource from '../src/ol/source/Vector.js'; import {Stroke, Style} from '../src/ol/style.js'; -import {getVectorContext} from '../src/ol/render'; +import {getVectorContext} from '../src/ol/render.js'; const tileLayer = new TileLayer({ source: new Stamen({ diff --git a/examples/igc.js b/examples/igc.js index b7cf70cbda..5dc8cd6ace 100644 --- a/examples/igc.js +++ b/examples/igc.js @@ -7,7 +7,7 @@ import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js'; import OSM, {ATTRIBUTION} from '../src/ol/source/OSM.js'; import VectorSource from '../src/ol/source/Vector.js'; import {Circle as CircleStyle, Fill, Stroke, Style} from '../src/ol/style.js'; -import {getVectorContext} from '../src/ol/render'; +import {getVectorContext} from '../src/ol/render.js'; const colors = { diff --git a/examples/synthetic-points.js b/examples/synthetic-points.js index 2fb7c5f3ef..1eceb86fdf 100644 --- a/examples/synthetic-points.js +++ b/examples/synthetic-points.js @@ -5,7 +5,7 @@ import {LineString, Point} from '../src/ol/geom.js'; import VectorLayer from '../src/ol/layer/Vector.js'; import VectorSource from '../src/ol/source/Vector.js'; import {Circle as CircleStyle, Fill, Stroke, Style} from '../src/ol/style.js'; -import {getVectorContext} from '../src/ol/render'; +import {getVectorContext} from '../src/ol/render.js'; const count = 20000; From 47ecd508faf5c490f1a7685765fc509682f8aaae Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 16 Nov 2018 12:43:24 +0100 Subject: [PATCH 61/73] Test hybrid mode in vt constructor --- test/spec/ol/layer/vectortile.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/spec/ol/layer/vectortile.test.js b/test/spec/ol/layer/vectortile.test.js index 1d1471dcfa..60e9332a53 100644 --- a/test/spec/ol/layer/vectortile.test.js +++ b/test/spec/ol/layer/vectortile.test.js @@ -39,10 +39,10 @@ describe('ol.layer.VectorTile', function() { describe('constructor (options)', function() { it('works with options', function() { let layer = new VectorTileLayer({ - renderMode: 'vector', + renderMode: 'hybrid', source: new VectorTileSource({}) }); - expect(layer.getRenderMode()).to.be('vector'); + expect(layer.getRenderMode()).to.be('hybrid'); layer = new VectorTileLayer({ renderMode: 'image', source: new VectorTileSource({}) From 3ecc6d60d2097700f6fa2a249eb94af20c589214 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 16 Nov 2018 12:47:14 +0100 Subject: [PATCH 62/73] The forEachLayerAtPixel method calls getDataAtPixel for each layer renderer --- test/spec/ol/map.test.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/spec/ol/map.test.js b/test/spec/ol/map.test.js index 7a228cc906..8d145682b7 100644 --- a/test/spec/ol/map.test.js +++ b/test/spec/ol/map.test.js @@ -325,9 +325,9 @@ describe('ol.Map', function() { beforeEach(function(done) { log = []; - original = TileLayerRenderer.prototype.forEachLayerAtCoordinate; - TileLayerRenderer.prototype.forEachLayerAtCoordinate = function(coordinate) { - log.push(coordinate.slice()); + original = TileLayerRenderer.prototype.getDataAtPixel; + TileLayerRenderer.prototype.getDataAtPixel = function(pixel) { + log.push(pixel.slice()); }; target = document.createElement('div'); @@ -364,13 +364,13 @@ describe('ol.Map', function() { }); afterEach(function() { - TileLayerRenderer.prototype.forEachLayerAtCoordinate = original; + TileLayerRenderer.prototype.getDataAtPixel = original; map.dispose(); document.body.removeChild(target); log = null; }); - it('calls each layer renderer with the same coordinate', function() { + it('calls each layer renderer with the same pixel', function() { const pixel = [10, 20]; map.forEachLayerAtPixel(pixel, function() {}); expect(log.length).to.equal(3); From 96437e3875e47a917c86a09871c2f07df8bebd13 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 16 Nov 2018 12:49:04 +0100 Subject: [PATCH 63/73] Test prerender and postrender events for a vector layer --- .../spec/ol/renderer/canvas/imagelayer.test.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/spec/ol/renderer/canvas/imagelayer.test.js b/test/spec/ol/renderer/canvas/imagelayer.test.js index 3e7ca3a1c8..507f9ad43c 100644 --- a/test/spec/ol/renderer/canvas/imagelayer.test.js +++ b/test/spec/ol/renderer/canvas/imagelayer.test.js @@ -95,18 +95,18 @@ describe('ol.renderer.canvas.ImageLayer', function() { map.dispose(); }); - it('dispatches precompose and postcompose events on the vector layer', function(done) { - let precompose = 0; - let postcompose = 0; - layer.on('precompose', function() { - ++precompose; + it('dispatches prerender and postrender events on the vector layer', function(done) { + let prerender = 0; + let postrender = 0; + layer.on('prerender', function() { + ++prerender; }); - layer.on('postcompose', function() { - ++postcompose; + layer.on('postrender', function() { + ++postrender; }); map.once('postrender', function() { - expect(precompose).to.be(1); - expect(postcompose).to.be(1); + expect(prerender).to.be(1); + expect(postrender).to.be(1); done(); }); }); From 9eb58088448957e716882381143f059ef693ecb2 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 16 Nov 2018 12:54:28 +0100 Subject: [PATCH 64/73] Test that postrender is dispatched (again) --- test/spec/ol/renderer/canvas/vectorlayer.test.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/spec/ol/renderer/canvas/vectorlayer.test.js b/test/spec/ol/renderer/canvas/vectorlayer.test.js index f8fa271f56..d540eb781d 100644 --- a/test/spec/ol/renderer/canvas/vectorlayer.test.js +++ b/test/spec/ol/renderer/canvas/vectorlayer.test.js @@ -296,22 +296,22 @@ describe('ol.renderer.canvas.VectorLayer', function() { expect(renderer.replayGroupChanged).to.be(false); }); - it('dispatches a render event when rendering to own context', function(done) { + it('dispatches a postrender event when rendering', function(done) { const layer = renderer.getLayer(); layer.getSource().addFeature(new Feature(new Point([0, 0]))); - layer.once('render', function() { + layer.once('postrender', function() { expect(true); done(); }); frameState.extent = [-10000, -10000, 10000, 10000]; frameState.size = [100, 100]; frameState.viewState.center = [0, 0]; - let composed = false; + let rendered = false; if (renderer.prepareFrame(frameState, {})) { - composed = true; - renderer.compose(renderer.context, frameState, layer.getLayerState); + rendered = true; + renderer.renderFrame(frameState, layer.getLayerState()); } - expect(composed).to.be(true); + expect(rendered).to.be(true); }); }); From a490c658fb37eab79359000925f3fa379cb24814 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 16 Nov 2018 13:34:13 +0100 Subject: [PATCH 65/73] Overscale canvas if sources have non-zero min zoom --- src/ol/renderer/canvas/TileLayer.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/ol/renderer/canvas/TileLayer.js b/src/ol/renderer/canvas/TileLayer.js index ca151222d3..3c8c56a8a2 100644 --- a/src/ol/renderer/canvas/TileLayer.js +++ b/src/ol/renderer/canvas/TileLayer.js @@ -153,9 +153,15 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { let width = Math.round(frameState.size[0] * tilePixelRatio); let height = Math.round(frameState.size[1] * tilePixelRatio); if (tileResolution < viewResolution) { - // scale canvas so it covers the viewport until new tiles come it - width *= 1.5; - height *= 1.5; + // scale canvas so it covers the viewport until new tiles come in + let scale; + if (z <= tileGrid.minZoom) { + scale = Math.round(viewResolution / tileResolution); + } else { + scale = 1.5; // rely on lower z tiles + } + width *= scale; + height *= scale; } if (rotation) { From d6add33df095a24beff4085faaeb4a03fff84e0a Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 16 Nov 2018 14:49:03 +0100 Subject: [PATCH 66/73] Use map and new method --- examples/d3.js | 2 +- examples/heatmap-earthquakes.js | 2 +- src/ol/renderer/canvas/VectorTileLayer.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/d3.js b/examples/d3.js index 8294539a76..8a9af6e19d 100644 --- a/examples/d3.js +++ b/examples/d3.js @@ -1,4 +1,4 @@ -import Map from '../src/ol/CompositeMap.js'; +import Map from '../src/ol/Map.js'; import View from '../src/ol/View.js'; import {getWidth, getCenter} from '../src/ol/extent.js'; import {Layer, Tile as TileLayer} from '../src/ol/layer.js'; diff --git a/examples/heatmap-earthquakes.js b/examples/heatmap-earthquakes.js index 6ee1beab8f..953f596946 100644 --- a/examples/heatmap-earthquakes.js +++ b/examples/heatmap-earthquakes.js @@ -1,4 +1,4 @@ -import Map from '../src/ol/CompositeMap.js'; +import Map from '../src/ol/Map.js'; import View from '../src/ol/View.js'; import KML from '../src/ol/format/KML.js'; import {Heatmap as HeatmapLayer, Tile as TileLayer} from '../src/ol/layer.js'; diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index c066a522f0..8d58fbafec 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -143,7 +143,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { getTile(z, x, y, pixelRatio, projection) { const tile = super.getTile(z, x, y, pixelRatio, projection); if (tile.getState() === TileState.LOADED) { - this.createReplayGroup_(/** @type {import("../../VectorImageTile.js").default} */ (tile), pixelRatio, projection); + this.createExecutorGroup_(/** @type {import("../../VectorImageTile.js").default} */ (tile), pixelRatio, projection); if (this.context) { this.renderTileImage_(/** @type {import("../../VectorImageTile.js").default} */ (tile), pixelRatio, projection); } From edbe2316efe32767c1b6ffb78fcba1ee5567a6e4 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 16 Nov 2018 15:01:00 +0100 Subject: [PATCH 67/73] Lazily create interim tiles (fixes most tests) --- src/ol/VectorImageTile.js | 47 +++++++++++-------- .../renderer/canvas/vectortilelayer.test.js | 7 +-- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/src/ol/VectorImageTile.js b/src/ol/VectorImageTile.js index a8531a7dbe..daacded3bb 100644 --- a/src/ol/VectorImageTile.js +++ b/src/ol/VectorImageTile.js @@ -103,8 +103,6 @@ class VectorImageTile extends Tile { */ this.sourceTileListenerKeys_ = []; - this.sourceTilesLoaded = false; - /** * Use only source tiles that are loaded already * @type {boolean} @@ -150,35 +148,44 @@ class VectorImageTile extends Tile { this.finishLoading_(); } - if (!this.sourceTilesLoaded && !useLoadedOnly) { - let bestZoom = -1; - for (const key in sourceTiles) { - const sourceTile = sourceTiles[key]; - if (sourceTile.getState() === TileState.LOADED) { - const sourceTileCoord = sourceTile.tileCoord; - const sourceTileExtent = sourceTileGrid.getTileCoordExtent(sourceTileCoord); - if (containsExtent(sourceTileExtent, extent) && sourceTileCoord[0] > bestZoom) { - bestZoom = sourceTileCoord[0]; + this.createInterimTile_ = function() { + if (this.getState() !== TileState.LOADED && !useLoadedOnly) { + let bestZoom = -1; + for (const key in sourceTiles) { + const sourceTile = sourceTiles[key]; + if (sourceTile.getState() === TileState.LOADED) { + const sourceTileCoord = sourceTile.tileCoord; + const sourceTileExtent = sourceTileGrid.getTileCoordExtent(sourceTileCoord); + if (containsExtent(sourceTileExtent, extent) && sourceTileCoord[0] > bestZoom) { + bestZoom = sourceTileCoord[0]; + } } } - } - if (bestZoom !== -1) { - const tile = new VectorImageTile(tileCoord, state, sourceRevision, - format, tileLoadFunction, urlTileCoord, tileUrlFunction, - sourceTileGrid, tileGrid, sourceTiles, pixelRatio, projection, - tileClass, VOID, bestZoom); - this.interimTile = tile; - } + if (bestZoom !== -1) { + const tile = new VectorImageTile(tileCoord, state, sourceRevision, + format, tileLoadFunction, urlTileCoord, tileUrlFunction, + sourceTileGrid, tileGrid, sourceTiles, pixelRatio, projection, + tileClass, VOID, bestZoom); + this.interimTile = tile; + } + } } } } + getInterimTile() { + if (!this.interimTile) { + this.createInterimTile_(); + } + return super.getInterimTile(); + } + /** * @inheritDoc */ disposeInternal() { - this.getInterimTile = super.getInterimTile; + delete this.createInterimTile_; this.state = TileState.ABORT; this.changed(); if (this.interimTile) { diff --git a/test/spec/ol/renderer/canvas/vectortilelayer.test.js b/test/spec/ol/renderer/canvas/vectortilelayer.test.js index 96f5ab22f6..0e8793850f 100644 --- a/test/spec/ol/renderer/canvas/vectortilelayer.test.js +++ b/test/spec/ol/renderer/canvas/vectortilelayer.test.js @@ -75,6 +75,7 @@ describe('ol.renderer.canvas.VectorTileLayer', function() { tileGrid: createXYZ() }); source.getTile = function() { + arguments[1] = TileState.LOADED; const tile = VectorTileSource.prototype.getTile.apply(source, arguments); tile.setState(TileState.LOADED); return tile; @@ -106,7 +107,7 @@ describe('ol.renderer.canvas.VectorTileLayer', function() { map.removeLayer(layer); map.addLayer(testLayer); const spy = sinon.spy(CanvasVectorTileLayerRenderer.prototype, - 'getTransform'); + 'getRenderTransform'); map.renderSync(); expect(spy.callCount).to.be(0); spy.restore(); @@ -114,7 +115,7 @@ describe('ol.renderer.canvas.VectorTileLayer', function() { it('renders both replays and images for hybrid rendering', function() { const spy1 = sinon.spy(CanvasVectorTileLayerRenderer.prototype, - 'getTransform'); + 'getRenderTransform'); const spy2 = sinon.spy(CanvasVectorTileLayerRenderer.prototype, 'renderTileImage_'); map.renderSync(); @@ -129,7 +130,7 @@ describe('ol.renderer.canvas.VectorTileLayer', function() { renderer: function() {} })); const spy = sinon.spy(CanvasVectorTileLayerRenderer.prototype, - 'getTransform'); + 'getRenderTransform'); map.renderSync(); expect(spy.callCount).to.be(1); spy.restore(); From 6cfb8f275be46aae9628fe285364698baa3b6d31 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 16 Nov 2018 16:16:07 +0100 Subject: [PATCH 68/73] Fix magnify example --- examples/magnify.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/examples/magnify.js b/examples/magnify.js index de64115baf..9193b2f1d2 100644 --- a/examples/magnify.js +++ b/examples/magnify.js @@ -3,6 +3,7 @@ import View from '../src/ol/View.js'; import TileLayer from '../src/ol/layer/Tile.js'; import {fromLonLat} from '../src/ol/proj.js'; import BingMaps from '../src/ol/source/BingMaps.js'; +import {getPixelFromPixel} from '../src/ol/render.js'; const key = 'As1HiMj1PvLPlqc_gtM7AqZfBL8ZL3VrjaS3zIb22Uvb9WKhuJObROC-qUpa81U5'; @@ -50,14 +51,15 @@ container.addEventListener('mouseout', function() { // after rendering the layer, show an oversampled version around the pointer imagery.on('postrender', function(event) { if (mousePosition) { + const pixel = getPixelFromPixel(event, mousePosition); + const offset = getPixelFromPixel(event, [mousePosition[0] + radius, mousePosition[1]]); + const half = Math.sqrt(Math.pow(offset[0] - pixel[0], 2) + Math.pow(offset[1] - pixel[1], 2)); const context = event.context; - const pixelRatio = event.frameState.pixelRatio; - const half = radius * pixelRatio; - const centerX = mousePosition[0] * pixelRatio; - const centerY = mousePosition[1] * pixelRatio; + const centerX = pixel[0]; + const centerY = pixel[1]; const originX = centerX - half; const originY = centerY - half; - const size = 2 * half + 1; + const size = Math.round(2 * half + 1); const sourceData = context.getImageData(originX, originY, size, size).data; const dest = context.createImageData(size, size); const destData = dest.data; @@ -82,7 +84,7 @@ imagery.on('postrender', function(event) { } context.beginPath(); context.arc(centerX, centerY, half, 0, 2 * Math.PI); - context.lineWidth = 3 * pixelRatio; + context.lineWidth = 3 * half / radius; context.strokeStyle = 'rgba(255,255,255,0.5)'; context.putImageData(dest, originX, originY); context.stroke(); From 5aa8db15f4e32d19720ea94a84168c07be6c9831 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 16 Nov 2018 16:25:39 +0100 Subject: [PATCH 69/73] Lint --- src/ol/VectorImageTile.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ol/VectorImageTile.js b/src/ol/VectorImageTile.js index daacded3bb..ce1459f3bc 100644 --- a/src/ol/VectorImageTile.js +++ b/src/ol/VectorImageTile.js @@ -168,8 +168,8 @@ class VectorImageTile extends Tile { tileClass, VOID, bestZoom); this.interimTile = tile; } - } - } + } + }; } } From 95c16cfa1186d3d5de21f46ae25afec9f785963e Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 16 Nov 2018 17:05:44 +0100 Subject: [PATCH 70/73] Fix vector tile renderer test --- test/spec/ol/renderer/canvas/vectortilelayer.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/spec/ol/renderer/canvas/vectortilelayer.test.js b/test/spec/ol/renderer/canvas/vectortilelayer.test.js index 0e8793850f..fb1e081fa4 100644 --- a/test/spec/ol/renderer/canvas/vectortilelayer.test.js +++ b/test/spec/ol/renderer/canvas/vectortilelayer.test.js @@ -243,7 +243,7 @@ describe('ol.renderer.canvas.VectorTileLayer', function() { return document.createElement('canvas'); }; const tile = new VectorImageTile([0, 0, 0], undefined, undefined, undefined, - undefined, undefined, undefined, undefined, undefined, undefined, undefined, + undefined, [0, 0, 0], undefined, createXYZ(), createXYZ(), undefined, undefined, undefined, undefined, undefined, 0); tile.transition_ = 0; tile.wrappedTileCoord = [0, 0, 0]; @@ -270,13 +270,13 @@ describe('ol.renderer.canvas.VectorTileLayer', function() { usedTiles: {}, wantedTiles: {} }; - renderer.prepareFrame(frameState, {}); + renderer.renderFrame(frameState, {}); const replayState = renderer.renderedTiles[0].getReplayState(layer); const revision = replayState.renderedTileRevision; - renderer.prepareFrame(frameState, {}); + renderer.renderFrame(frameState, {}); expect(replayState.renderedTileRevision).to.be(revision); layer.changed(); - renderer.prepareFrame(frameState, {}); + renderer.renderFrame(frameState, {}); expect(replayState.renderedTileRevision).to.be(revision + 1); }); }); From 358d86c33e3f3500fcf630417d12c75d638e196f Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 16 Nov 2018 18:09:24 +0100 Subject: [PATCH 71/73] Raster source listens for layer change --- src/ol/source/Raster.js | 69 +++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/src/ol/source/Raster.js b/src/ol/source/Raster.js index 3072182ae1..6f3cbe229c 100644 --- a/src/ol/source/Raster.js +++ b/src/ol/source/Raster.js @@ -169,26 +169,23 @@ class RasterSource extends ImageSource { /** * @private - * @type {Array} + * @type {Array} */ - this.renderers_ = createRenderers(options.sources); + this.layers_ = createLayers(options.sources); - for (let r = 0, rr = this.renderers_.length; r < rr; ++r) { - listen(this.renderers_[r], EventType.CHANGE, - this.changed, this); + for (let i = 0, ii = this.layers_.length; i < ii; ++i) { + listen(this.layers_[i], EventType.CHANGE, this.changed, this); } /** * @private * @type {import("../TileQueue.js").default} */ - this.tileQueue_ = new TileQueue( - function() { - return 1; - }, - this.changed.bind(this)); + this.tileQueue_ = new TileQueue(function() { + return 1; + }, this.changed.bind(this)); - const layerStatesArray = getLayerStatesArray(this.renderers_); + const layerStatesArray = getLayerStatesArray(this.layers_); /** * @type {Object} @@ -307,8 +304,8 @@ class RasterSource extends ImageSource { allSourcesReady_() { let ready = true; let source; - for (let i = 0, ii = this.renderers_.length; i < ii; ++i) { - source = this.renderers_[i].getLayer().getSource(); + for (let i = 0, ii = this.layers_.length; i < ii; ++i) { + source = this.layers_[i].getSource(); if (source.getState() !== SourceState.READY) { ready = false; break; @@ -356,11 +353,10 @@ class RasterSource extends ImageSource { */ processSources_() { const frameState = this.requestedFrameState_; - const len = this.renderers_.length; + const len = this.layers_.length; const imageDatas = new Array(len); for (let i = 0; i < len; ++i) { - const imageData = getImageData( - this.renderers_[i], frameState, frameState.layerStatesArray[i]); + const imageData = getImageData(this.layers_[i], frameState, frameState.layerStatesArray[i]); if (imageData) { imageDatas[i] = imageData; } else { @@ -429,13 +425,18 @@ let sharedContext = null; /** - * Get image data from a renderer. - * @param {import("../renderer/canvas/Layer.js").default} renderer Layer renderer. + * Get image data from a layer. + * @param {import("../layer/Layer.js").default} layer Layer to render. * @param {import("../PluggableMap.js").FrameState} frameState The frame state. * @param {import("../layer/Layer.js").State} layerState The layer state. * @return {ImageData} The image data. */ -function getImageData(renderer, frameState, layerState) { +function getImageData(layer, frameState, layerState) { + const renderer = layer.getRenderer(); + if (!renderer) { + throw new Error('Unsupported layer type: ' + layer); + } + if (!renderer.prepareFrame(frameState, layerState)) { return null; } @@ -466,38 +467,38 @@ function getImageData(renderer, frameState, layerState) { /** - * Get a list of layer states from a list of renderers. - * @param {Array} renderers Layer renderers. + * Get a list of layer states from a list of layers. + * @param {Array} layers Layers. * @return {Array} The layer states. */ -function getLayerStatesArray(renderers) { - return renderers.map(function(renderer) { - return renderer.getLayer().getLayerState(); +function getLayerStatesArray(layers) { + return layers.map(function(layer) { + return layer.getLayerState(); }); } /** - * Create renderers for all sources. + * Create layers for all sources. * @param {Array} sources The sources. - * @return {Array} Array of layer renderers. + * @return {Array} Array of layers. */ -function createRenderers(sources) { +function createLayers(sources) { const len = sources.length; - const renderers = new Array(len); + const layers = new Array(len); for (let i = 0; i < len; ++i) { - renderers[i] = createRenderer(sources[i]); + layers[i] = createLayer(sources[i]); } - return renderers; + return layers; } /** - * Create a renderer for the provided source. + * Create a layer for the provided source. * @param {import("./Source.js").default|import("../layer/Layer.js").default} layerOrSource The layer or source. - * @return {import("../renderer/canvas/Layer.js").default} The renderer. + * @return {import("../layer/Layer.js").default} The layer. */ -function createRenderer(layerOrSource) { +function createLayer(layerOrSource) { // @type {import("../layer/Layer.js").default} let layer; if (layerOrSource instanceof Source) { @@ -509,7 +510,7 @@ function createRenderer(layerOrSource) { } else { layer = layerOrSource; } - return layer ? /** @type {import("../renderer/canvas/Layer.js").default} */ (layer.createRenderer()) : null; + return layer; } From 6c4845a3043e4a8e520696763c433aa4c00a0138 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 16 Nov 2018 18:30:47 +0100 Subject: [PATCH 72/73] Upgrade notes --- changelog/upgrade-notes.md | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/changelog/upgrade-notes.md b/changelog/upgrade-notes.md index 65d5662a11..769ef39c51 100644 --- a/changelog/upgrade-notes.md +++ b/changelog/upgrade-notes.md @@ -2,15 +2,42 @@ ### Next version +#### Backwards incompatible changes -Breaking change: The `OverviewMap` control now cannot be instantiated without a list of layers. +##### New `prerender` and `postrender` layer events replace old `precompose` and `postcompose` events -Breaking change: layers can no longer be shared between several `Map` objects. +If you were previously registering for `precompose` and `postcompose` events, you should now register for `prerender` and `postrender` events on layers. Layers are no longer composed to a single Canvas element. Instead, they are added to the map viewport as individual elements. -Breaking change: the `Graticule` control has been replaced by a layer also called `Graticule`, found in `ol/layer/Graticule`. -The API remains similar. +##### New `getVectorContext` function provides access to the immediate vector rendering API -#### Breaking change: drop of support for most of WebGL features +Previously, render events included a `vectorContext` property that allowed you to render features or geometries directly to the map. This is still possible, but you now have to explicitly create a vector context with the `getVectorContext` function. This change makes the immediate rendering API an explicit dependency if your application uses it. If you don't use this API, your application bundle will not include the vector rendering modules (as it did before). + +Here is an abbreviated example of how to use the `getVectorContext` function: + +```js +import {getVectorContext} from 'ol/render'; + +// construct your map and layers as usual + +layer.on('postrender', function(event) { + const vectorContext = getVectorContext(event); + // use any of the drawing methods on the vector context +}); +``` + +##### Layers can only be added to a single map + +Previously, it was possible to render a single layer in two maps. Now, each layer can only belong to a single map (in the same way that a single DOM element can only have one parent). + +##### The `OverviewMap` requires a list of layers. + +Due to the constraint above (layers can only be added to a single map), the overview map needs to be constructed with a list of layers. + +##### The `ol/Graticule` has been replaced by `ol/layer/Graticule` + +Previously, a graticule was not a layer. Now it is. See the graticule example for details on how to add a graticule layer to your map. + +##### Drop of support for the experimental WebGL renderer The WebGL map and layers renderers are gone, replaced by a `WebGLHelper` function that provides a lightweight, low-level access to the WebGL API. This is implemented in a new `WebGLPointsLayer` which does simple rendering of large number @@ -31,7 +58,6 @@ The removed classes and components are: * The shader build process using `mustache` and the `Makefile` at the root - ### v5.3.0 #### The `getUid` function returns string From c004e9d64499962cd8643df99db55b9517722a5e Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 16 Nov 2018 18:38:51 +0100 Subject: [PATCH 73/73] More upgrade notes --- changelog/upgrade-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/changelog/upgrade-notes.md b/changelog/upgrade-notes.md index 769ef39c51..03503da390 100644 --- a/changelog/upgrade-notes.md +++ b/changelog/upgrade-notes.md @@ -4,6 +4,10 @@ #### Backwards incompatible changes +##### Removal of the "vector" render mode for vector tile layers + +If you were previously using `VectorTile` layers with `renderMode: 'vector'`, you have to remove this configuration option. That mode was removed. `'hybrid'` (default) and `'image'` are still available. + ##### New `prerender` and `postrender` layer events replace old `precompose` and `postcompose` events If you were previously registering for `precompose` and `postcompose` events, you should now register for `prerender` and `postrender` events on layers. Layers are no longer composed to a single Canvas element. Instead, they are added to the map viewport as individual elements.