/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* (c) 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.wms.featureinfo;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Icon;
import org.geotools.renderer.style.DynamicSymbolFactoryFinder;
import org.geotools.renderer.style.ExpressionExtractor;
import org.geotools.renderer.style.ExternalGraphicFactory;
import org.geotools.styling.ExternalGraphic;
import org.geotools.styling.FeatureTypeStyle;
import org.geotools.styling.Fill;
import org.geotools.styling.Graphic;
import org.geotools.styling.LineSymbolizer;
import org.geotools.styling.PointSymbolizer;
import org.geotools.styling.PolygonSymbolizer;
import org.geotools.styling.RasterSymbolizer;
import org.geotools.styling.Rule;
import org.geotools.styling.Stroke;
import org.geotools.styling.Style;
import org.geotools.styling.Symbolizer;
import org.geotools.styling.TextSymbolizer;
import org.geotools.styling.visitor.DuplicatingStyleVisitor;
import org.geotools.util.logging.Logging;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.expression.Literal;
import org.opengis.filter.expression.NilExpression;
import org.opengis.style.GraphicalSymbol;
/**
* Extract the portion of the style whose sizes depend on attribute values
*
* @author Andrea Aime - GeoSolutions
*/
class DynamicSizeStyleExtractor extends DuplicatingStyleVisitor {
static final Logger LOGGER = Logging.getLogger(DynamicSizeStyleExtractor.class);
boolean dynamic = false;
public void visit(Rule rule) {
super.visit(rule);
Rule copy = (Rule) pages.peek();
List<Symbolizer> nonNullCopies = new ArrayList<Symbolizer>();
for (Symbolizer s : copy.symbolizers()) {
if (s != null) {
nonNullCopies.add(s);
}
}
if (nonNullCopies.size() == 0) {
pages.pop();
pages.push(null);
} else {
copy.symbolizers().clear();
copy.symbolizers().addAll(nonNullCopies);
}
}
@Override
public void visit(Fill fill) {
// whatever goes on in a Fill does not affect the search area of fills
boolean dynamicPrevious = false;
try {
super.visit(fill);
} finally {
dynamic = dynamicPrevious;
}
}
@Override
public void visit(FeatureTypeStyle fts) {
super.visit(fts);
FeatureTypeStyle copy = (FeatureTypeStyle) pages.peek();
List<Rule> nonNullCopies = new ArrayList<Rule>();
for (Rule r : copy.rules()) {
if (r != null) {
nonNullCopies.add(r);
}
}
if (nonNullCopies.size() == 0) {
pages.pop();
pages.push(null);
} else {
copy.rules().clear();
copy.rules().addAll(nonNullCopies);
}
}
@Override
public void visit(Style style) {
super.visit(style);
Style copy = (Style) pages.peek();
List<FeatureTypeStyle> nonNullCopies = new ArrayList<FeatureTypeStyle>();
for (FeatureTypeStyle ft : copy.featureTypeStyles()) {
if (ft != null) {
nonNullCopies.add(ft);
}
}
if (nonNullCopies.size() == 0) {
pages.pop();
pages.push(null);
} else {
copy.featureTypeStyles().clear();
copy.featureTypeStyles().addAll(nonNullCopies);
}
}
@Override
public void visit(LineSymbolizer line) {
dynamic = false;
super.visit(line);
if (!dynamic) {
pages.pop();
pages.push(null);
}
}
@Override
public void visit(PolygonSymbolizer poly) {
dynamic = false;
super.visit(poly);
if (!dynamic) {
pages.pop();
pages.push(null);
}
}
@Override
public void visit(PointSymbolizer ps) {
dynamic = false;
super.visit(ps);
if (!dynamic) {
pages.pop();
pages.push(null);
}
}
@Override
public void visit(RasterSymbolizer raster) {
// nothing to do, this style cannot make the buffer grow
pages.push(null);
}
@Override
public void visit(TextSymbolizer text) {
// nothing to do, this style cannot make the buffer grow
pages.push(null);
}
@Override
public void visit(Graphic gr) {
super.visit(gr);
Expression sizeExpression = gr.getSize();
if(!dynamic) {
dynamic = !(sizeExpression != null && (sizeExpression instanceof Literal || sizeExpression instanceof NilExpression)) || hasDynamicGraphic(gr);
}
}
private boolean hasDynamicGraphic(Graphic gr) {
// not a fixed size, let's see if it has dynamic graphics inside
for (GraphicalSymbol gs : gr.graphicalSymbols()) {
if (gs instanceof ExternalGraphic) {
ExternalGraphic eg = (ExternalGraphic) gs;
try {
Icon icon = null;
if (eg.getInlineContent() != null) {
icon = eg.getInlineContent();
} else {
String location = eg.getLocation().toExternalForm();
// expand embedded cql expression
Expression expanded = ExpressionExtractor.extractCqlExpressions(location);
// if not a literal there is an attribute dependency
if (!(expanded instanceof Literal)) {
return true;
}
Iterator<ExternalGraphicFactory> it = DynamicSymbolFactoryFinder
.getExternalGraphicFactories();
while (it.hasNext()) {
try {
icon = it.next().getIcon(null, expanded, eg.getFormat(), 16);
} catch (Exception e) {
LOGGER.log(Level.FINE,
"Error occurred evaluating external graphic", e);
}
}
// evaluate the icon if found, if not SLD asks us to go to the next one
// and thus we should ignore this one
if (icon != null) {
break;
}
}
} catch (MalformedURLException e) {
LOGGER.log(Level.FINE,
"Failed to check graphics for attribute embedded in the path " + eg, e);
}
}
}
return false;
}
@Override
public void visit(Stroke stroke) {
super.visit(stroke);
if (!(stroke.getWidth() instanceof Literal)) {
dynamic = true;
}
}
}