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.
This commit is contained in:
ahocevar
2013-01-23 21:37:06 +01:00
parent 07a56bfe3f
commit 4a54cc69ce
2 changed files with 105 additions and 22 deletions

View File

@@ -4,6 +4,7 @@ goog.require('goog.vec.Mat4');
goog.require('ol.filter.Geometry'); goog.require('ol.filter.Geometry');
goog.require('ol.geom.GeometryType'); goog.require('ol.geom.GeometryType');
goog.require('ol.layer.Vector'); goog.require('ol.layer.Vector');
goog.require('ol.tilegrid.TileGrid');
@@ -35,6 +36,18 @@ ol.renderer.canvas.VectorLayer = function(mapRenderer, layer) {
*/ */
this.context_ = null; this.context_ = null;
/**
* @private
* @type {HTMLCanvasElement}
*/
this.sketchCanvas_ = null;
/**
* @private
* @type {Object.<string, HTMLCanvasElement>}
*/
this.tileCache_ = {};
/** /**
* @private * @private
* @type {!goog.vec.Mat4.Number} * @type {!goog.vec.Mat4.Number}
@@ -91,8 +104,25 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame =
var layer = this.getVectorLayer(); var layer = this.getVectorLayer();
var source = layer.getVectorSource(); var source = layer.getVectorSource();
var extent = frameState.extent; 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; var canvas, context;
if (goog.isNull(this.canvas_)) { if (goog.isNull(this.canvas_)) {
@@ -104,6 +134,8 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame =
this.canvas_ = canvas; this.canvas_ = canvas;
this.canvasSize_ = canvasSize; this.canvasSize_ = canvasSize;
this.context_ = context; this.context_ = context;
this.sketchCanvas_ = /** @type {HTMLCanvasElement} */
canvas.cloneNode(false);
} else { } else {
canvas = this.canvas_; canvas = this.canvas_;
context = this.context_; context = this.context_;
@@ -113,14 +145,6 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame =
this.canvasSize_ = canvasSize; 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 // TODO: implement layer.setStyle(style) where style is a set of rules
// and a rule has a filter and array of symbolizers // and a rule has a filter and array of symbolizers
var symbolizers = {}; var symbolizers = {};
@@ -144,19 +168,68 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame =
opacity: 0.5 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 // render features by geometry type
var filters = this.geometryFilters_, var filters = this.geometryFilters_,
numFilters = filters.length, numFilters = filters.length,
i, filter, type, features, symbolizer; i, filter, type, features, symbolizer;
for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
for (i = 0; i < numFilters; ++i) { for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
filter = filters[i]; tileCoord = new ol.TileCoord(z, x, y);
type = filter.getType(); key = tileCoord.toString();
features = source.getFeatures(filter); if (key in this.tileCache_) {
if (features.length) { tile = this.tileCache_[key];
// TODO: layer.getSymbolizerLiterals(features) or similar } else {
symbolizer = symbolizers[type]; tileExtent = tileGrid.getTileCoordExtent(tileCoord);
canvasRenderer.renderFeaturesByGeometryType(type, features, symbolizer); // 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);
} }
} }

View File

@@ -126,7 +126,8 @@ ol.source.FeatureCache.prototype.getFeaturesByIds_ = function(ids) {
/** /**
* @typedef {{attributions: (Array.<ol.Attribution>|undefined), * @typedef {{attributions: (Array.<ol.Attribution>|undefined),
* extent: (ol.Extent|undefined), * extent: (ol.Extent|undefined),
* projection: (ol.Projection|undefined)}} * projection: (ol.Projection|undefined),
* tileGrid: (ol.tilegrid.TileGrid|undefined)}}
*/ */
ol.source.VectorOptions; ol.source.VectorOptions;
@@ -134,7 +135,7 @@ ol.source.VectorOptions;
/** /**
* @constructor * @constructor
* @extends {ol.source.Source} * @extends {ol.source.TileSource}
* @param {ol.source.VectorOptions} options Source options. * @param {ol.source.VectorOptions} options Source options.
*/ */
ol.source.Vector = function(options) { ol.source.Vector = function(options) {
@@ -142,7 +143,8 @@ ol.source.Vector = function(options) {
goog.base(this, { goog.base(this, {
attributions: options.attributions, attributions: options.attributions,
extent: options.extent, 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(); 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;
};
/** /**