| Version 15 (modified by openlayers, 5 years ago) |
|---|
How do I save the current map view an an image, or print it?
OpenLayers itself doesn't handle printing, nor saving the image to a single image. This is because OpenLayers is actually an illusion, created by many different images being creatively overlaid in your browser's div. The only place the entire map exists, is in your browser's imagination. Nevertheless, there are a few options.
The Print button
The simplest method of printing, is simply the browser's own print function. You usually use File/Print to do this, but JavaScript also provides a print() function which does the same thing.
Mapserver
The following mechanism works if you're using UMN Mapserver. It's not very flexible, but it does work. It constructs a mapserv query containing all of the active layers and the view's extent and size.
Assumptions
- Your layers are either WMS or MapServer layers.
- All layers are being served from the same mapfile, be they WMS or MapServer layers.
Caveats
- This mechanism will not use TileCache, and will instead make a direct query to mapserv. If you have a layer that's very slow to render, then this method may take a long time.
// this assumes that the Map object is a JavaScript variable named "map"
// and that your layers are in a JavaScript array named "layers"
// and that both of these are in an iframe named "map"
// But changing these references is a simple matter to fit your setup.
function printMap() {
// fetch the extent and image size
var mapview = document.getElementById('map').contentWindow.map;
var layers = document.getElementById('map').contentWindow.layers;
var extent = mapview.getExtent();
extent = [extent.left,extent.bottom,extent.right,extent.top].join('+');
var width = mapview.getSize().w;
var height = mapview.getSize().h;
// build a comma-joined list of layers
var activelayers = [];
for (i in layers) {
if (!layers[i].getVisibility()) continue;
if (!layers[i].calculateInRange()) continue;
activelayers[activelayers.length] = layers[i].params['LAYERS'];
}
activelayers = activelayers.join('+');
// open a window to our pure-Mapserver version
var url = '/cgi-bin/mapserv?map=/maps/spraywatch2/wms/mapfile.map';
url += '&mode=map';
url += '&mapext=' + extent;
url += '&mapsize=' + width+'+'+height;
url += '&layers=' + activelayers;
window.open(url);
}
"Tile stitching" using a server-side program
This method uses JavaScript to collect a list of tiles' URLs and pixel locations, and sending these to a server-side script. The server-side script then assembles the numerous tiles into a whole image, and returns the URL of the completed document. NOTE: If using the "gutter" option with layers that will affect their placement in a printed image. With a little work, the document could easily be a PDF, or even an entire website with the image framed and annotated.
The client-side JavaScript follows. It uses the JSON JavaScript library, for encoding the collected tiles in JSON format for easy consumption by PHP. The JSON library can be obtained from http://www.JSON.org/js.html
// printMap() assumes that an iframe named 'map' contains a Map object named 'map'
// It should be very easy to change those first two lines to accommodate your situation.
function printMap() {
var mapview = document.getElementById('map').contentWindow.map;
var printurl = 'print.php';
var size = mapview.getSize();
// go through all layers, and collect a list of objects
// each object is a tile's URL and the tile's pixel location relative to the viewport
var tiles = [];
for (layername in mapview.layers) {
// if the layer isn't visible at this range, or is turned off, skip it
var layer = mapview.layers[layername];
if (!layer.getVisibility()) continue;
if (!layer.calculateInRange()) continue;
// iterate through their grid's tiles, collecting each tile's extent and pixel location at this moment
for (tilerow in layer.grid) {
for (tilei in layer.grid[tilerow]) {
var tile = layer.grid[tilerow][tilei]
var url = layer.getURL(tile.bounds);
var position = tile.position;
var opacity = layer.opacity ? parseInt(100*layer.opacity) : 100;
tiles[tiles.length] = {url:url, x:position.x, y:position.y, opacity:opacity};
}
}
}
// hand off the list to our server-side script, which will do the heavy lifting
var tiles_json = JSON.stringify(tiles);
var printparams = 'width='+size.w + '&height='+size.h + '&tiles='+escape(tiles_json) ;
new OpenLayers.Ajax.Request(printurl, {method:'post', parameters:printparams, onComplete:printDone });
// if using OpenLayers 2.7 or later use this request (printDone function not necessary):
// OpenLayers.Request.POST({url:printurl, data:OpenLayers.Util.getParameterString({width:size.w,
// height:size.h, tiles:tiles_json}), headers:{"Content-Type":"application/x-www-form-urlencoded"},
// callback: function(request) {window.open(request.responseText);}});
}
// when the request is ready (the image has been generated) this callback expects the output
// to have been simply an URL indicating where to find the generated document.
function printDone(request) {
var url = request.responseText;
window.open(url);
}
Server-side code for print.php. This requires that PHP have been compiled with GD support, which is the default for virtually all OSs. json_decode requires PHP 5.2.0 or later.
<?php
// fetch the request params, and generate the name of the tempfile and its URL
$width = @$_REQUEST['width']; if (!$width) $width = 1024;
$height = @$_REQUEST['height']; if (!$height) $height = 768;
$tiles = json_decode(@$_REQUEST['tiles']);
//$tiles = json_decode(stripslashes(@$_REQUEST['tiles'])); // use this if you use magic_quotes_gpc
$random = md5(microtime().mt_rand());
$file = sprintf("%s/%s.jpg", TEMP_DIR, $random );
$url = sprintf("%s/%s.jpg", TEMP_URL, $random );
// lay down an image canvas
// Notice: in MapServer if you have set a background color
// (eg. IMAGECOLOR 60 100 145) that color is your transparent value
// $transparent = imagecolorallocatealpha($image,60,100,145,127);
$image = imagecreatetruecolor($width,$height);
imagefill($image,0,0, imagecolorallocate($image,255,255,255) ); // fill with white
$transparent = imagecolorallocatealpha($image,255,255,255,127);
imagealphablending($image,true);
// loop through the tiles, blitting each one onto the canvas
foreach ($tiles as $tile) {
// try to convert relative URLs into full URLs
// this could probably use some improvement
$tile->url = urldecode($tile->url);
if (substr($tile->url,0,4)!=='http') {
$tile->url = preg_replace('/^\.\//',dirname($_SERVER['REQUEST_URI']).'/',$tile->url);
$tile->url = preg_replace('/^\.\.\//',dirname($_SERVER['REQUEST_URI']).'/../',$tile->url);
$tile->url = sprintf("%s://%s:%d/%s", isset($_SERVER['HTTPS'])?'https':'http', $_SERVER['SERVER_ADDR'], $_SERVER['SERVER_PORT'], $tile->url);
}
$tile->url = str_replace(' ','+',$tile->url);
// fetch the tile into a temp file, and analyze its type; bail if it's invalid
$tempfile = sprintf("%s/%s.img", TEMP_DIR, md5(microtime().mt_rand()) );
file_put_contents($tempfile,file_get_contents($tile->url));
list($tilewidth,$tileheight,$tileformat) = @getimagesize($tempfile);
if (!$tileformat) continue;
// load the tempfile's image, and blit it onto the canvas
switch ($tileformat) {
case IMAGETYPE_GIF:
$tileimage = imagecreatefromgif($tempfile);
break;
case IMAGETYPE_JPEG:
$tileimage = imagecreatefromjpeg($tempfile);
break;
case IMAGETYPE_PNG:
$tileimage = imagecreatefrompng($tempfile);
break;
}
imagealphablending($tileimage,true);
imagecopy($image, $tileimage, $tile->x, $tile->y, 0, 0, $tilewidth, $tileheight);
}
// save to disk and tell the client where they can pick it up
imagejpeg($image,$file);
print $url;
?>
Alternately, print.php could be print.py and the Python Imaging Library (PIL) could be used.
Anybody? :)
MapFish
MapFish is a framework based on OpenLayers that provides additional GUI elements and server side components. On of those components is a print service. Mappings exist for Java, Python and PHP. More information about this component here:
