/* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved.
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.wfs.response;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.zip.ZipOutputStream;
import javax.xml.namespace.QName;
import net.opengis.wfs.FeatureCollectionType;
import net.opengis.wfs.GetFeatureType;
import net.opengis.wfs.QueryType;
import org.geoserver.data.util.IOUtils;
import org.geoserver.ows.util.OwsUtils;
import org.geoserver.platform.Operation;
import org.geoserver.platform.ServiceException;
import org.geoserver.wfs.WFSException;
import org.geoserver.wfs.WFSGetFeatureOutputFormat;
import org.geotools.data.DataStore;
import org.geotools.data.FeatureStore;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.feature.FeatureCollection;
import org.geotools.gml.producer.FeatureTransformer;
import org.geotools.gml2.bindings.GML2EncodingUtils;
import org.opengis.feature.GeometryAttribute;
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.feature.type.GeometryType;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
public class Ogr2OgrOutputFormat extends WFSGetFeatureOutputFormat {
/**
* The types of geometries a shapefile can handle
*/
private static final Set<Class> SHAPEFILE_GEOM_TYPES = new HashSet<Class>() {
{
add(Point.class);
add(LineString.class);
add(LinearRing.class);
add(Polygon.class);
add(MultiPoint.class);
add(MultiLineString.class);
add(MultiPolygon.class);
}
};
/**
* The fs path to ogr2ogr. If null, we'll assume ogr2ogr is in the PATH and
* that we can execute it just by running ogr2ogr
*/
String ogrPath = null;
/**
* The full path to ogr2ogr
*/
String ogrExecutable = "ogr2ogr";
/**
* The GDAL_DATA folder
*/
String gdalData = null;
/**
* The output formats we can generate using ogr2ogr. Using a concurrent
* one so that it can be reconfigured while the output format is working
*/
static Map<String, OgrFormat> formats = new ConcurrentHashMap<String, OgrFormat>();
public Ogr2OgrOutputFormat() {
// initialize with the key set of formats, so that it will change as
// we register new formats
super(formats.keySet());
}
/**
* Returns the ogr2ogr executable full path
*
* @return
*/
public String getOgrExecutable() {
return ogrExecutable;
}
/**
* Sets the ogr2ogr executable full path. The default value is simply
* "ogr2ogr", which will work if ogr2ogr is in the path
*
* @param ogrExecutable
*/
public void setOgrExecutable(String ogrExecutable) {
this.ogrExecutable = ogrExecutable;
}
/**
* Returns the location of the gdal data folder (required to set the output srs)
* @return
*/
public String getGdalData() {
return gdalData;
}
/**
* Sets the location of the gdal data folder (requierd to set the output srs)
* @param gdalData
*/
public void setGdalData(String gdalData) {
this.gdalData = gdalData;
}
/**
* @see WFSGetFeatureOutputFormat#getMimeType(Object, Operation)
*/
public String getMimeType(Object value, Operation operation) throws ServiceException {
return "application/zip";
}
@Override
public String[][] getHeaders(Object value, Operation operation) throws ServiceException {
GetFeatureType request = (GetFeatureType) OwsUtils.parameter(operation.getParameters(),
GetFeatureType.class);
String outputFileName = ((QName) ((QueryType) request.getQuery().get(0)).getTypeName().get(0))
.getLocalPart();
return (String[][]) new String[][] {
{ "Content-Disposition", "attachment; filename=" + outputFileName + ".zip" }
};
}
/**
* Adds a ogr format among the supported ones
*
* @param parameters
*/
public void addFormat(OgrFormat parameters) {
formats.put(parameters.formatName, parameters);
}
/**
* Programmatically removes all formats
*
* @param parameters
*/
public void clearFormats() {
formats.clear();
}
/**
* Writes out the data to an OGR known format (GML/shapefile) to disk and
* then ogr2ogr each generated file into the destination format. Finally,
* zips up all the resulting files.
*/
@Override
protected void write(FeatureCollectionType featureCollection, OutputStream output,
Operation getFeature) throws IOException, ServiceException {
// figure out which output format we're going to generate
GetFeatureType gft = (GetFeatureType) getFeature.getParameters()[0];
OgrFormat format = formats.get(gft.getOutputFormat());
if (format == null)
throw new WFSException("Unknown output format " + gft.getOutputFormat());
// create the first temp directory, used for dumping gs generated
// content
File tempGS = org.geoserver.data.util.IOUtils.createTempDirectory("ogrtmpin");
File tempOGR = org.geoserver.data.util.IOUtils.createTempDirectory("ogrtmpout");
// build the ogr wrapper used to run the ogr2ogr commands
OGRWrapper wrapper = new OGRWrapper(ogrExecutable, gdalData);
// actually export each feature collection
try {
Iterator outputFeatureCollections = featureCollection.getFeature().iterator();
FeatureCollection<SimpleFeatureType, SimpleFeature> curCollection;
while (outputFeatureCollections.hasNext()) {
curCollection = (FeatureCollection<SimpleFeatureType, SimpleFeature>) outputFeatureCollections
.next();
// write out the gml
File intermediate = writeToDisk(tempGS, curCollection);
// convert with ogr2ogr
String epsgCode = null;
final SimpleFeatureType schema = curCollection.getSchema();
final CoordinateReferenceSystem crs = schema.getCoordinateReferenceSystem();
wrapper.convert(intermediate, tempOGR, schema.getTypeName(), format, crs);
// wipe out the input dir contents
IOUtils.emptyDirectory(tempGS);
}
// scan the output directory and zip it all
ZipOutputStream zipOut = new ZipOutputStream(output);
IOUtils.zipDirectory(tempOGR, zipOut, null);
// delete the input and output directories
IOUtils.delete(tempGS);
IOUtils.delete(tempOGR);
} catch (Exception e) {
throw new ServiceException("Exception occurred during output generation", e);
}
}
/**
* Writes to disk using shapefile if the feature type allows for it, GML otherwise
* @param tempDir
* @param curCollection
* @return
*/
private File writeToDisk(File tempDir,
FeatureCollection<SimpleFeatureType, SimpleFeature> curCollection) throws Exception {
if(isShapefileCompatible(curCollection.getSchema()))
return writeShapefile(tempDir, curCollection);
else
return writeGML(tempDir, curCollection);
}
private File writeShapefile(File tempDir,
FeatureCollection<SimpleFeatureType, SimpleFeature> collection) {
SimpleFeatureType schema = collection.getSchema();
FeatureStore<SimpleFeatureType, SimpleFeature> fstore = null;
DataStore dstore = null;
File file = null;
try {
file = new File(tempDir, schema.getTypeName() + ".shp");
dstore = new ShapefileDataStore(file.toURL());
dstore.createSchema(schema);
fstore = (FeatureStore<SimpleFeatureType, SimpleFeature>) dstore.getFeatureSource(schema.getTypeName());
fstore.addFeatures(collection);
} catch (IOException ioe) {
LOGGER.log(Level.WARNING,
"Error while writing featuretype '" + schema.getTypeName() + "' to shapefile.", ioe);
throw new ServiceException(ioe);
} finally {
if(dstore != null) {
dstore.dispose();
}
}
return file;
}
/**
* Returns true if the schema has just one geometry and the geom type is known
* @param schema
* @return
*/
private boolean isShapefileCompatible(SimpleFeatureType schema) {
GeometryType gt = null;
for (AttributeDescriptor at : schema.getAttributeDescriptors()) {
if(at instanceof GeometryDescriptor) {
if(gt == null)
gt = ((GeometryDescriptor) at).getType();
else
// more than one geometry
return false;
}
}
return gt != null && SHAPEFILE_GEOM_TYPES.contains(gt.getBinding());
}
private File writeGML(File tempDir,
FeatureCollection<SimpleFeatureType, SimpleFeature> curCollection) throws Exception {
// create the temp file for this output
File outFile = new File(tempDir, curCollection.getSchema().getTypeName() + ".gml");
// write out
OutputStream os = null;
try {
os = new FileOutputStream(outFile);
// let's invoke the transformer
FeatureTransformer ft = new FeatureTransformer();
ft.setNumDecimals(16);
ft.setNamespaceDeclarationEnabled(false);
ft.getFeatureNamespaces().declarePrefix("topp",
curCollection.getSchema().getName().getNamespaceURI());
ft.transform(curCollection, os);
} finally {
os.close();
}
return outFile;
}
}