7

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.

Craig
  • 8,093
  • 8
  • 42
  • 74
  • 1
    Have you looked at *GeoExt*? They provide a `LegendPanel` which I think does exactly what you want. – Chau Mar 11 '11 at 07:28

5 Answers5

6

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...

Samuli Neuvonen
  • 186
  • 3
  • 5
3

Actually, OpenLayers doesn't support what you want (or at least I don't know how to do it). As Chau told you, LegendPanel from GeoExt is your only hope.

Interesting links:

http://geoext.org/lib/GeoExt/widgets/LegendPanel.html

http://www.mail-archive.com/openlayers-users@lists.osgeo.org/msg01318.html

Fran Verona
  • 5,438
  • 6
  • 46
  • 85
  • That looks like what I want, but unfortunately I don't think I can move our project from jQuery to ExtJS so I won't be able to use it this time. :-( – Craig Mar 15 '11 at 22:51
2

As one option, you could create SVG elements with same attributes like in OL3 style. Here's example for circle style (you would need similar methods also other types):

getIconLegend = function(style) {
    style = style.getImage();
    var radius = style.getRadius();
    var strokeWidth = style.getStroke().getWidth();
    var dx = radius + strokeWidth;

    var svgElem = $('<svg />')
        .attr({
            width: dx * 2,
            height: dx * 2
        });

    $('<circle />')
        .attr({
            cx: dx,
            cy: dx,
            r: radius,
            stroke: style.getStroke().getColor(),
            'stroke-width': strokeWidth,
            fill: style.getFill().getColor()
        })
        .appendTo(svgElem);

    // Convert DOM object to string to overcome from some SVG manipulation related oddities
    return $('<div>').append(svgElem).html();
}

Since SVG manipulation with jQuery is bit different from HTML elements, I'm converting object to string in return. Details can be found from jquery's append not working with svg element?

Later, you can stick legend icon to HTML with

$('#legendText').prepend($(getIconLegend(yourFeature.getStyle())));
Community
  • 1
  • 1
user1702401
  • 1,658
  • 16
  • 17
0

The closest you can get to what you want with plain OpenLayers is if you use WMS service instead of WFS or whatever method you use to fetch features. WMS has request type GetLegendGraphic that, as name suggests, lets you dynamically fetch image that shows what style is applied to layer.

igorti
  • 3,828
  • 3
  • 22
  • 29
  • I don't have a server sending me WFS, so I can't switch it to WMS. I have my own XML which is turned into features that have use an OpenLayers.Style. No other server knows about the style I am using so I really need OpenLayers to make the legend. I'm surprised that it's not built in to OL, to me it's an obvious use case. – Craig Mar 16 '11 at 20:27
  • well,then send a patch for that :) – igorti Mar 16 '11 at 21:47
0

A half-handed legend can be made:

For WMS features
With a Geoserver, GetLegendGraphic can generate an image legend for each WMS layer

For WFS features
For each layer, a legend can be built based on its style attributes:

  • Fill's color (ex: style.getFill().getColor())
  • Stroke's attributes (ex: style.getStroke().getColor())
  • Image's image, which is an HTMLElement, depending on the style (ex: style.getImage().getImage())
NayoR
  • 702
  • 6
  • 13