GeoServer SLD – GetLegendGraphic and GetLegendUrl Filter by Extent

geoservergetlegendgraphicopenlayerssld

I tried to get a legend image from my GeoServer Instance filtered by extent (render only symbologies that are currently displayed on screen).

For this purpose I used the getLegendUrl function from OpenLayers 6 (docs here : https://openlayers.org/en/latest/apidoc/module-ol_source_TileWMS-TileWMS.html#getLegendUrl)

So far I tried this example : https://openlayers.org/en/latest/examples/wms-getlegendgraphic.html

But it does not work (even in this above example, any zoom to the map won't re-render the legend).

Now I tried this :

 var url = mylayer.getSource().getLegendUrl(mymap.getView().getResolution())

which gave me the good url with a SCALE parameter (which change every time I zoom in/out, because I had the listener on the 'change:resolution' event).

The URL looks like this :

http://localhost:8080/geoserver/MyStore/wms?&LAYERS=MyStore%3Ajunctions&SERVICE=WMS&VERSION=1.3.0&REQUEST=GetLegendGraphic&FORMAT=image%2Fpng&LAYER=MyStore%3Ajunctions&SCALE=1789.2350833905004

URL-decoded :

base url : http://localhost:8080/geoserver/MyStore/wms

**Params**

    LAYERS=MyStore:junctions
    SERVICE=WMS&VERSION=1.3.0
    REQUEST=GetLegendGraphic
    FORMAT=image/png
    LAYER=MyStore:junctions
    SCALE=1789.2350833905004  <=== THIS CHANGE ON ZOOM IN/OUT BUT HAS NO EFFECT : THE SAME LEGEND WILL BE RETURNED

Any ideas on how can I make the SCALE parameter (or anything else) modify the legend by extent?

Extra:

I found this old question : GetLegendGraphic with bbox or filter.
But it should be closed now because the link provided lead me to a 404 page…I did not found any questions on this specific issue anywhere else…

By the way here is my SLD (if it could somehow helps but I don't think that it has any effect here):

<?xml version="1.0" encoding="UTF-8"?>

<NamedLayer>
    <Name>Junctions Polygon</Name>
    <UserStyle>

        <Title>Junctions</Title>
        <Abstract>A sample style for junctions</Abstract>


        <FeatureTypeStyle>
            <Rule>
                <Name>710</Name>
                <ogc:Filter>
                    <ogc:PropertyIsEqualTo>
                        <ogc:PropertyName>junctions</ogc:PropertyName>
                        <ogc:Literal>710</ogc:Literal>
                    </ogc:PropertyIsEqualTo>
                </ogc:Filter>
                <Title>710</Title>
                <Abstract>junctions 710</Abstract>

                <!-- like a linesymbolizer but with a fill too -->
                <PolygonSymbolizer>
                    <Fill>
                        <CssParameter name="fill">#FFFF33</CssParameter>
                        <CssParameter name="fill-opacity">1</CssParameter>
                    </Fill>
                    <Stroke>
                        <CssParameter name="stroke">#000000</CssParameter>
                        <CssParameter name="stroke-width">1</CssParameter>
                    </Stroke>
                </PolygonSymbolizer>
            </Rule>
            <Rule>
                <Name>7730</Name>
                <ogc:Filter>
                    <ogc:PropertyIsEqualTo>
                        <ogc:PropertyName>junctions</ogc:PropertyName>
                        <ogc:Literal>7730</ogc:Literal>
                    </ogc:PropertyIsEqualTo>
                </ogc:Filter>
                <Title>7730</Title>
                <Abstract>junctions730</Abstract>

                <!-- like a linesymbolizer but with a fill too -->
                <PolygonSymbolizer>
                    <Fill>
                        <CssParameter name="fill">#78D232</CssParameter>
                        <CssParameter name="fill-opacity">1</CssParameter>
                    </Fill>
                    <Stroke>
                        <CssParameter name="stroke">#000000</CssParameter>
                        <CssParameter name="stroke-width">1</CssParameter>
                    </Stroke>
                </PolygonSymbolizer>
            </Rule>
            <Rule>
                <Name>Rule 1</Name>
                <Title>GreyFill BlackOutline</Title>
                <Abstract>Grey fill with a black outline 1 pixel in width</Abstract>

                <!-- like a linesymbolizer but with a fill too -->
                <PolygonSymbolizer>
                    <Fill>
                        <CssParameter name="fill">#AAAAAA</CssParameter>
                    </Fill>
                    <Stroke>
                        <CssParameter name="stroke">#000000</CssParameter>
                        <CssParameter name="stroke-width">1</CssParameter>
                    </Stroke>
                </PolygonSymbolizer>
            </Rule>

        </FeatureTypeStyle>
    </UserStyle>
</NamedLayer>

Update:

Okay I found this question : How to get the legends from geoserver

It somehow gives me a way to do this but it's a very hard way I think: It supposed to first request features by extent on my own and reduce those features by the symbology field name, and send back a request of getLegendUrl with a RULE parameter to get only the specific rules…

I would like to know why the SCALE params does not work…

Best Answer

As specified in the docs of GeoServer :

https://docs.geoserver.org/stable/en/user/services/wms/get_legend_graphic/index.html#content-dependent-legends

Content dependent evaluation is enabled via the following LEGEND_OPTIONS parameters:

countMatched: adds the number of features matching the particular rule at the end of the rule label (requires visible labels to work). Applicable only to vector layers.

hideEmptyRules: hides rules that are not matching any feature. Applicable only if countMatched is true.

Now the thingis that it also needs more params in the URL :

In order to support it the GetLegendGraphic call needs the following extra parameters:

1) BBOX

2) SRS or CRS (depending on the WMS version, SRS for 1.1.1 and CRS for 1.3.0)

3) SRCWIDTH and SRCHEIGHT, the size of the reference map (width and height already have a different meaning in GetLegendGraphic)

Let's do this with the given exemple here

https://openlayers.org/en/latest/examples/wms-getlegendgraphic.html

It has this function :

var updateLegend = function(resolution) {
  var graphicUrl = wmsSource.getLegendUrl(resolution);
  var img = document.getElementById('legend');
  img.src = graphicUrl;
};

Now in order to make it works as a "Content dependent" legend I added those steps :

First create two useful functions :

var getExtent = function () {
  return map.getView().calculateExtent()
}
var getProjectionCode = function () {
  return map.getView().getProjection().getCode()
}

Create a the CRS parameter :

  const crs = `&CRS=${getProjectionCode()}`;

Create the BBOX parameter :

 const bbox = `&BBOX=${getExtent().join(',')}`

Create the SRCWIDTH and SRCHEIGHT parameters :

 //For this first we need to import getWidth and getHeight functions 
 import { getHeight, getWidth } from 'ol/extent'
 // Then Create the params
 const height = getHeight(getExtent())
 const width = getWidth(getExtent()
 const heightAndWidth = `&srcwidth=${height}&srcheight=${width)}`

Create the LEGEND_OPTIONS params :

      const legenOptions = "&legend_options=countMatched:true;fontAntiAliasing:true;hideEmptyRules:true;forceLabels:on"

Finally build the url :

 img.src = `${graphicUrl}${crs}${bbox}${heightAndWidth}${legenOptions}`;

Job done : it works.

Final code :

import { getHeight, getWidth } from 'ol/extent';

var getExtent = function () {
  return map.getView().calculateExtent()
}
var getProjectionCode = function () {
  return map.getView().getProjection().getCode()
}

var updateLegend = function(resolution) {
  var graphicUrl = wmsSource.getLegendUrl(resolution);

  const crs = `&CRS=${getProjectionCode()}`;
  const bbox = `&BBOX=${getExtent().join(',')}`
  const height = getHeight(getExtent())
  const width = getWidth(getExtent()
  const heightAndWidth = `&srcwidth=${height}&srcheight=${width}`
  const legenOptions = "&legend_options=countMatched:true;fontAntiAliasing:true;hideEmptyRules:true;forceLabels:on"

  var img = document.getElementById('legend');
  img.src = `${graphicUrl}${crs}${bbox}${heightAndWidth}${legenOptions}`;
};

Of course : this is only the part of the code (in order to make it works it needs the map object, Geoserver instance with a WMS or WFS to query. Also, each service must be bound to an SLD with specified rules.

Now a last improvment of this function, just for less coupling purpose, is to remove the resolution params and get it directly from the map object, like so :

var currentResolution = map.getView().getResolution()

now the signature of the updateLegend function is empty, and will work independently:

var updateLegend = function() {
    var currentResolution = map.getView().getResolution()
    var graphicUrl = wmsSource.getLegendUrl(currentResolution);

//... rest of the code

Some screenshots :

Before zooming

After zooming