From 4a54cc69ceb549b95ea3c660cbce0a1364d78fbe Mon Sep 17 00:00:00 2001 From: ahocevar Date: Wed, 23 Jan 2013 21:37:06 +0100 Subject: [PATCH] Tiled vector rendering This needs more work still - see inline TODOs. It also has a major bug - rendered vector features do not scale and do not change their offset during panning. So only the initial view is correct. --- .../canvas/canvasvectorlayerrenderer.js | 109 +++++++++++++++--- src/ol/source/vectorsource.js | 18 ++- 2 files changed, 105 insertions(+), 22 deletions(-) diff --git a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js index 41f8fe10b7..dfd3f43629 100644 --- a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js @@ -4,6 +4,7 @@ goog.require('goog.vec.Mat4'); goog.require('ol.filter.Geometry'); goog.require('ol.geom.GeometryType'); goog.require('ol.layer.Vector'); +goog.require('ol.tilegrid.TileGrid'); @@ -35,6 +36,18 @@ ol.renderer.canvas.VectorLayer = function(mapRenderer, layer) { */ this.context_ = null; + /** + * @private + * @type {HTMLCanvasElement} + */ + this.sketchCanvas_ = null; + + /** + * @private + * @type {Object.} + */ + this.tileCache_ = {}; + /** * @private * @type {!goog.vec.Mat4.Number} @@ -91,8 +104,25 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame = var layer = this.getVectorLayer(); var source = layer.getVectorSource(); var extent = frameState.extent; + var view2DState = frameState.view2DState; + var tileGrid = source.getTileGrid(); - var canvasSize = frameState.size; + if (goog.isNull(tileGrid)) { + // lazy tile source creation to match the view projection + tileGrid = ol.tilegrid.createForProjection( + view2DState.projection, 22); + source.setTileGrid(tileGrid); + } + + var tileSize = tileGrid.getTileSize(); + var z = tileGrid.getZForResolution(view2DState.resolution); + var tileResolution = tileGrid.getResolution(z); + var tileRange = tileGrid.getTileRangeForExtentAndResolution( + frameState.extent, tileResolution); + + var canvasSize = new ol.Size( + tileSize.width * tileRange.getWidth(), + tileSize.height * tileRange.getHeight()); var canvas, context; if (goog.isNull(this.canvas_)) { @@ -104,6 +134,8 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame = this.canvas_ = canvas; this.canvasSize_ = canvasSize; this.context_ = context; + this.sketchCanvas_ = /** @type {HTMLCanvasElement} */ + canvas.cloneNode(false); } else { canvas = this.canvas_; context = this.context_; @@ -113,14 +145,6 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame = this.canvasSize_ = canvasSize; } - /** - * For now, we create a canvas renderer and have it render all features with - * every call to renderFrame. - * TODO: only render newly visible/dirty areas - */ - var canvasRenderer = new ol.renderer.canvas.Renderer( - canvas, frameState.coordinateToPixelMatrix); - // TODO: implement layer.setStyle(style) where style is a set of rules // and a rule has a filter and array of symbolizers var symbolizers = {}; @@ -144,19 +168,68 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame = opacity: 0.5 }); + var sketchCanvas = this.sketchCanvas_; + // clear the sketch canvas + sketchCanvas.width = canvasSize.width; + sketchCanvas.height = canvasSize.height; + // TODO: Use transform for tile resolution, not frameState resolution + var sketchCanvasRenderer = new ol.renderer.canvas.Renderer( + sketchCanvas, frameState.coordinateToPixelMatrix); + var renderedFeatures = {}; + var tile, tileContext, tileCoord, key, tileExtent, tileState, x, y; // render features by geometry type var filters = this.geometryFilters_, numFilters = filters.length, i, filter, type, features, symbolizer; - - for (i = 0; i < numFilters; ++i) { - filter = filters[i]; - type = filter.getType(); - features = source.getFeatures(filter); - if (features.length) { - // TODO: layer.getSymbolizerLiterals(features) or similar - symbolizer = symbolizers[type]; - canvasRenderer.renderFeaturesByGeometryType(type, features, symbolizer); + for (x = tileRange.minX; x <= tileRange.maxX; ++x) { + for (y = tileRange.minY; y <= tileRange.maxY; ++y) { + tileCoord = new ol.TileCoord(z, x, y); + key = tileCoord.toString(); + if (key in this.tileCache_) { + tile = this.tileCache_[key]; + } else { + tileExtent = tileGrid.getTileCoordExtent(tileCoord); + // TODO: instead of filtering here, do this on the source and maintain + // a spatial index + function filterFn(feature) { + var id = goog.getUid(feature); + var include = !(id in renderedFeatures) && + feature.getGeometry().getBounds().intersects(tileExtent); + if (include === true) { + renderedFeatures[id] = true; + } + return include; + } + for (i = 0; i < numFilters; ++i) { + filter = filters[i]; + type = filter.getType(); + features = source.getFeatures(filter); + // TODO: spatial indes of tiles - see filterFn above + features = goog.array.filter(features, filterFn); + if (features.length) { + // TODO: layer.getSymbolizerLiterals(features) or similar + symbolizer = symbolizers[type]; + sketchCanvasRenderer.renderFeaturesByGeometryType( + type, features, symbolizer); + } + } + tile = /** @type {HTMLCanvasElement} */ + goog.dom.createElement(goog.dom.TagName.CANVAS); + tile.width = tileSize.width; + tile.height = tileSize.height; + tileContext = tile.getContext('2d'); + tileContext.drawImage(sketchCanvas, + x * tileSize.width, (tileRange.maxY - y) * tileSize.height, + tileSize.width, tileSize.height, + 0, 0, tileSize.width, tileSize.height); + this.tileCache_[key] = tile; + } + // TODO: transform (scale, offset) + context.drawImage(tile, + 0, 0, + tileSize.width, tileSize.height, + x * tileSize.width, (tileRange.maxY - y) * tileSize.height, + tileSize.width, tileSize.height); } } diff --git a/src/ol/source/vectorsource.js b/src/ol/source/vectorsource.js index 3601bf3a6d..69fe06ecc8 100644 --- a/src/ol/source/vectorsource.js +++ b/src/ol/source/vectorsource.js @@ -126,7 +126,8 @@ ol.source.FeatureCache.prototype.getFeaturesByIds_ = function(ids) { /** * @typedef {{attributions: (Array.|undefined), * extent: (ol.Extent|undefined), - * projection: (ol.Projection|undefined)}} + * projection: (ol.Projection|undefined), + * tileGrid: (ol.tilegrid.TileGrid|undefined)}} */ ol.source.VectorOptions; @@ -134,7 +135,7 @@ ol.source.VectorOptions; /** * @constructor - * @extends {ol.source.Source} + * @extends {ol.source.TileSource} * @param {ol.source.VectorOptions} options Source options. */ ol.source.Vector = function(options) { @@ -142,7 +143,8 @@ ol.source.Vector = function(options) { goog.base(this, { attributions: options.attributions, extent: options.extent, - projection: options.projection + projection: options.projection, + tileGrid: options.tileGrid }); /** @@ -152,7 +154,15 @@ ol.source.Vector = function(options) { this.featureCache_ = new ol.source.FeatureCache(); }; -goog.inherits(ol.source.Vector, ol.source.Source); +goog.inherits(ol.source.Vector, ol.source.TileSource); + + +/** + * @param {ol.tilegrid.TileGrid} tileGrid tile grid. + */ +ol.source.Vector.prototype.setTileGrid = function(tileGrid) { + this.tileGrid = tileGrid; +}; /**