The third useful canvas operation we will discuss is saving the content of the canvas as an image. As you may already know, traditionally, files can be saved by downloading them from a server. There are serious security considerations behind the restriction of saving anything to the hard drive that's created on the client side. However, there is no restriction in displaying a dynamically created image.
In this example, called ch07_print, we will create a control that dynamically creates an image based on the map canvas's context and opens it in a new tab. This way, we can download the image just like any other plain image from a web page. First, we will create a new control to save map states:
ol.control.Print = function (opt_options) {
var options = opt_options || {};
var _this = this;
var controlDiv = document.createElement('div');
controlDiv.className = options.class || 'ol-print ol-unselectable ol-control';
var controlButton = document.createElement('button');
controlButton.textContent = options.label || 'P';
controlButton.title = options.tipLabel || 'Print map';
var dataURL;
controlButton.addEventListener('click', function (evt) {
_this.getMap().once('postcompose', function (evt) {
var canvas = evt.context.canvas;
dataURL = canvas.toDataURL('image/png');
});
_this.getMap().renderSync();
window.open(dataURL, '_blank');
dataURL = null;
});
controlDiv.appendChild(controlButton);
ol.control.Control.call(this, {
element: controlDiv,
target: options.target
});
};
ol.inherits(ol.control.Print, ol.control.Control);The control is very simple. If we push the button, it registers a one-time listener to the map object and converts the content of its canvas to a png image. More precisely, it converts it to a Base64 encoded URL of the png image, which can be opened by browsers, just like regular websites. After we have the URL, we call a synchronous rendering frame, making sure that the event occurs before we try to open our image. Finally, we simply open our image in a new tab.
Next, we modify our map constructor a little bit. We deploy two new layers, adjust their views, and add our new control:
map = new ol.Map({
target: 'map',
layers: [
new ol.layer.Tile({
source: new ol.source.TileWMS({
url: 'http://demo.opengeo.org/geoserver/wms',
params: {
layers: 'ned',
format: 'image/png'
},
wrapX: false,
crossOrigin: 'anonymous'
}),
name: 'Elevation'
}),
new ol.layer.Tile({
source: new ol.source.TileWMS({
url: 'http://demo.opengeo.org/geoserver/wms',
params: {
layers: 'nlcd',
format: 'image/png'
},
wrapX: false,
crossOrigin: 'anonymous'
}),
name: 'Land Cover'
})
],
controls: [
[…]
new ol.control.Print({
target: 'toolbar'
})
],
view: new ol.View({
center: [-8604363.40000572, 4741541.738586053],
zoom: 9
})
});As you can see, we used an extra property when we constructed the layer objects. The crossOrigin property is very important if we want to access the pixel values of the canvas. This is also a security consideration that does not let us manipulate or export the content of a canvas if we have used a non-CORS image at any point in time. To mark the images of a layer as cross origin, we only have to set the layer's crossOrigin property to anonymous. If we do not implement this step, we end up with a tainted canvas, which we cannot export.
If you save the example and load it up, you can open the map's content in the form of a picture in a new tab:
