/* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved.
* This code is licensed under the GPL 2.0 license, availible at the root
* application directory.
*/
package org.geoserver.kml;
import java.io.IOException;
import java.util.List;
import java.util.logging.Level;
import org.geoserver.config.GeoServer;
import org.geoserver.wms.WMS;
import org.geoserver.wms.WMSMapContent;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.map.Layer;
import org.geotools.styling.FeatureTypeStyle;
import org.geotools.styling.Symbolizer;
import org.geotools.xml.transform.Translator;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
/**
* Transforms a feature collection to a kml document consisting of nested "Style" and "Placemark"
* elements for each feature in the collection. A new transfomer must be instantianted for each
* feature collection, the feature collection provided to the translator is supposed to be the one
* coming out of the MapLayer
* <p>
* Usage:
* </p>
*
* @author Justin Deoliveira, The Open Planning Project, jdeolive@openplans.org
*
*/
public class KMLVectorTransformer extends KMLMapTransformer {
private KMLLookAt lookAtOpts;
public KMLVectorTransformer(WMS wms, WMSMapContent mapContent, Layer mapLayer) {
this(wms, mapContent, mapLayer, null);
}
public KMLVectorTransformer(WMS wms, WMSMapContent mapContent, Layer mapLayer,
KMLLookAt lookAtOpts) {
super(wms, mapContent, mapLayer);
setNamespaceDeclarationEnabled(false);
this.lookAtOpts = lookAtOpts;
}
/**
* Sets the scale denominator.
*/
public void setScaleDenominator(double scaleDenominator) {
this.scaleDenominator = scaleDenominator;
}
public Translator createTranslator(ContentHandler handler) {
return new KMLTranslator(handler);
}
protected class KMLTranslator extends KMLMapTranslatorSupport {
/**
* Store the regionating strategy being applied
*/
private RegionatingStrategy myStrategy;
public KMLTranslator(ContentHandler contentHandler) {
super(contentHandler);
KMLGeometryTransformer geometryTransformer = new KMLGeometryTransformer();
// geometryTransformer.setUseDummyZ( true );
geometryTransformer.setOmitXMLDeclaration(true);
geometryTransformer.setNamespaceDeclarationEnabled(true);
GeoServer config = wms.getGeoServer();
geometryTransformer.setNumDecimals(config.getGlobal().getNumDecimals());
geometryTranslator = (KMLGeometryTransformer.KMLGeometryTranslator) geometryTransformer
.createTranslator(contentHandler, mapContent);
}
public void setRegionatingStrategy(RegionatingStrategy rs) {
myStrategy = rs;
}
public void encode(Object o) throws IllegalArgumentException {
SimpleFeatureCollection features = (SimpleFeatureCollection) o;
SimpleFeatureType featureType = features.getSchema();
if (isStandAlone()) {
start("kml");
}
// start the root document, name it the name of the layer
start("Document",
KMLUtils.attributes(new String[] { "xmlns:atom", "http://purl.org/atom/ns#" }));
element("name", mapLayer.getTitle());
if(lookAtOpts != null){
ReferencedEnvelope bounds = features.getBounds();
if(bounds != null){
KMLLookAtTransformer tx;
tx = new KMLLookAtTransformer(bounds, getIndentation(), getEncoding());
Translator translator = tx.createTranslator(contentHandler);
translator.encode(lookAtOpts);
}
}
String relLinks = (String) mapContent.getRequest().getFormatOptions().get("relLinks");
// Add prev/next links if requested
if (mapContent.getRequest().getMaxFeatures() != null && relLinks != null
&& relLinks.equalsIgnoreCase("true")) {
String linkbase = "";
try {
linkbase = getFeatureTypeURL();
linkbase += ".kml";
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
int maxFeatures = mapContent.getRequest().getMaxFeatures();
int startIndex = (mapContent.getRequest().getStartIndex() == null) ? 0 : mapContent
.getRequest().getStartIndex().intValue();
int prevStart = startIndex - maxFeatures;
int nextStart = startIndex + maxFeatures;
// Previous page, if any
if (prevStart >= 0) {
String prevLink = linkbase + "?startindex=" + prevStart + "&maxfeatures="
+ maxFeatures;
element("atom:link", null,
KMLUtils.attributes(new String[] { "rel", "prev", "href", prevLink }));
encodeSequentialNetworkLink(linkbase, prevStart, maxFeatures, "prev",
"Previous page");
}
// Next page, if any
if (features.size() >= maxFeatures) {
String nextLink = linkbase + "?startindex=" + nextStart + "&maxfeatures="
+ maxFeatures;
element("atom:link", null,
KMLUtils.attributes(new String[] { "rel", "next", "href", nextLink }));
encodeSequentialNetworkLink(linkbase, nextStart, maxFeatures, "next",
"Next page");
}
}
// get the styles for the layer
FeatureTypeStyle[] featureTypeStyles = KMLUtils.filterFeatureTypeStyles(
mapLayer.getStyle(), featureType);
// encode the schemas (kml 2.2)
encodeSchemas(features);
// encode the layers
encode(features, featureTypeStyles);
// encode the legend
// encodeLegendScreenOverlay();
end("Document");
if (isStandAlone()) {
end("kml");
}
}
/**
*
* Encodes a networklink for previous or next document in a sequence
*
* Note that in KML 2.2 atom:link is supported and may be better.
*
* @param linkbase
* the base fore creating URLs
* @param prevStart
* previous start value
* @param maxFeatures
* maximum number of features to return
* @param id
* attribute to use for this NetworkLink
* @param readableName
* goes into linkName
*/
private void encodeSequentialNetworkLink(String linkbase, int prevStart, int maxFeatures,
String id, String readableName) {
String link = linkbase + "?startindex=" + prevStart + "&maxfeatures=" + maxFeatures;
start("NetworkLink", KMLUtils.attributes(new String[] { "id", id }));
element("description", readableName);
start("Link");
element("href", link);
end("Link");
end("NetworkLink");
}
/**
* Encodes the <Schema> element in kml 2.2
*
* @param featureTypeStyles
*/
protected void encodeSchemas(SimpleFeatureCollection featureTypeStyles) {
// the code is at the moment in KML3VectorTransformer
}
protected void encode(SimpleFeatureCollection features, FeatureTypeStyle[] styles) {
// grab a reader and process
SimpleFeatureIterator reader = null;
try {
// grab a reader and process
reader = features.features();
// Write Styles
while (reader.hasNext()) {
SimpleFeature feature = (SimpleFeature) reader.next();
try {
List<Symbolizer> symbolizers = filterSymbolizers(feature, styles);
if (symbolizers.size() > 0) {
encodePlacemark(feature, symbolizers, lookAtOpts);
}
} catch (RuntimeException t) {
// if the stream has been closed by the client don't keep on going forward,
// this is not a feature local issue
//
if (t.getCause() instanceof SAXException)
throw t;
else
LOGGER.log(Level.WARNING, "Failure tranforming feature to KML:"
+ feature.getID(), t);
}
}
} finally {
// make sure we always close
features.close(reader);
}
}
}
}