/* (c) 2014 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.kml.icons; import java.net.MalformedURLException; import java.util.List; import java.util.Map; import java.util.TreeMap; import org.geotools.filter.visitor.IsStaticExpressionVisitor; import org.geotools.renderer.style.ExpressionExtractor; import org.geotools.styling.ExternalGraphic; import org.geotools.styling.Fill; import org.geotools.styling.Graphic; import org.geotools.styling.Mark; import org.geotools.styling.PointSymbolizer; import org.geotools.styling.Stroke; import org.geotools.styling.Style; import org.opengis.feature.simple.SimpleFeature; import org.opengis.filter.expression.Expression; import org.opengis.style.GraphicalSymbol; /** * Utility to extract the values of dynamic properties from a style when applied to a particular * feature. * * @see IconPropertyInjector * * @author David Winslow, OpenGeo * */ public final class IconPropertyExtractor { private List<List<MiniRule>> style; private IconPropertyExtractor(List<List<MiniRule>> style) { this.style = style; } private IconProperties propertiesFor(SimpleFeature feature) { return new FeatureProperties(feature).properties(); } public static IconProperties extractProperties(Style style, SimpleFeature feature) { return new IconPropertyExtractor(MiniRule.minify(style)).propertiesFor(feature); } private class FeatureProperties { private static final String URL = ".url"; private static final String WIDTH = ".width"; private static final String LINEJOIN = ".linejoin"; private static final String LINECAP = ".linecap"; private static final String DASHOFFSET = ".dashoffset"; private static final String GRAPHIC = ".graphic"; private static final String COLOR = ".color"; private static final String STROKE = ".stroke"; private static final String FILL = ".fill"; private static final String NAME = ".name"; private static final String SIZE = ".size"; private static final String ROTATION = ".rotation"; private static final String OPACITY = ".opacity"; private final SimpleFeature feature; public FeatureProperties(SimpleFeature feature) { this.feature = feature; } public IconProperties properties() { IconProperties singleExternalGraphic = trySingleExternalGraphic(); if (singleExternalGraphic != null) { return singleExternalGraphic; } else { return embeddedIconProperties(); } } public IconProperties trySingleExternalGraphic() { MiniRule singleRule = null; for (List<MiniRule> rules : style) { boolean applied = false; for (MiniRule rule : rules) { final boolean applicable = (rule.isElseFilter && !applied) || (rule.filter == null) || (rule.filter.evaluate(feature)); if (applicable) { if (singleRule == null) { singleRule = rule; } else { return null; } } } } if (singleRule == null) { return null; } return isExternalGraphic(singleRule); } public IconProperties isExternalGraphic(MiniRule rule) { if (rule.symbolizers.size() != 1) { return null; } Graphic g = rule.symbolizers.get(0).getGraphic(); if (g == null) { return null; } if (g.graphicalSymbols().size() != 1) { return null; } GraphicalSymbol gSym = g.graphicalSymbols().get(0); if (! (gSym instanceof ExternalGraphic)) { return null; } ExternalGraphic exGraphic = (ExternalGraphic) gSym; try { Double opacity = g.getOpacity().evaluate(feature, Double.class); Double size = 1d*Icons.getExternalSize(exGraphic, feature); if (size != null) size = size / Icons.DEFAULT_SYMBOL_SIZE; Double rotation = g.getRotation().evaluate(feature, Double.class); Expression urlExpression = ExpressionExtractor.extractCqlExpressions(exGraphic.getLocation().toExternalForm()); return IconProperties.externalReference(opacity, size, rotation, urlExpression.evaluate(feature, String.class)); } catch (MalformedURLException e) { throw new RuntimeException(e); } } public IconProperties embeddedIconProperties() { Map<String,String> props = new TreeMap<String,String>(); Double size = null; boolean allRotated = true; for (int i = 0; i < style.size(); i++) { List<MiniRule> rules = style.get(i); for (int j = 0; j < rules.size(); j++) { MiniRule rule = rules.get(j); final boolean matches; if (rule.filter == null) { matches = !rule.isElseFilter || props.isEmpty(); } else { matches = rule.filter.evaluate(feature); } if (matches) { for (int k = 0; k < rule.symbolizers.size(); k++) { props.put(i + "." + j + "." + k, ""); PointSymbolizer sym = rule.symbolizers.get(k); if (sym.getGraphic() != null) { addGraphicProperties(i + "." + j + "." + k, sym.getGraphic(), props); final Double gRotation = graphicRotation(sym.getGraphic()); allRotated &= gRotation!=null; final Double gSize = Icons.graphicSize(sym.getGraphic(), gRotation, feature); if (size == null || (gSize != null && gSize > size)) { size = gSize; } } } } } } if (size != null) size = size / 16d; // If all the symbols in the stack were rotated, force it to be oriented to a bearing. final Double rotation = allRotated ? 0d: null; return IconProperties.generator(null, size, rotation, props); } public boolean isStatic(Expression ex) { return (Boolean) ex.accept(IsStaticExpressionVisitor.VISITOR, null); } private Double graphicRotation(Graphic g) { if (g.getRotation() != null) { return g.getRotation().evaluate(feature, Double.class); } else { return null; } } public void addGraphicProperties(String prefix, Graphic g, Map<String,String> props) { if (g.getOpacity() != null && !isStatic(g.getOpacity())) { props.put(prefix + OPACITY, g.getOpacity().evaluate(feature, String.class)); } if (g.getRotation() != null && !isStatic(g.getRotation())) { props.put(prefix + ROTATION, g.getRotation().evaluate(feature, String.class)); } if (g.getSize() != null && !isStatic(g.getSize())) { props.put(prefix + SIZE, g.getSize().evaluate(feature, String.class)); } if (!g.graphicalSymbols().isEmpty()) { if (g.graphicalSymbols().get(0) instanceof Mark) { Mark mark = (Mark) g.graphicalSymbols().get(0); addMarkProperties(prefix, mark, props); } else if (g.graphicalSymbols().get(0) instanceof ExternalGraphic) { ExternalGraphic exGraphic = (ExternalGraphic) g.graphicalSymbols().get(0); addExternalGraphicProperties(prefix, exGraphic, props); } } } public void addMarkProperties(String prefix, Mark mark, Map<String, String> props) { if (mark.getWellKnownName() != null && !isStatic(mark.getWellKnownName())) { props.put(prefix + NAME, mark.getWellKnownName().evaluate(feature, String.class)); } if (mark.getFill() != null) { addFillProperties(prefix + FILL, mark.getFill(), props); } if (mark.getStroke() != null) { addStrokeProperties(prefix + STROKE, mark.getStroke(), props); } } public void addFillProperties(String prefix, Fill fill, Map<String, String> props) { if (fill.getColor() != null && !isStatic(fill.getColor())) { props.put(prefix + COLOR, fill.getColor().evaluate(feature, String.class)); } if (fill.getOpacity() != null && !isStatic(fill.getOpacity())) { props.put(prefix + OPACITY, fill.getOpacity().evaluate(feature, String.class)); } if (fill.getGraphicFill() != null) { addGraphicProperties(prefix + GRAPHIC, fill.getGraphicFill(), props); } } public void addStrokeProperties(String prefix, Stroke stroke, Map<String, String> props) { if (stroke.getColor() != null && !isStatic(stroke.getColor())) { props.put(prefix + COLOR, stroke.getColor().evaluate(feature, String.class)); } if (stroke.getDashOffset() != null && !isStatic(stroke.getDashOffset())) { props.put(prefix + DASHOFFSET, stroke.getDashOffset().evaluate(feature, String.class)); } if (stroke.getLineCap() != null && !isStatic(stroke.getLineCap())) { props.put(prefix + LINECAP, stroke.getLineCap().evaluate(feature, String.class)); } if (stroke.getLineJoin() != null && !isStatic(stroke.getLineJoin())) { props.put(prefix + LINEJOIN, stroke.getLineJoin().evaluate(feature, String.class)); } if (stroke.getOpacity() != null && !isStatic(stroke.getOpacity())) { props.put(prefix + OPACITY, stroke.getOpacity().evaluate(feature, String.class)); } if (stroke.getWidth() != null && !isStatic(stroke.getWidth())) { props.put(prefix + WIDTH, stroke.getWidth().evaluate(feature, String.class)); } if (stroke.getGraphicStroke() != null) { addGraphicProperties(prefix + GRAPHIC, stroke.getGraphicStroke(), props); } if (stroke.getGraphicFill() != null) { addGraphicProperties(prefix + GRAPHIC, stroke.getGraphicFill(), props); } } public void addExternalGraphicProperties(String prefix, ExternalGraphic exGraphic, Map<String, String> props) { try { Expression ex = ExpressionExtractor.extractCqlExpressions(exGraphic.getLocation().toExternalForm()); if (!isStatic(ex)) { props.put(prefix + URL, ex.evaluate(feature, String.class)); } } catch (MalformedURLException e) { // Do nothing, it's just an icon we can't resolve. // TODO: Log at FINER or FINEST level? } } } }