HomeBlogBooksProjectsArchiveAboutlogo

Creating static maps in OpenLayers using PhantomJS

17 September, 2012 - 4 min read

Many times in a web mapping application it is desired to save a picture with the current map information.Those who works with Google Maps API has also the <a href="https://developers.google.com/maps/documentation/staticmaps/"

Static Maps API, which works similarly than Google Maps but produces static images.

For example, next call:

http://maps.googleapis.com/maps/api/staticmap?center=Brooklyn+Bridge,New+York,NY&zoom=13&size=600x300&maptype=roadmap
&markers=color:blue%7Clabel:S%7C40.702147,-74.015794&markers=color:green%7Clabel:G%7C40.711614,-74.012318
&markers=color:red%7Ccolor:red%7Clabel:C%7C40.718217,-73.998284&sensor=false

produces the image:

staticmap

Unfortunately, using libraries other than Google Maps, like OpenLayers or Leaflet, there is no similar solution. Probably the best, simple and powerful one, is to install a plugin on your browser to take screenshots. But well.. I think that does not deserve to write a post :p

How to render a web page element to an image?

After writing my last post (Taking Web Page Screenshots), where I show to to take a screenshot of a whole page, I was thinking on using PhantomJS to render only a portion of a page to an image.

The PhantomJS's WebPage object has a clipRect property which determines the portion of the web page that must be rendered. With this in mind we can see a solution could be:

  • Get the bounding rectangle of the desired DOM element to be rasterized.
  • Set the clipRect property
  • Render the page to a file.

For that purpose I have prepared a little JavaScript application to run with PhantomJS. Its usage is as follows:

./bin/phantomjs ./examples/rasterize_element.js URL output_file selector

For example, the next execution against the demo of Animated Cluster Strategy for OpenLayers selecting the first map:

./bin/phantomjs ./examples/rasterize_element.js http://www.acuriousanimal.com/AnimatedCluster map.png '#map1'

Produces the image:

map

Next is the whole code of the program (called rasterize_element.js and based on the rasterize.js application attached on the PhantomJS package):

Note: The code is accesible at GitHub:Gist.

var page = require('webpage').create(),
    system = require('system'),
    address, output, size;

if (system.args.length < 4 || system.args.length > 6) { console.log('Usage: rasterize_element.js URL filename selector [paperwidth*paperheight|paperformat] [zoom]'); console.log(' paper (pdf output) examples: "5in7.5in", "10cm20cm", "A4", "Letter"'); phantom.exit(1); } else { address = system.args[1]; output = system.args[2]; selector = system.args[3]; page.viewportSize = { width: 600, height: 600 }; if (system.args.length > 3 && system.args[2].substr(-4) === ".pdf") { size = system.args[3].split('*'); page.paperSize = size.length === 2 ? { width: size[0], height: size[1], margin: '0px' } : { format: system.args[3], orientation: 'portrait', margin: '1cm' }; } if (system.args.length > 4) { page.zoomFactor = system.args[4]; } console.log("Loading page..."); page.open(address, function (status) { if (status !== 'success') { console.log('Unable to load the address!'); } else { window.setTimeout(function () { console.log("Getting element clipRect..."); var clipRect = page.evaluate(function (s) { var cr = document.querySelector(s).getBoundingClientRect(); return cr; }, selector);

            page.clipRect = {
                top:    clipRect.top,
                left:   clipRect.left,
                width:  clipRect.width,
                height: clipRect.height
            };
            console.log("Rendering to file...");
            page.render(output);
            phantom.exit();
        }, 200);
    }
});

}

Alternatives and references

Of course I'm not the first one that has explore this issue. A nice snippet from n1k0 can be found at GitHub:Gist. It does more or less the same as the code shown in this post.

Another alternative is the use of CasperJS. As its home page says:

CasperJS is an open source navigation scripting & testing utility written in Javascript and based on PhantomJS — the scriptable headless WebKit engine. It eases the process of defining a full navigation scenario and provides useful high-level functions, methods & syntactic sugar for doing common tasks such as:

With CasperJS capturing a page element is as easy as:

casper.start('http://www.weather.com/', function() {
    this.captureSelector('weather.png', '.twc-story-block');
});
casper.run();