diff --git a/examples/drag-and-drop-image-vector.html b/examples/drag-and-drop-image-vector.html index 2635bb9e1a..f6f791278f 100644 --- a/examples/drag-and-drop-image-vector.html +++ b/examples/drag-and-drop-image-vector.html @@ -3,7 +3,7 @@ layout: example.html title: Drag-and-Drop Image Vector shortdesc: Example of using the drag-and-drop interaction with image vector rendering. docs: > - Example of using the drag-and-drop interaction with an `ol/layer/Vector` with `renderMode: 'image'`. Drag and drop GPX, GeoJSON, IGC, KML, or TopoJSON files on to the map. Each file is rendered to an image on the client. + Example of using the drag-and-drop interaction with an `ol/layer/VectorImage` layer. Drag and drop GPX, GeoJSON, IGC, KML, or TopoJSON files on to the map. Each file is rendered to an image on the client. tags: "drag-and-drop-image-vector, gpx, geojson, igc, kml, topojson, vector, image" cloak: - key: As1HiMj1PvLPlqc_gtM7AqZfBL8ZL3VrjaS3zIb22Uvb9WKhuJObROC-qUpa81U5 diff --git a/examples/drag-and-drop-image-vector.js b/examples/drag-and-drop-image-vector.js index bfbe18b276..6b0fb4a3c2 100644 --- a/examples/drag-and-drop-image-vector.js +++ b/examples/drag-and-drop-image-vector.js @@ -2,7 +2,7 @@ import Map from '../src/ol/Map.js'; import View from '../src/ol/View.js'; import {GPX, GeoJSON, IGC, KML, TopoJSON} from '../src/ol/format.js'; import {defaults as defaultInteractions, DragAndDrop} from '../src/ol/interaction.js'; -import {Vector as VectorLayer, Tile as TileLayer} from '../src/ol/layer.js'; +import {VectorImage as VectorImageLayer, Tile as TileLayer} from '../src/ol/layer.js'; import {BingMaps, Vector as VectorSource} from '../src/ol/source.js'; const dragAndDropInteraction = new DragAndDrop({ @@ -36,8 +36,7 @@ dragAndDropInteraction.on('addfeatures', function(event) { const vectorSource = new VectorSource({ features: event.features }); - map.addLayer(new VectorLayer({ - renderMode: 'image', + map.addLayer(new VectorImageLayer({ source: vectorSource })); map.getView().fit(vectorSource.getExtent()); diff --git a/examples/image-vector-layer.html b/examples/image-vector-layer.html index 99613624d3..03350b09c1 100644 --- a/examples/image-vector-layer.html +++ b/examples/image-vector-layer.html @@ -1,9 +1,9 @@ --- layout: example.html -title: Image Vector Layer -shortdesc: Example of an image vector layer. +title: Vector Image Layer +shortdesc: Example of rendering vector data as an image layer. docs: > -

This example uses ol/layer/Vector with `renderMode: 'image'`. This mode results in faster rendering during interaction and animations, at the cost of less accurate rendering.

+

This example uses ol/layer/VectorImage for faster rendering during interaction and animations, at the cost of less accurate rendering.

tags: "vector, image" ---
diff --git a/examples/image-vector-layer.js b/examples/image-vector-layer.js index 768ffa5198..16966e28ba 100644 --- a/examples/image-vector-layer.js +++ b/examples/image-vector-layer.js @@ -1,6 +1,7 @@ import Map from '../src/ol/Map.js'; import View from '../src/ol/View.js'; import GeoJSON from '../src/ol/format/GeoJSON.js'; +import VectorImageLayer from '../src/ol/layer/VectorImage.js'; import VectorLayer from '../src/ol/layer/Vector.js'; import VectorSource from '../src/ol/source/Vector.js'; import {Fill, Stroke, Style, Text} from '../src/ol/style.js'; @@ -19,8 +20,7 @@ const style = new Style({ const map = new Map({ layers: [ - new VectorLayer({ - renderMode: 'image', + new VectorImageLayer({ source: new VectorSource({ url: 'data/geojson/countries.geojson', format: new GeoJSON() diff --git a/src/ol/layer.js b/src/ol/layer.js index c218524084..7da542afe6 100644 --- a/src/ol/layer.js +++ b/src/ol/layer.js @@ -9,4 +9,5 @@ export {default as Image} from './layer/Image.js'; export {default as Layer} from './layer/Layer.js'; export {default as Tile} from './layer/Tile.js'; export {default as Vector} from './layer/Vector.js'; +export {default as VectorImage} from './layer/VectorImage.js'; export {default as VectorTile} from './layer/VectorTile.js'; diff --git a/src/ol/layer/BaseVector.js b/src/ol/layer/BaseVector.js index 98950bd2e9..57dc79eb90 100644 --- a/src/ol/layer/BaseVector.js +++ b/src/ol/layer/BaseVector.js @@ -3,7 +3,6 @@ */ import LayerType from '../LayerType.js'; import Layer from './Layer.js'; -import VectorRenderType from './VectorRenderType.js'; import {assign} from '../obj.js'; import {createDefaultStyle, toFunction as toStyleFunction} from '../style/Style.js'; @@ -28,11 +27,6 @@ import {createDefaultStyle, toFunction as toStyleFunction} from '../style/Style. * @property {number} [renderBuffer=100] The buffer in pixels around the viewport extent used by the * renderer when getting features from the vector source for the rendering or hit-detection. * Recommended value: the size of the largest symbol, line width or label. - * @property {import("./VectorRenderType.js").default|string} [renderMode='vector'] Render mode for vector layers: - * * `'image'`: Vector layers are rendered as images. Great performance, but point symbols and - * texts are always rotated with the view and pixels are scaled during zoom animations. - * * `'vector'`: Vector layers are rendered as vectors. Most accurate rendering even during - * animations, but slower performance. * @property {import("../source/Vector.js").default} [source] Source. * @property {import("../PluggableMap.js").default} [map] Sets the layer as overlay on a map. The map will not manage * this layer in its layers collection, and the layer will be rendered on top. This is useful for @@ -43,14 +37,12 @@ import {createDefaultStyle, toFunction as toStyleFunction} from '../style/Style. * means higher priority. * @property {import("../style/Style.js").StyleLike} [style] Layer style. See * {@link module:ol/style} for default style which will be used if this is not defined. - * @property {boolean} [updateWhileAnimating=false] When set to `true` and `renderMode` - * is `vector`, feature batches will be recreated during animations. This means that no - * vectors will be shown clipped, but the setting will have a performance impact for large - * amounts of vector data. When set to `false`, batches will be recreated when no animation - * is active. - * @property {boolean} [updateWhileInteracting=false] When set to `true` and `renderMode` - * is `vector`, feature batches will be recreated during interactions. See also - * `updateWhileAnimating`. + * @property {boolean} [updateWhileAnimating=false] When set to `true`, feature batches will + * be recreated during animations. This means that no vectors will be shown clipped, but the + * setting will have a performance impact for large amounts of vector data. When set to `false`, + * batches will be recreated when no animation is active. + * @property {boolean} [updateWhileInteracting=false] When set to `true`, feature batches will + * be recreated during interactions. See also `updateWhileAnimating`. */ @@ -131,12 +123,6 @@ class BaseVectorLayer extends Layer { this.updateWhileInteracting_ = options.updateWhileInteracting !== undefined ? options.updateWhileInteracting : false; - /** - * @private - * @type {import("./VectorTileRenderType.js").default|string} - */ - this.renderMode_ = options.renderMode || VectorRenderType.VECTOR; - /** * The layer type. * @protected @@ -237,12 +223,6 @@ class BaseVectorLayer extends Layer { this.changed(); } - /** - * @return {import("./VectorRenderType.js").default|string} The render mode. - */ - getRenderMode() { - return this.renderMode_; - } } diff --git a/src/ol/layer/Heatmap.js b/src/ol/layer/Heatmap.js index db062774c7..16f642f9dc 100644 --- a/src/ol/layer/Heatmap.js +++ b/src/ol/layer/Heatmap.js @@ -34,11 +34,6 @@ import Style from '../style/Style.js'; * @property {string|function(import("../Feature.js").default):number} [weight='weight'] The feature * attribute to use for the weight or a function that returns a weight from a feature. Weight values * should range from 0 to 1 (and values outside will be clamped to that range). - * @property {import("./VectorRenderType.js").default|string} [renderMode='vector'] Render mode for vector layers: - * * `'image'`: Vector layers are rendered as images. Great performance, but point symbols and - * texts are always rotated with the view and pixels are scaled during zoom animations. - * * `'vector'`: Vector layers are rendered as vectors. Most accurate rendering even during - * animations, but slower performance. * @property {import("../source/Vector.js").default} [source] Source. */ diff --git a/src/ol/layer/VectorImage.js b/src/ol/layer/VectorImage.js new file mode 100644 index 0000000000..42678c778d --- /dev/null +++ b/src/ol/layer/VectorImage.js @@ -0,0 +1,40 @@ +/** + * @module ol/layer/VectorImage + */ +import BaseVectorLayer from './BaseVector.js'; +import CanvasVectorImageLayerRenderer from '../renderer/canvas/VectorImageLayer.js'; + +/** + * @typedef {import("./BaseVector.js").Options} Options + */ + + +/** + * @classdesc + * Vector data that is rendered client-side. + * Note that any property set in the options is set as a {@link module:ol/Object~BaseObject} + * property on the layer object; for example, setting `title: 'My Title'` in the + * options means that `title` is observable, and has get/set accessors. + * + * @api + */ +class VectorImageLayer extends BaseVectorLayer { + /** + * @param {Options=} opt_options Options. + */ + constructor(opt_options) { + super(opt_options); + } + + /** + * Create a renderer for this layer. + * @return {import("../renderer/Layer.js").default} A layer renderer. + * @protected + */ + createRenderer() { + return new CanvasVectorImageLayerRenderer(this); + } +} + + +export default VectorImageLayer; diff --git a/src/ol/layer/VectorRenderType.js b/src/ol/layer/VectorRenderType.js deleted file mode 100644 index 03f4c87006..0000000000 --- a/src/ol/layer/VectorRenderType.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @module ol/layer/VectorRenderType - */ - -/** - * @enum {string} - * Render mode for vector layers: - * * `'image'`: Vector layers are rendered as images. Great performance, but - * point symbols and texts are always rotated with the view and pixels are - * scaled during zoom animations. - * * `'vector'`: Vector layers are rendered as vectors. Most accurate rendering - * even during animations, but slower performance. - * @api - */ -export default { - IMAGE: 'image', - VECTOR: 'vector' -}; diff --git a/src/ol/layer/VectorTile.js b/src/ol/layer/VectorTile.js index a40caa1057..7bafcc3943 100644 --- a/src/ol/layer/VectorTile.js +++ b/src/ol/layer/VectorTile.js @@ -86,22 +86,28 @@ class VectorTileLayer extends BaseVectorLayer { constructor(opt_options) { const options = opt_options ? opt_options : {}; + const baseOptions = /** @type {Object} */ (assign({}, options)); + delete baseOptions.preload; + delete baseOptions.useInterimTilesOnError; + + super(/** @type {import("./Vector.js").Options} */ (baseOptions)); + 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'` + if (options.declutter && renderMode == VectorTileRenderType.IMAGE) { renderMode = VectorTileRenderType.HYBRID; } - options.renderMode = renderMode; - const baseOptions = /** @type {Object} */ (assign({}, options)); - delete baseOptions.preload; - delete baseOptions.useInterimTilesOnError; - - super(/** @type {import("./Vector.js").Options} */ (baseOptions)); + /** + * @private + * @type {VectorTileRenderType} + */ + this.renderMode_ = renderMode; this.setPreload(options.preload ? options.preload : 0); this.setUseInterimTilesOnError(options.useInterimTilesOnError !== undefined ? @@ -124,6 +130,13 @@ class VectorTileLayer extends BaseVectorLayer { return new CanvasVectorTileLayerRenderer(this); } + /** + * @return {VectorTileRenderType} The render mode. + */ + getRenderMode() { + return this.renderMode_; + } + /** * Return the level as number to which we will preload tiles up to. * @return {number} The level to preload tiles up to. diff --git a/src/ol/renderer/canvas/ImageLayer.js b/src/ol/renderer/canvas/ImageLayer.js index 9436725087..677e7e48a8 100644 --- a/src/ol/renderer/canvas/ImageLayer.js +++ b/src/ol/renderer/canvas/ImageLayer.js @@ -2,14 +2,9 @@ * @module ol/renderer/canvas/ImageLayer */ import {ENABLE_RASTER_REPROJECTION} from '../../reproj/common.js'; -import ImageCanvas from '../../ImageCanvas.js'; import LayerType from '../../LayerType.js'; import ViewHint from '../../ViewHint.js'; -import {equals} from '../../array.js'; -import {getHeight, getIntersection, getWidth, isEmpty} from '../../extent.js'; -import VectorRenderType from '../../layer/VectorRenderType.js'; -import {assign} from '../../obj.js'; -import {layerRendererConstructors} from './Map.js'; +import {getIntersection, isEmpty} from '../../extent.js'; import IntermediateCanvasRenderer from './IntermediateCanvas.js'; import {create as createTransform, compose as composeTransform} from '../../transform.js'; @@ -21,55 +16,23 @@ import {create as createTransform, compose as composeTransform} from '../../tran class CanvasImageLayerRenderer extends IntermediateCanvasRenderer { /** - * @param {import("../../layer/Image.js").default|import("../../layer/Vector.js").default} imageLayer Image or vector layer. + * @param {import("../../layer/Image.js").default} imageLayer Image layer. */ constructor(imageLayer) { - super(imageLayer); /** - * @private + * @protected * @type {?import("../../ImageBase.js").default} */ this.image_ = null; /** - * @private + * @protected * @type {import("../../transform.js").Transform} */ this.imageTransform_ = createTransform(); - /** - * @type {!Array} - */ - this.skippedFeatures_ = []; - - /** - * @private - * @type {import("./VectorLayer.js").default} - */ - this.vectorRenderer_ = null; - - if (imageLayer.getType() === LayerType.VECTOR) { - for (let i = 0, ii = layerRendererConstructors.length; i < ii; ++i) { - const ctor = layerRendererConstructors[i]; - if (ctor !== CanvasImageLayerRenderer && ctor['handles'](imageLayer)) { - this.vectorRenderer_ = /** @type {import("./VectorLayer.js").default} */ (new ctor(imageLayer)); - break; - } - } - } - - } - - /** - * @inheritDoc - */ - disposeInternal() { - if (this.vectorRenderer_) { - this.vectorRenderer_.dispose(); - } - super.disposeInternal(); } /** @@ -103,9 +66,8 @@ class CanvasImageLayerRenderer extends IntermediateCanvasRenderer { const hints = frameState.viewHints; - const vectorRenderer = this.vectorRenderer_; let renderedExtent = frameState.extent; - if (!vectorRenderer && layerState.extent !== undefined) { + if (layerState.extent !== undefined) { renderedExtent = getIntersection(renderedExtent, layerState.extent); } @@ -118,37 +80,9 @@ class CanvasImageLayerRenderer extends IntermediateCanvasRenderer { projection = sourceProjection; } } - let skippedFeatures = this.skippedFeatures_; - if (vectorRenderer) { - const context = vectorRenderer.context; - const imageFrameState = /** @type {import("../../PluggableMap.js").FrameState} */ (assign({}, frameState, { - size: [ - getWidth(renderedExtent) / viewResolution, - getHeight(renderedExtent) / viewResolution - ], - viewState: /** @type {import("../../View.js").State} */ (assign({}, frameState.viewState, { - rotation: 0 - })) - })); - const newSkippedFeatures = Object.keys(imageFrameState.skippedFeatureUids).sort(); - image = new ImageCanvas(renderedExtent, viewResolution, pixelRatio, context.canvas, function(callback) { - if (vectorRenderer.prepareFrame(imageFrameState, layerState) && - (vectorRenderer.replayGroupChanged || - !equals(skippedFeatures, newSkippedFeatures))) { - context.canvas.width = imageFrameState.size[0] * pixelRatio; - context.canvas.height = imageFrameState.size[1] * pixelRatio; - vectorRenderer.compose(context, imageFrameState, layerState); - skippedFeatures = newSkippedFeatures; - callback(); - } - }); - } else { - image = imageSource.getImage( - renderedExtent, viewResolution, pixelRatio, projection); - } + const image = imageSource.getImage(renderedExtent, viewResolution, pixelRatio, projection); if (image && this.loadImage(image)) { this.image_ = image; - this.skippedFeatures_ = skippedFeatures; } } @@ -177,16 +111,6 @@ class CanvasImageLayerRenderer extends IntermediateCanvasRenderer { return !!this.image_; } - /** - * @inheritDoc - */ - forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback) { - if (this.vectorRenderer_) { - return this.vectorRenderer_.forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback); - } else { - return super.forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback); - } - } } @@ -196,9 +120,7 @@ class CanvasImageLayerRenderer extends IntermediateCanvasRenderer { * @return {boolean} The renderer can render the layer. */ CanvasImageLayerRenderer['handles'] = function(layer) { - return layer.getType() === LayerType.IMAGE || - layer.getType() === LayerType.VECTOR && - /** @type {import("../../layer/Vector.js").default} */ (layer).getRenderMode() === VectorRenderType.IMAGE; + return layer.getType() === LayerType.IMAGE; }; diff --git a/src/ol/renderer/canvas/VectorImageLayer.js b/src/ol/renderer/canvas/VectorImageLayer.js new file mode 100644 index 0000000000..034018f793 --- /dev/null +++ b/src/ol/renderer/canvas/VectorImageLayer.js @@ -0,0 +1,129 @@ +/** + * @module ol/renderer/canvas/ImageLayer + */ +import ImageCanvas from '../../ImageCanvas.js'; +import ViewHint from '../../ViewHint.js'; +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'; + +/** + * @classdesc + * Canvas renderer for image layers. + * @api + */ +class CanvasVectorImageLayerRenderer extends CanvasImageLayerRenderer { + + /** + * @param {import("../../layer/VectorImage.js").default} layer Vector image layer. + */ + constructor(layer) { + super(layer); + + /** + * @type {!Array} + */ + this.skippedFeatures_ = []; + + /** + * @private + * @type {import("./VectorLayer.js").default} + */ + this.vectorRenderer_ = new CanvasVectorLayerRenderer(layer); + + } + + /** + * @inheritDoc + */ + disposeInternal() { + this.vectorRenderer_.dispose(); + super.disposeInternal(); + } + + /** + * @inheritDoc + */ + 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; + const vectorRenderer = this.vectorRenderer_; + const renderedExtent = frameState.extent; + + if (!hints[ViewHint.ANIMATING] && !hints[ViewHint.INTERACTING] && !isEmpty(renderedExtent)) { + let skippedFeatures = this.skippedFeatures_; + const context = vectorRenderer.context; + const imageFrameState = /** @type {import("../../PluggableMap.js").FrameState} */ (assign({}, frameState, { + size: [ + getWidth(renderedExtent) / viewResolution, + getHeight(renderedExtent) / viewResolution + ], + viewState: /** @type {import("../../View.js").State} */ (assign({}, frameState.viewState, { + rotation: 0 + })) + })); + const newSkippedFeatures = Object.keys(imageFrameState.skippedFeatureUids).sort(); + const image = new ImageCanvas(renderedExtent, viewResolution, pixelRatio, context.canvas, function(callback) { + if (vectorRenderer.prepareFrame(imageFrameState, layerState) && + (vectorRenderer.replayGroupChanged || + !equals(skippedFeatures, newSkippedFeatures))) { + context.canvas.width = imageFrameState.size[0] * pixelRatio; + context.canvas.height = imageFrameState.size[1] * pixelRatio; + vectorRenderer.compose(context, imageFrameState, layerState); + skippedFeatures = newSkippedFeatures; + callback(); + } + }); + if (this.loadImage(image)) { + this.image_ = image; + this.skippedFeatures_ = skippedFeatures; + } + } + + 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.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_; + } + + /** + * @inheritDoc + */ + forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback) { + if (this.vectorRenderer_) { + return this.vectorRenderer_.forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback); + } else { + return super.forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback); + } + } +} + + +export default CanvasVectorImageLayerRenderer; diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index 60570990ec..01d2c73f39 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -134,7 +134,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { * @inheritDoc */ prepareFrame(frameState, layerState) { - const layer = /** @type {import("../../layer/Vector.js").default} */ (this.getLayer()); + const layer = /** @type {import("../../layer/VectorTile.js").default} */ (this.getLayer()); const layerRevision = layer.getRevision(); if (this.renderedLayerRevision_ != layerRevision) { this.renderedTiles.length = 0; @@ -328,7 +328,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { * @inheritDoc */ postCompose(context, frameState, layerState) { - const layer = /** @type {import("../../layer/Vector.js").default} */ (this.getLayer()); + const layer = /** @type {import("../../layer/VectorTile.js").default} */ (this.getLayer()); const renderMode = layer.getRenderMode(); if (renderMode != VectorTileRenderType.IMAGE) { const declutterReplays = layer.getDeclutter() ? {} : null; @@ -447,7 +447,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { * @private */ renderTileImage_(tile, pixelRatio, projection) { - const layer = /** @type {import("../../layer/Vector.js").default} */ (this.getLayer()); + const layer = /** @type {import("../../layer/VectorTile.js").default} */ (this.getLayer()); const replayState = tile.getReplayState(layer); const revision = layer.getRevision(); const replays = IMAGE_REPLAYS[layer.getRenderMode()]; diff --git a/src/ol/source/Raster.js b/src/ol/source/Raster.js index 816d01db60..aa952010fb 100644 --- a/src/ol/source/Raster.js +++ b/src/ol/source/Raster.js @@ -10,15 +10,14 @@ import Event from '../events/Event.js'; import EventType from '../events/EventType.js'; import {Processor} from 'pixelworks/lib/index'; import {equals, getCenter, getHeight, getWidth} from '../extent.js'; -import LayerType from '../LayerType.js'; import ImageLayer from '../layer/Image.js'; import TileLayer from '../layer/Tile.js'; import {assign} from '../obj.js'; -import CanvasImageLayerRenderer from '../renderer/canvas/ImageLayer.js'; -import CanvasTileLayerRenderer from '../renderer/canvas/TileLayer.js'; -import ImageSource from './Image.js'; -import SourceState from './State.js'; import {create as createTransform} from '../transform.js'; +import ImageSource from './Image.js'; +import TileSource from './Tile.js'; +import SourceState from './State.js'; +import Source from './Source.js'; /** @@ -113,7 +112,7 @@ class RasterSourceEvent extends Event { /** * @typedef {Object} Options * @property {Array} sources Input - * sources or layers. Vector layers must be configured with `renderMode: 'image'`. + * sources or layers. For vector data, use an VectorImage layer. * @property {Operation} [operation] Raster operation. * The operation will be called with data from input sources * and the output will be assigned to the raster source. @@ -490,42 +489,18 @@ function createRenderers(sources) { * @return {import("../renderer/canvas/Layer.js").default} The renderer. */ function createRenderer(layerOrSource) { - const tileSource = /** @type {import("./Tile.js").default} */ (layerOrSource); - const imageSource = /** @type {import("./Image.js").default} */ (layerOrSource); - const layer = /** @type {import("../layer/Layer.js").default} */ (layerOrSource); - let renderer = null; - if (typeof tileSource.getTile === 'function') { - renderer = createTileRenderer(tileSource); - } else if (typeof imageSource.getImage === 'function') { - renderer = createImageRenderer(imageSource); - } else if (layer.getType() === LayerType.TILE) { - renderer = new CanvasTileLayerRenderer(/** @type {import("../layer/Tile.js").default} */ (layer)); - } else if (layer.getType() == LayerType.IMAGE || layer.getType() == LayerType.VECTOR) { - renderer = new CanvasImageLayerRenderer(/** @type {import("../layer/Image.js").default} */ (layer)); + // @type {import("../layer/Layer.js").default} + let layer; + if (layerOrSource instanceof Source) { + if (layerOrSource instanceof TileSource) { + layer = new TileLayer({source: layerOrSource}); + } else if (layerOrSource instanceof ImageSource) { + layer = new ImageLayer({source: layerOrSource}); + } + } else { + layer = layerOrSource; } - return renderer; -} - - -/** - * Create an image renderer for the provided source. - * @param {import("./Image.js").default} source The source. - * @return {import("../renderer/canvas/Layer.js").default} The renderer. - */ -function createImageRenderer(source) { - const layer = new ImageLayer({source: source}); - return new CanvasImageLayerRenderer(layer); -} - - -/** - * Create a tile renderer for the provided source. - * @param {import("./Tile.js").default} source The source. - * @return {import("../renderer/canvas/Layer.js").default} The renderer. - */ -function createTileRenderer(source) { - const layer = new TileLayer({source: source}); - return new CanvasTileLayerRenderer(layer); + return layer ? /** @type {import("../renderer/canvas/Layer.js").default} */ (layer.createRenderer()) : null; } diff --git a/test/rendering/ol/layer/vector.test.js b/test/rendering/ol/layer/vector.test.js index ce3a1ac883..963e97b09f 100644 --- a/test/rendering/ol/layer/vector.test.js +++ b/test/rendering/ol/layer/vector.test.js @@ -99,33 +99,6 @@ describe('ol.rendering.layer.Vector', function() { }); }); - it('renders opacity correctly with renderMode: \'image\'', function(done) { - createMap('canvas'); - const smallLine = new Feature(new LineString([ - [center[0], center[1] - 1], - [center[0], center[1] + 1] - ])); - smallLine.setStyle(new Style({ - zIndex: -99, - stroke: new Stroke({width: 75, color: 'red'}) - })); - source.addFeature(smallLine); - addPolygon(100); - addCircle(200); - addPolygon(250); - addCircle(500); - addPolygon(600); - addPolygon(720); - map.addLayer(new VectorLayer({ - renerMode: 'image', - source: source - })); - map.once('postrender', function() { - expectResemble(map, 'rendering/ol/layer/expected/vector-canvas.png', - 17, done); - }); - }); - it('renders transparent layers correctly with the canvas renderer', function(done) { createMap('canvas'); const smallLine = new Feature(new LineString([ @@ -165,46 +138,6 @@ describe('ol.rendering.layer.Vector', function() { }); }); - it('renders transparent layers correctly with renderMode: \'image\'', function(done) { - createMap('canvas'); - const smallLine = new Feature(new LineString([ - [center[0], center[1] - 1], - [center[0], center[1] + 1] - ])); - smallLine.setStyle([ - new Style({ - stroke: new Stroke({width: 75, color: 'red'}) - }), - new Style({ - stroke: new Stroke({width: 45, color: 'white'}) - }) - ]); - source.addFeature(smallLine); - const smallLine2 = new Feature(new LineString([ - [center[0], center[1] - 1000], - [center[0], center[1] + 1000] - ])); - smallLine2.setStyle([ - new Style({ - stroke: new Stroke({width: 35, color: 'blue'}) - }), - new Style({ - stroke: new Stroke({width: 15, color: 'green'}) - }) - ]); - source.addFeature(smallLine2); - - map.addLayer(new VectorLayer({ - renderMode: 'image', - source: source, - opacity: 0.5 - })); - map.once('postrender', function() { - expectResemble(map, 'rendering/ol/layer/expected/vector-canvas-transparent.png', - 7, done); - }); - }); - it('renders rotation correctly with the canvas renderer', function(done) { createMap('canvas'); map.getView().setRotation(Math.PI + Math.PI / 4); @@ -225,53 +158,6 @@ describe('ol.rendering.layer.Vector', function() { }); }); - it('renders rotation correctly with renderMode: \'image\'', function(done) { - createMap('canvas'); - map.getView().setRotation(Math.PI + Math.PI / 4); - addPolygon(300); - addCircle(500); - map.addLayer(new VectorLayer({ - renderMode: 'image', - source: source, - style: new Style({ - stroke: new Stroke({ - width: 2, - color: 'black' - }) - }) - })); - map.once('postrender', function() { - expectResemble(map, 'rendering/ol/layer/expected/vector-canvas-rotated.png', - 2.9, done); - }); - }); - - it('unskips features correctly with renderMode: \'image\'', function(done) { - createMap('canvas'); - addCircle(500); - addPolygon(300); - map.skipFeature(source.getFeatures()[1]); - map.addLayer(new VectorLayer({ - renderMode: 'image', - source: source, - style: new Style({ - fill: new Fill({ - color: 'rgba(255,0,0,0.5)' - }), - stroke: new Stroke({ - width: 2, - color: 'black' - }) - }) - })); - map.renderSync(); - map.unskipFeature(source.getFeatures()[1]); - map.once('postrender', function() { - expectResemble(map, 'rendering/ol/layer/expected/vector.png', - IMAGE_TOLERANCE, done); - }); - }); - it('renders fill/stroke batches correctly with the canvas renderer', function(done) { createMap('canvas'); source = new VectorSource({ @@ -622,47 +508,6 @@ describe('ol.rendering.layer.Vector', function() { }); }); - it('declutters text with renderMode: \'image\'', function(done) { - createMap('canvas'); - const layer = new VectorLayer({ - renderMode: 'image', - source: source - }); - map.addLayer(layer); - - const centerFeature = new Feature({ - geometry: new Point(center), - text: 'center' - }); - source.addFeature(centerFeature); - source.addFeature(new Feature({ - geometry: new Point([center[0] - 540, center[1]]), - text: 'west' - })); - source.addFeature(new Feature({ - geometry: new Point([center[0] + 540, center[1]]), - text: 'east' - })); - - layer.setDeclutter(true); - layer.setStyle(function(feature) { - return new Style({ - text: new Text({ - text: feature.get('text'), - font: '12px sans-serif' - }) - }); - }); - - map.once('postrender', function() { - const hitDetected = map.getFeaturesAtPixel([42, 42]); - expect(hitDetected).to.have.length(1); - expect(hitDetected[0]).to.equal(centerFeature); - expectResemble(map, 'rendering/ol/layer/expected/vector-canvas-declutter.png', - 2.2, done); - }); - }); - it('declutters text and respects z-index', function(done) { createMap('canvas'); const layer = new VectorLayer({ @@ -742,46 +587,6 @@ describe('ol.rendering.layer.Vector', function() { }); }); - it('declutters images with renderMode: \'image\'', function(done) { - createMap('canvas'); - const layer = new VectorLayer({ - renderMode: 'image', - source: source - }); - map.addLayer(layer); - - const centerFeature = new Feature({ - geometry: new Point(center) - }); - source.addFeature(centerFeature); - source.addFeature(new Feature({ - geometry: new Point([center[0] - 540, center[1]]) - })); - source.addFeature(new Feature({ - geometry: new Point([center[0] + 540, center[1]]) - })); - - layer.setDeclutter(true); - layer.setStyle(function(feature) { - return new Style({ - image: new CircleStyle({ - radius: 15, - stroke: new Stroke({ - color: 'blue' - }) - }) - }); - }); - - map.once('postrender', function() { - const hitDetected = map.getFeaturesAtPixel([40, 40]); - expect(hitDetected).to.have.length(1); - expect(hitDetected[0]).to.equal(centerFeature); - expectResemble(map, 'rendering/ol/layer/expected/vector-canvas-declutter-image.png', - IMAGE_TOLERANCE, done); - }); - }); - it('declutters images and respects z-index', function(done) { createMap('canvas'); const layer = new VectorLayer({ @@ -908,49 +713,6 @@ describe('ol.rendering.layer.Vector', function() { }); }); - it('declutters text along lines and images with renderMode: \'image\'', function(done) { - createMap('canvas'); - const layer = new VectorLayer({ - source: source - }); - map.addLayer(layer); - - const point = new Feature(new Point(center)); - point.setStyle(new Style({ - image: new CircleStyle({ - radius: 8, - stroke: new Stroke({ - color: 'blue' - }) - }) - })); - const line = new Feature(new LineString([ - [center[0] - 650, center[1] - 200], - [center[0] + 650, center[1] - 200] - ])); - line.setStyle(new Style({ - stroke: new Stroke({ - color: '#CCC', - width: 12 - }), - text: new Text({ - placement: 'line', - text: 'east-west', - font: '12px sans-serif' - }) - })); - - source.addFeature(point); - source.addFeature(line); - - layer.setDeclutter(true); - - map.once('postrender', function() { - expectResemble(map, 'rendering/ol/layer/expected/vector-canvas-declutter-line.png', - IMAGE_TOLERANCE, done); - }); - }); - it('declutters text along lines and images with z-index', function(done) { createMap('canvas'); const layer = new VectorLayer({ diff --git a/test/rendering/ol/layer/vectorimage.test.js b/test/rendering/ol/layer/vectorimage.test.js new file mode 100644 index 0000000000..036f0f0654 --- /dev/null +++ b/test/rendering/ol/layer/vectorimage.test.js @@ -0,0 +1,289 @@ +import Circle from '../../../../src/ol/geom/Circle.js'; +import CircleStyle from '../../../../src/ol/style/Circle.js'; +import Feature from '../../../../src/ol/Feature.js'; +import Fill from '../../../../src/ol/style/Fill.js'; +import LineString from '../../../../src/ol/geom/LineString.js'; +import Map from '../../../../src/ol/Map.js'; +import Point from '../../../../src/ol/geom/Point.js'; +import Polygon from '../../../../src/ol/geom/Polygon.js'; +import Stroke from '../../../../src/ol/style/Stroke.js'; +import Style from '../../../../src/ol/style/Style.js'; +import Text from '../../../../src/ol/style/Text.js'; +import VectorImageLayer from '../../../../src/ol/layer/VectorImage.js'; +import VectorSource from '../../../../src/ol/source/Vector.js'; +import View from '../../../../src/ol/View.js'; + +describe('ol.rendering.layer.VectorImage', function() { + + const center = [1825927.7316762917, 6143091.089223046]; + + let map, source; + function createMap(renderer) { + source = new VectorSource(); + map = new Map({ + pixelRatio: 1, + target: createMapDiv(80, 80), + renderer: renderer, + view: new View({ + center: center, + zoom: 13 + }) + }); + } + + afterEach(function() { + if (map) { + disposeMap(map); + } + map = null; + }); + + function addCircle(r) { + source.addFeature(new Feature(new Circle(center, r))); + } + + function addPolygon(r) { + source.addFeature(new Feature(new Polygon([ + [ + [center[0] - r, center[1] - r], + [center[0] + r, center[1] - r], + [center[0] + r, center[1] + r], + [center[0] - r, center[1] + r], + [center[0] - r, center[1] - r] + ] + ]))); + } + + it('renders opacity correctly', function(done) { + createMap('canvas'); + const smallLine = new Feature(new LineString([ + [center[0], center[1] - 1], + [center[0], center[1] + 1] + ])); + smallLine.setStyle(new Style({ + zIndex: -99, + stroke: new Stroke({width: 75, color: 'red'}) + })); + source.addFeature(smallLine); + addPolygon(100); + addCircle(200); + addPolygon(250); + addCircle(500); + addPolygon(600); + addPolygon(720); + map.addLayer(new VectorImageLayer({ + source: source + })); + map.once('postrender', function() { + expectResemble(map, 'rendering/ol/layer/expected/vector-canvas.png', + 17, done); + }); + }); + + it('renders transparent layers correctly', function(done) { + createMap('canvas'); + const smallLine = new Feature(new LineString([ + [center[0], center[1] - 1], + [center[0], center[1] + 1] + ])); + smallLine.setStyle([ + new Style({ + stroke: new Stroke({width: 75, color: 'red'}) + }), + new Style({ + stroke: new Stroke({width: 45, color: 'white'}) + }) + ]); + source.addFeature(smallLine); + const smallLine2 = new Feature(new LineString([ + [center[0], center[1] - 1000], + [center[0], center[1] + 1000] + ])); + smallLine2.setStyle([ + new Style({ + stroke: new Stroke({width: 35, color: 'blue'}) + }), + new Style({ + stroke: new Stroke({width: 15, color: 'green'}) + }) + ]); + source.addFeature(smallLine2); + + map.addLayer(new VectorImageLayer({ + source: source, + opacity: 0.5 + })); + map.once('postrender', function() { + expectResemble(map, 'rendering/ol/layer/expected/vector-canvas-transparent.png', + 7, done); + }); + }); + + it('renders rotation correctly', function(done) { + createMap('canvas'); + map.getView().setRotation(Math.PI + Math.PI / 4); + addPolygon(300); + addCircle(500); + map.addLayer(new VectorImageLayer({ + source: source, + style: new Style({ + stroke: new Stroke({ + width: 2, + color: 'black' + }) + }) + })); + map.once('postrender', function() { + expectResemble(map, 'rendering/ol/layer/expected/vector-canvas-rotated.png', + 2.9, done); + }); + }); + + it('unskips features correctly', function(done) { + createMap('canvas'); + addCircle(500); + addPolygon(300); + map.skipFeature(source.getFeatures()[1]); + map.addLayer(new VectorImageLayer({ + source: source, + style: new Style({ + fill: new Fill({ + color: 'rgba(255,0,0,0.5)' + }), + stroke: new Stroke({ + width: 2, + color: 'black' + }) + }) + })); + map.renderSync(); + map.unskipFeature(source.getFeatures()[1]); + map.once('postrender', function() { + expectResemble(map, 'rendering/ol/layer/expected/vector.png', + IMAGE_TOLERANCE, done); + }); + }); + + it('declutters text', function(done) { + createMap('canvas'); + const layer = new VectorImageLayer({ + source: source + }); + map.addLayer(layer); + + const centerFeature = new Feature({ + geometry: new Point(center), + text: 'center' + }); + source.addFeature(centerFeature); + source.addFeature(new Feature({ + geometry: new Point([center[0] - 540, center[1]]), + text: 'west' + })); + source.addFeature(new Feature({ + geometry: new Point([center[0] + 540, center[1]]), + text: 'east' + })); + + layer.setDeclutter(true); + layer.setStyle(function(feature) { + return new Style({ + text: new Text({ + text: feature.get('text'), + font: '12px sans-serif' + }) + }); + }); + + map.once('postrender', function() { + const hitDetected = map.getFeaturesAtPixel([42, 42]); + expect(hitDetected).to.have.length(1); + expect(hitDetected[0]).to.equal(centerFeature); + expectResemble(map, 'rendering/ol/layer/expected/vector-canvas-declutter.png', + 2.2, done); + }); + }); + + it('declutters images', function(done) { + createMap('canvas'); + const layer = new VectorImageLayer({ + source: source + }); + map.addLayer(layer); + + const centerFeature = new Feature({ + geometry: new Point(center) + }); + source.addFeature(centerFeature); + source.addFeature(new Feature({ + geometry: new Point([center[0] - 540, center[1]]) + })); + source.addFeature(new Feature({ + geometry: new Point([center[0] + 540, center[1]]) + })); + + layer.setDeclutter(true); + layer.setStyle(function(feature) { + return new Style({ + image: new CircleStyle({ + radius: 15, + stroke: new Stroke({ + color: 'blue' + }) + }) + }); + }); + + map.once('postrender', function() { + const hitDetected = map.getFeaturesAtPixel([40, 40]); + expect(hitDetected).to.have.length(1); + expect(hitDetected[0]).to.equal(centerFeature); + expectResemble(map, 'rendering/ol/layer/expected/vector-canvas-declutter-image.png', + IMAGE_TOLERANCE, done); + }); + }); + + it('declutters text along lines and images', function(done) { + createMap('canvas'); + const layer = new VectorImageLayer({ + source: source + }); + map.addLayer(layer); + + const point = new Feature(new Point(center)); + point.setStyle(new Style({ + image: new CircleStyle({ + radius: 8, + stroke: new Stroke({ + color: 'blue' + }) + }) + })); + const line = new Feature(new LineString([ + [center[0] - 650, center[1] - 200], + [center[0] + 650, center[1] - 200] + ])); + line.setStyle(new Style({ + stroke: new Stroke({ + color: '#CCC', + width: 12 + }), + text: new Text({ + placement: 'line', + text: 'east-west', + font: '12px sans-serif' + }) + })); + + source.addFeature(point); + source.addFeature(line); + + layer.setDeclutter(true); + + map.once('postrender', function() { + expectResemble(map, 'rendering/ol/layer/expected/vector-canvas-declutter-line.png', + IMAGE_TOLERANCE, done); + }); + }); + +}); diff --git a/test/spec/ol/renderer/canvas/imagelayer.test.js b/test/spec/ol/renderer/canvas/imagelayer.test.js index 3068bec1f2..3e7ca3a1c8 100644 --- a/test/spec/ol/renderer/canvas/imagelayer.test.js +++ b/test/spec/ol/renderer/canvas/imagelayer.test.js @@ -1,45 +1,16 @@ import Map from '../../../../../src/ol/Map.js'; import View from '../../../../../src/ol/View.js'; import ImageLayer from '../../../../../src/ol/layer/Image.js'; -import VectorLayer from '../../../../../src/ol/layer/Vector.js'; +import VectorImageLayer from '../../../../../src/ol/layer/VectorImage.js'; import Feature from '../../../../../src/ol/Feature.js'; import Point from '../../../../../src/ol/geom/Point.js'; import Projection from '../../../../../src/ol/proj/Projection.js'; import Static from '../../../../../src/ol/source/ImageStatic.js'; import VectorSource from '../../../../../src/ol/source/Vector.js'; -import CanvasImageLayerRenderer from '../../../../../src/ol/renderer/canvas/ImageLayer.js'; -import CanvasVectorLayerRenderer from '../../../../../src/ol/renderer/canvas/VectorLayer.js'; describe('ol.renderer.canvas.ImageLayer', function() { - describe('#dispose()', function() { - let layer, imageRenderer, vectorRenderer; - - beforeEach(function() { - layer = new VectorLayer({ - renderMode: 'image', - source: new VectorSource() - }); - imageRenderer = new CanvasImageLayerRenderer(layer); - vectorRenderer = new CanvasVectorLayerRenderer(layer); - imageRenderer.vectorRenderer_ = vectorRenderer; - }); - - afterEach(function() { - imageRenderer.dispose(); - vectorRenderer.dispose(); - layer.dispose(); - }); - - it('cleans up CanvasVectorRenderer', function() { - const vectorRenderer = imageRenderer.vectorRenderer_; - const spy = sinon.spy(vectorRenderer, 'dispose'); - imageRenderer.dispose(); - expect(spy.called).to.be(true); - }); - }); - describe('#forEachLayerAtCoordinate', function() { let map, target, source; @@ -99,8 +70,7 @@ describe('ol.renderer.canvas.ImageLayer', function() { let map, div, layer; beforeEach(function() { - layer = new VectorLayer({ - renderMode: 'image', + layer = new VectorImageLayer({ source: new VectorSource({ features: [new Feature(new Point([0, 0]))] }) diff --git a/test/spec/ol/renderer/canvas/vectorimage.test.js b/test/spec/ol/renderer/canvas/vectorimage.test.js new file mode 100644 index 0000000000..5948f40a44 --- /dev/null +++ b/test/spec/ol/renderer/canvas/vectorimage.test.js @@ -0,0 +1,21 @@ +import VectorImageLayer from '../../../../../src/ol/layer/VectorImage.js'; +import VectorSource from '../../../../../src/ol/source/Vector.js'; +import CanvasVectorImageLayerRenderer from '../../../../../src/ol/renderer/canvas/VectorImageLayer.js'; + +describe('ol/renderer/canvas/VectorImageLayer', function() { + + describe('#dispose()', function() { + + it('cleans up CanvasVectorRenderer', function() { + const layer = new VectorImageLayer({ + source: new VectorSource() + }); + const renderer = new CanvasVectorImageLayerRenderer(layer); + const spy = sinon.spy(renderer.vectorRenderer_, 'dispose'); + renderer.dispose(); + expect(spy.called).to.be(true); + }); + + }); + +}); diff --git a/test/spec/ol/source/raster.test.js b/test/spec/ol/source/raster.test.js index 5f4d3928ad..510b357750 100644 --- a/test/spec/ol/source/raster.test.js +++ b/test/spec/ol/source/raster.test.js @@ -2,7 +2,7 @@ import Map from '../../../../src/ol/Map.js'; import TileState from '../../../../src/ol/TileState.js'; import View from '../../../../src/ol/View.js'; import ImageLayer from '../../../../src/ol/layer/Image.js'; -import VectorLayer from '../../../../src/ol/layer/Vector.js'; +import VectorImageLayer from '../../../../src/ol/layer/VectorImage.js'; import Projection from '../../../../src/ol/proj/Projection.js'; import Static from '../../../../src/ol/source/ImageStatic.js'; import RasterSource from '../../../../src/ol/source/Raster.js'; @@ -47,8 +47,7 @@ where('Uint8ClampedArray').describe('ol.source.Raster', function() { imageExtent: extent }); - blueSource = new VectorLayer({ - renderMode: 'image', + blueSource = new VectorImageLayer({ source: new VectorSource({ features: [new Feature(new Point([0, 0]))] }),