NOTE: take a look at updated solution
In an earlier post we showed how HTML5 Canvas could be used on mobile browsers. Although the example showed a map image, it was just that – an image rather than a web mapping app like Google maps or the popular open source OpenLayers. Getting canvas to work on a mapping application like Google / OpenLayers is somewhat more complicated as the map consists of dozens of image tiles, some within the view port, and others out of view but already cached in the DOM so that a smooth transition without page reloads is possible when a user pans around, creating a nice “slippy map” effect.
In this post I show how we tackled the problem of integrating HTML5 canvas into the Openlayers framework. We already posted on using OpenLayers on mobile browsers and how to make touch gestures rather than mouse events control panning and zooming. HTML5 lets us get inside the image itself enabling us to extract information from it and use that information to create our own graphics on the map. Below is a video showing one application, where we extracted pixel data from the base map layer ( greyscale showing high regions as white and low regions as black) and used the extracted height data to draw an elevation graph on the overlayed Ordnance Survey map layer.
btw. height data used in video can be obtained here.
First I should clarify our objective. A canvas renderer already exists in OpenLayers but this is intended to be used for programmers to create their own layer from source data using the Vector class. What we are trying to do is use a standard map layer like WMS, OSM, Google etc. and get OpenLayers to draw the tiles on a <canvas> element rather than the normal <img> element. Now there is a right way and a wrong way to do this. The right way is to update the open layers source so that images are redrawn on a single canvas element in the layer div. The wrong way is to use DOM manipulation library like jQuery to dynamically replace images with a canvas element when the user hovers over a tile; nothing less than an outrageous hack. Naturally we opted for the later. You can see our example ( does not work in IE ) in action with an OpenStreetMap layer that you can draw on ( hold down the ‘d’ key to draw on the map by moving mouse).
To understand how this works you need to first take a look at how a map layer looks in the DOM. In the video below the firebug debugger captures the DOM structure in the normal case where the map tiles are drawn in a bunch of <img> elements.
Our approach is simply to replace those <img> elements with canvas elements and redraw the original image on the canvas. In fact, we do not remove the <img> element but merely make it invisible and add a canvas element to the DOM as its sibling- as can be seen from the video clip below.
You see in the DOM that all the elements have been given the same class by OpenLayers
olTileImage
which provides a handle for jQuery to grab the content we want to change.
$(document).ready(function() {
mapinit() ;
$("img.olTileImage").live( "mouseover", function(){
var parentDiv = $(this).parent().get(0) ;
......
In the snippet above we add a live mouseover event listener to any image in the document with class=”olTileImage” and obtain a reference to the containing tile div which we use later to add a canvas element ( see below). The JQuery live function ensures that the event listener is added not only to the elements available after initial document load but also to any new olTileImages
loaded into the DOM dynamically. The code that adds the canvas element to the tile div is shown in the code below:
if( parentDiv.getElementsByTagName("canvas").length < 1 )
{
// make original img element invisible
$(this).get(0).style.display = "none" ;
tileCanvas = document.createElement("canvas");
parentDiv.appendChild(tileCanvas) ;
tileCanvas.width = 256 ;
tileCanvas.height = 256 ;
c = tileCanvas.getContext("2d") ;
// redraw image on canvas
c.drawImage($(this).get(0), 0, 0);
}
Note that the original <img> element has its style.dipslay
property changed to make it invisible. A canvas element is added to the same element that holds the orginal <img> and the image is pasted into the canvas using the HTML5 Canvas context.drawImage()
method. A trick I found useful was to make the <canvas> ever so slightly smaller than the image ( i.e. set height and width to 255 instead of 256 ) – then the canvas has a small white border making it clearer for debugging whether the canvas has attached – a bit faster than checking the DOM with Firebug.
Once the canvas has been created and the image copied into it, all we need to do is add an event listener to the new canvas element so that it tracks mouse movements and draws a line across the path.
tileCanvas.addEventListener('mousemove',function (e) {
if(drawMode)
{
var xy = getxy(e, tileCanvas ) ;
c = tileCanvas.getContext("2d") ;
c.fillStyle = '#00A308';
c.fillRect(xy.x,xy.y,3,3) ;
}
},false) ;
And that’s more or less it ( see code source documentation for the caveats). Obviously the mouse oriented event handling in the example needs to be adapted to mobile browsers. We would expect sensors such as GPS and compass to replace the mouse in determining where lines are drawn. For example, the elevation graph shown in the video above might work well with the compass where a line could be drawn on the map showing the direction and elevation profile you are pointing your device towards. It should be said that OpenLayers already lets you draw simple lines on the map but we think the most interesting applications will attempt to obtain meaningful data from the image using feature extraction techniques and that is where canvas comes into its own. Look out for future developments on geo mobile!
Thank you for this nice post! Is there any way i could get the source for the elevation extraction application shown n the first video? I’ve been struggeling with making something like that but gave up because of running into loading and security issues. I concluded the image data would have to be saved locally (on the same url as the script)in order to be manipulated in a canvas-element. But you seem to use image data from a wms. How do you do that without getting a security error report?
Kind regards, Mårten, Sweden
By: Mårten Karlberg on March 10, 2010
at 11:54 am
I ran into the same problem. The demo in the video in fact runs from our own WMS so I was able to put the html page in the same domain. A solution we are looking at is using Apache proxy mod to make the wms domain look the same as the application domain. You have to be very careful about it though as general forward proxy is a BIG security hole. We think using the Proxy Pass directive will be safe way of doing it though. I’ll try to put up a post with the elevation working on OpenStreetMap once we have that solution tested.
By: benismobile on March 10, 2010
at 5:00 pm
Nice! I’m a little late to the party it seems, but I tried my hands on drawing Google tiles on a single canvas.
http://www.mapfolds.com/2010/11/25/google-maps-on-html5-canvas-grains-on-a-chessboard
Hope you enjoy it.
By: Thomas on November 25, 2010
at 11:12 pm
I have problem integrating Openlayers in mobile Safari webkit with the latest mobile openlayers. It would have been nice if you post Openlayers project example that you describe in your blogs so we can try it in other platform.
Anyway, have you manage to run Openlayers in inside iPhone /iPad? I am not talking about rending from a server into iPhone / iPad, just to make it clear.
By: Noli on March 22, 2011
at 2:48 am
Reblogged this on HTML5 Spain and commented:
Un post muy interesante sobre el uso de OpenLayers y el elemento Canvas.
By: equintano on March 1, 2012
at 2:22 pm
Just a little question: since most of OpenLayers-maps consist of more the n just a single img-tag, how to handle this? Create more then one canvas?
By: Pimp on April 4, 2012
at 10:57 am