Properly rendering features to canvas tiles
It looks like this approach will work well for panning (as anticipated). For animated zooming, it is not going to work as is. It looks like the canvas tile generation is too much for this type of animation loop. Though there are clearly still areas for optimization: * Don't create new tiles while animating between zoom levels. Using existing tiles only while animating should bring a significant performance gain. * Simple spatial index for tiles - each tile coord in the matrix could have a feature lookup object (keyed by id). This needs to account for rendered dimension (as witnessed by the point being cut by a tile). Given that the current example uses only three features, adding the spatial index should only be a minor improvement. * Reuse a fixed set of canvas tiles that are generated at construction (and increased/decreased with view size changes). * If a fixed set of tiles is not used, at least new ones could be cloned from existing ones (minor). * Do some profiling to look for more ideas. In addition, world-wrapping needs addressed. I don't think this renderer is the right (or at least the only) place to address this. And the cache of tiles needs to be managed for real. But hey, at least we've got a working tiled vector renderer now.
This commit is contained in:
@@ -2,23 +2,46 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="chrome=1">
|
||||||
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
|
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
|
||||||
<link rel="stylesheet" href="style.css" type="text/css">
|
<link rel="stylesheet" href="style.css" type="text/css">
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
#map {
|
html, body, #map {
|
||||||
width: 512px;
|
margin: 0;
|
||||||
height: 256px;
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
#canvas {
|
#text {
|
||||||
padding-top: 2em;
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 8px;
|
||||||
|
z-index: 20000;
|
||||||
|
background-color: white;
|
||||||
|
padding: 0 0.5em 0.5em 0.5em;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
@media only screen and (max-width: 600px) {
|
||||||
|
#text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<link rel="stylesheet" href="../css/ol.css" type="text/css">
|
<title>Full-screen example</title>
|
||||||
<title>ol3 vector demo</title>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="map"></div>
|
<div id="map">
|
||||||
<div id="canvas"></div>
|
<div id="text">
|
||||||
|
<h1 id="title">Vector rendering example</h1>
|
||||||
|
<div id="shortdesc">Uses a canvas renderer for drawing vector features.</div>
|
||||||
|
<div id="docs">
|
||||||
|
<p>See the
|
||||||
|
<a href="vector-layer.js" target="_blank">vector-layer.js source</a>
|
||||||
|
to see how this is done.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="tags">vector, feature, canvas</div>
|
||||||
<script src="loader.js?id=vector-layer" type="text/javascript"></script>
|
<script src="loader.js?id=vector-layer" type="text/javascript"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -37,7 +37,3 @@ var map = new ol.Map({
|
|||||||
zoom: 0
|
zoom: 0
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: remove me
|
|
||||||
document.getElementById('canvas').appendChild(
|
|
||||||
map.renderer_.getLayerRenderer(vector).canvas_);
|
|
||||||
|
|||||||
@@ -78,10 +78,20 @@ ol.renderer.canvas.VectorLayer = function(mapRenderer, layer) {
|
|||||||
this.renderedResolution_;
|
this.renderedResolution_;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {ol.Extent}
|
||||||
|
*/
|
||||||
|
this.renderedExtent_ = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag to be set internally when we know something has changed that suggests
|
||||||
|
* we need to re-render.
|
||||||
|
* TODO: discuss setting this for all layers when something changes before
|
||||||
|
* calling map.render().
|
||||||
* @private
|
* @private
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
*/
|
*/
|
||||||
this.layerChanged_ = false;
|
this.dirty_ = false;
|
||||||
|
|
||||||
|
|
||||||
// TODO: implement layer.setStyle(style) where style is a set of rules
|
// TODO: implement layer.setStyle(style) where style is a set of rules
|
||||||
@@ -143,13 +153,14 @@ ol.renderer.canvas.VectorLayer.prototype.getTransform = function() {
|
|||||||
ol.renderer.canvas.VectorLayer.prototype.renderFrame =
|
ol.renderer.canvas.VectorLayer.prototype.renderFrame =
|
||||||
function(frameState, layerState) {
|
function(frameState, layerState) {
|
||||||
|
|
||||||
|
// TODO: consider bailing out here if rendered center and resolution
|
||||||
|
// have not changed. Requires that other change listeners set a dirty flag.
|
||||||
|
|
||||||
var view2DState = frameState.view2DState,
|
var view2DState = frameState.view2DState,
|
||||||
resolution = view2DState.resolution,
|
resolution = view2DState.resolution,
|
||||||
extent = frameState.extent;
|
extent = frameState.extent,
|
||||||
|
source = this.getVectorLayer().getVectorSource(),
|
||||||
var layer = this.getVectorLayer();
|
tileGrid = source.getTileGrid();
|
||||||
var source = layer.getVectorSource();
|
|
||||||
var tileGrid = source.getTileGrid();
|
|
||||||
|
|
||||||
if (goog.isNull(tileGrid)) {
|
if (goog.isNull(tileGrid)) {
|
||||||
// lazy tile source creation to match the view projection
|
// lazy tile source creation to match the view projection
|
||||||
@@ -158,12 +169,40 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame =
|
|||||||
source.setTileGrid(tileGrid);
|
source.setTileGrid(tileGrid);
|
||||||
}
|
}
|
||||||
|
|
||||||
var tileSize = tileGrid.getTileSize();
|
// set up transform for the layer canvas to be drawn to the map canvas
|
||||||
var z = tileGrid.getZForResolution(resolution);
|
var tileSize = tileGrid.getTileSize(),
|
||||||
var tileResolution = tileGrid.getResolution(z);
|
z = tileGrid.getZForResolution(resolution),
|
||||||
var tileRange = tileGrid.getTileRangeForExtentAndResolution(
|
tileResolution = tileGrid.getResolution(z),
|
||||||
extent, tileResolution);
|
tileRange = tileGrid.getTileRangeForExtentAndResolution(
|
||||||
var tileRangeExtent = tileGrid.getTileRangeExtent(z, tileRange);
|
extent, tileResolution),
|
||||||
|
tileRangeExtent = tileGrid.getTileRangeExtent(z, tileRange),
|
||||||
|
sketchOrigin = tileRangeExtent.getTopLeft(),
|
||||||
|
transform = this.transform_;
|
||||||
|
|
||||||
|
goog.vec.Mat4.makeIdentity(transform);
|
||||||
|
goog.vec.Mat4.translate(transform,
|
||||||
|
frameState.size.width / 2,
|
||||||
|
frameState.size.height / 2,
|
||||||
|
0);
|
||||||
|
goog.vec.Mat4.scale(transform,
|
||||||
|
tileResolution / resolution, tileResolution / resolution, 1);
|
||||||
|
goog.vec.Mat4.rotateZ(transform, view2DState.rotation);
|
||||||
|
goog.vec.Mat4.translate(transform,
|
||||||
|
(sketchOrigin.x - view2DState.center.x) / tileResolution,
|
||||||
|
(view2DState.center.y - sketchOrigin.y) / tileResolution,
|
||||||
|
0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fastest path out of here. This method is called many many times while
|
||||||
|
* there is nothing to do (e.g. while waiting for tiles from every other
|
||||||
|
* layer to load.) Do not put anything above here that is more expensive than
|
||||||
|
* necessary. And look for ways to get here faster.
|
||||||
|
*/
|
||||||
|
if (!this.dirty_ && this.renderedResolution_ === tileResolution &&
|
||||||
|
// TODO: extent.equals()
|
||||||
|
this.renderedResolution_.toString() === tileRangeExtent.toString()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// clear tiles at alt-z
|
// clear tiles at alt-z
|
||||||
if (this.renderedResolution_ != tileResolution) {
|
if (this.renderedResolution_ != tileResolution) {
|
||||||
@@ -179,48 +218,37 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame =
|
|||||||
tileSize.width * tileRange.getWidth(),
|
tileSize.width * tileRange.getWidth(),
|
||||||
tileSize.height * tileRange.getHeight());
|
tileSize.height * tileRange.getHeight());
|
||||||
|
|
||||||
|
// transform for map coords to sketch canvas pixel coords
|
||||||
|
var sketchTransform = this.sketchTransform_;
|
||||||
|
var halfWidth = sketchSize.width / 2;
|
||||||
|
var halfHeight = sketchSize.height / 2;
|
||||||
|
goog.vec.Mat4.makeIdentity(sketchTransform);
|
||||||
|
goog.vec.Mat4.translate(sketchTransform,
|
||||||
|
halfWidth,
|
||||||
|
halfHeight,
|
||||||
|
0);
|
||||||
|
goog.vec.Mat4.scale(sketchTransform,
|
||||||
|
1 / tileResolution,
|
||||||
|
-1 / tileResolution,
|
||||||
|
1);
|
||||||
|
goog.vec.Mat4.translate(sketchTransform,
|
||||||
|
-(sketchOrigin.x + halfWidth * tileResolution),
|
||||||
|
-(sketchOrigin.y - halfHeight * tileResolution),
|
||||||
|
0);
|
||||||
|
|
||||||
|
// clear/resize sketch canvas
|
||||||
sketchCanvas.width = sketchSize.width;
|
sketchCanvas.width = sketchSize.width;
|
||||||
sketchCanvas.height = sketchSize.height;
|
sketchCanvas.height = sketchSize.height;
|
||||||
|
|
||||||
|
var sketchCanvasRenderer = new ol.renderer.canvas.Renderer(
|
||||||
|
sketchCanvas, sketchTransform);
|
||||||
|
|
||||||
// clear/resize final canvas
|
// clear/resize final canvas
|
||||||
var finalCanvas = this.canvas_;
|
var finalCanvas = this.canvas_;
|
||||||
finalCanvas.width = sketchSize.width;
|
finalCanvas.width = sketchSize.width;
|
||||||
finalCanvas.height = sketchSize.height;
|
finalCanvas.height = sketchSize.height;
|
||||||
var finalContext = this.context_;
|
var finalContext = this.context_;
|
||||||
|
|
||||||
var sketchOrigin = tileRangeExtent.getTopLeft();
|
|
||||||
var frameOrigin = extent.getTopLeft();
|
|
||||||
var transform = this.transform_;
|
|
||||||
goog.vec.Mat4.makeIdentity(transform);
|
|
||||||
goog.vec.Mat4.translate(transform,
|
|
||||||
frameState.size.width / 2, frameState.size.height / 2, 0);
|
|
||||||
goog.vec.Mat4.rotateZ(transform, view2DState.rotation);
|
|
||||||
goog.vec.Mat4.scale(
|
|
||||||
transform,
|
|
||||||
tileResolution / view2DState.resolution,
|
|
||||||
tileResolution / view2DState.resolution,
|
|
||||||
1);
|
|
||||||
goog.vec.Mat4.translate(
|
|
||||||
transform,
|
|
||||||
(frameOrigin.x - view2DState.center.x) / tileResolution,
|
|
||||||
(view2DState.center.y - frameOrigin.y) / tileResolution,
|
|
||||||
0);
|
|
||||||
|
|
||||||
var sketchTransform = this.sketchTransform_;
|
|
||||||
goog.vec.Mat4.makeIdentity(sketchTransform);
|
|
||||||
goog.vec.Mat4.scale(
|
|
||||||
sketchTransform,
|
|
||||||
1 / tileResolution,
|
|
||||||
-1 / tileResolution,
|
|
||||||
1);
|
|
||||||
goog.vec.Mat4.translate(
|
|
||||||
sketchTransform,
|
|
||||||
-sketchOrigin.x,
|
|
||||||
-sketchOrigin.y,
|
|
||||||
0);
|
|
||||||
|
|
||||||
var sketchCanvasRenderer = new ol.renderer.canvas.Renderer(
|
|
||||||
sketchCanvas, sketchTransform);
|
|
||||||
var renderedFeatures = {};
|
var renderedFeatures = {};
|
||||||
var tile, tileContext, tileCoord, key, tileExtent, tileState, x, y;
|
var tile, tileContext, tileCoord, key, tileExtent, tileState, x, y;
|
||||||
// render features by geometry type
|
// render features by geometry type
|
||||||
@@ -264,26 +292,14 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame =
|
|||||||
tile.height = tileSize.height;
|
tile.height = tileSize.height;
|
||||||
tileContext = tile.getContext('2d');
|
tileContext = tile.getContext('2d');
|
||||||
|
|
||||||
// TODO: remove me
|
|
||||||
if (goog.DEBUG) {
|
|
||||||
tileContext.strokeStyle = '#999999';
|
|
||||||
tileContext.fillStyle = '#999999';
|
|
||||||
tileContext.textAlign = 'center';
|
|
||||||
tileContext.textBaseline = 'middle';
|
|
||||||
tileContext.font = '24px sans-serif';
|
|
||||||
tileContext.strokeRect(0.5, 0.5, tileSize.width - 1,
|
|
||||||
tileSize.height - 1);
|
|
||||||
tileContext.fillText(tileCoord.toString(), tileSize.width / 2,
|
|
||||||
tileSize.height / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
tileContext.drawImage(sketchCanvas,
|
tileContext.drawImage(sketchCanvas,
|
||||||
-x * tileSize.width, -(tileRange.maxY - y) * tileSize.height);
|
(tileRange.minX - x) * tileSize.width,
|
||||||
|
(y - tileRange.maxY) * tileSize.height);
|
||||||
this.tileCache_[key] = tile;
|
this.tileCache_[key] = tile;
|
||||||
}
|
}
|
||||||
finalContext.drawImage(tile,
|
finalContext.drawImage(tile,
|
||||||
(tileExtent.minX - frameOrigin.x) / tileResolution,
|
tileSize.width * (x - tileRange.minX),
|
||||||
(frameOrigin.y - tileExtent.maxY) / tileResolution);
|
tileSize.height * (tileRange.maxY - y));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user