/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.wps.ppio;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import org.geoserver.config.GeoServer;
import org.geoserver.config.ServiceInfo;
import org.geoserver.kml.KMLEncoder;
import org.geoserver.kml.KMLMapOutputFormat;
import org.geoserver.kml.KmlEncodingContext;
import org.geoserver.kml.decorator.KmlDecoratorFactory.KmlDecorator;
import org.geoserver.kml.sequence.SequenceList;
import org.geoserver.kml.sequence.WFSFeatureSequenceFactory;
import org.geoserver.platform.ServiceException;
import org.geoserver.wfs.WFSInfo;
import org.geotools.data.collection.ListFeatureCollection;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.store.ReprojectingFeatureCollection;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.kml.KML;
import org.geotools.kml.KMLConfiguration;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.util.logging.Logging;
import org.geotools.xml.Configuration;
import org.geotools.xml.StreamingParser;
import org.opengis.feature.Property;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.Name;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import com.vividsolutions.jts.geom.Geometry;
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;
/**
* PPIO for KML 2.2.
*
*/
public class KMLPPIO extends CDataPPIO {
private static final Logger LOGGER = Logging.getLogger(KMLPPIO.class);
GeoServer gs;
Configuration xml;
SimpleFeatureType type;
public KMLPPIO(GeoServer gs) {
super(FeatureCollection.class, FeatureCollection.class, KMLMapOutputFormat.MIME_TYPE);
this.gs = gs;
this.xml = new KMLConfiguration();
SimpleFeatureTypeBuilder b = new SimpleFeatureTypeBuilder();
b.setName("puregeometries");
b.setCRS(DefaultGeographicCRS.WGS84);
b.add("location", Geometry.class);
this.type = b.buildFeatureType();
}
private final static HashMap<Name, Class> getSignature(SimpleFeature f) {
HashMap ftype = new HashMap();
Collection properties = f.getProperties();
for (Object op : properties) {
Property p = (Property) op;
Class c = p.getType().getBinding();
if ((c.isAssignableFrom(String.class)) || (c.isAssignableFrom(Boolean.class))
|| (c.isAssignableFrom(Integer.class)) || (c.isAssignableFrom(Float.class))
|| (c.isAssignableFrom(Double.class)) || (c.isAssignableFrom(Geometry.class))) {
ftype.put(p.getName(), c);
}
}
return ftype;
}
private SimpleFeatureType getType(HashMap<Name, Class> ftype) {
SimpleFeatureTypeBuilder b = new SimpleFeatureTypeBuilder();
b.setName("puregeometries");
b.setCRS(DefaultGeographicCRS.WGS84);
for (Map.Entry entry : ftype.entrySet()) {
b.add(((Name) entry.getKey()).toString(), (Class) entry.getValue());
}
return b.buildFeatureType();
}
public Object decode(InputStream input) throws Exception {
StreamingParser parser = new StreamingParser(new KMLConfiguration(), input, KML.Placemark);
SimpleFeature f = null;
ListFeatureCollection features = null;
HashMap oldftype = null;
SimpleFeatureType type = null;
SimpleFeatureBuilder featureBuilder = null;
while ((f = (SimpleFeature) parser.parse()) != null) {
HashMap ftype = getSignature(f);
if (oldftype == null) {
oldftype = ftype;
type = getType(ftype);
featureBuilder = new SimpleFeatureBuilder(type);
features = new ListFeatureCollection(type);
} else {
if (!oldftype.equals(ftype)) {
break;
}
}
for (Object oentry : ftype.entrySet()) {
Map.Entry entry = (Map.Entry) oentry;
featureBuilder.add(f.getAttribute((Name) entry.getKey()));
}
SimpleFeature fnew = featureBuilder.buildFeature(f.getID());
features.add(fnew);
}
return features;
}
public Object decode(String input) throws Exception {
return decode(new ByteArrayInputStream(input.getBytes()));
}
@Override
public void encode(Object obj, OutputStream os) throws Exception {
LOGGER.info("KMLPPIO::encode: obj is of class " + obj.getClass().getName()
+ ", handler is of class " + os.getClass().getName());
// prepare the encoding context
KMLEncoder encoder = new KMLEncoder();
SimpleFeatureCollection fcObj = (SimpleFeatureCollection) obj;
CoordinateReferenceSystem crs = fcObj.getSchema().getCoordinateReferenceSystem();
// gpx is defined only in wgs84
if (crs != null && !CRS.equalsIgnoreMetadata(crs, DefaultGeographicCRS.WGS84)) {
fcObj = new ReprojectingFeatureCollection(fcObj, DefaultGeographicCRS.WGS84);
}
List<SimpleFeatureCollection> collections = new ArrayList<SimpleFeatureCollection>();
collections.add(fcObj);
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, os, context);
os.flush();
}
/**
* 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;
}
}
public String getFileExtension() {
return "kml";
}
}