Commit d24f1e5e authored by Mauro Bartolomeoli's avatar Mauro Bartolomeoli
Browse files

GEOS-6946: added support for cql in ColorMapEntry for GetLegendGraphic requests

parent afd4370d
......@@ -190,3 +190,9 @@ Let's now examine all the interesting elements, one by one. Notice that I am not
- **dx,dy,mx,my (double)** can be used to set the margin and the buffers between elements
- **border (boolean)** activates or deactivates the boder on the color elements in order to make the separations cleare. Notice that I decided to **always** have a line that would split the various color elements for the ramp type of colormap.
- **borderColor (hex)** allows us to set the color for the border in 0xRRGGBB format
CQL Expressions and ENV
If cql expressions are used in ColorMapEntry attributes (see :ref:`here <sld_reference_rastersymbolizer_colormap_cql>`) to create a dynamic color map taking values
from ENV, the same ENV parameters used for GetMap can be given to GetLegendGraphic to get the desired legend entries.
......@@ -191,7 +191,26 @@ extended
The ``extended`` attribute specifies whether the color map gradient uses 256 (8-bit) or 65536 (16-bit) colors.
The value ``false`` (the default) specifies that the color scale is calculated using 8-bit color, and ``true`` specifies using 16-bit color.
.. _sld_reference_rastersymbolizer_colormap_cql:
CQL Expressions
Most of the ColorMapEntry attributes (color, quantity and opacity) can be defined using ``cql expressions``, with the ${...expression...} syntax.
CQL expressions are useful to make the color map dynamic, using values taken from the client:
.. code-block:: xml
<ColorMapEntry color="#00FF00" quantity="${env('low',3)}" label="Low" opacity="1"/>
<ColorMapEntry color="#FFFF00" quantity="${env('medium',10)}" label="Medium" opacity="1"/>
<ColorMapEntry color="#FF0000" quantity="${env('high',1000)}" label="High" opacity="1"/>
In this example quantity values are not fixed, but can be specified by the client using the ENV request parameter:
For a complete reference of CQL capabilities, see :ref:`here <filter_ecql_reference>`
......@@ -32,6 +32,7 @@ import org.geotools.renderer.i18n.ErrorKeys;
import org.geotools.renderer.i18n.Errors;
import org.geotools.renderer.lite.RendererUtilities;
import org.geotools.renderer.lite.StreamingRenderer;
import org.geotools.styling.ColorMapEntry;
import org.geotools.styling.FeatureTypeStyle;
import org.geotools.styling.RasterSymbolizer;
......@@ -43,6 +44,7 @@ import org.opengis.feature.type.FeatureType;
import org.opengis.feature.type.PropertyDescriptor;
import org.opengis.feature.type.PropertyType;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.expression.Literal;
......@@ -296,12 +298,17 @@ public class LegendUtils {
// opacity is 1.0 (fully opaque)."
// //
final Expression opacity = entry.getOpacity();
Expression opacity = entry.getOpacity();
Double opacityValue = null;
if (opacity != null)
opacityValue = opacity.evaluate(null, Double.class);
return 1.0;
if(opacityValue == null && opacity instanceof Literal) {
String opacityExp = opacity.evaluate(null, String.class);
opacity = ExpressionExtractor.extractCqlExpressions(opacityExp);
opacityValue = opacity.evaluate(null, Double.class);
if ((opacityValue.doubleValue() - 1) > 0
|| opacityValue.doubleValue() < 0) {
throw new IllegalArgumentException(Errors.format(
......@@ -341,9 +348,13 @@ public class LegendUtils {
public static Color color(final ColorMapEntry entry){
ensureNotNull(entry, "entry");
final Expression color = entry.getColor();
Expression color = entry.getColor();
ensureNotNull(color, "color");
final String colorString= color.evaluate(null, String.class);
String colorString= color.evaluate(null, String.class);
if(colorString != null && colorString.startsWith("${")) {
color = ExpressionExtractor.extractCqlExpressions(colorString);
colorString = color.evaluate(null, String.class);
ensureNotNull(colorString, "colorString");
return color(colorString);
......@@ -359,6 +370,11 @@ public class LegendUtils {
Expression quantity = entry.getQuantity();
ensureNotNull(quantity, "quantity");
Double quantityString = quantity.evaluate(null, Double.class);
if(quantityString == null && quantity instanceof Literal) {
String quantityExp = quantity.evaluate(null, String.class);
quantity = ExpressionExtractor.extractCqlExpressions(quantityExp);
quantityString = quantity.evaluate(null, Double.class);
ensureNotNull(quantityString, "quantityString");
return quantityString.doubleValue();
......@@ -9,6 +9,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
......@@ -16,6 +17,7 @@ import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.IndexColorModel;
import java.awt.image.RenderedImage;
import java.util.ArrayList;
import java.util.Collections;
......@@ -24,8 +26,11 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.xml.namespace.QName;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.CoverageInfo;
import org.geoserver.catalog.FeatureTypeInfo;
......@@ -34,6 +39,8 @@ import;
import org.geoserver.wms.GetLegendGraphic;
import org.geoserver.wms.GetLegendGraphicRequest;
import org.geoserver.wms.WMSTestSupport;
import org.geoserver.wms.legendgraphic.Cell.ColorMapEntryLegendBuilder;
import org.geoserver.wms.legendgraphic.Cell.SingleColorMapEntryLegendBuilder;
import org.geotools.coverage.grid.GridCoverage2D;
......@@ -48,6 +55,9 @@ import org.geotools.referencing.CRS;
import org.geotools.renderer.lite.RendererUtilities;
import org.geotools.resources.coverage.FeatureUtilities;
import org.geotools.resources.image.ImageUtilities;
import org.geotools.styling.ColorMapEntry;
import org.geotools.styling.FeatureTypeStyle;
import org.geotools.styling.RasterSymbolizer;
import org.geotools.styling.Rule;
import org.geotools.styling.SLDParser;
import org.geotools.styling.Style;
......@@ -60,6 +70,7 @@ import org.opengis.feature.type.AttributeType;
import org.opengis.feature.type.FeatureType;
import org.opengis.feature.type.GeometryType;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
......@@ -905,6 +916,78 @@ public class AbstractLegendGraphicOutputFormatTest extends WMSTestSupport {
* Test that the legend is not the same if there is a rendering transformation that
* converts the rendered layer from raster to vector
public void testColorMapWithCql() throws Exception {
Style style = readSLD("ColorMapWithCql.sld");
assertEquals(1, style.featureTypeStyles().size());
FeatureTypeStyle fts = style.featureTypeStyles().get(0);
assertEquals(1, fts.rules().size());
Rule rule = fts.rules().get(0);
assertEquals(1, rule.symbolizers().size());
assertTrue(rule.symbolizers().get(0) instanceof RasterSymbolizer);
RasterSymbolizer symbolizer = (RasterSymbolizer)rule.symbolizers().get(0);
assertEquals(3, symbolizer.getColorMap().getColorMapEntries().length);
ColorMapEntry[] entries = symbolizer.getColorMap().getColorMapEntries();
Color color = LegendUtils.color(entries[0]);
int red = color.getRed();
assertEquals(255, red);
int green = color.getGreen();
assertEquals(0, green);
int blue = color.getBlue();
assertEquals(0, blue);
double quantity = LegendUtils.getQuantity(entries[1]);
assertEquals(20.0, quantity, 0.0);
double opacity = LegendUtils.getOpacity(entries[2]);
assertEquals(0.5, opacity, 0.0);
GetLegendGraphicRequest req = new GetLegendGraphicRequest();
CoverageInfo cInfo = getCatalog().getCoverageByName("world");
GridCoverage coverage = cInfo.getGridCoverage(null, null);
try {
SimpleFeatureCollection feature;
feature = FeatureUtilities.wrapGridCoverage((GridCoverage2D) coverage);
req.setLegendOptions(new HashMap());
final int HEIGHT_HINT = 30;
// use default values for the rest of parameters
BufferedImage image = this.legendProducer.buildLegendGraphic(req);
// was the legend painted?
assertNotBlank("testColorMapWithCql", image, LegendUtils.DEFAULT_BG_COLOR);
// was the legend painted?
assertNotBlank("testColorMapWithCql", image, LegendUtils.DEFAULT_BG_COLOR);
} finally {
RenderedImage ri = coverage.getRenderedImage();
if(coverage instanceof GridCoverage2D) {
((GridCoverage2D) coverage).dispose(true);
if(ri instanceof PlanarImage) {
ImageUtilities.disposePlanarImageChain((PlanarImage) ri);
* Test that the legend is not the same if there is a rendering transformation that
<?xml version="1.0" encoding="UTF-8"?>
<StyledLayerDescriptor xmlns="" xmlns:ogc="" xmlns:xlink="" xmlns:xsi="" xsi:schemaLocation="" version="1.0.0">
<Title>CQL test</Title>
<Abstract>CQL test</Abstract>
<ColorMap type="intervals">
<ColorMapEntry color="${strConcat('#FF','0000')}" quantity="10" opacity="1.0" label="&lt;-70 mm"/>
<ColorMapEntry color="#A80000" quantity="${15+5}" opacity="1.0" label="-69 - -50 mm"/>
<ColorMapEntry color="#FF0000" quantity="30" opacity="${0.25*2}" label="-49 - -20 mm"/>
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment