diff --git a/examples/mapbox-vector-tiles-advanced.js b/examples/mapbox-vector-tiles-advanced.js index 4bda0c39c9..b960ad8964 100644 --- a/examples/mapbox-vector-tiles-advanced.js +++ b/examples/mapbox-vector-tiles-advanced.js @@ -43,6 +43,7 @@ function tileUrlFunction(tileCoord) { var map = new ol.Map({ layers: [ new ol.layer.VectorTile({ + renderMode: 'vector', preload: Infinity, source: new ol.source.VectorTile({ attributions: '© Mapbox ' + diff --git a/externs/olx.js b/externs/olx.js index 730c14f24c..a1f80ec65d 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -3798,6 +3798,7 @@ olx.layer.VectorOptions.prototype.visible; * maxResolution: (number|undefined), * opacity: (number|undefined), * renderBuffer: (number|undefined), + * renderMode: (ol.layer.VectorTileRenderType|string|undefined), * renderOrder: (function(ol.Feature, ol.Feature):number|undefined), * source: (ol.source.VectorTile|undefined), * style: (ol.style.Style|Array.|ol.style.StyleFunction|undefined), @@ -3822,6 +3823,22 @@ olx.layer.VectorTileOptions; olx.layer.VectorTileOptions.prototype.renderBuffer; +/** + * Render mode for vector tiles: + * * `'image'`: Vector tiles are rendered as images. Great performance, but + * point symbols and texts are always rotated with the view and pixels are + * scaled during zoom animations. + * * `'hybrid'`: Polygon and line elements are rendered as images, so pixels + * are scaled during zoom animations. Point symbols and texts are accurately + * rendered as vectors and can stay upright on rotated views. + * * `'vector'`: Vector tiles are rendered as vectors. Most accurate rendering + * even during animations, but slower performance than the other options. + * The default is `'hybrid'`. + * @type {ol.layer.VectorTileRenderType|string|undefined} + * @api + */ +olx.layer.VectorTileOptions.prototype.renderMode; + /** * Render order. Function to be used when sorting features before rendering. By * default features are drawn in the order that they are created. @@ -4361,7 +4378,6 @@ olx.source.TileImageOptions.prototype.wrapX; * cacheSize: (number|undefined), * format: (ol.format.Feature|undefined), * logo: (string|olx.LogoOptions|undefined), - * opaque: (boolean|undefined), * projection: ol.proj.ProjectionLike, * state: (ol.source.State|undefined), * tileClass: (function(new: ol.VectorTile, ol.TileCoord, @@ -4412,14 +4428,6 @@ olx.source.VectorTileOptions.prototype.format; olx.source.VectorTileOptions.prototype.logo; -/** - * Whether the layer is opaque. - * @type {boolean|undefined} - * @api - */ -olx.source.VectorTileOptions.prototype.opaque; - - /** * Projection. * @type {ol.proj.ProjectionLike} diff --git a/src/ol/layer/vectortilelayer.js b/src/ol/layer/vectortilelayer.js index 0631c893b0..a1ecd639b7 100644 --- a/src/ol/layer/vectortilelayer.js +++ b/src/ol/layer/vectortilelayer.js @@ -1,5 +1,6 @@ goog.provide('ol.layer.VectorTile'); +goog.require('goog.asserts'); goog.require('ol.layer.Vector'); goog.require('ol.object'); @@ -13,6 +14,26 @@ ol.layer.VectorTileProperty = { }; +/** + * @enum {string} + * Render mode for vector tiles: + * * `'image'`: Vector tiles are rendered as images. Great performance, but + * point symbols and texts are always rotated with the view and pixels are + * scaled during zoom animations. + * * `'hybrid'`: Polygon and line elements are rendered as images, so pixels + * are scaled during zoom animations. Point symbols and texts are accurately + * rendered as vectors and can stay upright on rotated views. + * * `'vector'`: Vector tiles are rendered as vectors. Most accurate rendering + * even during animations, but slower performance than the other options. + * @api + */ +ol.layer.VectorTileRenderType = { + IMAGE: 'image', + HYBRID: 'hybrid', + VECTOR: 'vector' +}; + + /** * @classdesc * Layer for vector tile data that is rendered client-side. @@ -38,6 +59,18 @@ ol.layer.VectorTile = function(opt_options) { this.setUseInterimTilesOnError(options.useInterimTilesOnError ? options.useInterimTilesOnError : true); + goog.asserts.assert(options.renderMode == undefined || + options.renderMode == ol.layer.VectorTileRenderType.IMAGE || + options.renderMode == ol.layer.VectorTileRenderType.HYBRID || + options.renderMode == ol.layer.VectorTileRenderType.VECTOR, + 'renderMode needs to be \'image\', \'hybrid\' or \'vector\''); + + /** + * @private + * @type {ol.layer.VectorTileRenderType|string} + */ + this.renderMode_ = options.renderMode || ol.layer.VectorTileRenderType.HYBRID; + }; goog.inherits(ol.layer.VectorTile, ol.layer.Vector); @@ -53,6 +86,14 @@ ol.layer.VectorTile.prototype.getPreload = function() { }; +/** + * @return {ol.layer.VectorTileRenderType|string} The render mode. + */ +ol.layer.VectorTile.prototype.getRenderMode = function() { + return this.renderMode_; +}; + + /** * Whether we use interim tiles on error. * @return {boolean} Use interim tiles on error. diff --git a/src/ol/render/canvas/canvasreplay.js b/src/ol/render/canvas/canvasreplay.js index b907b04eb5..976839a403 100644 --- a/src/ol/render/canvas/canvasreplay.js +++ b/src/ol/render/canvas/canvasreplay.js @@ -1995,41 +1995,41 @@ ol.render.canvas.ReplayGroup.prototype.isEmpty = function() { * @param {number} viewRotation View rotation. * @param {Object.} skippedFeaturesHash Ids of features * to skip. - * @param {boolean=} opt_clip Clip at `maxExtent`. Default is true. + * @param {Array.=} opt_replayTypes Ordered replay types + * to replay. Default is {@link ol.render.REPLAY_ORDER} */ ol.render.canvas.ReplayGroup.prototype.replay = function(context, pixelRatio, - transform, viewRotation, skippedFeaturesHash, opt_clip) { + transform, viewRotation, skippedFeaturesHash, opt_replayTypes) { /** @type {Array.} */ var zs = Object.keys(this.replaysByZIndex_).map(Number); zs.sort(ol.array.numberSafeCompareFunction); - if (opt_clip !== false) { - // setup clipping so that the parts of over-simplified geometries are not - // visible outside the current extent when panning - var maxExtent = this.maxExtent_; - var minX = maxExtent[0]; - var minY = maxExtent[1]; - var maxX = maxExtent[2]; - var maxY = maxExtent[3]; - var flatClipCoords = [minX, minY, minX, maxY, maxX, maxY, maxX, minY]; - ol.geom.flat.transform.transform2D( - flatClipCoords, 0, 8, 2, transform, flatClipCoords); - context.save(); - context.beginPath(); - context.moveTo(flatClipCoords[0], flatClipCoords[1]); - context.lineTo(flatClipCoords[2], flatClipCoords[3]); - context.lineTo(flatClipCoords[4], flatClipCoords[5]); - context.lineTo(flatClipCoords[6], flatClipCoords[7]); - context.closePath(); - context.clip(); - } + // setup clipping so that the parts of over-simplified geometries are not + // visible outside the current extent when panning + var maxExtent = this.maxExtent_; + var minX = maxExtent[0]; + var minY = maxExtent[1]; + var maxX = maxExtent[2]; + var maxY = maxExtent[3]; + var flatClipCoords = [minX, minY, minX, maxY, maxX, maxY, maxX, minY]; + ol.geom.flat.transform.transform2D( + flatClipCoords, 0, 8, 2, transform, flatClipCoords); + context.save(); + context.beginPath(); + context.moveTo(flatClipCoords[0], flatClipCoords[1]); + context.lineTo(flatClipCoords[2], flatClipCoords[3]); + context.lineTo(flatClipCoords[4], flatClipCoords[5]); + context.lineTo(flatClipCoords[6], flatClipCoords[7]); + context.closePath(); + context.clip(); + var replayTypes = opt_replayTypes ? opt_replayTypes : ol.render.REPLAY_ORDER; var i, ii, j, jj, replays, replay; for (i = 0, ii = zs.length; i < ii; ++i) { replays = this.replaysByZIndex_[zs[i].toString()]; - for (j = 0, jj = ol.render.REPLAY_ORDER.length; j < jj; ++j) { - replay = replays[ol.render.REPLAY_ORDER[j]]; + for (j = 0, jj = replayTypes.length; j < jj; ++j) { + replay = replays[replayTypes[j]]; if (replay !== undefined) { replay.replay(context, pixelRatio, transform, viewRotation, skippedFeaturesHash); diff --git a/src/ol/renderer/canvas/canvastilelayerrenderer.js b/src/ol/renderer/canvas/canvastilelayerrenderer.js index 34ef387313..583f7a404f 100644 --- a/src/ol/renderer/canvas/canvastilelayerrenderer.js +++ b/src/ol/renderer/canvas/canvastilelayerrenderer.js @@ -9,9 +9,9 @@ goog.require('ol.TileState'); goog.require('ol.array'); goog.require('ol.dom'); goog.require('ol.extent'); -goog.require('ol.layer.Tile'); goog.require('ol.render.EventType'); goog.require('ol.renderer.canvas.Layer'); +goog.require('ol.size'); goog.require('ol.source.Tile'); goog.require('ol.vec.Mat4'); @@ -19,29 +19,29 @@ goog.require('ol.vec.Mat4'); /** * @constructor * @extends {ol.renderer.canvas.Layer} - * @param {ol.layer.Tile} tileLayer Tile layer. + * @param {ol.layer.Tile|ol.layer.VectorTile} tileLayer Tile layer. */ ol.renderer.canvas.TileLayer = function(tileLayer) { goog.base(this, tileLayer); /** - * @private + * @protected * @type {CanvasRenderingContext2D} */ - this.context_ = ol.dom.createCanvasContext2D(); + this.context = ol.dom.createCanvasContext2D(); /** - * @private + * @protected * @type {Array.} */ - this.renderedTiles_ = null; + this.renderedTiles = null; /** - * @private + * @protected * @type {ol.Extent} */ - this.tmpExtent_ = ol.extent.createEmpty(); + this.tmpExtent = ol.extent.createEmpty(); /** * @private @@ -55,6 +55,12 @@ ol.renderer.canvas.TileLayer = function(tileLayer) { */ this.imageTransform_ = goog.vec.Mat4.createNumber(); + /** + * @protected + * @type {number} + */ + this.zDirection = 0; + }; goog.inherits(ol.renderer.canvas.TileLayer, ol.renderer.canvas.Layer); @@ -64,127 +70,9 @@ goog.inherits(ol.renderer.canvas.TileLayer, ol.renderer.canvas.Layer); */ ol.renderer.canvas.TileLayer.prototype.composeFrame = function( frameState, layerState, context) { - var pixelRatio = frameState.pixelRatio; - var viewState = frameState.viewState; - var center = viewState.center; - var projection = viewState.projection; - var resolution = viewState.resolution; - var rotation = viewState.rotation; - var size = frameState.size; - var offsetX = Math.round(pixelRatio * size[0] / 2); - var offsetY = Math.round(pixelRatio * size[1] / 2); - var pixelScale = pixelRatio / resolution; - var layer = this.getLayer(); - var source = layer.getSource(); - goog.asserts.assertInstanceof(source, ol.source.Tile, - 'source is an ol.source.Tile'); - var tileGutter = source.getGutter(projection); - var transform = this.getTransform(frameState, 0); - this.dispatchPreComposeEvent(context, frameState, transform); - - var renderContext = context; - var hasRenderListeners = layer.hasListener(ol.render.EventType.RENDER); - var drawOffsetX, drawOffsetY, drawScale, drawSize; - if (rotation || hasRenderListeners) { - renderContext = this.context_; - var renderCanvas = renderContext.canvas; - var tilePixelRatio = source.getTilePixelRatio(pixelRatio); - drawScale = tilePixelRatio / pixelRatio; - var width = context.canvas.width * drawScale; - var height = context.canvas.height * drawScale; - // Make sure the canvas is big enough for all possible rotation angles - drawSize = Math.round(Math.sqrt(width * width + height * height)); - if (renderCanvas.width != drawSize) { - renderCanvas.width = renderCanvas.height = drawSize; - } else { - renderContext.clearRect(0, 0, drawSize, drawSize); - } - drawOffsetX = (drawSize - width) / 2 / drawScale; - drawOffsetY = (drawSize - height) / 2 / drawScale; - pixelScale *= drawScale; - offsetX = Math.round(drawScale * (offsetX + drawOffsetX)) - offsetY = Math.round(drawScale * (offsetY + drawOffsetY)); - } - - // 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 - var alpha = renderContext.globalAlpha; - renderContext.globalAlpha = layerState.opacity; - - var tileGrid = source.getTileGridForProjection(projection); - var tilesToDraw = this.renderedTiles_; - - var pixelExtents; - var opaque = source.getOpaque(projection) && layerState.opacity == 1; - if (!opaque) { - tilesToDraw.reverse(); - pixelExtents = []; - } - for (var i = 0, ii = tilesToDraw.length; i < ii; ++i) { - var tile = tilesToDraw[i]; - var tileCoord = tile.getTileCoord(); - var tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent_); - var currentZ = tileCoord[0]; - // Calculate all insert points by tile widths from a common origin to avoid - // gaps caused by rounding - var origin = ol.extent.getBottomLeft(tileGrid.getTileCoordExtent( - tileGrid.getTileCoordForCoordAndZ(center, currentZ, this.tmpTileCoord_))); - var w = Math.round(ol.extent.getWidth(tileExtent) * pixelScale); - var h = Math.round(ol.extent.getHeight(tileExtent) * pixelScale); - var left = Math.round((tileExtent[0] - origin[0]) * pixelScale / w) * w + - offsetX + Math.round((origin[0] - center[0]) * pixelScale); - var top = Math.round((origin[1] - tileExtent[3]) * pixelScale / h) * h + - offsetY + Math.round((center[1] - origin[1]) * pixelScale); - if (!opaque) { - var pixelExtent = [left, top, left + w, top + h]; - // Create a clip mask for regions in this low resolution tile that are - // already filled by a higher resolution tile - renderContext.save(); - for (var j = 0, jj = pixelExtents.length; j < jj; ++j) { - var clipExtent = pixelExtents[j]; - if (ol.extent.intersects(pixelExtent, clipExtent)) { - renderContext.beginPath(); - // counter-clockwise (outer ring) for current tile - renderContext.moveTo(pixelExtent[0], pixelExtent[1]); - renderContext.lineTo(pixelExtent[0], pixelExtent[3]); - renderContext.lineTo(pixelExtent[2], pixelExtent[3]); - renderContext.lineTo(pixelExtent[2], pixelExtent[1]); - // clockwise (inner ring) for higher resolution tile - renderContext.moveTo(clipExtent[0], clipExtent[1]); - renderContext.lineTo(clipExtent[2], clipExtent[1]); - renderContext.lineTo(clipExtent[2], clipExtent[3]); - renderContext.lineTo(clipExtent[0], clipExtent[3]); - renderContext.closePath(); - renderContext.clip(); - } - } - pixelExtents.push(pixelExtent); - } - var tilePixelSize = source.getTilePixelSize(currentZ, pixelRatio, projection); - renderContext.drawImage(tile.getImage(), tileGutter, tileGutter, - tilePixelSize[0], tilePixelSize[1], left, top, w, h); - if (!opaque) { - renderContext.restore(); - } - } - - if (hasRenderListeners) { - var dX = drawOffsetX - offsetX / drawScale + offsetX; - var dY = drawOffsetY - offsetY / drawScale + offsetY; - var imageTransform = ol.vec.Mat4.makeTransform2D(this.imageTransform_, - drawSize / 2 - dX, drawSize / 2 - dY, pixelScale, -pixelScale, - -rotation, -center[0] + dX / pixelScale, -center[1] - dY / pixelScale); - this.dispatchRenderEvent(renderContext, frameState, imageTransform); - } - if (rotation || hasRenderListeners) { - context.drawImage(renderContext.canvas, -Math.round(drawOffsetX), - -Math.round(drawOffsetY), drawSize / drawScale, drawSize / drawScale); - } - renderContext.globalAlpha = alpha; - + this.renderTileImages(context, frameState, layerState); this.dispatchPostComposeEvent(context, frameState, transform); }; @@ -200,11 +88,11 @@ ol.renderer.canvas.TileLayer.prototype.prepareFrame = function( var projection = viewState.projection; var tileLayer = this.getLayer(); - goog.asserts.assertInstanceof(tileLayer, ol.layer.Tile, - 'layer is an instance of ol.layer.Tile'); var tileSource = tileLayer.getSource(); + goog.asserts.assertInstanceof(tileSource, ol.source.Tile, + 'source is an ol.source.Tile'); var tileGrid = tileSource.getTileGridForProjection(projection); - var z = tileGrid.getZForResolution(viewState.resolution); + var z = tileGrid.getZForResolution(viewState.resolution, this.zDirection); var tileResolution = tileGrid.getResolution(z); var center = viewState.center; var extent; @@ -291,7 +179,7 @@ ol.renderer.canvas.TileLayer.prototype.prepareFrame = function( } } } - this.renderedTiles_ = renderables; + this.renderedTiles = renderables; this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange); this.manageTilePyramid(frameState, tileSource, tileGrid, pixelRatio, @@ -308,13 +196,13 @@ ol.renderer.canvas.TileLayer.prototype.prepareFrame = function( */ ol.renderer.canvas.TileLayer.prototype.forEachLayerAtPixel = function( pixel, frameState, callback, thisArg) { - var canvas = this.context_.canvas; + var canvas = this.context.canvas; var size = frameState.size; canvas.width = size[0]; canvas.height = size[1]; - this.composeFrame(frameState, this.getLayer().getLayerState(), this.context_); + this.composeFrame(frameState, this.getLayer().getLayerState(), this.context); - var imageData = this.context_.getImageData( + var imageData = this.context.getImageData( pixel[0], pixel[1], 1, 1).data; if (imageData[3] > 0) { @@ -323,3 +211,140 @@ ol.renderer.canvas.TileLayer.prototype.forEachLayerAtPixel = function( return undefined; } }; + + +/** + * @param {CanvasRenderingContext2D} context Context. + * @param {olx.FrameState} frameState Frame state. + * @param {ol.layer.LayerState} layerState Layer state. + * @protected + */ +ol.renderer.canvas.TileLayer.prototype.renderTileImages = function(context, frameState, layerState) { + var pixelRatio = frameState.pixelRatio; + var viewState = frameState.viewState; + var center = viewState.center; + var projection = viewState.projection; + var resolution = viewState.resolution; + var rotation = viewState.rotation; + var size = frameState.size; + var offsetX = Math.round(pixelRatio * size[0] / 2); + var offsetY = Math.round(pixelRatio * size[1] / 2); + var pixelScale = pixelRatio / resolution; + var layer = this.getLayer(); + var source = layer.getSource(); + goog.asserts.assertInstanceof(source, ol.source.Tile, + 'source is an ol.source.Tile'); + var tileGutter = source.getGutter(projection); + var tileGrid = source.getTileGridForProjection(projection); + + var hasRenderListeners = layer.hasListener(ol.render.EventType.RENDER); + var renderContext = context; + var drawOffsetX, drawOffsetY, drawScale, drawSize; + if (rotation || hasRenderListeners) { + renderContext = this.context; + var renderCanvas = renderContext.canvas; + var drawZ = tileGrid.getZForResolution(resolution); + var drawTileSize = source.getTilePixelSize(drawZ, pixelRatio, projection); + var tileSize = ol.size.toSize(tileGrid.getTileSize(drawZ)); + drawScale = drawTileSize[0] / tileSize[0]; + var width = context.canvas.width * drawScale; + var height = context.canvas.height * drawScale; + // Make sure the canvas is big enough for all possible rotation angles + drawSize = Math.round(Math.sqrt(width * width + height * height)); + if (renderCanvas.width != drawSize) { + renderCanvas.width = renderCanvas.height = drawSize; + } else { + renderContext.clearRect(0, 0, drawSize, drawSize); + } + drawOffsetX = (drawSize - width) / 2 / drawScale; + drawOffsetY = (drawSize - height) / 2 / drawScale; + pixelScale *= drawScale; + offsetX = Math.round(drawScale * (offsetX + drawOffsetX)) + offsetY = Math.round(drawScale * (offsetY + drawOffsetY)); + } + // 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 + var alpha = renderContext.globalAlpha; + renderContext.globalAlpha = layerState.opacity; + + var tilesToDraw = this.renderedTiles; + + var pixelExtents; + var opaque = source.getOpaque(projection) && layerState.opacity == 1; + if (!opaque) { + tilesToDraw.reverse(); + pixelExtents = []; + } + for (var i = 0, ii = tilesToDraw.length; i < ii; ++i) { + var tile = tilesToDraw[i]; + var tileCoord = tile.getTileCoord(); + var tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent); + var currentZ = tileCoord[0]; + // Calculate all insert points by tile widths from a common origin to avoid + // gaps caused by rounding + var origin = ol.extent.getBottomLeft(tileGrid.getTileCoordExtent( + tileGrid.getTileCoordForCoordAndZ(center, currentZ, this.tmpTileCoord_))); + var w = Math.round(ol.extent.getWidth(tileExtent) * pixelScale); + var h = Math.round(ol.extent.getHeight(tileExtent) * pixelScale); + var left = Math.round((tileExtent[0] - origin[0]) * pixelScale / w) * w + + offsetX + Math.round((origin[0] - center[0]) * pixelScale); + var top = Math.round((origin[1] - tileExtent[3]) * pixelScale / h) * h + + offsetY + Math.round((center[1] - origin[1]) * pixelScale); + if (!opaque) { + var pixelExtent = [left, top, left + w, top + h]; + // Create a clip mask for regions in this low resolution tile that are + // already filled by a higher resolution tile + renderContext.save(); + for (var j = 0, jj = pixelExtents.length; j < jj; ++j) { + var clipExtent = pixelExtents[j]; + if (ol.extent.intersects(pixelExtent, clipExtent)) { + renderContext.beginPath(); + // counter-clockwise (outer ring) for current tile + renderContext.moveTo(pixelExtent[0], pixelExtent[1]); + renderContext.lineTo(pixelExtent[0], pixelExtent[3]); + renderContext.lineTo(pixelExtent[2], pixelExtent[3]); + renderContext.lineTo(pixelExtent[2], pixelExtent[1]); + // clockwise (inner ring) for higher resolution tile + renderContext.moveTo(clipExtent[0], clipExtent[1]); + renderContext.lineTo(clipExtent[2], clipExtent[1]); + renderContext.lineTo(clipExtent[2], clipExtent[3]); + renderContext.lineTo(clipExtent[0], clipExtent[3]); + renderContext.closePath(); + renderContext.clip(); + } + } + pixelExtents.push(pixelExtent); + } + var tilePixelSize = source.getTilePixelSize(currentZ, pixelRatio, projection); + var image = tile.getImage(); + if (image) { + renderContext.drawImage(image, tileGutter, tileGutter, + tilePixelSize[0], tilePixelSize[1], left, top, w, h); + } + if (!opaque) { + renderContext.restore(); + } + } + + if (hasRenderListeners) { + var dX = drawOffsetX - offsetX / drawScale + offsetX; + var dY = drawOffsetY - offsetY / drawScale + offsetY; + var imageTransform = ol.vec.Mat4.makeTransform2D(this.imageTransform_, + drawSize / 2 - dX, drawSize / 2 - dY, pixelScale, -pixelScale, + -rotation, -center[0] + dX / pixelScale, -center[1] - dY / pixelScale); + this.dispatchRenderEvent(renderContext, frameState, imageTransform); + } + if (rotation || hasRenderListeners) { + context.drawImage(renderContext.canvas, -Math.round(drawOffsetX), + -Math.round(drawOffsetY), drawSize / drawScale, drawSize / drawScale); + } + renderContext.globalAlpha = alpha; +}; + + +/** + * @function + * @return {ol.layer.Tile|ol.layer.VectorTile} + */ +ol.renderer.canvas.TileLayer.prototype.getLayer; diff --git a/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js b/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js index 51a39fa76c..974dea56ce 100644 --- a/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js @@ -4,91 +4,107 @@ goog.require('goog.asserts'); goog.require('ol.events'); goog.require('goog.vec.Mat4'); goog.require('ol.Feature'); -goog.require('ol.TileRange'); -goog.require('ol.TileState'); goog.require('ol.VectorTile'); -goog.require('ol.ViewHint'); goog.require('ol.array'); -goog.require('ol.dom'); goog.require('ol.extent'); -goog.require('ol.geom.flat.transform'); goog.require('ol.layer.VectorTile'); goog.require('ol.proj'); goog.require('ol.proj.Units'); goog.require('ol.render.EventType'); +goog.require('ol.render.canvas'); goog.require('ol.render.canvas.ReplayGroup'); -goog.require('ol.renderer.canvas.Layer'); +goog.require('ol.renderer.canvas.TileLayer'); goog.require('ol.renderer.vector'); goog.require('ol.size'); goog.require('ol.source.VectorTile'); goog.require('ol.vec.Mat4'); +/** + * @const + * @type {!Object.>} + */ +ol.renderer.canvas.IMAGE_REPLAYS = { + 'image': ol.render.REPLAY_ORDER, + 'hybrid': [ol.render.ReplayType.POLYGON, ol.render.ReplayType.LINE_STRING] +}; + + +/** + * @const + * @type {!Object.>} + */ +ol.renderer.canvas.VECTOR_REPLAYS = { + 'hybrid': [ol.render.ReplayType.IMAGE, ol.render.ReplayType.TEXT], + 'vector': ol.render.REPLAY_ORDER +}; + + /** * @constructor - * @extends {ol.renderer.canvas.Layer} + * @extends {ol.renderer.canvas.TileLayer} * @param {ol.layer.VectorTile} layer VectorTile layer. */ ol.renderer.canvas.VectorTileLayer = function(layer) { goog.base(this, layer); - /** - * @private - * @type {CanvasRenderingContext2D} - */ - this.context_ = ol.dom.createCanvasContext2D(); - /** * @private * @type {boolean} */ this.dirty_ = false; - /** - * @private - * @type {Array.} - */ - this.renderedTiles_ = []; - - /** - * @private - * @type {ol.Extent} - */ - this.tmpExtent_ = ol.extent.createEmpty(); - - /** - * @private - * @type {ol.Size} - */ - this.tmpSize_ = [NaN, NaN]; - /** * @private * @type {!goog.vec.Mat4.Number} */ this.tmpTransform_ = goog.vec.Mat4.createNumber(); + // Use lower resolution for pure vector rendering. Closest resolution otherwise. + this.zDirection = + layer.getRenderMode() == ol.layer.VectorTileRenderType.VECTOR ? 1 : 0; + }; -goog.inherits(ol.renderer.canvas.VectorTileLayer, ol.renderer.canvas.Layer); +goog.inherits(ol.renderer.canvas.VectorTileLayer, ol.renderer.canvas.TileLayer); /** * @inheritDoc */ -ol.renderer.canvas.VectorTileLayer.prototype.composeFrame = function(frameState, layerState, context) { +ol.renderer.canvas.VectorTileLayer.prototype.composeFrame = function( + frameState, layerState, context) { + var transform = this.getTransform(frameState, 0); + this.dispatchPreComposeEvent(context, frameState, transform); + this.renderTileImages(context, frameState, layerState); + this.renderTileReplays_(context, frameState, layerState); + this.dispatchPostComposeEvent(context, frameState, transform); +}; + +/** + * @param {CanvasRenderingContext2D} context Context. + * @param {olx.FrameState} frameState Frame state. + * @param {ol.layer.LayerState} layerState Layer state. + * @private + */ +ol.renderer.canvas.VectorTileLayer.prototype.renderTileReplays_ = function( + context, frameState, layerState) { + + var layer = this.getLayer(); + var replays = ol.renderer.canvas.VECTOR_REPLAYS[layer.getRenderMode()]; + if (!replays) { + return; + } var pixelRatio = frameState.pixelRatio; var skippedFeatureUids = layerState.managed ? frameState.skippedFeatureUids : {}; var viewState = frameState.viewState; var center = viewState.center; - var projection = viewState.projection; var resolution = viewState.resolution; var rotation = viewState.rotation; var size = frameState.size; var pixelScale = pixelRatio / resolution; - var layer = this.getLayer(); var source = layer.getSource(); goog.asserts.assertInstanceof(source, ol.source.VectorTile, 'Source is an ol.source.VectorTile'); @@ -96,14 +112,12 @@ ol.renderer.canvas.VectorTileLayer.prototype.composeFrame = function(frameState, var transform = this.getTransform(frameState, 0); - this.dispatchPreComposeEvent(context, frameState, transform); - var replayContext; if (layer.hasListener(ol.render.EventType.RENDER)) { // resize and clear - this.context_.canvas.width = context.canvas.width; - this.context_.canvas.height = context.canvas.height; - replayContext = this.context_; + this.context.canvas.width = context.canvas.width; + this.context.canvas.height = context.canvas.height; + replayContext = this.context; } else { replayContext = context; } @@ -113,80 +127,39 @@ ol.renderer.canvas.VectorTileLayer.prototype.composeFrame = function(frameState, var alpha = replayContext.globalAlpha; replayContext.globalAlpha = layerState.opacity; - var tilesToDraw = this.renderedTiles_; + var tilesToDraw = this.renderedTiles; var tileGrid = source.getTileGrid(); - var currentZ, height, i, ii, insertPoint, insertTransform, offsetX, offsetY; - var origin, pixelSpace, replayState, resolutionRatio, tile, tileCenter; - var tileContext, tileExtent, tilePixelResolution, tilePixelSize; - var tileResolution, tileSize, tileTransform, width; + var currentZ, i, ii, offsetX, offsetY, origin, pixelSpace, replayState; + var tile, tileExtent, tilePixelResolution, tileResolution, tileTransform; for (i = 0, ii = tilesToDraw.length; i < ii; ++i) { tile = tilesToDraw[i]; replayState = tile.getReplayState(); tileExtent = tileGrid.getTileCoordExtent( - tile.getTileCoord(), this.tmpExtent_); + tile.getTileCoord(), this.tmpExtent); currentZ = tile.getTileCoord()[0]; - tileSize = ol.size.toSize(tileGrid.getTileSize(currentZ), this.tmpSize_); pixelSpace = tile.getProjection().getUnits() == ol.proj.Units.TILE_PIXELS; tileResolution = tileGrid.getResolution(currentZ); tilePixelResolution = tileResolution / tilePixelRatio; - resolutionRatio = tileResolution / resolution; offsetX = Math.round(pixelRatio * size[0] / 2); offsetY = Math.round(pixelRatio * size[1] / 2); - width = tileSize[0] * pixelRatio * resolutionRatio; - height = tileSize[1] * pixelRatio * resolutionRatio; - var unscaledPixelTileSize = tileSize[0] * pixelRatio; - if (width < unscaledPixelTileSize / 4 || width > unscaledPixelTileSize * 4) { - if (pixelSpace) { - origin = ol.extent.getTopLeft(tileExtent); - tileTransform = ol.vec.Mat4.makeTransform2D(this.tmpTransform_, - offsetX, offsetY, - pixelScale * tilePixelResolution, - pixelScale * tilePixelResolution, - rotation, - (origin[0] - center[0]) / tilePixelResolution, - (center[1] - origin[1]) / tilePixelResolution); - } else { - tileTransform = transform; - } - replayState.replayGroup.replay(replayContext, pixelRatio, - tileTransform, rotation, skippedFeatureUids); + + if (pixelSpace) { + origin = ol.extent.getTopLeft(tileExtent); + tileTransform = ol.vec.Mat4.makeTransform2D(this.tmpTransform_, + offsetX, offsetY, + pixelScale * tilePixelResolution, + pixelScale * tilePixelResolution, + rotation, + (origin[0] - center[0]) / tilePixelResolution, + (center[1] - origin[1]) / tilePixelResolution); } else { - tilePixelSize = source.getTilePixelSize(currentZ, pixelRatio, projection); - if (pixelSpace) { - tileTransform = ol.vec.Mat4.makeTransform2D(this.tmpTransform_, - 0, 0, - pixelScale * tilePixelResolution, pixelScale * tilePixelResolution, - rotation, - -tilePixelSize[0] / 2, -tilePixelSize[1] / 2); - } else { - tileCenter = ol.extent.getCenter(tileExtent); - tileTransform = ol.vec.Mat4.makeTransform2D(this.tmpTransform_, - 0, 0, - pixelScale, -pixelScale, - -rotation, - -tileCenter[0], -tileCenter[1]); - } - tileContext = tile.getContext(); - if (replayState.resolution !== resolution || - replayState.rotation !== rotation) { - replayState.resolution = resolution; - replayState.rotation = rotation; - tileContext.canvas.width = width + 0.5; - tileContext.canvas.height = height + 0.5; - tileContext.translate(width / 2, height / 2); - tileContext.rotate(-rotation); - replayState.replayGroup.replay(tileContext, pixelRatio, - tileTransform, rotation, skippedFeatureUids, false); - } - insertTransform = ol.vec.Mat4.makeTransform2D(this.tmpTransform_, - 0, 0, pixelScale, -pixelScale, 0, -center[0], -center[1]); - insertPoint = ol.geom.flat.transform.transform2D( - ol.extent.getTopLeft(tileExtent), 0, 1, 2, insertTransform); - replayContext.drawImage(tileContext.canvas, - Math.round(insertPoint[0] + offsetX), - Math.round(insertPoint[1]) + offsetY); + tileTransform = transform; } + ol.render.canvas.rotateAtOffset(replayContext, -rotation, offsetX, offsetY); + replayState.replayGroup.replay(replayContext, pixelRatio, + tileTransform, rotation, skippedFeatureUids, replays); + ol.render.canvas.rotateAtOffset(replayContext, rotation, offsetX, offsetY); } if (replayContext != context) { @@ -194,19 +167,18 @@ ol.renderer.canvas.VectorTileLayer.prototype.composeFrame = function(frameState, context.drawImage(replayContext.canvas, 0, 0); } replayContext.globalAlpha = alpha; - - this.dispatchPostComposeEvent(context, frameState, transform); }; /** * @param {ol.VectorTile} tile Tile. - * @param {ol.layer.VectorTile} layer Vector tile layer. - * @param {number} pixelRatio Pixel ratio. - * @param {ol.proj.Projection} projection Projection. + * @param {olx.FrameState} frameState Frame state. */ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup = function(tile, - layer, pixelRatio, projection) { + frameState) { + var layer = this.getLayer(); + var pixelRatio = frameState.pixelRatio; + var projection = frameState.viewState.projection; var revision = layer.getRevision(); var renderOrder = layer.getRenderOrder() || null; @@ -226,21 +198,20 @@ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup = function(tile, var tileCoord = tile.getTileCoord(); var tileProjection = tile.getProjection(); var pixelSpace = tileProjection.getUnits() == ol.proj.Units.TILE_PIXELS; - var extent, reproject; + var resolution = tileGrid.getResolution(tileCoord[0]); + var extent, reproject, tileResolution; if (pixelSpace) { - var tilePixelSize = source.getTilePixelSize(tileCoord[0], pixelRatio, - tile.getProjection()); - extent = [0, 0, tilePixelSize[0], tilePixelSize[1]]; + var tilePixelRatio = tileResolution = source.getTilePixelRatio(pixelRatio); + var tileSize = ol.size.toSize(tileGrid.getTileSize(tileCoord[0])); + extent = [0, 0, tileSize[0] * tilePixelRatio, tileSize[1] * tilePixelRatio]; } else { + tileResolution = resolution; extent = tileGrid.getTileCoordExtent(tileCoord); if (!ol.proj.equivalent(projection, tileProjection)) { reproject = true; tile.setProjection(projection); } } - var resolution = tileGrid.getResolution(tileCoord[0]); - var tileResolution = - pixelSpace ? source.getTilePixelRatio(pixelRatio) : resolution; replayState.dirty = false; var replayGroup = new ol.render.canvas.ReplayGroup(0, extent, tileResolution, layer.getRenderBuffer()); @@ -307,7 +278,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.forEachFeatureAtCoordinate = functi /** @type {Object.} */ var features = {}; - var replayables = this.renderedTiles_; + var replayables = this.renderedTiles; var source = layer.getSource(); goog.asserts.assertInstanceof(source, ol.source.VectorTile, 'Source is an ol.source.VectorTile'); @@ -319,7 +290,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.forEachFeatureAtCoordinate = functi tile = replayables[i]; tileCoord = tile.getTileCoord(); tileExtent = source.getTileGrid().getTileCoordExtent(tileCoord, - this.tmpExtent_); + this.tmpExtent); if (!ol.extent.containsCoordinate(tileExtent, coordinate)) { continue; } @@ -369,116 +340,17 @@ ol.renderer.canvas.VectorTileLayer.prototype.handleStyleImageChange_ = function( * @inheritDoc */ ol.renderer.canvas.VectorTileLayer.prototype.prepareFrame = function(frameState, layerState) { - var layer = /** @type {ol.layer.Vector} */ (this.getLayer()); - goog.asserts.assertInstanceof(layer, ol.layer.VectorTile, - 'layer is an instance of ol.layer.VectorTile'); - var source = layer.getSource(); - goog.asserts.assertInstanceof(source, ol.source.VectorTile, - 'Source is an ol.source.VectorTile'); - - this.updateAttributions( - frameState.attributions, source.getAttributions()); - this.updateLogos(frameState, source); - - var animating = frameState.viewHints[ol.ViewHint.ANIMATING]; - var interacting = frameState.viewHints[ol.ViewHint.INTERACTING]; - var updateWhileAnimating = layer.getUpdateWhileAnimating(); - var updateWhileInteracting = layer.getUpdateWhileInteracting(); - - if (!this.dirty_ && (!updateWhileAnimating && animating) || - (!updateWhileInteracting && interacting)) { - return true; - } - - var extent = frameState.extent; - if (layerState.extent) { - extent = ol.extent.getIntersection(extent, layerState.extent); - } - if (ol.extent.isEmpty(extent)) { - // Return false to prevent the rendering of the layer. - return false; - } - - var viewState = frameState.viewState; - var projection = viewState.projection; - var resolution = viewState.resolution; - var pixelRatio = frameState.pixelRatio; - - var tileGrid = source.getTileGrid(); - var resolutions = tileGrid.getResolutions(); - var z = resolutions.length - 1; - while (z > 0 && resolutions[z] < resolution) { - --z; - } - var tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z); - this.updateUsedTiles(frameState.usedTiles, source, z, tileRange); - this.manageTilePyramid(frameState, source, tileGrid, pixelRatio, - projection, extent, z, layer.getPreload()); - this.scheduleExpireCache(frameState, source); - - /** - * @type {Object.>} - */ - var tilesToDrawByZ = {}; - tilesToDrawByZ[z] = {}; - - var findLoadedTiles = this.createLoadedTileFinder(source, projection, - tilesToDrawByZ); - - var useInterimTilesOnError = layer.getUseInterimTilesOnError(); - - var tmpExtent = this.tmpExtent_; - var tmpTileRange = new ol.TileRange(0, 0, 0, 0); - var childTileRange, fullyLoaded, tile, tileState, x, y; - for (x = tileRange.minX; x <= tileRange.maxX; ++x) { - for (y = tileRange.minY; y <= tileRange.maxY; ++y) { - - tile = source.getTile(z, x, y, pixelRatio, projection); - goog.asserts.assertInstanceof(tile, ol.VectorTile, - 'Tile is an ol.VectorTile'); - tileState = tile.getState(); - if (tileState == ol.TileState.LOADED || - tileState == ol.TileState.EMPTY || - (tileState == ol.TileState.ERROR && !useInterimTilesOnError)) { - tilesToDrawByZ[z][tile.tileCoord.toString()] = tile; - continue; - } - - fullyLoaded = tileGrid.forEachTileCoordParentTileRange( - tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent); - if (!fullyLoaded) { - childTileRange = tileGrid.getTileCoordChildTileRange( - tile.tileCoord, tmpTileRange, tmpExtent); - if (childTileRange) { - findLoadedTiles(z + 1, childTileRange); - } - } - + var prepared = goog.base(this, 'prepareFrame', frameState, layerState); + if (prepared) { + var skippedFeatures = Object.keys(frameState.skippedFeatureUids_ || {}); + for (var i = 0, ii = this.renderedTiles.length; i < ii; ++i) { + var tile = this.renderedTiles[i]; + goog.asserts.assertInstanceof(tile, ol.VectorTile, 'got an ol.VectorTile'); + this.createReplayGroup(tile, frameState); + this.renderTileImage(tile, frameState, layerState, skippedFeatures); } } - - this.dirty_ = false; - - /** @type {Array.} */ - var zs = Object.keys(tilesToDrawByZ).map(Number); - zs.sort(ol.array.numberSafeCompareFunction); - var replayables = []; - var i, ii, currentZ, tileCoordKey, tilesToDraw; - for (i = 0, ii = zs.length; i < ii; ++i) { - currentZ = zs[i]; - tilesToDraw = tilesToDrawByZ[currentZ]; - for (tileCoordKey in tilesToDraw) { - tile = tilesToDraw[tileCoordKey]; - if (tile.getState() == ol.TileState.LOADED) { - replayables.push(tile); - this.createReplayGroup(tile, layer, pixelRatio, projection); - } - } - } - - this.renderedTiles_ = replayables; - - return true; + return prepared; }; @@ -508,3 +380,65 @@ ol.renderer.canvas.VectorTileLayer.prototype.renderFeature = function(feature, s } return loading; }; + + +/** + * @param {ol.VectorTile} tile Tile. + * @param {olx.FrameState} frameState Frame state. + * @param {ol.layer.LayerState} layerState Layer state. + * @param {Array.} skippedFeatures Skipped features. + */ +ol.renderer.canvas.VectorTileLayer.prototype.renderTileImage = function( + tile, frameState, layerState, skippedFeatures) { + var layer = this.getLayer(); + var replays = ol.renderer.canvas.IMAGE_REPLAYS[layer.getRenderMode()]; + if (!replays) { + return; + } + var pixelRatio = frameState.pixelRatio; + var replayState = tile.getReplayState(); + var revision = layer.getRevision(); + if (!ol.array.equals(replayState.skippedFeatures, skippedFeatures) || + replayState.renderedTileRevision !== revision) { + replayState.skippedFeatures = skippedFeatures; + replayState.renderedTileRevision = revision; + var tileContext = tile.getContext(); + var source = layer.getSource(); + var tileGrid = source.getTileGrid(); + var currentZ = tile.getTileCoord()[0]; + var resolution = tileGrid.getResolution(currentZ); + var tileSize = ol.size.toSize(tileGrid.getTileSize(currentZ)); + var tileResolution = tileGrid.getResolution(currentZ); + var resolutionRatio = tileResolution / resolution; + var width = tileSize[0] * pixelRatio * resolutionRatio; + var height = tileSize[1] * pixelRatio * resolutionRatio; + tileContext.canvas.width = width / resolutionRatio + 0.5; + tileContext.canvas.height = height / resolutionRatio + 0.5; + tileContext.scale(1 / resolutionRatio, 1 / resolutionRatio) + tileContext.translate(width / 2, height / 2); + var pixelSpace = tile.getProjection().getUnits() == ol.proj.Units.TILE_PIXELS; + var pixelScale = pixelRatio / resolution; + var tilePixelRatio = source.getTilePixelRatio(pixelRatio); + var tilePixelResolution = tileResolution / tilePixelRatio; + var tileExtent = tileGrid.getTileCoordExtent( + tile.getTileCoord(), this.tmpExtent); + var tileTransform; + if (pixelSpace) { + tileTransform = ol.vec.Mat4.makeTransform2D(this.tmpTransform_, + 0, 0, + pixelScale * tilePixelResolution, pixelScale * tilePixelResolution, + 0, + -tileSize[0] * tilePixelRatio / 2, -tileSize[1] * tilePixelRatio / 2); + } else { + var tileCenter = ol.extent.getCenter(tileExtent); + tileTransform = ol.vec.Mat4.makeTransform2D(this.tmpTransform_, + 0, 0, + pixelScale, -pixelScale, + 0, + -tileCenter[0], -tileCenter[1]); + } + + replayState.replayGroup.replay(tileContext, pixelRatio, + tileTransform, 0, frameState.skippedFeatureUids || {}, replays); + } +} diff --git a/src/ol/source/vectortilesource.js b/src/ol/source/vectortilesource.js index bbb6a825d6..8b7a57adba 100644 --- a/src/ol/source/vectortilesource.js +++ b/src/ol/source/vectortilesource.js @@ -5,6 +5,7 @@ goog.require('ol.VectorTile'); goog.require('ol.events'); goog.require('ol.events.EventType'); goog.require('ol.featureloader'); +goog.require('ol.size'); goog.require('ol.source.UrlTile'); @@ -31,7 +32,7 @@ ol.source.VectorTile = function(options) { cacheSize: options.cacheSize !== undefined ? options.cacheSize : 128, extent: options.extent, logo: options.logo, - opaque: options.opaque, + opaque: false, projection: options.projection, state: options.state, tileGrid: options.tileGrid, @@ -88,6 +89,15 @@ ol.source.VectorTile.prototype.getTile = function(z, x, y, pixelRatio, projectio }; +/** + * @inheritDoc + */ +ol.source.VectorTile.prototype.getTilePixelSize = function(z, pixelRatio, projection) { + var tileSize = ol.size.toSize(this.tileGrid.getTileSize(z)); + return [tileSize[0] * pixelRatio, tileSize[1] * pixelRatio]; +}; + + /** * @param {ol.VectorTile} vectorTile Vector tile. * @param {string} url URL. diff --git a/src/ol/tilegrid/tilegrid.js b/src/ol/tilegrid/tilegrid.js index 859fd62217..2c6717c393 100644 --- a/src/ol/tilegrid/tilegrid.js +++ b/src/ol/tilegrid/tilegrid.js @@ -472,10 +472,15 @@ ol.tilegrid.TileGrid.prototype.getFullTileRange = function(z) { /** * @param {number} resolution Resolution. + * @param {number=} opt_direction If 0, the nearest resolution will be used. + * If 1, the nearest lower resolution will be used. If -1, the nearest + * higher resolution will be used. Default is 0. * @return {number} Z. */ -ol.tilegrid.TileGrid.prototype.getZForResolution = function(resolution) { - var z = ol.array.linearFindNearest(this.resolutions_, resolution, 0); +ol.tilegrid.TileGrid.prototype.getZForResolution = function( + resolution, opt_direction) { + var z = ol.array.linearFindNearest(this.resolutions_, resolution, + opt_direction || 0); return ol.math.clamp(z, this.minZoom, this.maxZoom); }; diff --git a/src/ol/vectortile.js b/src/ol/vectortile.js index 85fb41e349..5aa00d4e00 100644 --- a/src/ol/vectortile.js +++ b/src/ol/vectortile.js @@ -12,8 +12,10 @@ goog.require('ol.proj.Projection'); * @typedef {{ * dirty: boolean, * renderedRenderOrder: (null|function(ol.Feature, ol.Feature):number), + * renderedTileRevision: number, * renderedRevision: number, - * replayGroup: ol.render.IReplayGroup}} + * replayGroup: ol.render.IReplayGroup, + * skippedFeatures: Array.}} */ ol.TileReplayState; @@ -70,7 +72,9 @@ ol.VectorTile = function(tileCoord, state, src, format, tileLoadFunction) { dirty: false, renderedRenderOrder: null, renderedRevision: -1, - replayGroup: null + renderedTileRevision: -1, + replayGroup: null, + skippedFeatures: [] }; /** @@ -100,8 +104,9 @@ ol.VectorTile.prototype.getContext = function() { /** * @inheritDoc */ -ol.VectorTile.prototype.disposeInternal = function() { - goog.base(this, 'disposeInternal'); +ol.VectorTile.prototype.getImage = function() { + return this.replayState_.renderedTileRevision == -1 ? + null : this.context_.canvas; }; diff --git a/test/spec/ol/layer/vectortilelayer.test.js b/test/spec/ol/layer/vectortilelayer.test.js index fcbbde71c2..44a5a73495 100644 --- a/test/spec/ol/layer/vectortilelayer.test.js +++ b/test/spec/ol/layer/vectortilelayer.test.js @@ -28,6 +28,29 @@ describe('ol.layer.VectorTile', function() { expect(layer.getUseInterimTilesOnError()).to.be(true); }); + it('provides default renderMode', function() { + expect(layer.getRenderMode()).to.be('hybrid'); + }) + + }); + + describe('constructor (options)', function() { + var layer = new ol.layer.VectorTile({ + renderMode: 'vector', + source: new ol.source.VectorTile({}) + }); + expect(layer.getRenderMode()).to.be('vector'); + layer = new ol.layer.VectorTile({ + renderMode: 'image', + source: new ol.source.VectorTile({}) + }); + expect(layer.getRenderMode()).to.be('image'); + expect(function() { + layer = new ol.layer.VectorTile({ + renderMode: 'foo', + source: new ol.source.VectorTile({}) + }); + }).to.throwException(); }); }); diff --git a/test/spec/ol/renderer/canvas/canvasvectortilelayerrenderer.test.js b/test/spec/ol/renderer/canvas/canvasvectortilelayerrenderer.test.js index e29d0ac0d2..0b1f13ccfa 100644 --- a/test/spec/ol/renderer/canvas/canvasvectortilelayerrenderer.test.js +++ b/test/spec/ol/renderer/canvas/canvasvectortilelayerrenderer.test.js @@ -52,16 +52,24 @@ describe('ol.renderer.canvas.VectorTileLayer', function() { map.addLayer(layer); }); - it('creates a new instance', function() { - var renderer = new ol.renderer.canvas.VectorTileLayer(layer); - expect(renderer).to.be.a(ol.renderer.canvas.VectorTileLayer); - }); - afterEach(function() { document.body.removeChild(target); map.dispose(); }); + it('creates a new instance', function() { + var renderer = new ol.renderer.canvas.VectorTileLayer(layer); + expect(renderer).to.be.a(ol.renderer.canvas.VectorTileLayer); + expect(renderer.zDirection).to.be(0); + }); + + it('uses lower resolution for pure vector rendering', function() { + layer.renderMode_ = 'vector'; + var renderer = new ol.renderer.canvas.VectorTileLayer(layer); + expect(renderer).to.be.a(ol.renderer.canvas.VectorTileLayer); + expect(renderer.zDirection).to.be(1); + }); + it('gives precedence to feature styles over layer styles', function() { var spy = sinon.spy(map.getRenderer().getLayerRenderer(layer), 'renderFeature'); @@ -134,7 +142,7 @@ describe('ol.renderer.canvas.VectorTileLayer', function() { } }; frameState.layerStates[goog.getUid(layer)] = {}; - renderer.renderedTiles_ = [new TileClass([0, 0, -1])]; + renderer.renderedTiles = [new TileClass([0, 0, -1])]; renderer.forEachFeatureAtCoordinate( coordinate, frameState, spy, undefined); expect(spy.callCount).to.be(1); diff --git a/test/spec/ol/tilegrid/tilegrid.test.js b/test/spec/ol/tilegrid/tilegrid.test.js index 08ed45679e..9599cbb0cb 100644 --- a/test/spec/ol/tilegrid/tilegrid.test.js +++ b/test/spec/ol/tilegrid/tilegrid.test.js @@ -1028,6 +1028,53 @@ describe('ol.tilegrid.TileGrid', function() { }); }); + describe('getZForResolution (lower)', function() { + it('returns the expected z value', function() { + var tileGrid = new ol.tilegrid.TileGrid({ + resolutions: resolutions, + origin: origin, + tileSize: tileSize + }); + + expect(tileGrid.getZForResolution(2000, 1)).to.eql(0); + expect(tileGrid.getZForResolution(1000, 1)).to.eql(0); + expect(tileGrid.getZForResolution(900, 1)).to.eql(0); + expect(tileGrid.getZForResolution(750, 1)).to.eql(0); + expect(tileGrid.getZForResolution(625, 1)).to.eql(0); + expect(tileGrid.getZForResolution(500, 1)).to.eql(1); + expect(tileGrid.getZForResolution(475, 1)).to.eql(1); + expect(tileGrid.getZForResolution(375, 1)).to.eql(1); + expect(tileGrid.getZForResolution(250, 1)).to.eql(2); + expect(tileGrid.getZForResolution(200, 1)).to.eql(2); + expect(tileGrid.getZForResolution(125, 1)).to.eql(2); + expect(tileGrid.getZForResolution(100, 1)).to.eql(3); + expect(tileGrid.getZForResolution(50, 1)).to.eql(3); + }); + }); + + describe('getZForResolution (higher)', function() { + it('returns the expected z value', function() { + var tileGrid = new ol.tilegrid.TileGrid({ + resolutions: resolutions, + origin: origin, + tileSize: tileSize + }); + + expect(tileGrid.getZForResolution(2000, -1)).to.eql(0); + expect(tileGrid.getZForResolution(1000, -1)).to.eql(0); + expect(tileGrid.getZForResolution(900, -1)).to.eql(1); + expect(tileGrid.getZForResolution(750, -1)).to.eql(1); + expect(tileGrid.getZForResolution(625, -1)).to.eql(1); + expect(tileGrid.getZForResolution(500, -1)).to.eql(1); + expect(tileGrid.getZForResolution(475, -1)).to.eql(2); + expect(tileGrid.getZForResolution(375, -1)).to.eql(2); + expect(tileGrid.getZForResolution(250, -1)).to.eql(2); + expect(tileGrid.getZForResolution(200, -1)).to.eql(3); + expect(tileGrid.getZForResolution(125, -1)).to.eql(3); + expect(tileGrid.getZForResolution(100, -1)).to.eql(3); + expect(tileGrid.getZForResolution(50, -1)).to.eql(3); + }); + }); }); goog.require('ol.Coordinate');