/* (c) 2015 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.wms.utfgrid;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.geotools.feature.FeatureCollection;
import org.geotools.renderer.composite.BlendComposite.BlendingMode;
import org.geotools.renderer.style.GraphicStyle2D;
import org.geotools.renderer.style.IconStyle2D;
import org.geotools.renderer.style.SLDStyleFactory;
import org.geotools.renderer.style.Style2D;
import org.geotools.styling.AnchorPoint;
import org.geotools.styling.Displacement;
import org.geotools.styling.ExternalGraphic;
import org.geotools.styling.FeatureTypeStyle;
import org.geotools.styling.Fill;
import org.geotools.styling.Graphic;
import org.geotools.styling.Mark;
import org.geotools.styling.PointSymbolizer;
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.opengis.filter.Filter;
import org.opengis.filter.capability.FunctionName;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.expression.Function;
import org.opengis.filter.expression.Literal;
import org.opengis.style.Description;
import org.opengis.style.GraphicalSymbol;
/**
* Prepares a style for a UTFGrid generation, in particular:
* <ul>
* <li>Removes all feature type styles with a transform function that is known not to return vector data
* <li>
* <li>Replaces all colors with the {@link UTFGridColorFunction}</li>
* <li>Replaces all external graphics with an "equivalent" solid color mark (ideally this would be a black and white version of the external graphic,
* with the same shape, but it's hard, so we use a square instead)</li>
* <li>Removes all text symbolizers</li>
* </ul>
*
* @author Andrea Aime - GeoSolutions
*/
class UTFGridStyleVisitor extends DuplicatingStyleVisitor {
private UTFGridColorFunction colorFunction;
/**
* Potentially vector transformations (including ones with unknown return type)
*/
boolean vectorTransformations = false;
boolean transformations = false;
private final Literal LITERAL_ONE = ff.literal(1);
SLDStyleFactory sldFactory = new SLDStyleFactory();
public UTFGridStyleVisitor(UTFGridColorFunction colorFunction) {
this.colorFunction = colorFunction;
}
public void visit(Style style) {
super.visit(style);
Style copy = (Style) pages.pop();
List<FeatureTypeStyle> featureTypeStyles = new ArrayList(copy.featureTypeStyles());
for (Iterator<FeatureTypeStyle> it = featureTypeStyles.iterator(); it.hasNext();) {
FeatureTypeStyle fts = it.next();
if (fts.rules().isEmpty()) {
it.remove();
}
}
copy.featureTypeStyles().clear();
copy.featureTypeStyles().addAll(featureTypeStyles);
pages.push(copy);
}
@Override
public void visit(FeatureTypeStyle fts) {
super.visit(fts);
FeatureTypeStyle copy = (FeatureTypeStyle) pages.peek();
// clean up empty rules
List<Rule> rules = new ArrayList<>(copy.rules());
for (Iterator<Rule> it = rules.iterator(); it.hasNext();) {
Rule rule = it.next();
if (rule.symbolizers().isEmpty()) {
it.remove();
}
}
copy.rules().clear();
copy.rules().addAll(rules);
// try to shave off transformations that won't generate a vector rendering
if (copy.getTransformation() instanceof Function) {
transformations = true;
Function f = (Function) fts.getTransformation();
Class returnType = getFunctionReturnType(f);
if (Object.class.equals(returnType)
|| FeatureCollection.class.isAssignableFrom(returnType)) {
vectorTransformations = true;
super.visit(fts);
} else {
// shave off
copy.rules().clear();
}
}
// remove color altering vendor options (colors are keys, we cannot have them be altered)
Map<String, String> options = copy.getOptions();
String composite = options.get(FeatureTypeStyle.COMPOSITE);
if (composite != null && BlendingMode.lookupByName(composite) != null) {
options.remove(FeatureTypeStyle.COMPOSITE);
options.remove(FeatureTypeStyle.COMPOSITE_BASE);
}
}
public void visit(Rule rule) {
super.visit(rule);
// clean up removed symbolizers
Rule copy = (Rule) pages.pop();
List<Symbolizer> symbolizers = new ArrayList(copy.symbolizers());
for (Iterator<Symbolizer> it = symbolizers.iterator(); it.hasNext();) {
Symbolizer symbolizer = it.next();
if (symbolizer == null) {
it.remove();
}
}
copy.symbolizers().clear();
copy.symbolizers().addAll(symbolizers);
pages.push(copy);
};
/**
* Returns the function return type, or {@link Object} if it could not be determined
*
* @param f
*
*/
Class getFunctionReturnType(Function f) {
FunctionName name = f.getFunctionName();
if (name == null || name.getReturn() == null) {
return Object.class;
}
return name.getReturn().getType();
}
@Override
public void visit(Graphic gr) {
Graphic copy = null;
Displacement displacementCopy = copy(gr.getDisplacement());
Expression rotationCopy = copy(gr.getRotation());
Expression sizeCopy = copy(gr.getSize());
AnchorPoint anchorCopy = copy(gr.getAnchorPoint());
// copy the symbols replacing them with a same sized mark filled with the color function
List<GraphicalSymbol> symbolsCopy = new ArrayList<>();
for (GraphicalSymbol gs : gr.graphicalSymbols()) {
if (gs instanceof Mark) {
Mark markCopy = copy((Mark) gs);
symbolsCopy.add(markCopy);
} else if (gs instanceof ExternalGraphic) {
if (gr.getSize() != null && !Expression.NIL.equals(gr.getSize())) {
Mark mark = sf.createMark(ff.literal("square"), null,
sf.createFill(colorFunction), sizeCopy, Expression.NIL);
symbolsCopy.add(mark);
} else {
// it's using the default size, compute it if possible (might be using dynamic symbolizers...)
ExternalGraphic eg = (ExternalGraphic) gs;
Literal sizeExpression = estimateGraphicSize(eg);
Mark mark = sf.createMark(ff.literal("square"), null,
sf.createFill(colorFunction), sizeExpression, Expression.NIL);
symbolsCopy.add(mark);
}
}
}
copy = sf.createDefaultGraphic();
copy.setDisplacement(displacementCopy);
copy.setAnchorPoint(anchorCopy);
copy.setOpacity(LITERAL_ONE);
copy.setRotation(rotationCopy);
copy.setSize(sizeCopy);
copy.graphicalSymbols().clear();
copy.graphicalSymbols().addAll(symbolsCopy);
if (STRICT) {
if (!copy.equals(gr)) {
throw new IllegalStateException("Was unable to duplicate provided Graphic:" + gr);
}
}
pages.push(copy);
}
private Literal estimateGraphicSize(ExternalGraphic eg) {
Graphic testGraphic = sf.createGraphic(new ExternalGraphic[] { eg }, null, null,
LITERAL_ONE, Expression.NIL, ff.literal(0));
PointSymbolizer testSymbolizer = sf.createPointSymbolizer(testGraphic, null);
Style2D style = sldFactory.createStyle(null, testSymbolizer);
int size = SLDStyleFactory.DEFAULT_MARK_SIZE;
if (style instanceof GraphicStyle2D) {
GraphicStyle2D gs2d = (GraphicStyle2D) style;
size = gs2d.getImage().getWidth();
} else if (style instanceof IconStyle2D) {
IconStyle2D is2d = (IconStyle2D) style;
size = is2d.getIcon().getIconWidth();
}
Literal sizeExpression = ff.literal(size);
return sizeExpression;
}
@Override
public void visit(Fill fill) {
super.visit(fill);
Fill copy = (Fill) pages.peek();
if (copy.getGraphicFill() != null) {
copy.setGraphicFill(null);
}
copy.setColor(colorFunction);
copy.setOpacity(LITERAL_ONE);
}
@Override
public void visit(Stroke stroke) {
super.visit(stroke);
Stroke copy = (Stroke) pages.peek();
if (copy.getGraphicFill() != null) {
copy.setGraphicFill(null);
}
if (copy.getGraphicStroke() != null) {
copy.setWidth(getSymbolsSize(copy.getGraphicStroke()));
copy.setGraphicStroke(null);
}
copy.setColor(colorFunction);
copy.setOpacity(LITERAL_ONE);
if (copy.dashArray() != null) {
copy.dashArray().clear();
}
copy.setDashOffset(null);
}
private Expression getSymbolsSize(Graphic graphic) {
Expression size = graphic.getSize();
if (size != null && !Expression.NIL.equals(size)) {
return size;
} else {
for (GraphicalSymbol gs : graphic.graphicalSymbols()) {
if (gs instanceof Mark) {
return ff.literal(SLDStyleFactory.DEFAULT_MARK_SIZE);
} else if (gs instanceof ExternalGraphic) {
return estimateGraphicSize((ExternalGraphic) gs);
}
}
}
return ff.literal(SLDStyleFactory.DEFAULT_MARK_SIZE);
}
@Override
public void visit(TextSymbolizer text) {
// eliminate text symbolizers
pages.push(null);
}
@Override
public void visit(RasterSymbolizer raster) {
pages.push(null);
}
public boolean hasVectorTransformations() {
return vectorTransformations;
}
public boolean hasTransformations() {
return transformations;
}
}