Create map legend to match OpenLayers.Style

Craig picture Craig · Mar 11, 2011 · Viewed 14.1k times · Source

I've created an OpenLayers.Style that colours my polygons, a style that sizes my points and all that jazz, now I want to explain to the user what those styles represent.

I can't see anything in OpenLayers that allows me to draw my own legend using those styles. Everything seems to point me towards the assumed map server that is sending me data, which I don't have.

At the moment it looks like I'll have to draw some sample points/areas and screen grab them to make my own legend. Is there a better way to do it based straight off the Style so I don't have to regenerate the images when the Style gets changed?

Update I've had a good answer that relies on GeoExt (and thus ExtJS), I'd still like to hear if anyone has a jQuery compatible answer. Especially if it is plain Javascript and OpenLayers.

Answer

Samuli Neuvonen picture Samuli Neuvonen · May 12, 2017

I was able to solve my legend needs using the ol.Map-class as a container for the symbols. Maybe a bit of a hack, but seems to work for most (?) vector layers (I have no WMS's).

So what I'm doing is:

  1. loop through the map layers and pick out the vector type with

    if(lyr instanceof ol.layer.Vector)

  2. check what type of style is used and store in an array

    var style = lyr.getStyle();
    var image = style.getImage();
    if(image){
        if(image instanceof ol.style.Icon){
            //raster icon from url
            var icon2 = new ol.style.Icon( ({
                src: image.getSrc()
            }))
            var iconStyle2 = new ol.style.Style({
                image: icon2
            });
            row = {};
            row.style = iconStyle2;
            row.title = lyr.get('title');               
        }
        else{ //ol.style.RegularShape?
            row = {};
            row.style = style;
            row.title = lyr.get('title');               
        }
    }else{
        row = {};
        row.style = style;
        row.title = lyr.get('title');
    }
    
  3. also store the geometry type

    //geometry type
    var feats = lyr.getSource().getFeatures();
    if (feats && feats.length>0){
        if(feats[0].getGeometry() instanceof ol.geom.Point || feats[0].getGeometry() instanceof ol.geom.MultiPoint){
            row.geomType="point";
        }else if (feats[0].getGeometry() instanceof ol.geom.LineString || feats[0].getGeometry() instanceof ol.geom.MultiLineString){
            row.geomType="line";
        }else{
            row.geomType="polygon";
        }
    }
    
  4. loop through the stored to-be legend rows and construct the HTML elements needed, typically a div for the "mini-map" and the layer name

    for (i = 0; i < legendRows.length; i++) { 
        row = document.createElement("tr");
        //symbol
        cell = document.createElement("td");
        cell.style="width:35px";
        var div = document.createElement("div");
        div.style="width:32px; height:32px;";
        div.id = "mapLegendRowSymbolDiv" + i; 
        tble.appendChild(row);
        row.appendChild(cell);
        cell.appendChild(div);
        //layer title
        cell = document.createElement("td");      
        tble.appendChild(row);
        row.appendChild(cell);
        cell.innerHTML=legendRows[i].title; 
    }
    
    //append table
    $( "#legendText" ).empty();
    $( "#legendText" ).append(tble);
    
  5. once HTML elements have been added to the page, initiate the maps and insert the fake features to display the symbols

    //loop legend rows and and insert the maps
    var extent = [0, 0, 32, 32];
    var projection = new ol.proj.Projection({
        code: 'xkcd-image',
        units: 'pixels',
        extent: extent
    });
    for (i = 0; i < legendRows.length; i++) { 
        //target div
        var targetDiv = document.getElementById("mapLegendRowSymbolDiv" + i);
        //layer for icon
        var sourceLegend = new ol.source.Vector({wrapX: false});
        var vectorLegend = new ol.layer.Vector({
            source: sourceLegend,
            style: legendRows[i].style
        });
        //map
        var mapLegend = new ol.Map({
            controls: [],
            layers: [
                new ol.layer.Image({
                    source: new ol.source.ImageStatic({
                        projection: projection,
                        imageExtent: extent
                    })
                }),
                vectorLegend
            ],
            target: targetDiv,
            view: new ol.View({
                projection: projection,
                center: ol.extent.getCenter(extent),
                zoom: 2,
                maxZoom: 2
            })
        });
        //icon feature depending on type
        var geom;
        if(legendRows[i].geomType=='point'){
            geom = new ol.geom.Point([16,16]);
        }else if(legendRows[i].geomType=='polygon'){
            var polyCoords = [];
            polyCoords.push([15.7, 15.7]);
            polyCoords.push([16.3, 15.7]);
            polyCoords.push([16.3, 16.3]);
            polyCoords.push([15.7, 16.3]);
            polyCoords.push([15.7, 15.7]);
            geom = new ol.geom.Polygon([polyCoords]);
        }else{
            var lineCoords = [];                
            lineCoords.push([15.6, 15.6]);
            lineCoords.push([16, 16]);
            lineCoords.push([16, 15.8]);
            lineCoords.push([16.4, 16.2]);
            geom = new ol.geom.LineString(lineCoords);
        }
        var feature = new ol.Feature({
            geometry: geom
        });
        vectorLegend.getSource().addFeature(feature);
    }   
    

With this, I was able create and update a separate Legend dialog (jQuery UI):

enter image description here

I haven't tested a lot yet, there may be some problems with this approach...