Skip to content

Commit

Permalink
ysld generate styles break kml output formats [GEOS-8885] (#3042)
Browse files Browse the repository at this point in the history
* placemark fill defaults for opacity and color

* placemark null expression checks

* Add additional null checks

* tests for icon properties

* fix for dynamic color generation test
  • Loading branch information
jodygarnett committed Aug 14, 2018
1 parent 62b0e49 commit 4a2da2c
Show file tree
Hide file tree
Showing 7 changed files with 217 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,12 @@ public Feature decorate(Feature feature, KmlEncodingContext context) {
for (Symbolizer sym : context.getCurrentSymbolizers()) {
if (sym instanceof TextSymbolizer) {
Expression e = SLD.textLabel((TextSymbolizer) sym);
String value = e.evaluate(sf, String.class);
if (e != null) {
String value = e.evaluate(sf, String.class);

if ((value != null) && !"".equals(value.trim())) {
label.append(value);
if ((value != null) && !"".equals(value.trim())) {
label.append(value);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import org.geoserver.wms.WMSInfo;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.renderer.style.ExpressionExtractor;
import org.geotools.styling.ExternalGraphic;
import org.geotools.styling.Fill;
import org.geotools.styling.Font;
import org.geotools.styling.LineSymbolizer;
Expand All @@ -45,7 +44,6 @@
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.filter.FilterFactory2;
import org.opengis.filter.expression.Expression;
import org.opengis.style.GraphicalSymbol;

/**
* Encodes the SLD styles into KML corresponding styles and adds them to the Placemark
Expand Down Expand Up @@ -290,22 +288,19 @@ protected void setDefaultLabelStyle(Style style) {
protected void setLabelStyle(
Style style, SimpleFeature feature, TextSymbolizer symbolizer) {
LabelStyle ls = style.createAndSetLabelStyle();
double scale = 1;
double scale = 1.0;
Font font = symbolizer.getFont();
if (font != null && font.getSize() != null) {
// we make the scale proportional to the normal font size
double size = font.getSize().evaluate(feature, Double.class);
double size = evaluate(font.getSize(), feature, Font.DEFAULT_FONTSIZE);
scale = Math.round(size / Font.DEFAULT_FONTSIZE * 100) / 100.0;
}
ls.setScale(scale);

Fill fill = symbolizer.getFill();
if (fill != null) {
Double opacity = fill.getOpacity().evaluate(feature, Double.class);
if (opacity == null || Double.isNaN(opacity)) {
opacity = 1.0;
}
Color color = fill.getColor().evaluate(feature, Color.class);
Double opacity = evaluate(fill.getOpacity(), feature, 1.0);
Color color = evaluate(fill.getColor(), feature, Color.WHITE);
ls.setColor(colorToHex(color, opacity));
} else {
ls.setColor("ffffffff");
Expand All @@ -328,13 +323,8 @@ protected void setPolygonStyle(
PolyStyle ps = style.createAndSetPolyStyle();
Fill fill = symbolizer.getFill();
if (fill != null) {
// get opacity
Double opacity = fill.getOpacity().evaluate(feature, Double.class);
if (opacity == null || Double.isNaN(opacity)) {
opacity = 1.0;
}

Color color = (Color) fill.getColor().evaluate(feature, Color.class);
Double opacity = evaluate(fill.getOpacity(), feature, 1.0);
Color color = evaluate(fill.getColor(), feature, new Color(0xAAAAAA));
ps.setColor(colorToHex(color, opacity));
} else {
// make it transparent
Expand All @@ -347,36 +337,65 @@ protected void setPolygonStyle(
}
}

/**
* Safe expression execution with default fallback.
*
* @param expression
* @param feature
* @param defaultValue
* @return evaluated value or defaultValue if unavailable
*/
private Double evaluate(Expression expression, SimpleFeature feature, double defaultValue) {
if (expression == null) {
return defaultValue;
}
Double value = expression.evaluate(feature, Double.class);
if (value == null || Double.isNaN(value)) {
return defaultValue;
}
return value;
}
/**
* Safe expression execution with default fallback.
*
* @param expression
* @param feature
* @param defaultColor
* @return evaluated value or defaultColor if unavailable
*/
private Color evaluate(Expression expression, SimpleFeature feature, Color defaultColor) {
if (expression == null) {
return defaultColor;
}
Color color = expression.evaluate(feature, Color.class);
if (color == null) {
return defaultColor;
}
return color;
}

/** Encodes a KML IconStyle + LineStyle from a polygon style and symbolizer. */
protected void setLineStyle(Style style, SimpleFeature feature, Stroke stroke) {
LineStyle ls = style.createAndSetLineStyle();

if (stroke != null) {
// opacity
Double opacity = stroke.getOpacity().evaluate(feature, Double.class);
if (opacity == null || Double.isNaN(opacity)) {
opacity = 1.0;
}
Double opacity = evaluate(stroke.getOpacity(), feature, 1.0);

Color color = null;
Expression sc = stroke.getColor();
if (sc != null) {
color = (Color) sc.evaluate(feature, Color.class);
}
if (color == null) {
// Different from BLACK provided by Stroke.DEFAULT.getColor()
color = Color.DARK_GRAY;
}
ls.setColor(colorToHex(color, opacity));

// width
Double width = null;
Expression sw = stroke.getWidth();
if (sw != null) {
width = sw.evaluate(feature, Double.class);
}
if (width == null) {
width = 1d;
}
Double width =
evaluate(stroke.getWidth(), feature, 1d); // from Stroke.DEFAULT.getWidth()
ls.setWidth(width);
} else {
// default
Expand All @@ -385,16 +404,6 @@ protected void setLineStyle(Style style, SimpleFeature feature, Stroke stroke) {
}
}

private ExternalGraphic getExternalGraphic(PointSymbolizer symbolizer) {
for (GraphicalSymbol s : symbolizer.getGraphic().graphicalSymbols()) {
if (s instanceof ExternalGraphic) {
return (ExternalGraphic) s;
}
}

return null;
}

/**
* Does value substitution on a URL with embedded CQL expressions
*
Expand Down Expand Up @@ -431,7 +440,10 @@ protected String evaluateDynamicSymbolizer(String strLocation, SimpleFeature fea
* @param opacity Opacity / alpha, double from 0 to 1.0.
* @return A String of the form "AABBGGRR".
*/
String colorToHex(Color c, double opacity) {
String colorToHex(Color c, Double opacity) {
if (opacity == null || Double.isNaN(opacity)) {
opacity = 1.0;
}
return new StringBuffer()
.append(intToHex(new Float(255 * opacity).intValue()))
.append(intToHex(c.getBlue()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,24 @@ private class FeatureProperties {
public FeatureProperties(SimpleFeature feature) {
this.feature = feature;
}
/**
* Safe expression execution with default fallback.
*
* @param expression
* @param feature
* @param defaultValue
* @return evaluated value or defaultValue if unavailable
*/
private Double evaluate(Expression expression, SimpleFeature feature, double defaultValue) {
if (expression == null) {
return defaultValue;
}
Double value = expression.evaluate(feature, Double.class);
if (value == null || Double.isNaN(value)) {
return defaultValue;
}
return value;
}

public IconProperties properties() {
IconProperties singleExternalGraphic = trySingleExternalGraphic();
Expand Down Expand Up @@ -116,10 +134,10 @@ public IconProperties isExternalGraphic(MiniRule rule) {
}
ExternalGraphic exGraphic = (ExternalGraphic) gSym;
try {
Double opacity = g.getOpacity().evaluate(feature, Double.class);
Double opacity = evaluate(g.getOpacity(), feature, 1.0);
Double size = 1d * Icons.getExternalSize(exGraphic, feature);
if (size != null) size = size / Icons.DEFAULT_SYMBOL_SIZE;
Double rotation = g.getRotation().evaluate(feature, Double.class);
Double rotation = evaluate(g.getRotation(), feature, 0.0);
Expression urlExpression =
ExpressionExtractor.extractCqlExpressions(
exGraphic.getLocation().toExternalForm());
Expand Down
14 changes: 11 additions & 3 deletions src/kml/src/main/java/org/geoserver/kml/icons/Icons.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,11 @@ public static double rotationScaleFactor(double rotation) {
* @param f
*/
public static @Nullable Double getRotation(Graphic g, @Nullable Feature f) {
return g.getRotation().evaluate(f, Double.class);
if (g.getRotation() != null) {
return g.getRotation().evaluate(f, Double.class);
} else {
return null;
}
}

/**
Expand All @@ -93,7 +97,11 @@ public static double rotationScaleFactor(double rotation) {
* @param f
*/
public static @Nullable Double getSpecifiedSize(Graphic g, @Nullable Feature f) {
return g.getSize().evaluate(f, Double.class);
if (g.getSize() != null) {
return g.getSize().evaluate(f, Double.class);
} else {
return null;
}
}

private static @Nullable Icon getIcon(ExternalGraphic eg, @Nullable Feature f) {
Expand Down Expand Up @@ -158,7 +166,7 @@ public static double rotationScaleFactor(double rotation) {

if (gs instanceof Mark) {
Stroke stroke = ((Mark) gs).getStroke();
if (stroke != null) {
if (stroke != null && stroke.getWidth() != null) {
Double width = stroke.getWidth().evaluate(f, Double.class);
if (width != null) {
border = width;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* (c) 2018 Open Source Geospatial Foundation - all rights reserved
*
* This code is licensed under the GPL 2.0 license, available at the root application directory.
*/
package org.geoserver.kml.decorator;

import de.micromata.opengis.kml.v_2_2_0.Placemark;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.geoserver.kml.KmlEncodingContext;
import org.geoserver.kml.decorator.PlacemarkStyleDecoratorFactory.PlacemarkStyleDecorator;
import org.geoserver.kml.icons.IconTestSupport;
import org.geotools.data.DataUtilities;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.styling.TextSymbolizer;
import org.junit.Test;
import org.opengis.feature.simple.SimpleFeatureType;

public class PlacemarkStyleDecoratorTest extends IconTestSupport {

/**
* Test partial style transformation.
*
* <p>The YSLD parser is not supplying the same defaults as the SLD parser, producing styles
* that violate some of the (perfectly sensible SLD 1.0) assumptions made by
* PlacemarkStyleDecorator.
*/
@Test
public void testYSLDTextSymbolizerEncoding() {
TextSymbolizer text =
text("text", "NAME", font("Arial Black", null, "bold", 8), fill(Color.white, null));

PlacemarkStyleDecoratorFactory.PlacemarkStyleDecorator decorator =
new PlacemarkStyleDecorator();

KmlEncodingContext context = new FakeKmlEncodingContext(featureType);
SimpleFeatureCollection collection = DataUtilities.collection(fieldIs1);
context.setCurrentFeatureCollection(collection);
context.setCurrentFeature(fieldIs1);
context.setCurrentSymbolizers(Collections.singletonList(text));

Placemark placemark = new Placemark();
decorator.decorate(placemark, context);
}

public static class FakeKmlEncodingContext extends KmlEncodingContext {
private SimpleFeatureType featureType;

public FakeKmlEncodingContext(SimpleFeatureType featureType) {
this.featureType = featureType;
}

public List<SimpleFeatureType> getFeatureTypes() {
List<SimpleFeatureType> results = new ArrayList<SimpleFeatureType>();
results.add(featureType);
return results;
}

@Override
public SimpleFeatureType getCurrentFeatureType() {
return featureType;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Collections;
import org.geotools.filter.text.cql2.CQLException;
import org.geotools.styling.Fill;
import org.geotools.styling.Graphic;
import org.geotools.styling.Mark;
import org.geotools.styling.PointSymbolizer;
Expand All @@ -21,6 +23,7 @@
import org.junit.Test;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.filter.expression.Expression;
import org.opengis.style.Stroke;

public class IconPropertiesTest extends IconTestSupport {
@Test
Expand Down Expand Up @@ -84,6 +87,27 @@ public void testDynamicMark() throws CQLException {
assertEquals("0.0.0=&0.0.0.name=square", encode(s, fieldIs2));
}

@Test
public void testDynamicColor() throws CQLException {
Expression color = toExpression("if_then_else(equalTo(field, 1), '#8080C0', '#CC8030')");
Stroke stroke = styleFactory.stroke(color, null, null, null, null, null, null);
Fill fill = styleFactory.fill(null, color, null);
Mark mark = styleFactory.mark(toExpression("circle"), fill, stroke);
Graphic graphic =
styleFactory.graphic(Collections.singletonList(mark), null, null, null, null, null);
PointSymbolizer symbolizer =
styleFactory.pointSymbolizer(
"symbolizer", toExpression("geom"), null, null, graphic);

final Style s = styleFromRules(catchAllRule(symbolizer));
assertEquals(
"0.0.0=&0.0.0.fill.color=%238080C0&0.0.0.name=&0.0.0.stroke.color=%238080C0",
encode(s, fieldIs1));
assertEquals(
"0.0.0=&0.0.0.fill.color=%23CC8030&0.0.0.name=&0.0.0.stroke.color=%23CC8030",
encode(s, fieldIs2));
}

@Test
public void testDynamicOpacity() throws CQLException {
final PointSymbolizer symbolizer = grayCircle();
Expand Down

0 comments on commit 4a2da2c

Please sign in to comment.