From 785e7135a713d5885df5e2edcf8b36374e457b81 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Mon, 8 May 2017 17:19:55 +0200 Subject: [PATCH 1/5] Decouple source and rendered tile grid of vector tile sources --- examples/mapbox-vector-tiles-advanced.html | 2 +- examples/mapbox-vector-tiles-advanced.js | 26 +- externs/olx.js | 2 +- src/ol/imagetile.js | 4 +- src/ol/renderer/canvas/vectortilelayer.js | 352 ++++++++++-------- src/ol/reproj/tile.js | 3 +- src/ol/source/tiledebug.js | 1 - src/ol/source/tileutfgrid.js | 1 - src/ol/source/vectortile.js | 33 +- src/ol/tile.js | 8 - src/ol/tilegrid.js | 2 +- src/ol/typedefs.js | 3 +- src/ol/vectorimagetile.js | 288 ++++++++++++++ src/ol/vectortile.js | 70 +--- .../renderer/canvas/vectortilelayer.test.js | 34 +- test/spec/ol/source/vectortile.test.js | 4 +- test/spec/ol/vectorimagetile.test.js | 43 +++ test/spec/ol/vectortile.test.js | 31 +- 18 files changed, 620 insertions(+), 287 deletions(-) create mode 100644 src/ol/vectorimagetile.js create mode 100644 test/spec/ol/vectorimagetile.test.js diff --git a/examples/mapbox-vector-tiles-advanced.html b/examples/mapbox-vector-tiles-advanced.html index 62db367a3c..77e0c9c62a 100644 --- a/examples/mapbox-vector-tiles-advanced.html +++ b/examples/mapbox-vector-tiles-advanced.html @@ -3,7 +3,7 @@ layout: example.html title: Advanced Mapbox Vector Tiles shortdesc: Example of a Mapbox vector tiles map with custom tile grid. docs: > - A vector tiles map which reuses the same tiles for subsequent zoom levels to save bandwidth on mobile devices. **Note**: No map will be visible when the access token has expired. + A vector tiles map which reuses the same source tiles for subsequent zoom levels to save bandwidth on mobile devices. **Note**: No map will be visible when the access token has expired. tags: "mapbox, vector, tiles, mobile" resources: - resources/mapbox-streets-v6-style.js diff --git a/examples/mapbox-vector-tiles-advanced.js b/examples/mapbox-vector-tiles-advanced.js index 31718d77e5..3dfc4950c5 100644 --- a/examples/mapbox-vector-tiles-advanced.js +++ b/examples/mapbox-vector-tiles-advanced.js @@ -15,25 +15,16 @@ goog.require('ol.tilegrid.TileGrid'); var key = 'pk.eyJ1IjoiYWhvY2V2YXIiLCJhIjoiRk1kMWZaSSJ9.E5BkluenyWQMsBLsuByrmg'; -// For how many zoom levels do we want to use the same vector tiles? -// 1 means "use tiles from all zoom levels". 2 means "use the same tiles for 2 -// subsequent zoom levels". -var reuseZoomLevels = 2; - -// Offset of loaded tiles from web mercator zoom level 0. -// 0 means "At map zoom level 0, use tiles from zoom level 0". 1 means "At map -// zoom level 0, use tiles from zoom level 1". -var zoomOffset = 1; - -// Calculation of tile urls +// Calculation of resolutions that match zoom levels 1, 3, 5, 7, 9, 11, 13, 15. var resolutions = []; -for (var z = zoomOffset / reuseZoomLevels; z <= 22 / reuseZoomLevels; ++z) { - resolutions.push(156543.03392804097 / Math.pow(2, z * reuseZoomLevels)); +for (var i = 0; i <= 7; ++i) { + resolutions.push(156543.03392804097 / Math.pow(2, i * 2)); } +// Calculation of tile urls for zoom levels 1, 3, 5, 7, 9, 11, 13, 15. function tileUrlFunction(tileCoord) { return ('https://{a-d}.tiles.mapbox.com/v4/mapbox.mapbox-streets-v6/' + '{z}/{x}/{y}.vector.pbf?access_token=' + key) - .replace('{z}', String(tileCoord[0] * reuseZoomLevels + zoomOffset)) + .replace('{z}', String(tileCoord[0] * 2 - 1)) .replace('{x}', String(tileCoord[1])) .replace('{y}', String(-tileCoord[2] - 1)) .replace('{a-d}', 'abcd'.substr( @@ -43,8 +34,6 @@ function tileUrlFunction(tileCoord) { var map = new ol.Map({ layers: [ new ol.layer.VectorTile({ - renderMode: 'vector', - preload: Infinity, source: new ol.source.VectorTile({ attributions: '© Mapbox ' + '© ' + @@ -52,9 +41,10 @@ var map = new ol.Map({ format: new ol.format.MVT(), tileGrid: new ol.tilegrid.TileGrid({ extent: ol.proj.get('EPSG:3857').getExtent(), - resolutions: resolutions + resolutions: resolutions, + tileSize: 512 }), - tilePixelRatio: 16, + tilePixelRatio: 8, tileUrlFunction: tileUrlFunction }), style: createMapboxStreetsV6Style() diff --git a/externs/olx.js b/externs/olx.js index a47e678e71..ad3e61083c 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -4864,7 +4864,7 @@ olx.source.VectorTileOptions.prototype.state; /** - * Class used to instantiate image tiles. Default is {@link ol.VectorTile}. + * Class used to instantiate vector tiles. Default is {@link ol.VectorTile}. * @type {function(new: ol.VectorTile, ol.TileCoord, * ol.TileState, string, ol.format.Feature, * ol.TileLoadFunctionType)|undefined} diff --git a/src/ol/imagetile.js b/src/ol/imagetile.js index c10af92364..a474374a01 100644 --- a/src/ol/imagetile.js +++ b/src/ol/imagetile.js @@ -70,8 +70,8 @@ ol.ImageTile.prototype.disposeInternal = function() { /** - * Get the image element for this tile. - * @inheritDoc + * Get the HTML image element for this tile (may be a Canvas, Image, or Video). + * @return {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} Image. * @api */ ol.ImageTile.prototype.getImage = function() { diff --git a/src/ol/renderer/canvas/vectortilelayer.js b/src/ol/renderer/canvas/vectortilelayer.js index a7f4b38279..1a3b71ea6e 100644 --- a/src/ol/renderer/canvas/vectortilelayer.js +++ b/src/ol/renderer/canvas/vectortilelayer.js @@ -1,6 +1,7 @@ goog.provide('ol.renderer.canvas.VectorTileLayer'); goog.require('ol'); +goog.require('ol.TileState'); goog.require('ol.dom'); goog.require('ol.extent'); goog.require('ol.proj'); @@ -12,7 +13,6 @@ goog.require('ol.render.canvas.ReplayGroup'); goog.require('ol.render.replay'); goog.require('ol.renderer.canvas.TileLayer'); goog.require('ol.renderer.vector'); -goog.require('ol.size'); goog.require('ol.transform'); @@ -23,6 +23,9 @@ goog.require('ol.transform'); */ ol.renderer.canvas.VectorTileLayer = function(layer) { + /** + * @type {CanvasRenderingContext2D} + */ this.context = null; ol.renderer.canvas.TileLayer.call(this, layer); @@ -95,12 +98,12 @@ ol.renderer.canvas.VectorTileLayer.prototype.prepareFrame = function(frameState, /** - * @param {ol.VectorTile} tile Tile. + * @param {ol.VectorImageTile} tile Tile. * @param {olx.FrameState} frameState Frame state. * @private */ -ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup_ = function(tile, - frameState) { +ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup_ = function( + tile, frameState) { var layer = this.getLayer(); var pixelRatio = frameState.pixelRatio; var projection = frameState.viewState.projection; @@ -113,78 +116,89 @@ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup_ = function(tile, return; } - replayState.replayGroup = null; - replayState.dirty = false; + var sourceTiles = tile.getSourceTiles(); + for (var t = 0, tt = sourceTiles.length; t < tt; ++t) { + var sourceTile = sourceTiles[t]; + sourceTile.replayGroup = null; + replayState.dirty = false; - var source = /** @type {ol.source.VectorTile} */ (layer.getSource()); - var tileGrid = source.getTileGrid(); - var tileCoord = tile.tileCoord; - var tileProjection = tile.getProjection(); - var resolution = tileGrid.getResolution(tileCoord[0]); - var extent, reproject, tileResolution; - if (tileProjection.getUnits() == ol.proj.Units.TILE_PIXELS) { - var tilePixelRatio = tileResolution = source.getTilePixelRatio(); - 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); - } - } - replayState.dirty = false; - var replayGroup = new ol.render.canvas.ReplayGroup(0, extent, - tileResolution, source.getOverlaps(), layer.getRenderBuffer()); - var squaredTolerance = ol.renderer.vector.getSquaredTolerance( - tileResolution, pixelRatio); - - /** - * @param {ol.Feature|ol.render.Feature} feature Feature. - * @this {ol.renderer.canvas.VectorTileLayer} - */ - function renderFeature(feature) { - var styles; - var styleFunction = feature.getStyleFunction(); - if (styleFunction) { - styles = styleFunction.call(/** @type {ol.Feature} */ (feature), resolution); + var source = /** @type {ol.source.VectorTile} */ (layer.getSource()); + var sourceTileGrid = source.getTileGrid(); + var sourceTileCoord = sourceTile.tileCoord; + var tileProjection = sourceTile.getProjection(); + var tileGrid = source.getTileGridForProjection(projection); + var resolution = tileGrid.getResolution(tile.tileCoord[0]); + var sourceTileResolution = sourceTileGrid.getResolution(sourceTile.tileCoord[0]); + var tileExtent = tileGrid.getTileCoordExtent(tile.wrappedTileCoord); + var sourceTileExtent = sourceTileGrid.getTileCoordExtent(sourceTileCoord); + var sharedExtent = ol.extent.getIntersection(tileExtent, sourceTileExtent); + var extent, reproject, tileResolution; + if (tileProjection.getUnits() == ol.proj.Units.TILE_PIXELS) { + var tilePixelRatio = tileResolution = source.getTilePixelRatio(); + var transform = ol.transform.compose(this.tmpTransform_, + 0, 0, + 1 / sourceTileResolution * tilePixelRatio, -1 / sourceTileResolution * tilePixelRatio, + 0, + -sourceTileExtent[0], -sourceTileExtent[3]); + extent = (ol.transform.apply(transform, [sharedExtent[0], sharedExtent[3]]) + .concat(ol.transform.apply(transform, [sharedExtent[2], sharedExtent[1]]))); } else { - styleFunction = layer.getStyleFunction(); + tileResolution = resolution; + extent = sharedExtent; + if (!ol.proj.equivalent(projection, tileProjection)) { + reproject = true; + sourceTile.setProjection(projection); + } + } + replayState.dirty = false; + var replayGroup = new ol.render.canvas.ReplayGroup(0, extent, + tileResolution, source.getOverlaps(), layer.getRenderBuffer()); + var squaredTolerance = ol.renderer.vector.getSquaredTolerance( + tileResolution, pixelRatio); + + /** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @this {ol.renderer.canvas.VectorTileLayer} + */ + var renderFeature = function(feature) { + var styles; + var styleFunction = feature.getStyleFunction(); if (styleFunction) { - styles = styleFunction(feature, resolution); + styles = styleFunction.call(/** @type {ol.Feature} */ (feature), resolution); + } else { + styleFunction = layer.getStyleFunction(); + if (styleFunction) { + styles = styleFunction(feature, resolution); + } } - } - if (styles) { - if (!Array.isArray(styles)) { - styles = [styles]; + if (styles) { + if (!Array.isArray(styles)) { + styles = [styles]; + } + var dirty = this.renderFeature(feature, squaredTolerance, styles, + replayGroup); + this.dirty_ = this.dirty_ || dirty; + replayState.dirty = replayState.dirty || dirty; } - var dirty = this.renderFeature(feature, squaredTolerance, styles, - replayGroup); - this.dirty_ = this.dirty_ || dirty; - replayState.dirty = replayState.dirty || dirty; + }; + + var features = sourceTile.getFeatures(); + if (renderOrder && renderOrder !== replayState.renderedRenderOrder) { + features.sort(renderOrder); } - } - - var features = tile.getFeatures(); - if (renderOrder && renderOrder !== replayState.renderedRenderOrder) { - features.sort(renderOrder); - } - var feature; - for (var i = 0, ii = features.length; i < ii; ++i) { - feature = features[i]; - if (reproject) { - feature.getGeometry().transform(tileProjection, projection); + var feature; + for (var i = 0, ii = features.length; i < ii; ++i) { + feature = features[i]; + if (reproject) { + feature.getGeometry().transform(tileProjection, projection); + } + renderFeature.call(this, feature); } - renderFeature.call(this, feature); + replayGroup.finish(); + sourceTile.setReplayGroup(tile.tileCoord.toString(), replayGroup); } - - replayGroup.finish(); - replayState.renderedRevision = revision; replayState.renderedRenderOrder = renderOrder; - replayState.replayGroup = replayGroup; - replayState.resolution = NaN; }; @@ -193,10 +207,10 @@ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup_ = function(tile, */ ol.renderer.canvas.VectorTileLayer.prototype.drawTileImage = function( tile, frameState, layerState, x, y, w, h, gutter) { - var vectorTile = /** @type {ol.VectorTile} */ (tile); - this.createReplayGroup_(vectorTile, frameState); + var vectorImageTile = /** @type {ol.VectorImageTile} */ (tile); + this.createReplayGroup_(vectorImageTile, frameState); if (this.context) { - this.renderTileImage_(vectorTile, frameState, layerState); + this.renderTileImage_(vectorImageTile, frameState, layerState); ol.renderer.canvas.TileLayer.prototype.drawTileImage.apply(this, arguments); } }; @@ -213,54 +227,62 @@ ol.renderer.canvas.VectorTileLayer.prototype.forEachFeatureAtCoordinate = functi /** @type {Object.} */ var features = {}; - /** @type {Array.} */ - var replayables = this.renderedTiles; + /** @type {Array.} */ + var renderedTiles = this.renderedTiles; var source = /** @type {ol.source.VectorTile} */ (layer.getSource()); - var tileGrid = source.getTileGrid(); - var found, tileSpaceCoordinate; + var tileGrid = source.getTileGridForProjection(frameState.viewState.projection); + var sourceTileGrid = source.getTileGrid(); + var bufferedExtent, found, tileSpaceCoordinate; var i, ii, origin, replayGroup; var tile, tileCoord, tileExtent, tilePixelRatio, tileResolution; - for (i = 0, ii = replayables.length; i < ii; ++i) { - tile = replayables[i]; + for (i = 0, ii = renderedTiles.length; i < ii; ++i) { + tile = renderedTiles[i]; tileCoord = tile.tileCoord; - tileExtent = source.getTileGrid().getTileCoordExtent(tileCoord, this.tmpExtent); - if (!ol.extent.containsCoordinate(ol.extent.buffer(tileExtent, hitTolerance * resolution), coordinate)) { + tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent); + bufferedExtent = ol.extent.buffer(tileExtent, hitTolerance * resolution, bufferedExtent); + if (!ol.extent.containsCoordinate(bufferedExtent, coordinate)) { continue; } - if (tile.getProjection().getUnits() === ol.proj.Units.TILE_PIXELS) { - origin = ol.extent.getTopLeft(tileExtent); - tilePixelRatio = source.getTilePixelRatio(); - tileResolution = tileGrid.getResolution(tileCoord[0]) / tilePixelRatio; - tileSpaceCoordinate = [ - (coordinate[0] - origin[0]) / tileResolution, - (origin[1] - coordinate[1]) / tileResolution - ]; - resolution = tilePixelRatio; - } else { - tileSpaceCoordinate = coordinate; + var sourceTiles = tile.getSourceTiles(); + for (var t = 0, tt = sourceTiles.length; t < tt; ++t) { + var sourceTile = sourceTiles[t]; + if (sourceTile.getProjection().getUnits() === ol.proj.Units.TILE_PIXELS) { + var sourceTileCoord = sourceTile.tileCoord; + var sourceTileExtent = sourceTileGrid.getTileCoordExtent(sourceTileCoord, this.tmpExtent); + origin = ol.extent.getTopLeft(sourceTileExtent); + tilePixelRatio = source.getTilePixelRatio(); + tileResolution = sourceTileGrid.getResolution(sourceTileCoord[0]) / tilePixelRatio; + tileSpaceCoordinate = [ + (coordinate[0] - origin[0]) / tileResolution, + (origin[1] - coordinate[1]) / tileResolution + ]; + resolution = tilePixelRatio; + } else { + tileSpaceCoordinate = coordinate; + } + replayGroup = sourceTile.getReplayGroup(tile.tileCoord); + found = found || replayGroup.forEachFeatureAtCoordinate( + tileSpaceCoordinate, resolution, rotation, hitTolerance, {}, + /** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + var key = ol.getUid(feature).toString(); + if (!(key in features)) { + features[key] = true; + return callback.call(thisArg, feature, layer); + } + }); } - replayGroup = tile.getReplayState().replayGroup; - found = found || replayGroup.forEachFeatureAtCoordinate( - tileSpaceCoordinate, resolution, rotation, hitTolerance, {}, - /** - * @param {ol.Feature|ol.render.Feature} feature Feature. - * @return {?} Callback result. - */ - function(feature) { - var key = ol.getUid(feature).toString(); - if (!(key in features)) { - features[key] = true; - return callback.call(thisArg, feature, layer); - } - }); } return found; }; /** - * @param {ol.Tile} tile Tile. + * @param {ol.VectorTile} tile Tile. * @param {olx.FrameState} frameState Frame state. * @return {ol.Transform} transform Transform. * @private @@ -308,7 +330,9 @@ ol.renderer.canvas.VectorTileLayer.prototype.handleStyleImageChange_ = function( * @inheritDoc */ ol.renderer.canvas.VectorTileLayer.prototype.postCompose = function(context, frameState, layerState) { - var renderMode = this.getLayer().getRenderMode(); + var layer = this.getLayer(); + var source = layer.getSource(); + var renderMode = layer.getRenderMode(); var replays = ol.renderer.canvas.VectorTileLayer.VECTOR_REPLAYS[renderMode]; if (replays) { var pixelRatio = frameState.pixelRatio; @@ -317,40 +341,55 @@ ol.renderer.canvas.VectorTileLayer.prototype.postCompose = function(context, fra var offsetX = Math.round(pixelRatio * size[0] / 2); var offsetY = Math.round(pixelRatio * size[1] / 2); var tiles = this.renderedTiles; + var tilePixelRatio = layer.getSource().getTilePixelRatio(); + var sourceTileGrid = source.getTileGrid(); + var tileGrid = source.getTileGridForProjection(frameState.viewState.projection); var clips = []; var zs = []; for (var i = tiles.length - 1; i >= 0; --i) { - var tile = /** @type {ol.VectorTile} */ (tiles[i]); - // Create a clip mask for regions in this low resolution tile that are - // already filled by a higher resolution tile - var transform = this.getReplayTransform_(tile, frameState); - var currentClip = tile.getReplayState().replayGroup.getClipCoords(transform); - var currentZ = tile.tileCoord[0]; - context.save(); - context.globalAlpha = layerState.opacity; - ol.render.canvas.rotateAtOffset(context, -rotation, offsetX, offsetY); - for (var j = 0, jj = clips.length; j < jj; ++j) { - var clip = clips[j]; - if (currentZ < zs[j]) { - context.beginPath(); - // counter-clockwise (outer ring) for current tile - context.moveTo(currentClip[0], currentClip[1]); - context.lineTo(currentClip[2], currentClip[3]); - context.lineTo(currentClip[4], currentClip[5]); - context.lineTo(currentClip[6], currentClip[7]); - // clockwise (inner ring) for higher resolution tile - context.moveTo(clip[6], clip[7]); - context.lineTo(clip[4], clip[5]); - context.lineTo(clip[2], clip[3]); - context.lineTo(clip[0], clip[1]); - context.clip(); - } + var tile = /** @type {ol.VectorImageTile} */ (tiles[i]); + if (tile.getState() == ol.TileState.ABORT) { + continue; + } + var tileCoord = tile.tileCoord; + var worldOffset = tileGrid.getTileCoordExtent(tileCoord)[0] - + tileGrid.getTileCoordExtent(tile.wrappedTileCoord)[0]; + var sourceTiles = tile.getSourceTiles(); + for (var t = 0, tt = sourceTiles.length; t < tt; ++t) { + var sourceTile = sourceTiles[t]; + var currentZ = sourceTile.tileCoord[0]; + var sourceResolution = sourceTileGrid.getResolution(currentZ); + var transform = this.getReplayTransform_(sourceTile, frameState); + ol.transform.translate(transform, worldOffset * tilePixelRatio / sourceResolution, 0); + var replayGroup = sourceTile.getReplayGroup(tileCoord.toString()); + var currentClip = replayGroup.getClipCoords(transform); + context.save(); + context.globalAlpha = layerState.opacity; + ol.render.canvas.rotateAtOffset(context, -rotation, offsetX, offsetY); + // Create a clip mask for regions in this low resolution tile that are + // already filled by a higher resolution tile + for (var j = 0, jj = clips.length; j < jj; ++j) { + var clip = clips[j]; + if (currentZ < zs[j]) { + context.beginPath(); + // counter-clockwise (outer ring) for current tile + context.moveTo(currentClip[0], currentClip[1]); + context.lineTo(currentClip[2], currentClip[3]); + context.lineTo(currentClip[4], currentClip[5]); + context.lineTo(currentClip[6], currentClip[7]); + // clockwise (inner ring) for higher resolution tile + context.moveTo(clip[6], clip[7]); + context.lineTo(clip[4], clip[5]); + context.lineTo(clip[2], clip[3]); + context.lineTo(clip[0], clip[1]); + context.clip(); + } + } + replayGroup.replay(context, pixelRatio, transform, rotation, {}, replays); + context.restore(); + clips.push(currentClip); + zs.push(currentZ); } - var replayGroup = tile.getReplayState().replayGroup; - replayGroup.replay(context, pixelRatio, transform, rotation, {}, replays); - context.restore(); - clips.push(currentClip); - zs.push(currentZ); } } ol.renderer.canvas.TileLayer.prototype.postCompose.apply(this, arguments); @@ -386,7 +425,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.renderFeature = function(feature, s /** - * @param {ol.VectorTile} tile Tile. + * @param {ol.VectorImageTile} tile Tile. * @param {olx.FrameState} frameState Frame state. * @param {ol.LayerState} layerState Layer state. * @private @@ -399,28 +438,39 @@ ol.renderer.canvas.VectorTileLayer.prototype.renderTileImage_ = function( var replays = ol.renderer.canvas.VectorTileLayer.IMAGE_REPLAYS[layer.getRenderMode()]; if (replays && replayState.renderedTileRevision !== revision) { replayState.renderedTileRevision = revision; - var tileCoord = tile.tileCoord; - var z = tile.tileCoord[0]; + var tileCoord = tile.wrappedTileCoord; + var z = tileCoord[0]; var pixelRatio = frameState.pixelRatio; var source = layer.getSource(); - var tileGrid = source.getTileGrid(); + var sourceTileGrid = source.getTileGrid(); + var tileGrid = source.getTileGridForProjection(frameState.viewState.projection); + var resolution = tileGrid.getResolution(z); var tilePixelRatio = source.getTilePixelRatio(); - var transform = ol.transform.reset(this.tmpTransform_); - if (tile.getProjection().getUnits() == ol.proj.Units.TILE_PIXELS) { - var renderPixelRatio = pixelRatio / tilePixelRatio; - ol.transform.scale(transform, renderPixelRatio, renderPixelRatio); - } else { - var resolution = tileGrid.getResolution(z); - var pixelScale = pixelRatio / resolution; - var tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent); - ol.transform.scale(transform, pixelScale, -pixelScale); - ol.transform.translate(transform, -tileExtent[0], -tileExtent[3]); - } - var context = tile.getContext(); var size = source.getTilePixelSize(z, pixelRatio, frameState.viewState.projection); context.canvas.width = size[0]; context.canvas.height = size[1]; - replayState.replayGroup.replay(context, pixelRatio, transform, 0, {}, replays); + var tileExtent = tileGrid.getTileCoordExtent(tileCoord); + var sourceTiles = tile.getSourceTiles(); + for (var i = 0, ii = sourceTiles.length; i < ii; ++i) { + var sourceTile = sourceTiles[i]; + var sourceTileCoord = sourceTile.tileCoord; + var pixelScale = pixelRatio / resolution; + var transform = ol.transform.reset(this.tmpTransform_); + if (sourceTile.getProjection().getUnits() == ol.proj.Units.TILE_PIXELS) { + var sourceTileExtent = sourceTileGrid.getTileCoordExtent(sourceTileCoord, this.tmpExtent); + var sourceResolution = sourceTileGrid.getResolution(sourceTileCoord[0]); + var renderPixelRatio = pixelRatio / tilePixelRatio * sourceResolution / resolution; + ol.transform.scale(transform, renderPixelRatio, renderPixelRatio); + var offsetX = (sourceTileExtent[0] - tileExtent[0]) / sourceResolution * tilePixelRatio; + var offsetY = (tileExtent[3] - sourceTileExtent[3]) / sourceResolution * tilePixelRatio; + ol.transform.translate(transform, Math.round(offsetX), Math.round(offsetY)); + } else { + ol.transform.scale(transform, pixelScale, -pixelScale); + ol.transform.translate(transform, -tileExtent[0], -tileExtent[3]); + } + var replayGroup = sourceTile.getReplayGroup(tile.tileCoord.toString()); + replayGroup.replay(context, pixelRatio, transform, 0, {}, replays); + } } }; diff --git a/src/ol/reproj/tile.js b/src/ol/reproj/tile.js index 36b83f0371..95c77e85c0 100644 --- a/src/ol/reproj/tile.js +++ b/src/ol/reproj/tile.js @@ -202,7 +202,8 @@ ol.reproj.Tile.prototype.disposeInternal = function() { /** - * @inheritDoc + * Get the HTML Canvas element for this tile. + * @return {HTMLCanvasElement} Canvas. */ ol.reproj.Tile.prototype.getImage = function() { return this.canvas_; diff --git a/src/ol/source/tiledebug.js b/src/ol/source/tiledebug.js index 3e301386cc..4325f16124 100644 --- a/src/ol/source/tiledebug.js +++ b/src/ol/source/tiledebug.js @@ -91,7 +91,6 @@ ol.inherits(ol.source.TileDebug.Tile_, ol.Tile); /** * Get the image element for this tile. * @return {HTMLCanvasElement} Image. - * @override */ ol.source.TileDebug.Tile_.prototype.getImage = function() { if (this.canvas_) { diff --git a/src/ol/source/tileutfgrid.js b/src/ol/source/tileutfgrid.js index 0166caace1..70d469e04d 100644 --- a/src/ol/source/tileutfgrid.js +++ b/src/ol/source/tileutfgrid.js @@ -320,7 +320,6 @@ ol.inherits(ol.source.TileUTFGrid.Tile_, ol.Tile); /** * Get the image element for this tile. * @return {Image} Image. - * @override */ ol.source.TileUTFGrid.Tile_.prototype.getImage = function() { return null; diff --git a/src/ol/source/vectortile.js b/src/ol/source/vectortile.js index 007b75dc86..1f296ef31a 100644 --- a/src/ol/source/vectortile.js +++ b/src/ol/source/vectortile.js @@ -2,9 +2,11 @@ goog.provide('ol.source.VectorTile'); goog.require('ol'); goog.require('ol.TileState'); +goog.require('ol.VectorImageTile'); goog.require('ol.VectorTile'); goog.require('ol.events'); goog.require('ol.events.EventType'); +goog.require('ol.proj'); goog.require('ol.size'); goog.require('ol.source.UrlTile'); @@ -35,9 +37,8 @@ ol.source.VectorTile = function(options) { opaque: false, projection: options.projection, state: options.state, - tileGrid: options.tileGrid, tileLoadFunction: options.tileLoadFunction ? - options.tileLoadFunction : ol.VectorTile.defaultLoadFunction, + options.tileLoadFunction : ol.VectorImageTile.defaultLoadFunction, tileUrlFunction: options.tileUrlFunction, tilePixelRatio: options.tilePixelRatio, url: options.url, @@ -51,6 +52,18 @@ ol.source.VectorTile = function(options) { */ this.format_ = options.format ? options.format : null; + /** + * @private + * @type {Object.} + */ + this.sourceTiles_ = {}; + + /** + * @type {ol.tilegrid.TileGrid} + */ + this.sourceTileGrid_ = options.tileGrid || + this.getTileGridForProjection(ol.proj.get(options.projection || 'EPSG:3857')); + /** * @private * @type {boolean} @@ -89,11 +102,13 @@ ol.source.VectorTile.prototype.getTile = function(z, x, y, pixelRatio, projectio tileCoord, projection); var tileUrl = urlTileCoord ? this.tileUrlFunction(urlTileCoord, pixelRatio, projection) : undefined; - var tile = new this.tileClass( + var tile = new ol.VectorImageTile( tileCoord, tileUrl !== undefined ? ol.TileState.IDLE : ol.TileState.EMPTY, tileUrl !== undefined ? tileUrl : '', - this.format_, this.tileLoadFunction); + this.format_, this.tileLoadFunction, urlTileCoord, this.tileUrlFunction, + this.sourceTileGrid_, this.getTileGridForProjection(projection), + this.sourceTiles_, pixelRatio, projection, this.tileClass); ol.events.listen(tile, ol.events.EventType.CHANGE, this.handleTileChange, this); @@ -103,6 +118,14 @@ ol.source.VectorTile.prototype.getTile = function(z, x, y, pixelRatio, projectio }; +/** + * @inheritDoc + */ +ol.source.VectorTile.prototype.getTileGrid = function() { + return this.sourceTileGrid_; +}; + + /** * @inheritDoc */ @@ -117,6 +140,6 @@ ol.source.VectorTile.prototype.getTilePixelRatio = function(opt_pixelRatio) { * @inheritDoc */ ol.source.VectorTile.prototype.getTilePixelSize = function(z, pixelRatio, projection) { - var tileSize = ol.size.toSize(this.tileGrid.getTileSize(z)); + var tileSize = ol.size.toSize(this.getTileGridForProjection(projection).getTileSize(z)); return [Math.round(tileSize[0] * pixelRatio), Math.round(tileSize[1] * pixelRatio)]; }; diff --git a/src/ol/tile.js b/src/ol/tile.js index c329c33017..4d1628bea4 100644 --- a/src/ol/tile.js +++ b/src/ol/tile.js @@ -59,14 +59,6 @@ ol.Tile.prototype.changed = function() { }; -/** - * Get the HTML image element for this tile (may be a Canvas, Image, or Video). - * @abstract - * @return {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} Image. - */ -ol.Tile.prototype.getImage = function() {}; - - /** * @return {string} Key. */ diff --git a/src/ol/tilegrid.js b/src/ol/tilegrid.js index 2b39e0cec7..3eb2f43afa 100644 --- a/src/ol/tilegrid.js +++ b/src/ol/tilegrid.js @@ -74,7 +74,7 @@ ol.tilegrid.createForExtent = function(extent, opt_maxZoom, opt_tileSize, opt_co /** * Creates a tile grid with a standard XYZ tiling scheme. * @param {olx.tilegrid.XYZOptions=} opt_options Tile grid options. - * @return {ol.tilegrid.TileGrid} Tile grid instance. + * @return {!ol.tilegrid.TileGrid} Tile grid instance. * @api */ ol.tilegrid.createXYZ = function(opt_options) { diff --git a/src/ol/typedefs.js b/src/ol/typedefs.js index dc436d0c03..a0c7be7310 100644 --- a/src/ol/typedefs.js +++ b/src/ol/typedefs.js @@ -652,8 +652,7 @@ ol.TilePriorityFunction; * dirty: boolean, * renderedRenderOrder: (null|ol.RenderOrderFunction), * renderedTileRevision: number, - * renderedRevision: number, - * replayGroup: ol.render.ReplayGroup}} + * renderedRevision: number}} */ ol.TileReplayState; diff --git a/src/ol/vectorimagetile.js b/src/ol/vectorimagetile.js new file mode 100644 index 0000000000..795281ff40 --- /dev/null +++ b/src/ol/vectorimagetile.js @@ -0,0 +1,288 @@ +goog.provide('ol.VectorImageTile'); + +goog.require('ol'); +goog.require('ol.Tile'); +goog.require('ol.TileState'); +goog.require('ol.array'); +goog.require('ol.dom'); +goog.require('ol.events'); +goog.require('ol.extent'); +goog.require('ol.events.EventType'); +goog.require('ol.featureloader'); + + +/** + * @constructor + * @extends {ol.Tile} + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.TileState} state State. + * @param {string} src Data source url. + * @param {ol.format.Feature} format Feature format. + * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function. + * @param {ol.TileCoord} urlTileCoord Wrapped tile coordinate for source urls. + * @param {ol.TileUrlFunctionType} tileUrlFunction Tile url function. + * @param {ol.tilegrid.TileGrid} sourceTileGrid Tile grid of the source. + * @param {ol.tilegrid.TileGrid} tileGrid Tile grid of the renderer. + * @param {Object.} sourceTiles Source tiles. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.proj.Projection} projection Projection. + * @param {function(new: ol.VectorTile, ol.TileCoord, ol.TileState, string, + * ol.format.Feature, ol.TileLoadFunctionType)} tileClass Class to + * instantiate for source tiles. + */ +ol.VectorImageTile = function(tileCoord, state, src, format, tileLoadFunction, + urlTileCoord, tileUrlFunction, sourceTileGrid, tileGrid, sourceTiles, + pixelRatio, projection, tileClass) { + + ol.Tile.call(this, tileCoord, state); + + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.context_ = null; + + /** + * @private + * @type {ol.format.Feature} + */ + this.format_ = format; + + /** + * @private + * @type {ol.FeatureLoader} + */ + this.loader_; + + /** + * @private + * @type {ol.TileReplayState} + */ + this.replayState_ = { + dirty: false, + renderedRenderOrder: null, + renderedRevision: -1, + renderedTileRevision: -1 + }; + + /** + * @private + * @type {Object.} + */ + this.sourceTiles_ = sourceTiles; + + /** + * @private + * @type {Array.} + */ + this.usedSourceTileKeys_ = []; + + /** + * @type {string} + */ + this.src_ = src; + + /** + * @type {ol.TileCoord} + */ + this.wrappedTileCoord = urlTileCoord; + + /** + * @type {Array.} + */ + this.loadListenerKeys_ = []; + + if (urlTileCoord) { + var extent = tileGrid.getTileCoordExtent(urlTileCoord); + var resolution = tileGrid.getResolution(tileCoord[0]); + var sourceZ = sourceTileGrid.getZForResolution(resolution); + sourceTileGrid.forEachTileCoord(extent, sourceZ, function(sourceTileCoord) { + var sharedExtent = ol.extent.getIntersection(extent, + sourceTileGrid.getTileCoordExtent(sourceTileCoord)); + if (ol.extent.getWidth(sharedExtent) / resolution >= 0.5 && + ol.extent.getHeight(sharedExtent) / resolution >= 0.5) { + // only include source tile if overlap is at least 1 pixel + var sourceTileKey = sourceTileCoord.toString(); + var sourceTile = sourceTiles[sourceTileKey]; + if (!sourceTile) { + var tileUrl = /** @type {string} */ + (tileUrlFunction(sourceTileCoord, pixelRatio, projection)); + sourceTile = sourceTiles[sourceTileKey] = new tileClass( + sourceTileCoord, ol.TileState.IDLE, tileUrl, format, tileLoadFunction); + } + sourceTile.consumers++; + this.usedSourceTileKeys_.push(sourceTileKey); + } + }.bind(this)); + } + +}; +ol.inherits(ol.VectorImageTile, ol.Tile); + + +/** + * @inheritDoc + */ +ol.VectorImageTile.prototype.disposeInternal = function() { + var sourceTiles = this.getSourceTiles(); + for (var i = 0, ii = sourceTiles.length; i < ii; ++i) { + var sourceTile = sourceTiles[i]; + sourceTile.consumers--; + if (sourceTile.consumers == 0) { + delete this.sourceTiles_[sourceTile.tileCoord.toString()]; + sourceTile.dispose(); + } + } + this.tileKeys.length = 0; + this.sourceTiles_ = null; + if (this.state == ol.TileState.LOADING) { + this.loadListenerKeys_.forEach(ol.events.unlistenByKey); + this.loadListenerKeys_.length = 0; + } + if (this.interimTile) { + this.interimTile.dispose(); + } + this.state = ol.TileState.ABORT; + this.changed(); + ol.Tile.prototype.disposeInternal.call(this); +}; + + +/** + * @return {CanvasRenderingContext2D} The rendering context. + */ +ol.VectorImageTile.prototype.getContext = function() { + if (!this.context_) { + this.context_ = ol.dom.createCanvasContext2D(); + } + return this.context_; +}; + + +/** + * Get the Canvas for this tile. + * @return {HTMLCanvasElement} Canvas. + * @api + */ +ol.VectorImageTile.prototype.getImage = function() { + return this.replayState_.renderedTileRevision == -1 ? + null : this.context_.canvas; +}; + + +/** + * Get the feature format assigned for reading this tile's features. + * @return {ol.format.Feature} Feature format. + * @api + */ +ol.VectorImageTile.prototype.getFormat = function() { + return this.format_; +}; + + +/** + * @return {Array.} Features. + */ +ol.VectorImageTile.prototype.getFeatures = function() { + return this.features_; +}; + + +/** + * @return {ol.TileReplayState} The replay state. + */ +ol.VectorImageTile.prototype.getReplayState = function() { + return this.replayState_; +}; + + +/** + * @inheritDoc + */ +ol.VectorImageTile.prototype.getKey = function() { + return this.usedSourceTileKeys_.join('/') + '/' + this.src_; +}; + + +/** + * @return {Array.} Source tiles for this tile. + */ +ol.VectorImageTile.prototype.getSourceTiles = function() { + return this.usedSourceTileKeys_.map(function(sourceTileKey) { + return this.sourceTiles_[sourceTileKey]; + }.bind(this)); +}; + + +/** + * @inheritDoc + */ +ol.VectorImageTile.prototype.load = function() { + var leftToLoad = 0; + if (this.state == ol.TileState.IDLE) { + this.setState(ol.TileState.LOADING); + } + if (this.state == ol.TileState.LOADING) { + this.usedSourceTileKeys_.forEach(function(sourceTileKey) { + var sourceTile = this.sourceTiles_[sourceTileKey]; + if (sourceTile.state == ol.TileState.IDLE) { + sourceTile.setLoader(this.loader_); + sourceTile.load(); + } + if (sourceTile.state == ol.TileState.LOADING) { + var key = ol.events.listen(sourceTile, ol.events.EventType.CHANGE, function(e) { + var state = sourceTile.getState(); + if (state == ol.TileState.LOADED || + state == ol.TileState.ERROR || + state == ol.TileState.EMPTY) { + --leftToLoad; + ol.events.unlistenByKey(key); + ol.array.remove(this.loadListenerKeys_, key); + if (leftToLoad == 0) { + this.setState(ol.TileState.LOADED); + } + } + }.bind(this)); + this.loadListenerKeys_.push(key); + ++leftToLoad; + } + }.bind(this)); + } + if (leftToLoad == 0) { + setTimeout(function() { + this.setState(ol.TileState.LOADED); + }.bind(this), 0); + } +}; + + +/** + * @param {Array.} features Features. + * @api + */ +ol.VectorImageTile.prototype.setFeatures = function(features) { + this.features_ = features; + this.setState(ol.TileState.LOADED); +}; + + +/** + * @param {ol.TileState} tileState Tile state. + */ +ol.VectorImageTile.prototype.setState = function(tileState) { + this.state = tileState; + this.changed(); +}; + + +/** + * Sets the loader for a tile. + * @param {ol.VectorTile} tile Vector tile. + * @param {string} url URL. + */ +ol.VectorImageTile.defaultLoadFunction = function(tile, url) { + var loader = ol.featureloader.loadFeaturesXhr( + url, tile.getFormat(), tile.onLoad_.bind(tile), tile.onError_.bind(tile)); + + tile.setLoader(loader); +}; diff --git a/src/ol/vectortile.js b/src/ol/vectortile.js index ade49cf9e2..11fa1f8f05 100644 --- a/src/ol/vectortile.js +++ b/src/ol/vectortile.js @@ -3,8 +3,6 @@ goog.provide('ol.VectorTile'); goog.require('ol'); goog.require('ol.Tile'); goog.require('ol.TileState'); -goog.require('ol.dom'); -goog.require('ol.featureloader'); /** @@ -21,10 +19,9 @@ ol.VectorTile = function(tileCoord, state, src, format, tileLoadFunction) { ol.Tile.call(this, tileCoord, state); /** - * @private - * @type {CanvasRenderingContext2D} + * @type {number} */ - this.context_ = null; + this.consumers = 0; /** * @private @@ -51,17 +48,7 @@ ol.VectorTile = function(tileCoord, state, src, format, tileLoadFunction) { */ this.projection_; - /** - * @private - * @type {ol.TileReplayState} - */ - this.replayState_ = { - dirty: false, - renderedRenderOrder: null, - renderedRevision: -1, - renderedTileRevision: -1, - replayGroup: null - }; + this.replayGroups_ = {}; /** * @private @@ -79,26 +66,6 @@ ol.VectorTile = function(tileCoord, state, src, format, tileLoadFunction) { ol.inherits(ol.VectorTile, ol.Tile); -/** - * @return {CanvasRenderingContext2D} The rendering context. - */ -ol.VectorTile.prototype.getContext = function() { - if (!this.context_) { - this.context_ = ol.dom.createCanvasContext2D(); - } - return this.context_; -}; - - -/** - * @override - */ -ol.VectorTile.prototype.getImage = function() { - return this.replayState_.renderedTileRevision == -1 ? - null : this.context_.canvas; -}; - - /** * Get the feature format assigned for reading this tile's features. * @return {ol.format.Feature} Feature format. @@ -117,14 +84,6 @@ ol.VectorTile.prototype.getFeatures = function() { }; -/** - * @return {ol.TileReplayState} The replay state. - */ -ol.VectorTile.prototype.getReplayState = function() { - return this.replayState_; -}; - - /** * @inheritDoc */ @@ -141,6 +100,11 @@ ol.VectorTile.prototype.getProjection = function() { }; +ol.VectorTile.prototype.getReplayGroup = function(key) { + return this.replayGroups_[key]; +}; + + /** * @inheritDoc */ @@ -192,6 +156,11 @@ ol.VectorTile.prototype.setProjection = function(projection) { }; +ol.VectorTile.prototype.setReplayGroup = function(key, replayGroup) { + this.replayGroups_[key] = replayGroup; +}; + + /** * @param {ol.TileState} tileState Tile state. */ @@ -209,16 +178,3 @@ ol.VectorTile.prototype.setState = function(tileState) { ol.VectorTile.prototype.setLoader = function(loader) { this.loader_ = loader; }; - - -/** - * Sets the loader for a tile. - * @param {ol.VectorTile} tile Vector tile. - * @param {string} url URL. - */ -ol.VectorTile.defaultLoadFunction = function(tile, url) { - var loader = ol.featureloader.loadFeaturesXhr( - url, tile.getFormat(), tile.onLoad_.bind(tile), tile.onError_.bind(tile)); - - tile.setLoader(loader); -}; diff --git a/test/spec/ol/renderer/canvas/vectortilelayer.test.js b/test/spec/ol/renderer/canvas/vectortilelayer.test.js index e0bcc638a1..a0d0d04917 100644 --- a/test/spec/ol/renderer/canvas/vectortilelayer.test.js +++ b/test/spec/ol/renderer/canvas/vectortilelayer.test.js @@ -62,6 +62,11 @@ describe('ol.renderer.canvas.VectorTileLayer', function() { tileClass: TileClass, tileGrid: ol.tilegrid.createXYZ() }); + source.getTile = function() { + var tile = ol.source.VectorTile.prototype.getTile.apply(source, arguments); + tile.setState(ol.TileState.LOADED); + return tile; + }; layer = new ol.layer.VectorTile({ source: source, style: layerStyle @@ -156,12 +161,18 @@ describe('ol.renderer.canvas.VectorTileLayer', function() { tileGrid: ol.tilegrid.createXYZ() }) }); - var tile = new ol.VectorTile([0, 0, 0], 2); - tile.projection_ = ol.proj.get('EPSG:3857'); - tile.features_ = []; - tile.getImage = function() { + var sourceTile = new ol.VectorTile([0, 0, 0], 2); + sourceTile.setProjection(ol.proj.get('EPSG:3857')); + sourceTile.features_ = []; + sourceTile.getImage = function() { return document.createElement('canvas'); }; + var tile = new ol.VectorImageTile([0, 0, 0]); + tile.wrappedTileCoord = [0, 0, 0]; + tile.setState(ol.TileState.LOADED); + tile.getSourceTiles = function() { + return [sourceTile]; + }; layer.getSource().getTile = function() { return tile; }; @@ -192,12 +203,18 @@ describe('ol.renderer.canvas.VectorTileLayer', function() { describe('#forEachFeatureAtCoordinate', function() { var layer, renderer, replayGroup; var TileClass = function() { - ol.VectorTile.apply(this, arguments); + ol.VectorImageTile.apply(this, arguments); this.setState('loaded'); - this.setProjection(ol.proj.get('EPSG:3857')); - this.replayState_.replayGroup = replayGroup; + var sourceTile = new ol.VectorTile(); + sourceTile.setProjection(ol.proj.get('EPSG:3857')); + sourceTile.getReplayGroup = function() { + return replayGroup; + }; + this.getSourceTiles = function() { + return [sourceTile]; + }; }; - ol.inherits(TileClass, ol.VectorTile); + ol.inherits(TileClass, ol.VectorImageTile); beforeEach(function() { replayGroup = {}; @@ -223,6 +240,7 @@ describe('ol.renderer.canvas.VectorTileLayer', function() { layerStates: {}, skippedFeatureUids: {}, viewState: { + projection: ol.proj.get('EPSG:3857'), resolution: 1, rotation: 0 } diff --git a/test/spec/ol/source/vectortile.test.js b/test/spec/ol/source/vectortile.test.js index b634c7a69c..1c9b0e7312 100644 --- a/test/spec/ol/source/vectortile.test.js +++ b/test/spec/ol/source/vectortile.test.js @@ -1,6 +1,6 @@ goog.provide('ol.test.source.VectorTile'); -goog.require('ol.VectorTile'); +goog.require('ol.VectorImageTile'); goog.require('ol.format.MVT'); goog.require('ol.proj'); goog.require('ol.source.VectorTile'); @@ -29,7 +29,7 @@ describe('ol.source.VectorTile', function() { describe('#getTile()', function() { it('creates a tile with the correct tile class', function() { tile = source.getTile(0, 0, 0, 1, ol.proj.get('EPSG:3857')); - expect(tile).to.be.a(ol.VectorTile); + expect(tile).to.be.a(ol.VectorImageTile); }); it('sets the correct tileCoord on the created tile', function() { expect(tile.getTileCoord()).to.eql([0, 0, 0]); diff --git a/test/spec/ol/vectorimagetile.test.js b/test/spec/ol/vectorimagetile.test.js new file mode 100644 index 0000000000..8a3d6d25bb --- /dev/null +++ b/test/spec/ol/vectorimagetile.test.js @@ -0,0 +1,43 @@ +goog.provide('ol.test.VectorImageTile'); + +goog.require('ol.events'); +goog.require('ol.VectorImageTile'); +goog.require('ol.VectorTile'); +goog.require('ol.format.GeoJSON'); +goog.require('ol.proj'); + + +describe('ol.VectorImageTile', function() { + + it('sets the loader function on source tiles', function() { + var format = new ol.format.GeoJSON(); + var url = 'spec/ol/data/point.json'; + var tile = new ol.VectorImageTile([0, 0, 0], 0, url, format, + ol.VectorImageTile.defaultLoadFunction, + [0, 0, 0], function() {}, ol.tilegrid.createXYZ(), ol.tilegrid.createXYZ(), {}, + 1, ol.proj.get('EPSG:3857'), ol.VectorTile); + + tile.load(); + var loader = tile.getSourceTiles()[0].loader_; + expect(typeof loader).to.be('function'); + }); + + it('loader sets features on the source tile', function(done) { + var format = new ol.format.GeoJSON(); + var url = 'spec/ol/data/point.json'; + var tile = new ol.VectorImageTile([0, 0, 0], 0, url, format, + ol.VectorImageTile.defaultLoadFunction, [0, 0, 0], function() { + return url; + }, ol.tilegrid.createXYZ(), ol.tilegrid.createXYZ(), {}, + 1, ol.proj.get('EPSG:3857'), ol.VectorTile); + + tile.load(); + var sourceTile = tile.getSourceTiles()[0]; + + ol.events.listen(sourceTile, 'change', function(e) { + expect(sourceTile.getFeatures().length).to.be.greaterThan(0); + done(); + }); + }); + +}); diff --git a/test/spec/ol/vectortile.test.js b/test/spec/ol/vectortile.test.js index 33531d21e6..b794ef0d90 100644 --- a/test/spec/ol/vectortile.test.js +++ b/test/spec/ol/vectortile.test.js @@ -1,39 +1,14 @@ goog.provide('ol.test.VectorTile'); goog.require('ol.events'); +goog.require('ol.VectorImageTile'); goog.require('ol.VectorTile'); goog.require('ol.Feature'); -goog.require('ol.format.GeoJSON'); goog.require('ol.format.TextFeature'); goog.require('ol.proj'); -describe('ol.VectorTile.defaultLoadFunction()', function() { - - it('sets the loader function on the tile', function() { - var format = new ol.format.GeoJSON(); - var tile = new ol.VectorTile([0, 0, 0], null, null, format); - var url = 'https://example.com/'; - - ol.VectorTile.defaultLoadFunction(tile, url); - var loader = tile.loader_; - expect(typeof loader).to.be('function'); - }); - - it('loader sets features on the tile', function(done) { - var format = new ol.format.GeoJSON(); - var tile = new ol.VectorTile([0, 0, 0], null, null, format); - var url = 'spec/ol/data/point.json'; - - ol.VectorTile.defaultLoadFunction(tile, url); - var loader = tile.loader_; - - ol.events.listen(tile, 'change', function(e) { - expect(tile.getFeatures().length).to.be.greaterThan(0); - done(); - }); - loader.call(tile, [], 1, ol.proj.get('EPSG:3857')); - }); +describe('ol.VectorTile', function() { it('loader sets features on the tile and updates proj units', function(done) { // mock format that return a tile-pixels feature @@ -51,7 +26,7 @@ describe('ol.VectorTile.defaultLoadFunction()', function() { var tile = new ol.VectorTile([0, 0, 0], null, null, format); var url = 'spec/ol/data/point.json'; - ol.VectorTile.defaultLoadFunction(tile, url); + ol.VectorImageTile.defaultLoadFunction(tile, url); var loader = tile.loader_; ol.events.listen(tile, 'change', function(e) { expect(tile.getFeatures().length).to.be.greaterThan(0); From 355ce9f679603c46f334e057e986ea9c2fd00310 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Mon, 8 May 2017 19:39:37 +0200 Subject: [PATCH 2/5] Use same tile size in rendered and source tile grid --- src/ol/source/vectortile.js | 33 +++++++++++++++++++------- src/ol/tilegrid.js | 3 ++- test/spec/ol/source/vectortile.test.js | 9 ++++++- 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/ol/source/vectortile.js b/src/ol/source/vectortile.js index 1f296ef31a..af3a529873 100644 --- a/src/ol/source/vectortile.js +++ b/src/ol/source/vectortile.js @@ -8,6 +8,7 @@ goog.require('ol.events'); goog.require('ol.events.EventType'); goog.require('ol.proj'); goog.require('ol.size'); +goog.require('ol.tilegrid'); goog.require('ol.source.UrlTile'); @@ -37,6 +38,7 @@ ol.source.VectorTile = function(options) { opaque: false, projection: options.projection, state: options.state, + tileGrid: options.tileGrid, tileLoadFunction: options.tileLoadFunction ? options.tileLoadFunction : ol.VectorImageTile.defaultLoadFunction, tileUrlFunction: options.tileUrlFunction, @@ -58,12 +60,6 @@ ol.source.VectorTile = function(options) { */ this.sourceTiles_ = {}; - /** - * @type {ol.tilegrid.TileGrid} - */ - this.sourceTileGrid_ = options.tileGrid || - this.getTileGridForProjection(ol.proj.get(options.projection || 'EPSG:3857')); - /** * @private * @type {boolean} @@ -77,6 +73,16 @@ ol.source.VectorTile = function(options) { */ this.tileClass = options.tileClass ? options.tileClass : ol.VectorTile; + /** + * @private + * @type {Object.} + */ + this.tileGrids_ = {}; + + if (!this.tileGrid) { + this.tileGrid = this.getTileGridForProjection(ol.proj.get(options.projection || 'EPSG:3857')); + } + }; ol.inherits(ol.source.VectorTile, ol.source.UrlTile); @@ -107,7 +113,7 @@ ol.source.VectorTile.prototype.getTile = function(z, x, y, pixelRatio, projectio tileUrl !== undefined ? ol.TileState.IDLE : ol.TileState.EMPTY, tileUrl !== undefined ? tileUrl : '', this.format_, this.tileLoadFunction, urlTileCoord, this.tileUrlFunction, - this.sourceTileGrid_, this.getTileGridForProjection(projection), + this.tileGrid, this.getTileGridForProjection(projection), this.sourceTiles_, pixelRatio, projection, this.tileClass); ol.events.listen(tile, ol.events.EventType.CHANGE, this.handleTileChange, this); @@ -121,8 +127,17 @@ ol.source.VectorTile.prototype.getTile = function(z, x, y, pixelRatio, projectio /** * @inheritDoc */ -ol.source.VectorTile.prototype.getTileGrid = function() { - return this.sourceTileGrid_; +ol.source.VectorTile.prototype.getTileGridForProjection = function(projection) { + var code = projection.getCode(); + var tileGrid = this.tileGrids_[code]; + if (!tileGrid) { + // A tile grid that matches the tile size of the source tile grid is more + // likely to have 1:1 relationships between source tiles and rendered tiles. + var sourceTileGrid = this.tileGrid; + tileGrid = this.tileGrids_[code] = ol.tilegrid.createForProjection(projection, undefined, + sourceTileGrid ? sourceTileGrid.getTileSize(sourceTileGrid.getMinZoom()) : undefined); + } + return tileGrid; }; diff --git a/src/ol/tilegrid.js b/src/ol/tilegrid.js index 3eb2f43afa..92cc8966b6 100644 --- a/src/ol/tilegrid.js +++ b/src/ol/tilegrid.js @@ -126,7 +126,8 @@ ol.tilegrid.resolutionsFromExtent = function(extent, opt_maxZoom, opt_tileSize) * @param {ol.ProjectionLike} projection Projection. * @param {number=} opt_maxZoom Maximum zoom level (default is * ol.DEFAULT_MAX_ZOOM). - * @param {ol.Size=} opt_tileSize Tile size (default uses ol.DEFAULT_TILE_SIZE). + * @param {number|ol.Size=} opt_tileSize Tile size (default uses + * ol.DEFAULT_TILE_SIZE). * @param {ol.extent.Corner=} opt_corner Extent corner (default is * ol.extent.Corner.BOTTOM_LEFT). * @return {!ol.tilegrid.TileGrid} TileGrid instance. diff --git a/test/spec/ol/source/vectortile.test.js b/test/spec/ol/source/vectortile.test.js index 1c9b0e7312..7a90d3225a 100644 --- a/test/spec/ol/source/vectortile.test.js +++ b/test/spec/ol/source/vectortile.test.js @@ -12,7 +12,7 @@ describe('ol.source.VectorTile', function() { var format = new ol.format.MVT(); var source = new ol.source.VectorTile({ format: format, - tileGrid: ol.tilegrid.createXYZ(), + tileGrid: ol.tilegrid.createXYZ({tileSize: 512}), url: '{z}/{x}/{y}.pbf' }); var tile; @@ -40,4 +40,11 @@ describe('ol.source.VectorTile', function() { }); }); + describe('#getTileGridForProjection', function() { + it('creates a tile grid with the source tile grid\'s tile size', function() { + var tileGrid = source.getTileGridForProjection(ol.proj.get('EPSG:3857')); + expect(tileGrid.getTileSize(0)).to.be(512); + }); + }); + }); From a59a147dbd24f27d12a519c85ffce80a9b4c385b Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Mon, 8 May 2017 22:37:55 +0200 Subject: [PATCH 3/5] Handle tile state of ol.VectorImageTile properly --- src/ol/vectorimagetile.js | 28 +++++++++++----- test/spec/ol/vectorimagetile.test.js | 50 ++++++++++++++++++++-------- 2 files changed, 56 insertions(+), 22 deletions(-) diff --git a/src/ol/vectorimagetile.js b/src/ol/vectorimagetile.js index 795281ff40..5938191d6a 100644 --- a/src/ol/vectorimagetile.js +++ b/src/ol/vectorimagetile.js @@ -105,10 +105,11 @@ ol.VectorImageTile = function(tileCoord, state, src, format, tileLoadFunction, var sourceTileKey = sourceTileCoord.toString(); var sourceTile = sourceTiles[sourceTileKey]; if (!sourceTile) { - var tileUrl = /** @type {string} */ - (tileUrlFunction(sourceTileCoord, pixelRatio, projection)); - sourceTile = sourceTiles[sourceTileKey] = new tileClass( - sourceTileCoord, ol.TileState.IDLE, tileUrl, format, tileLoadFunction); + var tileUrl = tileUrlFunction(sourceTileCoord, pixelRatio, projection); + sourceTile = sourceTiles[sourceTileKey] = new tileClass(sourceTileCoord, + tileUrl == undefined ? ol.TileState.EMPTY : ol.TileState.IDLE, + tileUrl == undefined ? '' : tileUrl, + format, tileLoadFunction); } sourceTile.consumers++; this.usedSourceTileKeys_.push(sourceTileKey); @@ -219,6 +220,7 @@ ol.VectorImageTile.prototype.getSourceTiles = function() { */ ol.VectorImageTile.prototype.load = function() { var leftToLoad = 0; + var errors = false; if (this.state == ol.TileState.IDLE) { this.setState(ol.TileState.LOADING); } @@ -228,18 +230,26 @@ ol.VectorImageTile.prototype.load = function() { if (sourceTile.state == ol.TileState.IDLE) { sourceTile.setLoader(this.loader_); sourceTile.load(); + } else if (sourceTile.state == ol.TileState.ERROR) { + errors = true; + } else if (sourceTile.state == ol.TileState.EMPTY) { + ol.array.remove(this.usedSourceTileKeys_, sourceTileKey); } if (sourceTile.state == ol.TileState.LOADING) { var key = ol.events.listen(sourceTile, ol.events.EventType.CHANGE, function(e) { var state = sourceTile.getState(); if (state == ol.TileState.LOADED || - state == ol.TileState.ERROR || - state == ol.TileState.EMPTY) { + state == ol.TileState.ERROR) { --leftToLoad; ol.events.unlistenByKey(key); ol.array.remove(this.loadListenerKeys_, key); + if (state == ol.TileState.ERROR) { + ol.array.remove(this.usedSourceTileKeys_, sourceTileKey); + errors = true; + } if (leftToLoad == 0) { - this.setState(ol.TileState.LOADED); + this.setState(this.usedSourceTileKeys_.length > 0 ? + ol.TileState.LOADED : ol.TileState.ERROR); } } }.bind(this)); @@ -250,7 +260,9 @@ ol.VectorImageTile.prototype.load = function() { } if (leftToLoad == 0) { setTimeout(function() { - this.setState(ol.TileState.LOADED); + this.setState(this.usedSourceTileKeys_.length > 0 ? + ol.TileState.LOADED : + (errors ? ol.TileState.ERROR : ol.TileState.EMPTY)); }.bind(this), 0); } }; diff --git a/test/spec/ol/vectorimagetile.test.js b/test/spec/ol/vectorimagetile.test.js index 8a3d6d25bb..03b7029683 100644 --- a/test/spec/ol/vectorimagetile.test.js +++ b/test/spec/ol/vectorimagetile.test.js @@ -9,20 +9,7 @@ goog.require('ol.proj'); describe('ol.VectorImageTile', function() { - it('sets the loader function on source tiles', function() { - var format = new ol.format.GeoJSON(); - var url = 'spec/ol/data/point.json'; - var tile = new ol.VectorImageTile([0, 0, 0], 0, url, format, - ol.VectorImageTile.defaultLoadFunction, - [0, 0, 0], function() {}, ol.tilegrid.createXYZ(), ol.tilegrid.createXYZ(), {}, - 1, ol.proj.get('EPSG:3857'), ol.VectorTile); - - tile.load(); - var loader = tile.getSourceTiles()[0].loader_; - expect(typeof loader).to.be('function'); - }); - - it('loader sets features on the source tile', function(done) { + it('configures loader that sets features on the source tile', function(done) { var format = new ol.format.GeoJSON(); var url = 'spec/ol/data/point.json'; var tile = new ol.VectorImageTile([0, 0, 0], 0, url, format, @@ -33,6 +20,8 @@ describe('ol.VectorImageTile', function() { tile.load(); var sourceTile = tile.getSourceTiles()[0]; + var loader = sourceTile.loader_; + expect(typeof loader).to.be('function'); ol.events.listen(sourceTile, 'change', function(e) { expect(sourceTile.getFeatures().length).to.be.greaterThan(0); @@ -40,4 +29,37 @@ describe('ol.VectorImageTile', function() { }); }); + it('sets ERROR state when source tiles fail to load', function(done) { + var format = new ol.format.GeoJSON(); + var url = 'spec/ol/data/unavailable.json'; + var tile = new ol.VectorImageTile([0, 0, 0], 0, url, format, + ol.VectorImageTile.defaultLoadFunction, [0, 0, 0], function() { + return url; + }, ol.tilegrid.createXYZ(), ol.tilegrid.createXYZ(), {}, + 1, ol.proj.get('EPSG:3857'), ol.VectorTile); + + tile.load(); + + ol.events.listen(tile, 'change', function(e) { + expect(tile.getState()).to.be(ol.TileState.ERROR); + done(); + }); + }); + + it('sets EMPTY state when tile has only empty source tiles', function(done) { + var format = new ol.format.GeoJSON(); + var url = ''; + var tile = new ol.VectorImageTile([0, 0, 0], 0, url, format, + ol.VectorImageTile.defaultLoadFunction, [0, 0, 0], function() {}, + ol.tilegrid.createXYZ(), ol.tilegrid.createXYZ(), {}, + 1, ol.proj.get('EPSG:3857'), ol.VectorTile); + + tile.load(); + + ol.events.listen(tile, 'change', function(e) { + expect(tile.getState()).to.be(ol.TileState.EMPTY); + done(); + }); + }); + }); From 55e3746554359e04b571b48f6b61d8467b570874 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Tue, 9 May 2017 10:19:02 +0200 Subject: [PATCH 4/5] Avoid garbage creation on frame preparation/composition --- src/ol/renderer/canvas/vectortilelayer.js | 20 ++++----- src/ol/vectorimagetile.js | 45 ++++++++----------- .../renderer/canvas/vectortilelayer.test.js | 13 +++--- test/spec/ol/vectorimagetile.test.js | 2 +- 4 files changed, 34 insertions(+), 46 deletions(-) diff --git a/src/ol/renderer/canvas/vectortilelayer.js b/src/ol/renderer/canvas/vectortilelayer.js index 1a3b71ea6e..2f4a026b09 100644 --- a/src/ol/renderer/canvas/vectortilelayer.js +++ b/src/ol/renderer/canvas/vectortilelayer.js @@ -116,9 +116,8 @@ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup_ = function( return; } - var sourceTiles = tile.getSourceTiles(); - for (var t = 0, tt = sourceTiles.length; t < tt; ++t) { - var sourceTile = sourceTiles[t]; + for (var t = 0, tt = tile.tileKeys.length; t < tt; ++t) { + var sourceTile = tile.getTile(tile.tileKeys[t]); sourceTile.replayGroup = null; replayState.dirty = false; @@ -244,9 +243,8 @@ ol.renderer.canvas.VectorTileLayer.prototype.forEachFeatureAtCoordinate = functi if (!ol.extent.containsCoordinate(bufferedExtent, coordinate)) { continue; } - var sourceTiles = tile.getSourceTiles(); - for (var t = 0, tt = sourceTiles.length; t < tt; ++t) { - var sourceTile = sourceTiles[t]; + for (var t = 0, tt = tile.tileKeys.length; t < tt; ++t) { + var sourceTile = tile.getTile(tile.tileKeys[t]); if (sourceTile.getProjection().getUnits() === ol.proj.Units.TILE_PIXELS) { var sourceTileCoord = sourceTile.tileCoord; var sourceTileExtent = sourceTileGrid.getTileCoordExtent(sourceTileCoord, this.tmpExtent); @@ -354,9 +352,8 @@ ol.renderer.canvas.VectorTileLayer.prototype.postCompose = function(context, fra var tileCoord = tile.tileCoord; var worldOffset = tileGrid.getTileCoordExtent(tileCoord)[0] - tileGrid.getTileCoordExtent(tile.wrappedTileCoord)[0]; - var sourceTiles = tile.getSourceTiles(); - for (var t = 0, tt = sourceTiles.length; t < tt; ++t) { - var sourceTile = sourceTiles[t]; + for (var t = 0, tt = tile.tileKeys.length; t < tt; ++t) { + var sourceTile = tile.getTile(tile.tileKeys[t]); var currentZ = sourceTile.tileCoord[0]; var sourceResolution = sourceTileGrid.getResolution(currentZ); var transform = this.getReplayTransform_(sourceTile, frameState); @@ -451,9 +448,8 @@ ol.renderer.canvas.VectorTileLayer.prototype.renderTileImage_ = function( context.canvas.width = size[0]; context.canvas.height = size[1]; var tileExtent = tileGrid.getTileCoordExtent(tileCoord); - var sourceTiles = tile.getSourceTiles(); - for (var i = 0, ii = sourceTiles.length; i < ii; ++i) { - var sourceTile = sourceTiles[i]; + for (var i = 0, ii = tile.tileKeys.length; i < ii; ++i) { + var sourceTile = tile.getTile(tile.tileKeys[i]); var sourceTileCoord = sourceTile.tileCoord; var pixelScale = pixelRatio / resolution; var transform = ol.transform.reset(this.tmpTransform_); diff --git a/src/ol/vectorimagetile.js b/src/ol/vectorimagetile.js index 5938191d6a..6ff79e74be 100644 --- a/src/ol/vectorimagetile.js +++ b/src/ol/vectorimagetile.js @@ -72,10 +72,10 @@ ol.VectorImageTile = function(tileCoord, state, src, format, tileLoadFunction, this.sourceTiles_ = sourceTiles; /** - * @private + * Keys of source tiles used by this tile. Use with {@link #getTile}. * @type {Array.} */ - this.usedSourceTileKeys_ = []; + this.tileKeys = []; /** * @type {string} @@ -112,7 +112,7 @@ ol.VectorImageTile = function(tileCoord, state, src, format, tileLoadFunction, format, tileLoadFunction); } sourceTile.consumers++; - this.usedSourceTileKeys_.push(sourceTileKey); + this.tileKeys.push(sourceTileKey); } }.bind(this)); } @@ -125,12 +125,12 @@ ol.inherits(ol.VectorImageTile, ol.Tile); * @inheritDoc */ ol.VectorImageTile.prototype.disposeInternal = function() { - var sourceTiles = this.getSourceTiles(); - for (var i = 0, ii = sourceTiles.length; i < ii; ++i) { - var sourceTile = sourceTiles[i]; + for (var i = 0, ii = this.tileKeys.length; i < ii; ++i) { + var sourceTileKey = this.tileKeys[i]; + var sourceTile = this.getTile(sourceTileKey); sourceTile.consumers--; if (sourceTile.consumers == 0) { - delete this.sourceTiles_[sourceTile.tileCoord.toString()]; + delete this.sourceTiles_[sourceTileKey]; sourceTile.dispose(); } } @@ -181,14 +181,6 @@ ol.VectorImageTile.prototype.getFormat = function() { }; -/** - * @return {Array.} Features. - */ -ol.VectorImageTile.prototype.getFeatures = function() { - return this.features_; -}; - - /** * @return {ol.TileReplayState} The replay state. */ @@ -201,17 +193,16 @@ ol.VectorImageTile.prototype.getReplayState = function() { * @inheritDoc */ ol.VectorImageTile.prototype.getKey = function() { - return this.usedSourceTileKeys_.join('/') + '/' + this.src_; + return this.tileKeys.join('/') + '/' + this.src_; }; /** - * @return {Array.} Source tiles for this tile. + * @param {string} tileKey Key (tileCoord) of the source tile. + * @return {ol.VectorTile} Source tile. */ -ol.VectorImageTile.prototype.getSourceTiles = function() { - return this.usedSourceTileKeys_.map(function(sourceTileKey) { - return this.sourceTiles_[sourceTileKey]; - }.bind(this)); +ol.VectorImageTile.prototype.getTile = function(tileKey) { + return this.sourceTiles_[tileKey]; }; @@ -225,15 +216,15 @@ ol.VectorImageTile.prototype.load = function() { this.setState(ol.TileState.LOADING); } if (this.state == ol.TileState.LOADING) { - this.usedSourceTileKeys_.forEach(function(sourceTileKey) { - var sourceTile = this.sourceTiles_[sourceTileKey]; + this.tileKeys.forEach(function(sourceTileKey) { + var sourceTile = this.getTile(sourceTileKey); if (sourceTile.state == ol.TileState.IDLE) { sourceTile.setLoader(this.loader_); sourceTile.load(); } else if (sourceTile.state == ol.TileState.ERROR) { errors = true; } else if (sourceTile.state == ol.TileState.EMPTY) { - ol.array.remove(this.usedSourceTileKeys_, sourceTileKey); + ol.array.remove(this.tileKeys, sourceTileKey); } if (sourceTile.state == ol.TileState.LOADING) { var key = ol.events.listen(sourceTile, ol.events.EventType.CHANGE, function(e) { @@ -244,11 +235,11 @@ ol.VectorImageTile.prototype.load = function() { ol.events.unlistenByKey(key); ol.array.remove(this.loadListenerKeys_, key); if (state == ol.TileState.ERROR) { - ol.array.remove(this.usedSourceTileKeys_, sourceTileKey); + ol.array.remove(this.tileKeys, sourceTileKey); errors = true; } if (leftToLoad == 0) { - this.setState(this.usedSourceTileKeys_.length > 0 ? + this.setState(this.tileKeys.length > 0 ? ol.TileState.LOADED : ol.TileState.ERROR); } } @@ -260,7 +251,7 @@ ol.VectorImageTile.prototype.load = function() { } if (leftToLoad == 0) { setTimeout(function() { - this.setState(this.usedSourceTileKeys_.length > 0 ? + this.setState(this.tileKeys.length > 0 ? ol.TileState.LOADED : (errors ? ol.TileState.ERROR : ol.TileState.EMPTY)); }.bind(this), 0); diff --git a/test/spec/ol/renderer/canvas/vectortilelayer.test.js b/test/spec/ol/renderer/canvas/vectortilelayer.test.js index a0d0d04917..0faf7c99eb 100644 --- a/test/spec/ol/renderer/canvas/vectortilelayer.test.js +++ b/test/spec/ol/renderer/canvas/vectortilelayer.test.js @@ -170,8 +170,8 @@ describe('ol.renderer.canvas.VectorTileLayer', function() { var tile = new ol.VectorImageTile([0, 0, 0]); tile.wrappedTileCoord = [0, 0, 0]; tile.setState(ol.TileState.LOADED); - tile.getSourceTiles = function() { - return [sourceTile]; + tile.getSourceTile = function() { + return sourceTile; }; layer.getSource().getTile = function() { return tile; @@ -205,14 +205,15 @@ describe('ol.renderer.canvas.VectorTileLayer', function() { var TileClass = function() { ol.VectorImageTile.apply(this, arguments); this.setState('loaded'); - var sourceTile = new ol.VectorTile(); + var sourceTile = new ol.VectorTile([0, 0, 0]); sourceTile.setProjection(ol.proj.get('EPSG:3857')); sourceTile.getReplayGroup = function() { return replayGroup; }; - this.getSourceTiles = function() { - return [sourceTile]; - }; + var key = sourceTile.tileCoord.toString(); + this.tileKeys = [key]; + this.sourceTiles_ = {}; + this.sourceTiles_[key] = sourceTile; }; ol.inherits(TileClass, ol.VectorImageTile); diff --git a/test/spec/ol/vectorimagetile.test.js b/test/spec/ol/vectorimagetile.test.js index 03b7029683..fa3862f5dc 100644 --- a/test/spec/ol/vectorimagetile.test.js +++ b/test/spec/ol/vectorimagetile.test.js @@ -19,7 +19,7 @@ describe('ol.VectorImageTile', function() { 1, ol.proj.get('EPSG:3857'), ol.VectorTile); tile.load(); - var sourceTile = tile.getSourceTiles()[0]; + var sourceTile = tile.getTile(tile.tileKeys[0]); var loader = sourceTile.loader_; expect(typeof loader).to.be('function'); From e7dc09cf9f159bb0c3eabd98e3ad4bfb624ca2e4 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Tue, 9 May 2017 14:25:28 +0200 Subject: [PATCH 5/5] Add more tests --- test/spec/ol/vectorimagetile.test.js | 44 +++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/test/spec/ol/vectorimagetile.test.js b/test/spec/ol/vectorimagetile.test.js index fa3862f5dc..8abe2ef5a9 100644 --- a/test/spec/ol/vectorimagetile.test.js +++ b/test/spec/ol/vectorimagetile.test.js @@ -56,10 +56,52 @@ describe('ol.VectorImageTile', function() { tile.load(); - ol.events.listen(tile, 'change', function(e) { + ol.events.listen(tile, 'change', function() { expect(tile.getState()).to.be(ol.TileState.EMPTY); done(); }); }); + it('#dispose() while loading', function() { + var format = new ol.format.GeoJSON(); + var url = 'spec/ol/data/point.json'; + var tile = new ol.VectorImageTile([0, 0, 0], 0, url, format, + ol.VectorImageTile.defaultLoadFunction, [0, 0, 0], function() { + return url; + }, ol.tilegrid.createXYZ(), ol.tilegrid.createXYZ({tileSize: 512}), {}, + 1, ol.proj.get('EPSG:3857'), ol.VectorTile); + + tile.load(); + expect(tile.loadListenerKeys_.length).to.be(4); + expect(tile.tileKeys.length).to.be(4); + expect(tile.getState()).to.be(ol.TileState.LOADING); + tile.dispose(); + expect(tile.loadListenerKeys_.length).to.be(0); + expect(tile.tileKeys.length).to.be(0); + expect(tile.sourceTiles_).to.be(null); + expect(tile.getState()).to.be(ol.TileState.ABORT); + }); + + it('#dispose() when loaded', function(done) { + var format = new ol.format.GeoJSON(); + var url = 'spec/ol/data/point.json'; + var tile = new ol.VectorImageTile([0, 0, 0], 0, url, format, + ol.VectorImageTile.defaultLoadFunction, [0, 0, 0], function() { + return url; + }, ol.tilegrid.createXYZ(), ol.tilegrid.createXYZ({tileSize: 512}), {}, + 1, ol.proj.get('EPSG:3857'), ol.VectorTile); + + tile.load(); + ol.events.listenOnce(tile, 'change', function() { + expect(tile.getState()).to.be(ol.TileState.LOADED); + expect(tile.loadListenerKeys_.length).to.be(0); + expect(tile.tileKeys.length).to.be(4); + tile.dispose(); + expect(tile.tileKeys.length).to.be(0); + expect(tile.sourceTiles_).to.be(null); + expect(tile.getState()).to.be(ol.TileState.ABORT); + done(); + }); + }); + });