Version 20 (modified by guest, 18 months 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

// this assumes that the Map object is a JavaScript variable named "map"
// changed 20090928:
// * fixed the weird references about iframes, since they're not the usual use case
// * added the Please Wait window
// * used newer-style OpenLayers.Request.POST() for final AJAX call
var print_wait_win = null;
function PrintMap() {
    //-- post a wait message
    print_wait_win = window.open("pleasewait.html", "print_wait_win", "scrollbars=no, status=0, height=5, width=10, resizable=1");

    // 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 size  = map.getSize();
    var tiles = [];
    for (layername in map.layers) {
        // if the layer isn't visible at this range, or is turned off, skip it
        var layer = map.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) ;
    OpenLayers.Request.POST(
      { url:'lib/print.php',
        data:OpenLayers.Util.getParameterString({width:size.w,height:size.h,tiles:tiles_json}),
        headers:{'Content-Type':'application/x-www-form-urlencoded'},
        callback: function(request) {
           print_wait_win.close();
           window.open(request.responseText);
        }
      }
    );
}

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
  $TEMP_DIR = '/var/www/htdocs/tmp/';
  $TEMP_URL = '/tmp/';

  function imagecopymerge_alpha($dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $opacity){
      $w = imagesx($src_im);
      $h = imagesy($src_im);
      $cut = imagecreatetruecolor($src_w, $src_h);
      imagecopy($cut, $dst_im, 0, 0, $dst_x, $dst_y, $src_w, $src_h);
      imagecopy($cut, $src_im, 0, 0, $src_x, $src_y, $src_w, $src_h);
      imagecopymerge($dst_im, $cut, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $opacity);
  }

  // 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

  // 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;
     }
       imagecopymerge_alpha($image, $tileimage, $tile->x, $tile->y, 0, 0, $tilewidth, $tileheight, $tile->opacity);
  }

  // save to disk and tell the client where they can pick it up
  imagejpeg($image,$file,100);
  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:

 https://trac.mapfish.org/trac/mapfish/wiki/PrintModuleDoc

wkhtmltopdf

 wkhtmltopdf is a command-line tool which converts a HTMl document into a PDF document. We have had significant success with implementing a web page in jQuery and OpenLayers: setting up an OpenLayers Map with a given extent, Bing basemap, WMS layers, and Markers, and then converting this HTML to PDF. The resulting PDF even includes OpenLayers Controls such as ScaleLine.