/* 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.vfny.geoserver.wms.responses.map.kml;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Level;
import org.apache.batik.dom.util.HashTable;
import org.geoserver.catalog.Catalog;
import org.geoserver.config.GeoServer;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.map.MapLayer;
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.vfny.geoserver.wms.WMSMapContext;
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 {
public KMLVectorTransformer(WMSMapContext mapContext, MapLayer mapLayer) {
super(mapContext, mapLayer);
setNamespaceDeclarationEnabled(false);
}
/**
* 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 = mapContext.getRequest().getWMS().getGeoServer();
geometryTransformer.setNumDecimals(config.getGlobal().getNumDecimals());
geometryTranslator =
(KMLGeometryTransformer.KMLGeometryTranslator)
geometryTransformer.createTranslator(contentHandler, mapContext);
}
public void setRegionatingStrategy(RegionatingStrategy rs){
myStrategy = rs;
}
public void encode(Object o) throws IllegalArgumentException {
FeatureCollection<SimpleFeatureType, SimpleFeature> features = (FeatureCollection) o;
SimpleFeatureType featureType = features.getSchema();
Catalog catalog = mapContext.getRequest().getWMS().getGeoServer().getCatalog();
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());
String relLinks = (String)mapContext.getRequest().getFormatOptions().get("relLinks");
// Add prev/next links if requested
if (mapContext.getRequest().getMaxFeatures() != null &&
relLinks != null && relLinks.equalsIgnoreCase("true") ){
String linkbase = "";
try {
linkbase = getFeatureTypeURL();
linkbase += ".kml";
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
int maxFeatures = mapContext.getRequest().getMaxFeatures();
int startIndex =
(mapContext.getRequest().getStartIndex() == null)
? 0
: mapContext.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(FeatureCollection<SimpleFeatureType, SimpleFeature> featureTypeStyles) {
// the code is at the moment in KML3VectorTransformer
}
protected void encode(FeatureCollection<SimpleFeatureType, SimpleFeature> features,
FeatureTypeStyle[] styles) {
//grab a reader and process
FeatureIterator<SimpleFeature> 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);
}
} 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);
}
}
}
}