/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2003 - 2016, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.styling;
import java.awt.Color;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Logger;
import javax.measure.quantity.Length;
import javax.measure.unit.Unit;
import org.geotools.data.DataStore;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.filter.FilterTransformer;
import org.geotools.gml.producer.FeatureTransformer;
import org.geotools.referencing.CRS;
import org.geotools.util.GrowableInternationalString;
import org.geotools.xml.transform.TransformerBase;
import org.geotools.xml.transform.Translator;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory;
import org.opengis.filter.FilterFactory2;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.expression.Function;
import org.opengis.filter.expression.Literal;
import org.opengis.filter.expression.PropertyName;
import org.opengis.referencing.ReferenceIdentifier;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.style.ContrastMethod;
import org.opengis.style.SemanticType;
import org.opengis.util.InternationalString;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
/**
* Produces SLD to an output stream.
*
* @author Ian Schneider
*
*
* @source $URL$
*/
public class SLDTransformer extends TransformerBase {
/** The logger for this package. */
private static final Logger LOGGER = org.geotools.util.logging.Logging
.getLogger("org.geotools.styling");
static final String XLINK_NAMESPACE = "http://www.w3.org/1999/xlink";
static final FilterFactory ff = CommonFactoryFinder.getFilterFactory(null);
static final Font DEFAULT_FONT = CommonFactoryFinder.getStyleFactory().getDefaultFont();
/**
* Additional namespace mappings to emit in the start element of the generated. Each entry has a URI key and an associated prefix string value.
*/
final private Map uri2prefix;
/**
* don't suppress the export of default values
*/
private boolean exportDefaultValues = false;
/**
* Construct a new instance of <code>SLDTransformer</code> with the default namespace mappings usually found in a simple Styled Layer Descriptor
* element.
*/
public SLDTransformer() {
this(null);
}
/**
* Construct a new instance of <code>SLDTransformer</code> with the additional namespace mappings contained in <code>nsBindings</code>.
* <p>
* The designated collection contains mappings of {@link URI} to associated prefix (string) to emit in the generated XML element.
*/
public SLDTransformer(Map nsBindings) {
super();
if (nsBindings == null || nsBindings.isEmpty()) {
uri2prefix = new HashMap();
} else {
uri2prefix = new HashMap(nsBindings.size());
int count = 0;
for (Iterator it = nsBindings.entrySet().iterator(); it.hasNext();) {
Map.Entry e = (Entry) it.next();
URI uri = (URI) e.getKey();
String prefix = (String) e.getValue();
if (uri != null && prefix != null) {
uri2prefix.put(uri, prefix.trim());
count++;
}
}
LOGGER.info("Added [" + count + "] namespace entries resulting in [" + uri2prefix.size()
+ "] distinct entries");
}
}
/**
* @return the exportDefaultValues
*/
public boolean isExportDefaultValues() {
return exportDefaultValues;
}
/**
* @param exportDefaultValues the exportDefaultValues to set
*/
public void setExportDefaultValues(boolean exportDefaultValues) {
this.exportDefaultValues = exportDefaultValues;
}
public Translator createTranslator(ContentHandler handler) {
Translator result = new SLDTranslator(handler);
// add pre-configured namespace mappings
if (!uri2prefix.isEmpty()) {
for (Iterator it = uri2prefix.entrySet().iterator(); it.hasNext();) {
Map.Entry e = (Entry) it.next();
URI uri = (URI) e.getKey();
if (uri != null) {
String prefix = (String) e.getValue();
// FIXME handle default namespace and possible clash with
// one already known to the namespace-support delegate; i.e.
// the entry with an empty prefix
String uriStr = String.valueOf(uri);
result.getNamespaceSupport().declarePrefix(prefix, uriStr);
}
}
}
((SLDTranslator)result).setExportDefaultValues(isExportDefaultValues());
return result;
}
/**
* Currently does nothing.
*
* @param args DOCUMENT ME!
*
* @throws Exception DOCUMENT ME!
*/
public static final void main(String[] args) throws Exception {
java.net.URL url = new java.io.File(args[0]).toURI().toURL();
SLDParser s = new SLDParser(CommonFactoryFinder.getStyleFactory(null), url);
SLDTransformer transformer = new SLDTransformer();
transformer.setIndentation(4);
transformer.transform(s.readXML(),
new FileOutputStream(System.getProperty("java.io.tmpdir") + "/junk.eraseme"));
}
/**
* Translates the Style data structure into a series of XML events that can be encoded etc...
* <p>
* This Translator makes use of the following (currently hardcoded) information:
* <ul>
* <li>prefix: sld
* <li>namespace: http://www.opengis.net/sld
* </ul>
*
* @author Jody
*/
static class SLDTranslator extends TranslatorSupport implements StyleVisitor {
/**
* Handles any Filters used in our data structure.
*/
FilterTransformer.FilterTranslator filterTranslator;
private boolean exportDefaultValues = false;
/**
* Translates into the default of prefix "sld" for "http://www.opengis.net/sld".
*
* @param handler
*/
public SLDTranslator(ContentHandler handler) {
this(handler, "sld", "http://www.opengis.net/sld");
}
/**
* Translates
*
* @param handler
*/
public SLDTranslator(ContentHandler handler, String prefix, String uri) {
super(handler, prefix, uri);
filterTranslator = new FilterTransformer.FilterTranslator(handler);
addNamespaceDeclarations(filterTranslator);
}
boolean isNull(Expression expr) {
if (expr == null)
return true;
if (expr == Expression.NIL)
return true;
if (expr instanceof Literal) {
Literal literal = (Literal) expr;
return literal.getValue() == null;
}
return false; // must be some other non null thing
}
boolean isDefault(Expression expr, Object defaultValue) {
if (isExportDefaultValues()) {
return false;
}
if (defaultValue == null)
return isNull(expr);
if (expr == null)
return false;
if (expr == Expression.NIL)
return false;
if (expr instanceof Literal) {
Literal literal = (Literal) expr;
if (defaultValue.equals(literal.getValue())) {
return true;
}
if (defaultValue.toString().equals(literal.getValue().toString())) {
return true;
}
}
return false;
}
/**
* @param exportDefaultValues
*/
public void setExportDefaultValues(boolean exportDefaultValues) {
this.exportDefaultValues = exportDefaultValues;
}
/**
* @return the exportDefaultValues
*/
public boolean isExportDefaultValues() {
return exportDefaultValues;
}
/**
* Utility method used to quickly package up the provided expression.
*
* @param element
* @param expr
*/
void element(String element, Expression expr) {
element(element, expr, null);
}
/**
* Utility method used to quickly package up the provided InternationalString.
*
* @param element
* @param expr
*/
void element(String element, InternationalString intString) {
if (intString instanceof GrowableInternationalString) {
GrowableInternationalString growable = (GrowableInternationalString) intString;
if (growable.getLocales().isEmpty()) {
element(element, intString.toString());
} else {
start(element);
chars(intString.toString());
for (Locale locale : growable.getLocales()) {
if (locale != null) {
AttributesImpl atts = new AttributesImpl();
atts.addAttribute("", "lang", "lang", "", locale.toString());
element("Localized", growable.toString(locale), atts);
}
}
end(element);
}
} else {
element(element, intString.toString());
}
}
/**
* Utility method used to quickly package up the provided expression.
*
* @param element
* @param expr
*/
void element(String element, Expression expr, Object defaultValue) {
element(element, expr, defaultValue, null);
}
void element(String element, Expression expr, Object defaultValue, AttributesImpl atts) {
if (expr == null || expr == Expression.NIL)
return;
// skip encoding if we are using the default value
if (expr instanceof Literal) {
if (defaultValue != null) {
Object value = expr.evaluate(null, defaultValue.getClass());
if (value != null && (!value.equals(defaultValue)||isExportDefaultValues())) {
element(element, value.toString(), atts);
}
} else {
String value = expr.evaluate(null, String.class);
if (value != null) {
element(element, value, atts);
}
}
return;
}
start(element, atts);
filterTranslator.encode(expr);
end(element);
}
void labelContent(Expression expr) {
if (expr instanceof Literal) {
Literal literalLabel = ((Literal) expr);
String label = literalLabel.evaluate(null, String.class);
if (label != null) {
// do we need a CDATA expansion?
if (label.matches("^\\s+.*$|^.*\\s+$|^.*\\s{2,}.*$")) {
cdata(label);
} else {
chars(label);
}
}
} else if (expr instanceof Function && ("strConcat".equals(((Function) expr).getName())
|| "concat".equals(((Function) expr).getName())
|| "Concatenate".equals(((Function) expr).getName()))) {
List<Expression> parameters = ((Function) expr).getParameters();
for (Expression parameter : parameters) {
labelContent(parameter);
}
} else {
filterTranslator.encode(expr);
}
}
/**
* To be used when the expression is a single literal whose value must be written out as element.
* <p>
* For Example OverlapBehaviour is represented as an expression but v 1.0.0 specifications do not define it as an expression. (<AVERAGE/>)
* </p>
*
*/
void elementLiteral(String element, Expression e, String defaultValue) {
if (e == null || e == Expression.NIL)
return;
final String value = e.evaluate(null, String.class);
if (defaultValue == null || !defaultValue.equals(value)) {
start(element);
start(value);
end(value);
end(element);
}
}
public void visit(PointPlacement pp) {
start("LabelPlacement");
start("PointPlacement");
if (pp.getAnchorPoint() != null) {
pp.getAnchorPoint().accept(this);
}
visit(pp.getDisplacement());
encodeValue("Rotation", null, pp.getRotation(), Double.valueOf(0.0));
end("PointPlacement");
end("LabelPlacement");
}
public void visit(Stroke stroke) {
start("Stroke");
if (stroke.getGraphicFill() != null) {
start("GraphicFill");
stroke.getGraphicFill().accept(this);
end("GraphicFill");
}
if (stroke.getGraphicStroke() != null) {
start("GraphicStroke");
stroke.getGraphicStroke().accept(this);
end("GraphicStroke");
}
encodeCssParam("stroke", stroke.getColor(), Color.BLACK);
encodeCssParam("stroke-linecap", stroke.getLineCap(), "butt");
encodeCssParam("stroke-linejoin", stroke.getLineJoin(), "miter");
encodeCssParam("stroke-opacity", stroke.getOpacity(), 1.0);
encodeCssParam("stroke-width", stroke.getWidth(), 1.0);
encodeCssParam("stroke-dashoffset", stroke.getDashOffset(), 0.0);
encodeStrokeDasharray(stroke.dashArray());
end("Stroke");
}
private void encodeStrokeDasharray(List<Expression> expressions) {
if (expressions == null || expressions.isEmpty()) return;
boolean isLiteral = true;
for (Expression expression : expressions) {
if(!(expression instanceof Literal)) isLiteral = false;
}
if(isLiteral) encodeLiteralStrokeDasharray(expressions);
else encodeMixedStrokeDasharray(expressions);
}
private void encodeLiteralStrokeDasharray(List<Expression> expressions) {
StringBuilder literalDash = new StringBuilder();
for (Expression expression : expressions) {
literalDash.append(((Literal) expression).getValue()).append(" ");
}
literalDash.deleteCharAt(literalDash.length() - 1);
encodeCssParam("stroke-dasharray", ff.literal(literalDash.toString()));
}
private void encodeMixedStrokeDasharray(List<Expression> expressions) {
AttributesImpl attributes = new AttributesImpl();
attributes.addAttribute("", "name", "name", "", "stroke-dasharray");
start("CssParameter", attributes);
for (Expression expression : expressions) {
filterTranslator.encode(expression);
}
end("CssParameter");
}
public void visit(LinePlacement lp) {
start("LabelPlacement");
start("LinePlacement");
element("PerpendicularOffset", lp.getPerpendicularOffset());
end("LinePlacement");
end("LabelPlacement");
}
public void visit(AnchorPoint ap) {
start("AnchorPoint");
element("AnchorPointX", ap.getAnchorPointX());
element("AnchorPointY", ap.getAnchorPointY());
end("AnchorPoint");
}
public void visit(TextSymbolizer text) {
if (text == null) {
return;
}
// adds the uom attribute according to the OGC SE specification
AttributesImpl atts = new AttributesImpl();
Unit<Length> uom = text.getUnitOfMeasure();
if (uom != null)
atts.addAttribute("", "uom", "uom", "", UomOgcMapping.get(uom).getSEString());
start("TextSymbolizer", atts);
encodeGeometryExpression(text.getGeometry());
if (text.getLabel() != null) {
start("Label");
labelContent(text.getLabel());
end("Label");
}
if ((text.fonts() != null) && (!text.fonts().isEmpty())) {
List<Font> fonts = text.fonts();
if(areFontsUniform(fonts)) {
// go for standard encoding, SLD 1.0 does not allow more than one
// Font item in a TextSymbolizer
start("Font");
Font initialFont = fonts.get(0);
for (Font font : fonts ) {
encodeCssParam("font-family", font.getFamily().get(0));
}
encodeCssParam("font-size", initialFont.getSize());
encodeCssParam("font-style", initialFont.getStyle());
encodeCssParam("font-weight", initialFont.getWeight());
end("Font");
} else {
// use a GT specific encoding with multiple fonts, matching our
// internal data model (which we can also parse)
for (Font font : fonts ) {
start("Font");
encodeCssParam("font-family", font.getFamily().get(0));
encodeCssParam("font-size", font.getSize());
encodeCssParam("font-style", font.getStyle());
encodeCssParam("font-weight", font.getWeight());
end("Font");
}
}
}
if (text.getLabelPlacement() != null) {
text.getLabelPlacement().accept(this);
}
if (text.getHalo() != null) {
text.getHalo().accept(this);
}
if (text.getFill() != null) {
text.getFill().accept(this);
}
if (text instanceof TextSymbolizer2) {
TextSymbolizer2 text2 = (TextSymbolizer2) text;
if (text2.getGraphic() != null)
visit(text2.getGraphic());
if (text2.getSnippet() != null)
element("Snippet", text2.getSnippet());
if (text2.getFeatureDescription() != null)
element("FeatureDescription", text2.getFeatureDescription());
OtherText otherText = text2.getOtherText();
if (otherText != null) {
AttributesImpl otherTextAtts = new AttributesImpl();
otherTextAtts.addAttribute("", "target", "target", "", otherText.getTarget());
element("OtherText", otherText.getText(), null, otherTextAtts);
}
}
if (text.getPriority() != null) {
element("Priority", text.getPriority());
}
if (text.getOptions() != null) {
encodeVendorOptions(text.getOptions());
}
end("TextSymbolizer");
}
/**
* Returns true if the list of fonts has the same settings for
* everything besides the font family, and can thus be represented
* as a single Font element
* @param fonts
* @return
*/
private boolean areFontsUniform(List<Font> fonts) {
if(fonts.size() == 1) {
return true;
}
Font reference = fonts.get(0);
Expression referenceSize = reference.getSize();
Expression referenceStyle = reference.getStyle();
Expression referenceWeight = reference.getWeight();
for (int i = 1; i < fonts.size() ; i++) {
Font f = fonts.get(i);
Expression size = f.getSize();
if(!expressionEquals(referenceSize, size, DEFAULT_FONT.getSize())) {
return false;
}
Expression style = f.getStyle();
if(!expressionEquals(referenceStyle, style, DEFAULT_FONT.getStyle())) {
return false;
}
Expression weight = f.getWeight();
if(!expressionEquals(referenceWeight, weight, DEFAULT_FONT.getWeight())) {
return false;
}
}
return true;
}
private boolean expressionEquals(Expression reference, Expression exp, Expression defaultValue) {
if(exp == null) {
return reference == null || defaultValue.equals(reference);
} else if(reference == null) {
return defaultValue.equals(exp);
} else {
return reference.equals(exp);
}
}
public void visit(RasterSymbolizer raster) {
if (raster == null) {
return;
}
// adds the uom attribute according to the OGC SE specification
AttributesImpl atts = new AttributesImpl();
Unit<Length> uom = raster.getUnitOfMeasure();
if (uom != null)
atts.addAttribute("", "uom", "uom", "", UomOgcMapping.get(uom).getSEString());
start("RasterSymbolizer", atts);
encodeGeometryExpression(raster.getGeometry());
element("Opacity", raster.getOpacity(), 1.0);
if (raster.getChannelSelection() != null) {
final ChannelSelection cs = raster.getChannelSelection();
if (cs.getGrayChannel() != null) {
start("ChannelSelection");
SelectedChannelType gray = cs.getGrayChannel();
start("GrayChannel");
gray.accept(this);
end("GrayChannel");
end("ChannelSelection");
} else if (cs.getRGBChannels() != null && cs.getRGBChannels().length == 3
&& cs.getRGBChannels()[0] != null && cs.getRGBChannels()[1] != null
&& cs.getRGBChannels()[2] != null) {
start("ChannelSelection");
SelectedChannelType[] rgb = cs.getRGBChannels();
start("RedChannel");
rgb[0].accept(this);
end("RedChannel");
start("GreenChannel");
rgb[1].accept(this);
end("GreenChannel");
start("BlueChannel");
rgb[2].accept(this);
end("BlueChannel");
end("ChannelSelection");
} else {
// we have an invalid ChannelSelection ?
}
}
if (raster.getOverlap() != null) {
Expression overlaps = raster.getOverlap();
if (overlaps instanceof PropertyName) {
final String pn = ((PropertyName) overlaps).getPropertyName();
if ("RANDOM".equals(pn)) {
start("OverlapBehavior");
start(pn);
end(pn);
end("OverlapBehavior");
}
} else {
// this expression needs to be converted to a single string and then written
// 1.0.0 specs don't allow it to be written as an expression
elementLiteral("OverlapBehavior", overlaps, "RANDOM");
}
}
ColorMap colorMap = raster.getColorMap();
if (colorMap != null && colorMap.getColorMapEntries() != null
&& colorMap.getColorMapEntries().length > 0) {
colorMap.accept(this);
}
if (raster.getContrastEnhancement() != null) {
raster.getContrastEnhancement().accept(this);
}
if (raster.getShadedRelief() != null) {
raster.getShadedRelief().accept(this);
}
if (raster.getImageOutline() != null) {
start("ImageOutline");
raster.getImageOutline().accept(this);
end("ImageOutline");
}
end("RasterSymbolizer");
}
public void visit(ColorMap colorMap) {
// The type of the ColorMap is stored in an attribute "type" and may store
// string-values: "ramp", "intervals" or "values".
AttributesImpl atts = new AttributesImpl();
String typeString;
if (colorMap.getType() == ColorMap.TYPE_INTERVALS)
typeString = "intervals";
else if (colorMap.getType() == ColorMap.TYPE_VALUES)
typeString = "values";
else
typeString = "ramp"; // Also the default in the parser
if (!"ramp".equals(typeString)) {
atts.addAttribute("", "type", "type", "", typeString);
}
final boolean extended = colorMap.getExtendedColors();
if (extended) {
atts.addAttribute("", "extended", "extended", "", "" + extended);
}
start("ColorMap", atts);
ColorMapEntry[] mapEntries = colorMap.getColorMapEntries();
for (int i = 0; i < mapEntries.length; i++) {
mapEntries[i].accept(this);
}
end("ColorMap");
}
public void visit(ColorMapEntry colorEntry) {
if (colorEntry != null) {
AttributesImpl atts = new AttributesImpl();
atts.addAttribute("", "color", "color", "",
colorEntry.getColor().evaluate(null, String.class));
if (colorEntry.getOpacity() != null) {
atts.addAttribute("", "opacity", "opacity", "",
colorEntry.getOpacity().toString());
}
if (colorEntry.getQuantity() != null) {
atts.addAttribute("", "quantity", "quantity", "",
colorEntry.getQuantity().toString());
}
if (colorEntry.getLabel() != null) {
atts.addAttribute("", "label", "label", "", colorEntry.getLabel());
}
element("ColorMapEntry", (String) null, atts);
}
}
public void visit(Symbolizer sym) {
try {
contentHandler.startElement("", "!--", "!--", NULL_ATTS);
chars("Unidentified Symbolizer " + sym.getClass());
contentHandler.endElement("", "--", "--");
} catch (SAXException se) {
throw new RuntimeException(se);
}
}
public void visit(PolygonSymbolizer poly) {
// adds the uom attribute according to the OGC SE specification
AttributesImpl atts = new AttributesImpl();
Unit<Length> uom = poly.getUnitOfMeasure();
if (uom != null)
atts.addAttribute("", "uom", "uom", "", UomOgcMapping.get(uom).getSEString());
start("PolygonSymbolizer", atts);
encodeGeometryExpression(poly.getGeometry());
if (poly.getFill() != null) {
poly.getFill().accept(this);
}
if (poly.getStroke() != null) {
poly.getStroke().accept(this);
}
if (poly.getOptions() != null) {
encodeVendorOptions(poly.getOptions());
}
end("PolygonSymbolizer");
}
public void visit(ExternalGraphic exgr) {
start("ExternalGraphic");
AttributesImpl atts = new AttributesImpl();
atts.addAttribute(XMLNS_NAMESPACE, "xlink", "xmlns:xlink", "", XLINK_NAMESPACE);
atts.addAttribute(XLINK_NAMESPACE, "type", "xlink:type", "", "simple");
atts.addAttribute(XLINK_NAMESPACE, "xlink", "xlink:href", "", exgr.getURI());
element("OnlineResource", (String) null, atts);
element("Format", exgr.getFormat());
end("ExternalGraphic");
}
public void visit(LineSymbolizer line) {
// adds the uom attribute according to the OGC SE specification
AttributesImpl atts = new AttributesImpl();
Unit<Length> uom = line.getUnitOfMeasure();
if (uom != null)
atts.addAttribute("", "uom", "uom", "", UomOgcMapping.get(uom).getSEString());
start("LineSymbolizer", atts);
encodeGeometryExpression(line.getGeometry());
if (line.getStroke() != null) {
line.getStroke().accept(this);
}
if (line.getOptions() != null) {
encodeVendorOptions(line.getOptions());
}
if (line.getPerpendicularOffset() != null) {
element("PerpendicularOffset", line.getPerpendicularOffset());
}
end("LineSymbolizer");
}
public void visit(Fill fill) {
start("Fill");
if (fill.getGraphicFill() != null) {
start("GraphicFill");
fill.getGraphicFill().accept(this);
end("GraphicFill");
}
encodeCssParam("fill", fill.getColor(), "#808080");
encodeCssParam("fill-opacity", fill.getOpacity(), 1.0);
end("Fill");
}
public void visit(Rule rule) {
start("Rule");
if (rule.getName() != null)
element("Name", rule.getName());
if (rule.getDescription() != null && rule.getDescription().getTitle() != null)
element("Title", rule.getDescription().getTitle());
if (rule.getDescription() != null && rule.getDescription().getAbstract() != null)
element("Abstract", rule.getDescription().getAbstract());
Graphic[] gr = rule.getLegendGraphic();
for (int i = 0; i < gr.length; i++) {
start("LegendGraphic");
gr[i].accept(this);
end("LegendGraphic");
}
Filter filter = rule.getFilter();
if (filter == null || filter == Filter.INCLUDE) {
// no filter
} else {
visit(filter);
}
if (rule.isElseFilter()) {
start("ElseFilter");
end("ElseFilter");
}
if (rule.getMinScaleDenominator() != 0.0) {
element("MinScaleDenominator", rule.getMinScaleDenominator() + "");
}
if (rule.getMaxScaleDenominator() != Double.POSITIVE_INFINITY) {
element("MaxScaleDenominator", rule.getMaxScaleDenominator() + "");
}
Symbolizer[] sym = rule.getSymbolizers();
for (int i = 0; i < sym.length; i++) {
sym[i].accept(this);
}
end("Rule");
}
public void visit(Mark mark) {
start("Mark");
if (mark.getWellKnownName() != null
&& (!"square".equals(mark.getWellKnownName().evaluate(null))||isExportDefaultValues())) {
encodeValue("WellKnownName", null, mark.getWellKnownName(), null);
}
if (mark.getFill() != null) {
mark.getFill().accept(this);
}
if (mark.getStroke() != null) {
mark.getStroke().accept(this);
}
end("Mark");
}
public void visit(PointSymbolizer ps) {
// adds the uom attribute according to the OGC SE specification
AttributesImpl atts = new AttributesImpl();
Unit<Length> uom = ps.getUnitOfMeasure();
if (uom != null)
atts.addAttribute("", "uom", "uom", "", UomOgcMapping.get(uom).getSEString());
start("PointSymbolizer", atts);
encodeGeometryExpression(ps.getGeometry());
ps.getGraphic().accept(this);
if (ps.getOptions() != null) {
encodeVendorOptions(ps.getOptions());
}
end("PointSymbolizer");
}
public void visit(Halo halo) {
start("Halo");
if (halo.getRadius() != null) {
encodeValue("Radius", null, halo.getRadius(), null);
}
if (halo.getFill() != null) {
halo.getFill().accept(this);
}
end("Halo");
}
public void visit(Graphic gr) {
start("Graphic");
// encodeGeometryProperty(gr.getGeometryPropertyName());
Symbol[] symbols = gr.getSymbols();
for (int i = 0; i < symbols.length; i++) {
symbols[i].accept(this);
}
element("Opacity", gr.getOpacity(), 1.0);
element("Size", gr.getSize());
element("Rotation", gr.getRotation(), 0.0);
if (gr.getAnchorPoint() != null) {
visit(gr.getAnchorPoint());
}
if (gr.getDisplacement() != null) {
visit(gr.getDisplacement());
}
end("Graphic");
}
public void visit(StyledLayerDescriptor sld) {
AttributesImpl atts = new AttributesImpl();
atts.addAttribute("", "version", "version", "", "1.0.0");
start("StyledLayerDescriptor", atts);
if ((sld.getName() != null) && (sld.getName().length() > 0)) {
element("Name", sld.getName()); // optional
}
if ((sld.getTitle() != null) && (sld.getTitle().length() > 0)) {
element("Title", sld.getTitle()); // optional
}
if ((sld.getAbstract() != null) && (sld.getAbstract().length() > 0)) {
element("Abstract", sld.getAbstract()); // optional
}
StyledLayer[] layers = sld.getStyledLayers();
for (int i = 0; i < layers.length; i++) {
if (layers[i] instanceof NamedLayer) {
visit((NamedLayer) layers[i]);
} else if (layers[i] instanceof UserLayer) {
visit((UserLayer) layers[i]);
} else {
throw new IllegalArgumentException(
"StyledLayer '" + layers[i].getClass().toString() + "' not found");
}
}
end("StyledLayerDescriptor");
}
public void visit(NamedLayer layer) {
start("NamedLayer");
element("Name", layer.getName());
FeatureTypeConstraint[] lfc = layer.getLayerFeatureConstraints();
if ((lfc != null) && lfc.length > 0) {
start("LayerFeatureConstraints"); // optional
for (int i = 0; i < lfc.length; i++) {
visit(lfc[i]);
}
end("LayerFeatureConstraints");
}
Style[] styles = layer.getStyles();
for (int i = 0; i < styles.length; i++) {
visit(styles[i]);
}
end("NamedLayer");
}
public void visit(UserLayer layer) {
start("UserLayer");
if ((layer.getName() != null) && (layer.getName().length() > 0)) {
element("Name", layer.getName()); // optional
}
DataStore inlineFDS = layer.getInlineFeatureDatastore();
if (inlineFDS != null) {
visitInlineFeatureType(inlineFDS, layer.getInlineFeatureType());
} else if (layer.getRemoteOWS() != null) {
visit(layer.getRemoteOWS());
}
start("LayerFeatureConstraints"); // required
FeatureTypeConstraint[] lfc = layer.getLayerFeatureConstraints();
if ((lfc != null) && lfc.length > 0) {
for (int i = 0; i < lfc.length; i++) {
visit(lfc[i]);
}
} else { // create an empty FeatureTypeConstraint, since it is required
start("FeatureTypeConstraint");
end("FeatureTypeConstraint");
}
end("LayerFeatureConstraints");
Style[] styles = layer.getUserStyles();
for (int i = 0; i < styles.length; i++) {
visit(styles[i]);
}
end("UserLayer");
}
private void visitInlineFeatureType(DataStore dataStore, SimpleFeatureType featureType) {
start("InlineFeature");
try {
final String ftName = featureType.getTypeName();
final SimpleFeatureSource fs = dataStore.getFeatureSource(ftName);
final SimpleFeatureCollection fc = fs.getFeatures();
final FeatureTransformer ftrax = new FeatureTransformer();
ftrax.setCollectionNamespace(null);
ftrax.setCollectionPrefix(null);
ftrax.setGmlPrefixing(true);
ftrax.setIndentation(2);
final CoordinateReferenceSystem crs = featureType.getGeometryDescriptor()
.getCoordinateReferenceSystem();
String srsName = null;
if (crs == null) {
LOGGER.warning("Null CRS in feature type named [" + ftName + "]. Ignore CRS");
} else {
srsName = CRS.toSRS(crs, true); // single implementation of toSRS
if (srsName == null) {
// fallback on origional code
// assume the first named identifier of this CRS is its
// fully
// qualified code; e.g. authoriy and SRID
Set<ReferenceIdentifier> ids = crs.getIdentifiers();
if (ids == null || ids.isEmpty()) {
LOGGER.warning("Null or empty set of named identifiers " + "in CRS ["
+ crs + "] of feature type named [" + ftName + "]. Ignore CRS");
} else {
for (ReferenceIdentifier id : ids) {
if (id != null) {
srsName = String.valueOf(id);
break;
}
}
}
}
if (srsName != null) {
// Some Server implementations using older versions of this
// library barf on a fully qualified CRS name with messages
// like : "couldnt decode SRS - EPSG:EPSG:4326. currently
// only supporting EPSG #"; looks like they only needs the
// SRID. adjust
final int ndx = srsName.indexOf(':');
if (ndx > 0) {
LOGGER.info("Reducing CRS name [" + srsName + "] to its SRID");
srsName = srsName.substring(ndx + 1).trim();
}
}
}
if (srsName != null) {
ftrax.setSrsName(srsName);
}
final String defaultNS = this.getDefaultNamespace();
ftrax.getFeatureTypeNamespaces().declareDefaultNamespace("", defaultNS);
final String ns = featureType.getName().getNamespaceURI();
if (ns == null) {
LOGGER.info("Null namespace URI in feature type named [" + ftName
+ "]. Ignore namespace in features");
} else {
// find the URI's prefix mapping in this namespace support
// delegate and use it; otherwise ignore it
final String prefix = this.nsSupport.getPrefix(ns);
if (prefix != null)
ftrax.getFeatureTypeNamespaces().declareNamespace(featureType, prefix, ns);
}
final Translator t = ftrax.createTranslator(this.contentHandler);
t.encode(fc);
} catch (IOException ignored) {
}
end("InlineFeature");
}
public void visit(RemoteOWS remoteOWS) {
start("RemoteOWS");
element("Service", remoteOWS.getService());
element("OnlineResource", remoteOWS.getOnlineResource());
end("RemoteOWS");
}
public void visit(FeatureTypeConstraint ftc) {
start("FeatureTypeConstraint");
if (ftc != null) {
element("FeatureTypeName", ftc.getFeatureTypeName());
visit(ftc.getFilter());
Extent[] extent = ftc.getExtents();
for (int i = 0; i < extent.length; i++) {
visit(extent[i]);
}
}
end("FeatureTypeConstraint");
}
public void visit(Extent extent) {
start("Extent");
element("Name", extent.getName());
element("Value", extent.getValue());
end("Extent");
}
public void visit(Filter filter) {
try {
contentHandler.startElement("", "", "ogc:Filter", NULL_ATTS);
filterTranslator.encode(filter);
contentHandler.endElement("", "", "ogc:Filter");
} catch (SAXException se) {
throw new RuntimeException(se);
}
}
public void visit(Style style) {
if (style instanceof NamedStyle) {
start("NamedStyle");
element("Name", style.getName());
end("NamedStyle");
} else {
start("UserStyle");
element("Name", style.getName());
if (style.getDescription() != null && style.getDescription().getTitle() != null)
element("Title", style.getDescription().getTitle());
if (style.isDefault()) {
element("IsDefault", "1");
}
if (style.getDescription() != null && style.getDescription().getAbstract() != null)
element("Abstract", style.getDescription().getAbstract());
FeatureTypeStyle[] fts = style.getFeatureTypeStyles();
for (int i = 0; i < fts.length; i++) {
visit(fts[i]);
}
end("UserStyle");
}
}
public void visit(FeatureTypeStyle fts) {
start("FeatureTypeStyle");
if ((fts.getName() != null) && (fts.getName().length() > 0)) {
element("Name", fts.getName());
}
if (fts.getDescription() != null && fts.getDescription().getTitle() != null)
element("Title", fts.getDescription().getTitle());
if (fts.getDescription() != null && fts.getDescription().getAbstract() != null)
element("Abstract", fts.getDescription().getAbstract());
if ((fts.featureTypeNames() != null) && (fts.featureTypeNames().size() > 0)) {
element("FeatureTypeName", fts.featureTypeNames().iterator().next().toString());
}
if (fts.getTransformation() != null) {
element("Transformation", fts.getTransformation());
}
String[] sti = fts.getSemanticTypeIdentifiers();
if (sti.length == 1 && sti[0].equals(SemanticType.ANY.toString())) {
// skip, it's the default
} else {
for (int i = 0; i < sti.length; i++) {
element("SemanticTypeIdentifier", sti[i]);
}
}
Rule[] rules = fts.getRules();
for (int i = 0; i < rules.length; i++) {
rules[i].accept(this);
}
encodeVendorOptions(fts.getOptions());
end("FeatureTypeStyle");
}
public void visit(Displacement dis) {
if (dis == null) {
return;
}
// We don't want to get huge SLDs with default values. So if displacement = 0 and 0 we
// drop it.
Expression dx = dis.getDisplacementX();
Expression dy = dis.getDisplacementY();
if (isNull(dx) && isNull(dy)) {
return;
}
if (isDefault(dx, 0) && isDefault(dy, 0)) {
return;
}
start("Displacement");
element("DisplacementX", dis.getDisplacementX());
element("DisplacementY", dis.getDisplacementY());
end("Displacement");
}
void encodeGeometryProperty(String name) {
if ((name == null) || (name.trim().length() == 0)) {
return;
}
// create a property name out the name and encode it
FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2(null);
Expression expression = ff.property(name);
start("Geometry");
filterTranslator.encode(expression);
end("Geometry");
}
void encodeGeometryExpression(Expression geom) {
if ((geom == null)) {
return;
}
start("Geometry");
filterTranslator.encode(geom);
end("Geometry");
}
void encodeCssParam(String name, Expression expression) {
encodeCssParam(name, expression, null);
}
void encodeCssParam(String name, Expression expression, Object defaultValue) {
if (expression == null) {
return; // protect ourselves from things like a null Stroke Color
}
AttributesImpl atts = new AttributesImpl();
atts.addAttribute("", "name", "name", "", name);
encodeValue("CssParameter", atts, expression, defaultValue);
}
void encodeValue(String elementName, Attributes atts, Expression expression,
Object defaultValue) {
if (expression == null) {
return; // protect ourselves from things like a null Stroke Color
}
if (!isExportDefaultValues()) {
// skip encoding if we are using the default value
if (expression instanceof Literal && defaultValue != null) {
Object value = expression.evaluate(null, defaultValue.getClass());
if (value != null && value.equals(defaultValue)) {
return;
}
}
}
if (atts == null) {
atts = NULL_ATTS;
}
if (expression instanceof Literal) {
// use more compact encoding
element(elementName, expression.evaluate(null, String.class), atts);
} else {
start(elementName, atts);
filterTranslator.encode(expression);
end(elementName);
}
}
void encodeVendorOptions(Map options) {
if (options != null) {
Iterator it = options.keySet().iterator();
while (it.hasNext()) {
String key = (String) it.next();
String value = (String) options.get(key);
encodeVendorOption(key, value);
}
}
}
void encodeVendorOption(String key, String value) {
AttributesImpl atts = new AttributesImpl();
atts.addAttribute("", "name", "name", "", key);
start("VendorOption", atts);
chars(value);
end("VendorOption");
}
public void encode(Style[] styles) {
try {
contentHandler.startDocument();
start("StyledLayerDescriptor", NULL_ATTS);
start("NamedLayer", NULL_ATTS); // this is correct?
for (int i = 0, ii = styles.length; i < ii; i++) {
styles[i].accept(this);
}
end("NamedLayer");
end("StyledLayerDescriptor");
contentHandler.endDocument();
} catch (SAXException se) {
throw new RuntimeException(se);
}
}
public void encode(StyledLayerDescriptor sld) {
try {
contentHandler.startDocument();
sld.accept(this);
contentHandler.endDocument();
} catch (SAXException se) {
throw new RuntimeException(se);
}
}
public void encode(Object o) throws IllegalArgumentException {
if (o instanceof StyledLayerDescriptor) {
encode((StyledLayerDescriptor) o);
} else if (o instanceof Style[]) {
encode((Style[]) o);
} else {
Class c = o.getClass();
try {
java.lang.reflect.Method m = c.getMethod("accept",
new Class[] { StyleVisitor.class });
m.invoke(o, new Object[] { this });
} catch (NoSuchMethodException nsme) {
throw new IllegalArgumentException("Cannot encode " + o);
} catch (Exception e) {
throw new RuntimeException("Internal transformation exception", e);
}
}
}
public void visit(ContrastEnhancement ce) {
if (ce == null )
return;
start("ContrastEnhancement");
// histogram
ContrastMethod method = ce.getMethod();
if (method != null && method != ContrastMethod.NONE) {
String val = method.name();
val = val.substring(0, 1).toUpperCase() + val.substring(1).toLowerCase();
start(val);
Map<String, Expression> opts = ce.getOptions();
for (Entry<String, Expression> entry : opts.entrySet()) {
AttributesImpl atts = new AttributesImpl();
atts.addAttribute("", "name", "name", "", entry.getKey());
element("VendorOption",
entry.getValue().evaluate(null).toString(), atts);
}
end(val);
}
// gamma
Expression exp = ce.getGammaValue();
if (exp != null) {
// gamma is a double so the actual value needs to be printed here
element("GammaValue", ((Literal) exp).getValue().toString());
// element("GammaValue", exp);
}
end("ContrastEnhancement");
}
public void visit(ImageOutline outline) {
if (outline == null)
return;
start("ImageOutline");
outline.getSymbolizer().accept(this);
end("ImageOutline");
}
public void visit(ChannelSelection cs) {
if (cs == null)
return;
start("ChannelSelection");
final SelectedChannelType[] sct = cs.getSelectedChannels();
for (int i = 0; i < sct.length && sct != null; i++)
visit(sct[i]);
end("ChannelSelection");
}
public void visit(OverlapBehavior ob) {
start("OverlapBehavior");
final String pn = (String) ob.getValue();
start(pn);
end(pn);
end("OverlapBehavior");
}
public void visit(SelectedChannelType sct) {
element("SourceChannelName", sct.getChannelName());
final ContrastEnhancement ce = sct.getContrastEnhancement();
if (ce != null)
ce.accept(this);
}
public void visit(ShadedRelief sr) {
start("ShadedRelief");
// brightnessonly
if (sr.isBrightnessOnly())
element("BrightnessOnly", "true");
else
element("BrightnessOnly", "false");
// relief factor
if (sr.getReliefFactor() != null) {
// element("ReliefFactor",sr.getReliefFactor());
// this expression needs to be converted to a single string and then written
// 1.0.0 specs don't allow it to be written as an expression
Literal l = (Literal) sr.getReliefFactor();
element("ReliefFactor", l.getValue().toString());
}
end("ShadedRelief");
}
}
}