package org.geoserver.kml;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import org.geoserver.config.GeoServer;
import org.geoserver.config.ServiceInfo;
import org.geoserver.kml.decorator.KmlDecoratorFactory.KmlDecorator;
import org.geoserver.kml.sequence.SequenceList;
import org.geoserver.kml.sequence.WFSFeatureSequenceFactory;
import org.geoserver.platform.Operation;
import org.geoserver.platform.ServiceException;
import org.geoserver.wfs.WFSGetFeatureOutputFormat;
import org.geoserver.wfs.WFSInfo;
import org.geoserver.wfs.request.FeatureCollectionResponse;
import org.geoserver.wfs.request.GetFeatureRequest;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.store.ReprojectingFeatureCollection;
import org.geotools.feature.FeatureCollection;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import de.micromata.opengis.kml.v_2_2_0.Document;
import de.micromata.opengis.kml.v_2_2_0.Feature;
import de.micromata.opengis.kml.v_2_2_0.Folder;
import de.micromata.opengis.kml.v_2_2_0.Kml;
public class WFSKMLOutputFormat extends WFSGetFeatureOutputFormat {
private KMLEncoder encoder;
public WFSKMLOutputFormat(KMLEncoder encoder, GeoServer gs) {
super(gs, new HashSet(Arrays.asList(new String[] { "KML", KMLMapOutputFormat.MIME_TYPE,
// added this one to allow people copying and pasting the format name
KMLMapOutputFormat.MIME_TYPE.replace('+', ' ')})));
this.encoder = encoder;
}
@Override
public String getMimeType(Object value, Operation operation) throws ServiceException {
return KMLMapOutputFormat.MIME_TYPE;
}
@Override
protected void write(FeatureCollectionResponse featureCollection, OutputStream output,
Operation getFeature) throws IOException, ServiceException {
// prepare the encoding context
List<SimpleFeatureCollection> collections = getFeatureCollections(featureCollection);
KmlEncodingContext context = new WFSKmlEncodingContext(gs.getService(WFSInfo.class), collections);
// create the document
Kml kml = new Kml();
Document document = kml.createAndSetDocument();
// get the callbacks for the document and let them loose
List<KmlDecorator> docDecorators = context.getDecoratorsForClass(Document.class);
for (KmlDecorator decorator : docDecorators) {
document = (Document) decorator.decorate(document, context);
if (document == null) {
throw new ServiceException("Coding error in decorator " + decorator
+ ", document objects cannot be set to null");
}
}
// build the contents
for (SimpleFeatureCollection collection : collections) {
// create the folder
SimpleFeatureCollection fc = (SimpleFeatureCollection) collection;
Folder folder = document.createAndAddFolder();
folder.setName(fc.getSchema().getTypeName());
// have it be decorated
List<KmlDecorator> folderDecorators = context.getDecoratorsForClass(Folder.class);
for (KmlDecorator decorator : folderDecorators) {
folder = (Folder) decorator.decorate(folder, context);
if (folder == null) {
break;
}
}
if (folder == null) {
continue;
}
// create the streaming features
context.setCurrentFeatureCollection(fc);
List<Feature> features = new SequenceList<Feature>(new WFSFeatureSequenceFactory(
context));
context.addFeatures(folder, features);
}
// write out the output
encoder.encode(kml, output, context);
}
private List<SimpleFeatureCollection> getFeatureCollections(FeatureCollectionResponse featureCollection) {
List<FeatureCollection> inputs = featureCollection.getFeatures();
List<SimpleFeatureCollection> result = new ArrayList<SimpleFeatureCollection>();
for (FeatureCollection fc : inputs) {
if (!(fc instanceof SimpleFeatureCollection)) {
throw new ServiceException(
"The KML output format can only be applied to simple features");
}
// KML is defined only over wgs84 in lon/lat order, ignore what the WFS thinks the output
// crs should be
SimpleFeatureCollection sfc = (SimpleFeatureCollection) fc;
CoordinateReferenceSystem sourceCRS = sfc.getSchema().getCoordinateReferenceSystem();
if(sourceCRS != null && !CRS.equalsIgnoreMetadata(sourceCRS, DefaultGeographicCRS.WGS84)) {
sfc = new ReprojectingFeatureCollection(sfc, DefaultGeographicCRS.WGS84);
}
result.add(sfc);
}
return result;
}
@Override
public String getAttachmentFileName(Object value, Operation operation) {
GetFeatureRequest request = GetFeatureRequest.adapt(operation.getParameters()[0]);
String outputFileName = request.getQueries().get(0).getTypeNames().get(0).getLocalPart();
return outputFileName + ".kml";
}
@Override
public String getCapabilitiesElementName() {
return "KML";
}
/**
* A special KML encoding context for the WFS case
*
* @author Andrea Aime - GeoSolutions
*
*/
static class WFSKmlEncodingContext extends KmlEncodingContext {
private List<SimpleFeatureCollection> collections;
private SimpleFeatureType featureType;
public WFSKmlEncodingContext(ServiceInfo si, List<SimpleFeatureCollection> collections) {
this.service = getService();
this.collections = collections;
// set some defaults for wfs encoding
this.descriptionEnabled = false;
this.kmScore = 100;
this.extendedDataEnabled = true;
this.kmz = false;
}
public List<SimpleFeatureType> getFeatureTypes() {
List<SimpleFeatureType> results = new ArrayList<SimpleFeatureType>();
for(SimpleFeatureCollection fc : collections) {
results.add(fc.getSchema());
}
return results;
}
@Override
public void setCurrentFeatureCollection(SimpleFeatureCollection currentFeatureCollection) {
super.setCurrentFeatureCollection(currentFeatureCollection);
this.layerIndex++;
this.featureType = currentFeatureCollection.getSchema();
}
@Override
public SimpleFeatureType getCurrentFeatureType() {
return featureType;
}
}
}