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 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:
Image may be NSFW.
Clik here to view.
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://acuriousanimal.com/code/animatedCluster/ map.png '#map1'
Produces the image:
Image may be NSFW.
Clik here to view.
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: "5in*7.5in", "10cm*20cm", "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:
- defining & ordering browsing navigation steps
- filling & submitting forms
- clicking & following links
- capturing screenshots of a page (or part of it)
- testing remote DOM
- logging events
- downloading resources, including binary ones
- writing functional test suites, saving results as JUnit XML
- scraping Web contents
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();