5

As far as I understand this example from the OpenLayers page and the linked source code for this example, OpenLayers3 now has the offical ability to save maps as static image files.

However, the saving is done via a button on the website (and I do not fully understand how as I am not too much into Javascript).

Is there also a possibility to do this programatically in the following way?

  1. Programatically create a webpage with an embedded OpenLayers3 map, displaying data from whatever sources
  2. Export the webpage's canvas (all layers that would be displayed when opening the page in a browser) into a static image file

I think that creating the webpage would not be that difficult. However, the question is more on whether it is possible to use the exporting without a clickable button but by calling it via a script, command-line or similar.

I did some testing with this approach using PhantomJS to save a screenshot of the webpage. However, I have not managed yet to get this working properly (map object from my webpage not found, when found then rendering is started before all tiles have been loaded, size parameters are ignored.


Removed the OpenLayers3 dependency from the title, solutions using OpenLayers2 are also welcome.

2 Answers 2

2

Are you using GeoServer as a backend?
You could construct a GetMap request via JavaScript and use a HTML GET request to get that image.
I use this same approach to programmatically embed static maps into reports in Ms-Access.

EDIT:
I use PostgreSQL to store my data, and GeoServer handles the rendering and serving of that data.
To embed static maps in Ms-Access reports I first get the BoundingBox of the area I'd like a map of. This is done purely in SQL via a custom function to query my PostgreSQL database.
You could get the same answers using ol.extent and its methods (e.g. ol.extent.getBottomLeft()) to get your BoundingBox.

Here is my VBA Code constructing the request.

    GetMapString = "http://" + GeoServerHost + "/geoserver/wms?request=GetMap&service=WMS&version=1.1.3" & _
              "&layers=" + GeoServerWorkspace + ":" + LayerName & _
              "&styles=" & _
              "&srs=EPSG:27700" & _
              "&bbox=" & x1 & "," & y1 & "," & x2 & "," & y2 & _
              "&width=1200&height=1200" & _
              "&format_options=dpi:300;antialiasing:on" & _
              "&format=image%2Fpng8"

Building an equivalent GetMapString in JavaScript, then sending that with the HTML GET request, should return a PNG8 image.
Be sure to check & change the SRS parameter as you probably wouldn't want your image projected in British National Grid.

5
  • Well, not yet, but I would try to set up GeoServer if it helps... could you please post an example of how your approach works in detail? Meanwhile, I was making progress with saving a screenshot of the map content of a page with PhantomJS. I'll post the code as soon as it works as intended. However, I am not sure if this is the best way to go...
    – Dirk
    Jan 20, 2015 at 8:44
  • Thank you for your edit! I will try that out and see if I get it working (may take one or a few days). Meanwhile, I got it working (for not too large datasets) by using the phantomJS headless browser, I'll post my code below.
    – Dirk
    Jan 21, 2015 at 11:05
  • Works great! I have not tested it yet with really large data sets (drawback of the phantomJS approach given above) but this is hopefully unproblematic, given the right configuration of GeoServer (max. image size etc.). Moreover, this is quite flexible as you can also combine your own geodata (Shapefiles, GeoJSON, ...) with public WMS servers (there are e.g. some that offer OSM maps in different styles). See further: Setting up GeoServer & Serving WMS with GeoServer
    – Dirk
    Feb 5, 2015 at 9:50
  • @Dirk How would you combine your own geodata with public WMS servers to produce a static image? Do you have an example I can see? Thanks Mar 23, 2018 at 21:13
  • @OwenJLamb I have no working examples for that except for the links I mentioned in my last comment, sorry :/
    – Dirk
    Mar 28, 2018 at 16:10
2

The following approach works for me to save static maps to png files from a webpage with an embedded OpenLayers2 map (I see no reason why it should not work with OpenLayers3 though).

The following code is based on the example given at http://acuriousanimal.com/blog/tag/phantomjs/. I slightly modified it to integrate the code given by user cjoudrey on GitHut/Gist. This was the best working approach that I found to make phantomJS wait for the complete page to load (including all geodata and map tiles) before saving the map to an image file - otherwise, one would often get an image with the geodata plotted without the basemap tiles. Besides, I preferred hardcoding the necessary parameters instead of passing them from the command-line.

  1. Install phantomJS on your system as described on the official page. phantomJS is a headless browser kit which lets you access webpages from the command line without any GUI, controlling the webpage navigation with a JavaScript file. Moreover, it has a rendering function which lets you save screenshots from either complete webpages or only specific HTML elements.

  2. Create the following JavaScript file (naming it e.g. download.js) and make sure to modify the first three lines according to the webpage you want to query. mapID refers to the <div id="mymapid"</div> section on your webpage that embeds the OpenLayers map, respectively to the ID used in the map constructor, e.g. var map = new OpenLayers.Map("mymapid");.

download.js:

// modify these parameters accordingly!
var url = 'http://localhost:8000/OL.html';
var     mapID           = 'mymapid';
var     outputFilename  = 'map.png';

var resourceWait  = 300,
    maxRenderWait = 10000;

var page          = require('webpage').create(),
    count         = 0,
    forcedRenderTimeout,
    renderTimeout;

// function to execute map clipping and saving when page has fully loaded
function doRender() {

    console.log("Page has fully loaded");       
    console.log('clipping map...')

    var clipRect = page.evaluate(function(mapID){
        console.log(mapID)
        return document.getElementById(mapID).getBoundingClientRect();
        }, mapID);

    page.clipRect = {
        top:    clipRect.top,
        left:   clipRect.left,
        width:  clipRect.width,
        height: clipRect.height
        };

    console.log('rendering image...')
    console.log('top, left, width, height:')
    console.log(clipRect.top)
    console.log(clipRect.left)
    console.log(clipRect.width)
    console.log(clipRect.height)

    page.render(outputFilename);
    phantom.exit();

}

page.onResourceRequested = function (req) {
    count += 1;
    console.log('> ' + req.id + ' - ' + req.url);
    clearTimeout(renderTimeout);
};

page.onResourceReceived = function (res) {
    if (!res.stage || res.stage === 'end') {
        count -= 1;
        console.log(res.id + ' ' + res.status + ' - ' + res.url);
        if (count === 0) {
            renderTimeout = setTimeout(doRender, resourceWait);
        }
    }
};

page.open(url, function (status) {

    if (status !== "success") {
        console.log('Unable to load url');
        phantom.exit();
    } else {
        forcedRenderTimeout = setTimeout(function () {
            console.log(count);
            doRender();
        }, maxRenderWait);
    }
});

Running the script To run this, either use your own webpage with an embedded OpenLayers map or look into the source code of some existing page in the internet and find out the id attribute of the map, then modify the url and mapID parameters in the download.js file accordingly.

Then, supposed that phantomJS is correctly installed and available in your systems's environment path, open a command-line windows, navigate to the directory of your script and simply execute it with phantomjs download.js. You should see some debug messages telling you what exact part of the page is currently loading and when the process has finished, you should find a file map.png in the directory you started the script from.

Testing the script: To test the script if no OpenLayers map webpage at hand, you might want to create a simple page with a map like this (assuming OpenLayers is properly installed, adjust the <script type="text/javascript" src="../lib/OpenLayers.js"></script> line to your installation path if necessary:

OL_testpage.html:

<!DOCTYPE html> 
<html> 
    <head> 
        <title>Creating a simple map</title> 
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 

        <!-- Include OpenLayers library --> 
        <script type="text/javascript" src="../lib/OpenLayers.js"></script> 
        <style> 
            html, body { 
                width: 1600px; 
                height: 1200px; 
                margin: 0; 
                padding: 0; 
            } 
        </style> 


        <!-- The magic comes here --> 
        <script type="text/javascript"> 
            function init() { 

                // Create the map using the specified // DOM element 
                var map = new OpenLayers.Map("mymapid", {
                    allOverlays: false
                    }); 

                // Create an OpenStreeMap raster layer // and add to the map 
                var osm = new OpenLayers.Layer.OSM(); 
                osm.wrapDateLine = false;
                map.addLayer(osm); 

                // Center the map somewhere
                map.setCenter(new OpenLayers.LonLat(-10, 35), 4);

            }   
        </script>       
    </head> 
    <body onload="init()"> 

        <div id="mymapid" style="width: 100%; height: 100%;"></div> 
    </body> 
</html>

Put both the webpage and the download.js file on your webserver, adapt parameters and start as described above. Hint: To start a minimalistic webserver for running the page, you can use Python if installed on your system (should be the case on most Linux systems, on Windows simply install as described on the homepage. Start a command-shell, navigate to the folder where your files are stored and type python -m http.server (Python 3.x) or python -m SimpleHTTPServer (Python 2.x). Then, you can access your file via http://localhost:8000/OL_testpage.html

Drawbacks from this approach: To my current knowledge, this approach has two drawbacks. 1. The image is only rendered in correct size if the map was created with width and height given in absolute pixels (as in my example above, lines width: 1600px; and height: 1200px;). If width and height are given as percentage of the browser's windows as many people do (e.g. width: 100%;) then the resulting image is quite small and has some quadratic format. 2. Using phantomJS worked fine for me when applying it to maps with rather few features. I am still experimenting to execute this on maps which load a large number of features into a vector layer (e.g. a street network with 900k lines). This makes phantomJS crash each time.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.