package org.vfny.geoserver.wms.responses.map.kml;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import org.geotools.feature.FeatureCollection;
import org.geotools.map.MapLayer;
import org.geotools.styling.FeatureTypeStyle;
import org.geotools.styling.OtherText;
import org.geotools.styling.Rule;
import org.geotools.styling.Symbolizer;
import org.geotools.styling.TextSymbolizer2;
import org.geotools.xml.impl.DatatypeConverterImpl;
import org.geotools.xml.impl.DatatypeConverterInterface;
import org.geotools.xml.transform.Translator;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.filter.expression.Expression;
import org.vfny.geoserver.global.GeoServer;
import org.vfny.geoserver.wms.WMSMapContext;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
public class OWS5VectorTransformer extends KMLVectorTransformer {
private static DatatypeConverterInterface dataTypeConverter = DatatypeConverterImpl.getInstance();
private boolean extendedDataModule;
private boolean styleModule;
public OWS5VectorTransformer(WMSMapContext mapContext, MapLayer mapLayer,
boolean extendedDataModule, boolean styleModule) {
super(mapContext, mapLayer);
this.extendedDataModule = extendedDataModule;
this.styleModule = styleModule;
}
public Translator createTranslator(ContentHandler handler) {
return new KML3Translator(handler);
}
protected class KML3Translator extends KMLTranslator {
private boolean kml22DataStyle = false;
protected String schemaId;
public KML3Translator(ContentHandler contentHandler) {
super(contentHandler);
KMLGeometryTransformer geometryTransformer = new KMLGeometryTransformer();
//geometryTransformer.setUseDummyZ( true );
geometryTransformer.setOmitXMLDeclaration(true);
geometryTransformer.setNamespaceDeclarationEnabled(true);
GeoServer config = mapContext.getRequest().getGeoServer();
geometryTransformer.setNumDecimals(config.getNumDecimals());
geometryTranslator = geometryTransformer.createTranslator(contentHandler);
// GML3 outputting transformer
// OWS5GeometryTransformer geometryTransformer = new OWS5GeometryTransformer();
// geometryTranslator = (KML3GeometryTranslator) geometryTransformer.createTranslator(contentHandler);
// we need to make sure the data type converter is registered properly
// We need to create a unique ID for this schema. Given the
// restrictions of the XML ID
// type and the fact the same feature type may appear multiple times
// in the same
// MapContext, we need to create an artificial ID that's guaranteed
// to be unique across
// the encoded KML document. We'll use the layer position as the ID.
final MapLayer[] layers = OWS5VectorTransformer.this.mapContext.getLayers();
for (int i = 0; i < layers.length; i++) {
if (layers[i] == OWS5VectorTransformer.this.mapLayer) {
schemaId = "Schema" + (i + 1);
}
}
if (schemaId == null)
throw new IllegalStateException("Wrapping KMLVectorTransformer holds "
+ "a layer reference that's not among the MapLayer ones");
}
public void encodeSchemas(FeatureCollection collection) {
// if the extended data module is not active, don't encode schema and extended data
if(!extendedDataModule)
return;
// TODO: consider turning this into a Freemarker template
final SimpleFeatureType schema = (SimpleFeatureType) collection.getSchema();
final String[] atts = new String[] { "name", schema.getTypeName(), "id", schemaId };
start("Schema", KMLUtils.attributes(atts));
// output all non geometric attribute types
// TODO: or shall we encode only the types that are well known in SchemaData
// and jump over the rest? Besides, how does one limit the number of attributes
// displayed? a PROPERTY=x,y,z like in GetFeature would be beneficial
// to GetFeatureInfo as well
for (int i = 0; i < schema.getAttributeCount(); i++) {
AttributeDescriptor at = schema.getDescriptor(i);
if (at instanceof GeometryDescriptor)
continue;
final String[] atAttributes = new String[] { "type", getType(at), "name",
at.getLocalName() };
start("SimpleField", KMLUtils.attributes(atAttributes));
element("displayName", at.getLocalName());
end("SimpleField");
}
end("Schema");
}
protected String getType(AttributeDescriptor at) {
// see
// http://code.google.com/apis/kml/documentation/kml_tags_beta1.html#simplefield
// Eventually see if we need to support uint/ushort as well (do we
// have any standard filter for positive numbers?) and clarify
// what's the range of int and short
if (Short.class.equals(at.getType().getBinding()))
return "short";
else if (Integer.class.equals(at.getType().getBinding()))
return "int";
else if (Float.class.equals(at.getType().getBinding()))
return "float";
else if (Double.class.equals(at.getType().getBinding()))
return "double";
else if (Boolean.class.equals(at.getType().getBinding()))
return "bool";
else
return "string";
}
protected void encodeExtendedData(SimpleFeature feature) {
// if the extended data module is not active, don't encode schema and extended data
if(!extendedDataModule)
return;
if(kml22DataStyle)
encodeKML22ExtendedData(feature);
else
encodeKMLOWS5ExtendedData(feature);
}
private void encodeKML22ExtendedData(SimpleFeature feature) {
// TODO: consider turning this into a Freemarker template
start("ExtendedData");
start("SchemaData", KMLUtils.attributes(new String[] { "schemaUrl", "#" + schemaId }));
final int count = feature.getAttributeCount();
final SimpleFeatureType schema = feature.getFeatureType();
for (int i = 0; i < count; i++) {
final AttributeDescriptor at = schema.getDescriptor(i);
if(at instanceof GeometryDescriptor)
continue;
final Attributes atts = KMLUtils.attributes(new String[] { "name",
at.getLocalName() });
element("SimpleData", encodeValue(feature.getAttribute(i)), atts);
}
end("SchemaData");
end("ExtendedData");
}
private void encodeKMLOWS5ExtendedData(SimpleFeature feature) {
// TODO: consider turning this into a Freemarker template
start("ExtendedData", KMLUtils.attributes(new String[] { "schemaUrl", "#" + schemaId }));
final int count = feature.getAttributeCount();
final SimpleFeatureType schema = feature.getFeatureType();
for (int i = 0; i < count; i++) {
final AttributeDescriptor at = schema.getDescriptor(i);
if(at instanceof GeometryDescriptor)
continue;
final Attributes atts = KMLUtils.attributes(new String[] { "name",
at.getLocalName() });
element("Data", encodeValue(feature.getAttribute(i)), atts);
}
end("ExtendedData");
}
protected String encodeValue(Object o) {
if (o == null) {
return "";
} else if (o instanceof Number) {
if (o instanceof Byte)
return dataTypeConverter.printByte(((Byte) o).byteValue());
else if (o instanceof Short)
return dataTypeConverter.printShort(((Short) o).shortValue());
else if (o instanceof Integer)
return dataTypeConverter.printInt((((Integer) o).intValue()));
else if (o instanceof Long)
return dataTypeConverter.printLong(((Long) o).intValue());
else if (o instanceof Float)
return dataTypeConverter.printInt((((Float) o).intValue()));
else if (o instanceof Double)
return dataTypeConverter.printInt(((Double) o).intValue());
else if (o instanceof BigDecimal)
return dataTypeConverter.printDecimal((BigDecimal) o);
else
return dataTypeConverter.printString(o.toString());
} else if (o instanceof Boolean) {
return dataTypeConverter.printBoolean((((Boolean) o).booleanValue()));
} else if (o instanceof Date) {
final Date d = (Date) o;
final Calendar cal = Calendar.getInstance();
cal.setTime(d);
if (d instanceof java.sql.Date)
return dataTypeConverter.printDate(cal);
else if (d instanceof java.sql.Time)
return dataTypeConverter.printTime(cal);
else
return dataTypeConverter.printDateTime(cal);
} else {
return dataTypeConverter.printString(o.toString());
}
}
// /**
// * Encodes a KML Placemark geometry from a geometry + centroid.
// */
// protected void encodePlacemarkGeometry(Geometry geometry, Coordinate centroid, FeatureTypeStyle[] styles) {
// // if point, just encode a single point, otherwise encode the geometry + centroid
// if ( geometry instanceof Point ||
// (geometry instanceof MultiPoint) && ((MultiPoint)geometry).getNumPoints() == 1 ) {
// encodeGeometry( geometry, styles);
// }
// else {
// start("MultiGeometry");
//
// //the centroid
// start("Point");
//
// if (!Double.isNaN(centroid.z)) {
// element("pos", centroid.x + " " + centroid.y + " " + centroid.z);
// } else {
// element("pos", centroid.x + " " + centroid.y);
// }
//
// end("Point");
//
// //the actual geometry
// encodeGeometry(geometry, styles);
//
// end("MultiGeometry");
// }
//
// }
@Override
protected void encodePlacemarkDescription(SimpleFeature feature, FeatureTypeStyle[] styles)
throws IOException {
// look for a kml text style with the description attribute
List<TextSymbolizer2> textSymbolizers = getTextSymbolizers2(feature, styles);
Expression description = null;
for (TextSymbolizer2 ts : textSymbolizers) {
if(ts.getFeatureDescription() != null)
description = ts.getFeatureDescription();
}
if(description == null) {
// use the freemarker template as a fallback
super.encodePlacemarkDescription(feature, styles);
return;
}
start("description");
cdata(description.evaluate(feature, String.class));
end("description");
}
@Override
protected void encodePlacemarkSnippet(SimpleFeature feature, FeatureTypeStyle[] styles) {
// look for a kml text style with the abstract attribute
List<TextSymbolizer2> textSymbolizers = getTextSymbolizers2(feature, styles);
Expression abxtract = null;
for (TextSymbolizer2 ts : textSymbolizers) {
if(ts.getSnippet() != null)
abxtract = ts.getSnippet();
}
if(abxtract == null) {
// no snippet then...
return;
}
start("Snippet");
cdata(abxtract.evaluate(feature, String.class));
end("Snippet");
}
@Override
protected void encodePlacemarkTime(SimpleFeature feature, FeatureTypeStyle[] styles)
throws IOException {
// look for a kml text style with the time/startTime/endTime otherText attributes
List<TextSymbolizer2> textSymbolizers = getTextSymbolizers2(feature, styles);
Expression abxtract = null;
Date fromDate = null;
Date toDate = null;
Date timestamp = null;
for (TextSymbolizer2 ts : textSymbolizers) {
final OtherText ot = ts.getOtherText();
if(ot != null && ot.getTarget() != null && ot.getText() != null) {
if(ot.getTarget().toLowerCase().equals("kml:fromdate"))
fromDate = ot.getText().evaluate(feature, Date.class);
else if(ot.getTarget().toLowerCase().equals("kml:todate"))
toDate = ot.getText().evaluate(feature, Date.class);
else if(ot.getTarget().toLowerCase().equals("kml:timestamp"))
timestamp = ot.getText().evaluate(feature, Date.class);
}
}
try {
if(fromDate != null || toDate != null)
encodeKmlTimeSpan(fromDate, toDate);
else if(timestamp != null)
encodeKmlTimeStamp(timestamp);
else
super.encodePlacemarkTime(feature, styles);
} catch(Exception e) {
throw (IOException) new IOException().initCause(e);
}
}
/**
* Extracts all of the TextSymbolizer2 from the active rules, in the order
* they are declared.
* @param feature
* @param styles
* @return
*/
private List<TextSymbolizer2> getTextSymbolizers2(SimpleFeature feature,
FeatureTypeStyle[] styles) {
List<TextSymbolizer2> textSymbolizers = new ArrayList<TextSymbolizer2>();
for (int i = 0; i < styles.length; i++) {
Rule[] rules = filterRules(styles[i], feature );
for ( int j = 0; j < rules.length; j++ ) {
Symbolizer[] syms = rules[j].getSymbolizers();
for ( int k = 0; k < syms.length; k++) {
if ( syms[k] instanceof TextSymbolizer2) {
textSymbolizers.add((TextSymbolizer2) syms[k]);
}
}
}
}
return textSymbolizers;
}
/**
* Encodes the provided set of rules as KML styles. Override that handles
* the style definition and encodes it only if the style module is enabled
*/
protected boolean encodeStyle(SimpleFeature feature, FeatureTypeStyle[] styles) {
//encode hte Line/Poly styles
List symbolizerList = new ArrayList();
for ( int j = 0; j < styles.length ; j++ ) {
Rule[] rules = filterRules(styles[j], feature);
for (int i = 0; i < rules.length; i++) {
symbolizerList.addAll(Arrays.asList(rules[i].getSymbolizers()));
}
}
if ( !symbolizerList.isEmpty() ) {
if(styleModule) {
//start the style
start("Style",
KMLUtils.attributes(new String[] { "id", "GeoServerStyle" + feature.getID() }));
//encode the icon
encodeIconStyle(feature, styles);
Symbolizer[] symbolizers = (Symbolizer[]) symbolizerList.toArray(new Symbolizer[symbolizerList.size()]);
encodeStyle(feature, symbolizers);
//end the style
end("Style");
}
// we return true anyways because otherwise the geometry won't be encoded
return true;
}
else {
//dont encode
return false;
}
}
}
}