[GIS] Drawing WMS layer with Attribute-Based Dynamic SLD to HTML Canvas

geoserverhtml5openlayers-2wms

I'm working on an app using GeoServer and OpenLayers 2.13.1. I have several WMS layers and I need to export whatever in my openlayers map div as an image. After researching, I found this blog: http://blogs.edina.ac.uk/category/openlayers-html5-canvas/ which is working fine (exporting my layers as an image).

However, my wms layer also has dynamic sld functionality (using SLD_BODY). The problem shows up when I use attribute-based sld: it wasn't drawn on the canvas. It only works when I use non-attribute-based sld.

Did I miss something here?

This is how I call the wms layer:

wms = new OpenLayers.Layer.WMS(layer_name,
    WMSServiceURL, {
        layers: "bappeko:"+map_name, 
        styles: null,
        transparent: true,
        format: "image/jpeg"
    },{
        ratio: 1,
        singleTile: true,
        tileOptions: {maxGetUrlLength: 2048},
        buffer: 0,
        projection: new OpenLayers.Projection("EPSG:32749")
    }
);
map.addLayer(wms);

This is JS function I used to export:

function exportMap() {
var mapcanvasDiv = null ;
var mapDiv = document.getElementById("mapdiv") ;

if (mapcanvasDiv === null) {              
    var canvasElement = document.createElement("canvas");
    canvasElement.id = "mapcvs";
    mapcanvasDiv = document.getElementById("mapcanvas");
    mapcanvasDiv.appendChild(canvasElement) ;

    canvasElement.width = mapDiv.clientWidth ;
    canvasElement.height = mapDiv.clientHeight ;
}

var mapCanvas = document.getElementById("mapcvs");
var mapContainer = document.getElementById("OpenLayers_Map_2_OpenLayers_Container");

if (mapCanvas !== null) {
    var ctx = mapCanvas.getContext("2d");
    ctx.clearRect(0, 0, mapCanvas.width, mapCanvas.height);

    var olLayersDiv = document.getElementsByClassName("olLayerDiv");
    var olLayers = map.layers;

    for (var i=0; i<olLayersDiv.length; i++){
        //ONLY EXPORT VISIBLE LAYERS
        if (olLayers[i].visibility == true) {
            var layertiles = olLayersDiv[i].getElementsByClassName("olTileImage");
            for(var j=0; j<layertiles.length; j++) {
                var tileImg = layertiles[j];
                var offsetLeft = tileImg.offsetLeft;
                var offsetTop = tileImg.offsetTop;
                var left = Number(mapContainer.style.left.slice(0, mapContainer.style.left.indexOf("p")));
                var top = Number(mapContainer.style.top.slice(0, mapContainer.style.top.indexOf("p")));
                ctx.drawImage(tileImg, 0, 0);
            }
        }
    }

    var dataUrl = mapCanvas.toDataURL();
    window.open(dataUrl, "toDataURL() image", "width=600, height=360");
}

This is how I set my dynamic SLD (the SLD is created dynamically by PHP script via Ajax) in JS:

l = map.getLayersByName(layer_name)[0];
l.mergeNewParams({sld_body: sld, styles: null});

I guess I've found the culprit: It seems that my PHP-created SLD is way too long even when I've set tileOptions: {maxGetUrlLength: 2048} because I use many <ogc:Filter> for my Attribute-Based SLD.

Here's what I've tried: I put my SLD in a text file and load it using SLD: some_url/text.sld param, instead of putting it straight to SLD_BODY like I did before. But another error happens, the map won't load (pink image) and I'm getting this error Resource interpreted as Image but transferred with MIME type application/vnd.ogc.se_xml: http://localhost/geoserver/wms?... Seems like the WMS request doesn't return an image but XML.

Best Answer

Here's what I did, and it finally works.

First, I created a sld file and saved it to the some folder in PHP:

$sld = "sld_here..";
$temp_link = sys_get_temp_dir()."/";

$tmpfname = tempnam($temp_link, $map_name);
file_put_contents($tmpfname, $sld);

echo $tmpfname;

Then the file url is passed to JS using AJAX and set it to the layer using SLD param:

l = map.getLayersByName("layer_name")[0];

$.ajax({
    type: 'POST',
    dataType: "text",
    url: "SLDCreator.php",
    data: {
        //Sending what is required to create a valid SLD file
        map_name: "map_name",
        data: "some sld parameters here"
    },
    success: function(sld) {
        //Set the SLD to the layer
        l.mergeNewParams({sld: "file:///"+sld});
    }
});

the file:/// will works on localhost. When online, you need to save it to a folder (e.g. sld/) and call it from http_your_domain/sld/your_sld_file.

So this way, I don't need to send my complete sld (which can be very-very long when we use many filters). I just need to send the url of sld file created which is much shorter.

Related Question