/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* (c) 2014 MangoSystem
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.wps.spatialstatistics.ppio;
import java.io.InputStream;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import net.opengis.wfs.FeatureCollectionType;
import net.opengis.wfs.WfsFactory;
import org.geoserver.feature.RetypingFeatureCollection;
import org.geoserver.wps.ppio.XMLPPIO;
import org.geotools.data.crs.ForceCoordinateSystemFeatureResults;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.store.ReprojectingFeatureCollection;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.referencing.CRS;
import org.geotools.xml.Encoder;
import org.geotools.xml.Parser;
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.referencing.crs.CoordinateReferenceSystem;
import org.xml.sax.ContentHandler;
import com.vividsolutions.jts.geom.Geometry;
/**
* A PPIO to generate good looking xml for the StatisticsFeatures process results
*
* @author Minpa Lee, MangoSystem
*
* @source $URL$
*/
public class FeatureCollectionGML311PPIO extends XMLPPIO {
private org.geotools.xml.Configuration configuration;
protected FeatureCollectionGML311PPIO() {
super(FeatureCollectionType.class, FeatureCollection.class, "text/xml; subtype=gml/3.1.1",
org.geoserver.wfs.xml.v1_1_0.WFS.FEATURECOLLECTION);
this.configuration = new org.geotools.wfs.v1_1.WFSConfiguration();
}
@Override
public Object decode(InputStream input) throws Exception {
Parser p = new Parser(configuration);
Object result = p.parse(input);
if (result instanceof SimpleFeatureCollection) {
return result;
}
FeatureCollectionType fct = (FeatureCollectionType) result;
return decode(fct);
}
@Override
public Object decode(Object input) throws Exception {
// xml parsing will most likely return it as parsed already, but if CDATA is used or if
// it's a KVP parse it will be a string instead
if (input instanceof String) {
Parser p = new Parser(configuration);
input = p.parse(new StringReader((String) input));
} else if (input instanceof SimpleFeatureCollection) {
return input;
}
// cast and handle the axis flipping
FeatureCollectionType fct = (FeatureCollectionType) input;
SimpleFeatureCollection fc = (SimpleFeatureCollection) fct.getFeature().get(0);
// Axis flipping issue, we should determine if the collection needs flipping
if (fc.getSchema().getGeometryDescriptor() != null) {
CoordinateReferenceSystem crs = getCollectionCRS(fc);
if (crs != null) {
// do we need to force the crs onto the collection?
CoordinateReferenceSystem nativeCrs = fc.getSchema().getCoordinateReferenceSystem();
if (nativeCrs == null) {
// we need crs forcing
fc = new ForceCoordinateSystemFeatureResults(fc, crs, false);
}
// we assume the crs has a valid EPSG code
Integer code = CRS.lookupEpsgCode(crs, false);
if (code != null) {
CoordinateReferenceSystem lonLatCrs = CRS.decode("EPSG:" + code, true);
if (!CRS.equalsIgnoreMetadata(crs, lonLatCrs)) {
// we need axis flipping
fc = new ReprojectingFeatureCollection(fc, lonLatCrs);
}
}
}
}
return eliminateFeatureBounds(fc);
}
/**
* Parsing GML we often end up with empty attributes that will break shapefiles and common
* processing algorithms because they introduce bounding boxes (boundedBy) or hijack the default
* geometry property (location). We sanitize the collection in this method by removing them. It
* is not the best approach, but works in most cases, whilst not doing it would break the code
* in most cases. Would be better to find a more general approach...
*
* @param fc
* @return
*/
private SimpleFeatureCollection eliminateFeatureBounds(SimpleFeatureCollection fc) {
// metaDataProperty, description, boundedBy
final SimpleFeatureType original = fc.getSchema();
List<String> names = new ArrayList<String>();
boolean alternateGeometry = true;
for (AttributeDescriptor ad : original.getAttributeDescriptors()) {
final String name = ad.getLocalName();
// Modified 2012/05/29 MapPlus
if (!"boundedBy".equalsIgnoreCase(name) && !"metaDataProperty".equalsIgnoreCase(name)
&& !"description".equalsIgnoreCase(name)) {
names.add(name);
}
if (!"location".equalsIgnoreCase(name) && ad instanceof GeometryDescriptor) {
alternateGeometry = true;
}
}
// if there is another geometry we assume "location" is going to be empty,
// otherwise we're going to get always a null geometry
if (alternateGeometry) {
names.remove("location");
}
if (names.size() < original.getDescriptors().size()) {
// System.out.println("RetypingFeatureCollection !");
String[] namesArray = names.toArray(new String[names.size()]);
SimpleFeatureType target = SimpleFeatureTypeBuilder.retype(original, namesArray);
return new RetypingFeatureCollection(fc, target);
}
return fc;
}
/**
* Gets the collection CRS, either from metadata or by scanning the collection contents
*
* @param fc
* @return
* @throws Exception
*/
CoordinateReferenceSystem getCollectionCRS(SimpleFeatureCollection fc) throws Exception {
// this is unlikely to work for remote or embedded collections, but it's also easy to check
if (fc.getSchema().getCoordinateReferenceSystem() != null) {
return fc.getSchema().getCoordinateReferenceSystem();
}
// ok, let's scan the entire collection then...
CoordinateReferenceSystem crs = null;
SimpleFeatureIterator fi = null;
try {
fi = fc.features();
while (fi.hasNext()) {
SimpleFeature f = fi.next();
CoordinateReferenceSystem featureCrs = null;
GeometryDescriptor gd = f.getType().getGeometryDescriptor();
if (gd != null && gd.getCoordinateReferenceSystem() != null) {
featureCrs = gd.getCoordinateReferenceSystem();
}
if (f.getDefaultGeometry() != null) {
Geometry g = (Geometry) f.getDefaultGeometry();
if (g.getUserData() instanceof CoordinateReferenceSystem) {
featureCrs = (CoordinateReferenceSystem) g.getUserData();
}
}
// collect the feature crs, if it's new, use it, otherwise
// check the collection does not have mixed crs
if (featureCrs != null) {
if (crs == null) {
crs = featureCrs;
} else if (!CRS.equalsIgnoreMetadata(featureCrs, crs)) {
return null;
}
}
}
} finally {
fi.close();
}
return crs;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public void encode(Object object, ContentHandler handler) throws Exception {
FeatureCollection features = (FeatureCollection) object;
SimpleFeatureType featureType = (SimpleFeatureType) features.getSchema();
FeatureCollectionType fc = WfsFactory.eINSTANCE.createFeatureCollectionType();
fc.getFeature().add(features);
Encoder e = new Encoder(configuration);
e.getNamespaces().declarePrefix("feature", featureType.getName().getNamespaceURI());
e.encode(fc, getElement(), handler);
}
}