Future/OpenLayersWithCanvas


Note

This page is partly out-of-date, please refer to OpenLayers and HTML5.


OpenLayers with Canvas

Currently OpenLayers has already a vector renderer ( OpenLayers.Renderer.Canvas) that uses the HTML element  Canvas. There are also experiments using Canvas for rendering raster layers:  Transition Example.

This Wiki page will present results of performance evaluations comparing SVG vs. Canvas for vector layers and comparing Image vs. Canvas for Grid layers. The changes to OpenLayers can be found in this  sandbox.

Vector Layers

Performance evaluation

A small test suite has been built to run the tests:  performance-tests

First test/notes

Test data:  World Countries Boundaries (World Countries)
# of Features: 190
# of Vertices: 6925
Geometry type: (Multi-) Polygon
Map size: 800 px x 800 px
Browser: Chromium 5.0.342.9

Test CaseCanvasSVG
Display layer67 ms215 ms
Display layer and pan 10 times1192 ms380 ms
Display layer and zoom 10 times1174 ms1181 ms
Select feature158 ms0 ms

The shown numbers are the average of 10 test runs.

Notes:

  • Canvas is fast! Displaying a layer for the first times is up to 3-times faster than SVG.
  • But once the layer is drawn, panning is faster with SVG. The Canvas renderer simply redraws the whole layer every time the map is panned, SVG seems to profit from some internal optimization.
  • Zooming takes approx. the same time for SVG and Canvas.
  • Selecting features is much faster in SVG. This is because in SVG you can set click- and hover-events on geometries inside the SVG element. But for Canvas the method Canvas.getFeatureIdFromEvent(evt) has to loop all geometries to check if a bounding-box around the mouse position intersects the geometry. For large data sets this gets a performance problem especially when hovering, when the method is executed on every mouse move.

Optimization

The first test has shown that the canvas renderer compared to the SVG renderer is slower for panning and selecting features (by mouse click/hover). To improve this two operations, a spatial index based on a R-Tree ( R-Tree Library for Javascript) has been integrated.

The method Canvas.getFeatureIdFromEvent(evt) now uses the R-Tree to query the features that are near the current mouse position. The features are inserted into the R-Tree, when they are added to the renderer with Canvas.drawFeature(). Canvas.eraseFeatures() also removes the feature from the R-Tree.

Canvas.redraw() also benefits from the R-Tree. The canvas renderer used to draw all geometries even if they were not inside the current map extent. Now using a R-Tree query only those features are drawn whose bounding box is inside the current extent. If the whole map is shown, so that all geometries are rendered, this won't improve the performance. But if a smaller extent is displayed, this speeds up the render time.

The usage of the R-Tree is optional, by default no R-Tree is used for the canvas renderer. The R-Tree can be activated by setting a flag in rendererOptions for a layer (rendererOptions: {'useRTree': useRTRee}).

The canvas renderer when used without a R-Tree has also been improved. Canvas.redraw() now also only draws the geometries which are inside the map extent. These geometries are selected by checking if the bounding box of the geometry intersects the mouse position rectangle. Canvas.getFeatureIdFromEvent(evt) used to run slow intersection tests for every geometry. Now a bounding-box intersection test is executed first, and only if this test is successful, a correct intersection test with the original geometry is run.

Test of the optimizations

Canvas 1: Canvas renderer without any optimizations ( rev. 10316)
Canvas 2: Canvas renderer without R-Tree (only features in the current extent are rendered and getFeatureIdFromEvent first checks if the bounding boxes intersect)
Canvas 3: Canvas renderer with R-Tree

Test-Case 01: Show

Map size: 800 px x 800 px
Geometry type: (Multi-)Polygon
Test-Data: countries-non-simplified.json / countries-simplified-x.json (Source:  Natural Earth: Admin 0 – Countries)
# of Features: 246
Test runs: 10

Test-Data# of VerticesCanvas 1Canvas 2Canvas 3 (with R-Tree)SVG
countries-simplified-1215035846171
countries-simplified-0.5450655120108146
countries-simplified-0.054503134211199181263
countries-simplified-0.005159771834314930153782
countries-non-simplified4031501822838470768953

http://trac.openlayers.org/attachment/wiki/Future/OpenLayersWithCanvas/01.png?format=raw

Test-Case 02: Pan 10-times in a smaller extent after the map is shown

Map size: 800 px x 800 px
Geometry type: (Multi-)Polygon
Test-Data: countries-non-simplified.json / countries-simplified-x.json (Source:  Natural Earth: Admin 0 – Countries)
# of Features: 246
Test runs: 10
Start extent: 6, 45, 6.5, 45.5

Test-Data# of VerticesCanvas 1Canvas 2Canvas 3 (with R-Tree)SVG
countries-simplified-1215052430135885
countries-simplified-0.54506759380452141
countries-simplified-0.05450314085138417291058
countries-simplified-0.00515977111236387634523372
countries-non-simplified40315020850776072795129

http://trac.openlayers.org/attachment/wiki/Future/OpenLayersWithCanvas/02.png?format=raw

Test-Case 03: Zoom 10-times after the map is shown

Map size: 800 px x 800 px
Geometry type: (Multi-)Polygon
Test-Data: countries-non-simplified.json / countries-simplified-x.json (Source:  Natural Earth: Admin 0 – Countries)
# of Features: 246
Test runs: 10

Test-Data# of VerticesCanvas 1Canvas 2Canvas 3 (with R-Tree)SVG
countries-simplified-12150332321316423
countries-simplified-0.54506486455371653
countries-simplified-0.05450312152233320795008
countries-simplified-0.00515977157116225596712208
countries-non-simplified40315015602125381218623367

http://trac.openlayers.org/attachment/wiki/Future/OpenLayersWithCanvas/03.png?format=raw

Test-Case 04: Select 10 Features (Simulate feature selection by mouse click)

Map size: 800 px x 800 px
Geometry type: (Multi-)Polygon
Test-Data: countries-non-simplified.json / countries-simplified-x.json (Source:  Natural Earth: Admin 0 – Countries)
# of Features: 246
Test runs: 10

Test-Data# of VerticesCanvas 1Canvas 2Canvas 3 (with R-Tree)SVG
countries-simplified-1215032144430.6
countries-simplified-0.5450647543100.6
countries-simplified-0.054503123351042891.3
countries-simplified-0.00515977126372245116201.5
countries-non-simplified40315047912443525112.0

http://trac.openlayers.org/attachment/wiki/Future/OpenLayersWithCanvas/04.png?format=raw

Test-Case 05: Add Features

Map size: 800 px x 800 px
Geometry type: (Multi-)Polygon
Test-Data: countries-non-simplified.json / countries-simplified-x.json (Source:  Natural Earth: Admin 0 – Countries)
# of Features: 246
Test runs: 10

Test-Data# of VerticesCanvas 1Canvas 2Canvas 3 (with R-Tree)SVG
countries-simplified-1215057684834
countries-simplified-0.5450661766442
countries-simplified-0.054503132333828650
countries-simplified-0.00515977196269084953
countries-non-simplified40315017991533149671

http://trac.openlayers.org/attachment/wiki/Future/OpenLayersWithCanvas/05.png?format=raw

Notes

  • The improvements speed up but still can't reach the times for SVG.
  • Canvas.getFeatureIdFromEvent(evt) has been improved and selecting a feature takes a reasonable time, but editing/moving a feature does still not feel very smooth.
  • The SVG renderer is the fastest for all test cases, except for test case 01, displaying a layer for the first time. This is also because Canvas 01 does not need to calculate the bounds for all geometries, all other renderer do. This takes a large amount of the time. The following diagram shows the result for test-case 01 without the time needed to calculate the bounds.

Test-Case 01: Show (without bounds calculation)

Test-Data# of VerticesCanvas 1Canvas 2Canvas 3 (with R-Tree)SVG
countries-simplified-1215035524239
countries-simplified-0.5450655657691
countries-simplified-0.0545031342330337474
countries-simplified-0.00515977183494514791578
countries-non-simplified4031501822284431843413

http://trac.openlayers.org/attachment/wiki/Future/OpenLayersWithCanvas/01-pre-calculated-bounds.png?format=raw

Evaluation

For rendering vector data the canvas renderer won't replace the SVG renderer. SVG (Scalable Vector Graphics) is optimized for displaying vector data, all improvements to the canvas renderer are actually attempts to re-invent SVG in JavaScript. And then the main limiting factor is JavaScript.

In some special cases the canvas renderer may be an alternative. For example when you just want to display features on a map, but don't want to allow any interaction with the map (similar to the  Google Static Maps API and maybe also the  Google Visualization API).

It could also be used to render all layers onto one canvas (including all raster layers), so that you can simply copy the image and paste it into a OpenOffice/Word document or save it as image file (see  Raster - Discussion).

Beside that, by taking advantage of the canvas' abilities to modify graphics, a heat map can be created to visualize the density of point features (see  examples/heatMap.html and  OpenLayers.Layer.Vector.HeatMap).

http://trac.openlayers.org/attachment/wiki/Future/OpenLayersWithCanvas/canvas-vector-heatmap.png?format=raw

Raster Layers

There are basically two approaches to use the canvas element to render the tiles of a Grid layer. The first is to render all tiles of a layer on one canvas element (MODE.ONECANVASPERLAYER), like in this  Canvas Transition Example, and the second approach is to render every tile on its own canvas (MODE.ONECANVASPERTILE).

Both ways have their advantages and disadvantages. A single canvas reduces the number of elements in the DOM and also the number of DOM modifications. But every time the map is panned, the whole canvas has to be redrawn. In this case a canvas for every tile works much better, because only the new tiles have to been drawn when panning.

Performance evaluation

A test suite similar to the one for vector layers has been used to run the tests:  performance-tests

Test-Case 01: Display

Test-Data: OpenStreetMap tiles on a local HDD
 Grid-Buffer: 0
Zoom-Level: 4
Test runs: 20

# of TilesMap-SizeNOCANVASONECANVASPERLAYERONECANVASPERTILE
95126,36,78,2
1676810,513,213,5
25102418,317,921,9
8120484766,264,7
2894096150,6242,7245,8

http://trac.openlayers.org/attachment/wiki/Future/OpenLayersWithCanvas/01-raster.png?format=raw

Test-Case 02: Display the map and pan 10 times

Test-Data: OpenStreetMap tiles on a local HDD
 Grid-Buffer: 0
Zoom-Level: 5
Pan Offset: (+15, -10)
Start Point: (-160,60)
Test runs: 20

# of TilesMap-SizeNOCANVASONECANVASPERLAYERONECANVASPERTILE
951256,460,572,8
1676863,9125,180,5
25102478,4219,8106,1
812048293,5789593,4
289409655629861201

http://trac.openlayers.org/attachment/wiki/Future/OpenLayersWithCanvas/02-raster.png?format=raw

Discussion

One could have expected that the reduced number of DOM modifications shows a better result for ONECANVASPERLAYER compared to the original renderer. But in fact drawing one large canvas seems to be slower than adding an element to the DOM for every tile.

In all tests both canvas implementations weren't faster than the original renderer. But still it is worth knowing that drawing an image on a canvas is not much slower than displaying that image directly in an ordinary img-element.

Just for displaying tiles, the original approach (showing the tiles in img-elements) seems to be the better solution. But canvas allows to do new things that are not possible with an img-element. For example:

Attachments