package org.geoserver.geopkg.wps;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import javax.xml.namespace.QName;
import net.opengis.wfs20.GetFeatureType;
import net.opengis.wfs20.QueryType;
import net.opengis.wfs20.Wfs20Factory;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.FeatureTypeInfo;
import org.geoserver.catalog.LayerInfo;
import org.geoserver.catalog.ResourceInfo;
import org.geoserver.catalog.StyleInfo;
import org.geoserver.config.GeoServer;
import org.geoserver.geopkg.GeoPackageGetMapOutputFormat;
import org.geoserver.platform.ServiceException;
import org.geoserver.wfs.GetFeature;
import org.geoserver.wfs.WFSInfo;
import org.geoserver.wfs.request.FeatureCollectionResponse;
import org.geoserver.wfs.request.GetFeatureRequest;
import org.geoserver.wms.GetMapRequest;
import org.geoserver.wms.MapLayerInfo;
import org.geoserver.wps.resource.WPSResourceManager;
import org.geotools.data.DataUtilities;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.NameImpl;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.geopkg.Entry;
import org.geotools.geopkg.FeatureEntry;
import org.geotools.geopkg.GeoPackage;
import org.geotools.geopkg.TileEntry;
import org.geotools.geopkg.wps.GeoPackageProcessRequest;
import org.geotools.geopkg.wps.GeoPackageProcessRequest.FeaturesLayer;
import org.geotools.geopkg.wps.GeoPackageProcessRequest.Layer;
import org.geotools.geopkg.wps.GeoPackageProcessRequest.LayerType;
import org.geotools.geopkg.wps.GeoPackageProcessRequest.TilesLayer;
import org.geotools.process.factory.DescribeParameter;
import org.geotools.process.factory.DescribeProcess;
import org.geotools.process.factory.DescribeResult;
import org.geotools.process.gs.GSProcess;
import org.geotools.referencing.CRS;
import org.geotools.styling.Style;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory2;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.crs.GeographicCRS;
import com.vividsolutions.jts.geom.Envelope;
@DescribeProcess(title="GeoPackage", description="Geopackage Process")
public class GeoPackageProcess implements GSProcess {
private Catalog catalog;
private WPSResourceManager resources;
private GetFeature getFeatureDelegate;
private GeoPackageGetMapOutputFormat mapOutput;
private FilterFactory2 filterFactory;
public GeoPackageProcess(GeoServer geoServer, GeoPackageGetMapOutputFormat mapOutput,
WPSResourceManager resources, FilterFactory2 filterFactory) {
this.resources = resources;
this.mapOutput = mapOutput;
this.filterFactory = filterFactory;
catalog = geoServer.getCatalog();
getFeatureDelegate = new GetFeature(geoServer.getService(WFSInfo.class), catalog);
getFeatureDelegate.setFilterFactory(filterFactory);
}
private static final int TEMP_DIR_ATTEMPTS = 10000;
public static File createTempDir(File baseDir) {
String baseName = System.currentTimeMillis() + "-";
for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) {
File tempDir = new File(baseDir, baseName + counter);
if (tempDir.mkdir()) {
return tempDir;
}
}
throw new IllegalStateException("Failed to create directory within " + TEMP_DIR_ATTEMPTS
+ " attempts (tried " + baseName + "0 to " + baseName + (TEMP_DIR_ATTEMPTS - 1)
+ ')');
}
@DescribeResult(name="geopackage", description="Link to Compiled Geopackage File")
public URL execute(@DescribeParameter(name="contents", description="xml scheme describing geopackage contents") GeoPackageProcessRequest contents) throws IOException {
final File file;
URL path = contents.getPath();
boolean remove = contents.getRemove() != null ? contents.getRemove() : true;
String outputName = contents.getName() + ".gpkg";
if(!remove && path != null){
File urlToFile = DataUtilities.urlToFile(path);
urlToFile.mkdirs();
file =new File(urlToFile, contents.getName()+ ".gpkg");
}else{
file = resources.getOutputResource(null, outputName).file();
}
GeoPackage gpkg = new GeoPackage(file);
// Initialize the GeoPackage file in order to avoid exceptions when accessing the geoPackage file
gpkg.init();
for (int i=0; i < contents.getLayerCount() ; i++) {
Layer layer = contents.getLayer(i);
if (layer.getType() == LayerType.FEATURES){
FeaturesLayer features = (FeaturesLayer) layer;
QName ftName = features.getFeatureType();
QueryType query = Wfs20Factory.eINSTANCE.createQueryType();
query.getTypeNames().add(ftName);
if (features.getSrs() == null) {
String ns = ftName.getNamespaceURI() != null
? ftName.getNamespaceURI() : ftName.getPrefix();
FeatureTypeInfo ft =
catalog.getFeatureTypeByName(ns, ftName.getLocalPart());
if (ft != null) {
try {
query.setSrsName(new URI(ft.getSRS()));
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
}
else {
query.setSrsName(features.getSrs());
}
if (features.getPropertyNames() != null) {
query.getPropertyNames().addAll(features.getPropertyNames());
}
Filter filter = features.getFilter();
//add bbox to filter if there is one
if (features.getBbox() != null){
String defaultGeometry = catalog.getFeatureTypeByName(features.getFeatureType().getLocalPart())
.getFeatureType().getGeometryDescriptor().getLocalName();
Envelope e = features.getBbox();
// HACK: because we are going through wfs 2.0, flip the coordinates (specified in xy)
// which will then be later flipped back to xy
if (query.getSrsName() != null) {
try {
CoordinateReferenceSystem crs = CRS.decode(query.getSrsName().toString());
if (crs instanceof GeographicCRS) {
// flip the bbox
e = new Envelope(e.getMinY(), e.getMaxY(), e.getMinX(), e.getMaxX());
}
}
catch(Exception ex) {
throw new RuntimeException(ex);
}
}
Filter bboxFilter = filterFactory.bbox(filterFactory.property(defaultGeometry),
ReferencedEnvelope.reference(e));
if (filter == null) {
filter = bboxFilter;
} else {
filter = filterFactory.and(filter, bboxFilter);
}
}
query.setFilter(filter);
GetFeatureType getFeature = Wfs20Factory.eINSTANCE.createGetFeatureType();
getFeature.getAbstractQueryExpression().add(query);
FeatureCollectionResponse fc = getFeatureDelegate.run(GetFeatureRequest.adapt(getFeature));
for (FeatureCollection collection: fc.getFeatures()) {
if (! (collection instanceof SimpleFeatureCollection)) {
throw new ServiceException("GeoPackage OutputFormat does not support Complex Features.");
}
FeatureEntry e = new FeatureEntry();
e.setTableName(layer.getName());
addLayerMetadata(e, features);
ReferencedEnvelope bounds = collection.getBounds();
if (features.getBbox() != null){
bounds = ReferencedEnvelope.reference(bounds.intersection(features.getBbox()));
}
e.setBounds(bounds);
gpkg.add(e, (SimpleFeatureCollection) collection);
if (features.isIndexed()) {
gpkg.createSpatialIndex(e);
}
}
} else if (layer.getType() == LayerType.TILES) {
TilesLayer tiles = (TilesLayer) layer;
GetMapRequest request = new GetMapRequest();
request.setLayers(new ArrayList<MapLayerInfo>());
for (QName layerQName : tiles.getLayers()) {
LayerInfo layerInfo = null;
if ("".equals(layerQName.getNamespaceURI())) {
layerInfo = catalog.getLayerByName(layerQName.getLocalPart());
} else {
layerInfo = catalog.getLayerByName(new NameImpl(layerQName.getNamespaceURI(), layerQName.getLocalPart()));
}
if (layerInfo == null) {
throw new ServiceException("Layer not found: " + layerQName);
}
request.getLayers().add(new MapLayerInfo(layerInfo));
}
if (tiles.getBbox() == null) {
try {
// generate one from requests layers
CoordinateReferenceSystem crs =
tiles.getSrs() != null ? CRS.decode(tiles.getSrs().toString()) : null;
ReferencedEnvelope bbox = null;
for (MapLayerInfo l : request.getLayers()) {
ResourceInfo r = l.getResource();
ReferencedEnvelope b = null;
if (crs != null) {
// transform from lat lon bbox
b = r.getLatLonBoundingBox().transform(crs, true);
}
else {
// use native bbox
b = r.getNativeBoundingBox();
if (bbox != null) {
// transform
b = b.transform(bbox.getCoordinateReferenceSystem(), true);
}
}
if (bbox != null) {
bbox.include(b);
}
else {
bbox = b;
}
}
request.setBbox(bbox);
}
catch(Exception e) {
String msg = "Must specify bbox, unable to derive from requested layers";
throw new RuntimeException(msg ,e);
}
}
else {
request.setBbox(tiles.getBbox());
}
if (tiles.getSrs() == null) {
// use srs of first layer
ResourceInfo r = request.getLayers().iterator().next().getResource();
request.setSRS(r.getSRS());
}
else {
request.setSRS(tiles.getSrs().toString());
}
// Get the request SRS defined and set is as the request CRS
String srs = request.getSRS();
if(srs != null && !srs.isEmpty()){
try {
request.setCrs(CRS.decode(srs));
} catch (FactoryException e) {
throw new RuntimeException(e);
}
}
request.setBgColor(tiles.getBgColor());
request.setTransparent(tiles.isTransparent());
request.setStyleBody(tiles.getSldBody());
if (tiles.getSld() != null) {
request.setStyleUrl(tiles.getSld().toURL());
}
else if (tiles.getSldBody() != null) {
request.setStyleBody(tiles.getSldBody());
}
else {
request.setStyles(new ArrayList<Style>());
if (tiles.getStyles() != null) {
for (String styleName : tiles.getStyles()) {
StyleInfo info = catalog.getStyleByName(styleName);
if (info != null){
request.getStyles().add(info.getStyle());
}
}
}
if (request.getStyles().isEmpty()) {
for (MapLayerInfo layerInfo : request.getLayers()) {
request.getStyles().add(layerInfo.getDefaultStyle());
}
}
}
request.setFormat("none");
Map formatOptions = new HashMap();
formatOptions.put("format",tiles.getFormat());
if (tiles.getCoverage() != null) {
if (tiles.getCoverage().getMinZoom() != null) {
formatOptions.put("min_zoom", tiles.getCoverage().getMinZoom());
}
if (tiles.getCoverage().getMaxZoom() != null) {
formatOptions.put("max_zoom", tiles.getCoverage().getMaxZoom());
}
if (tiles.getCoverage().getMinColumn() != null) {
formatOptions.put("min_column", tiles.getCoverage().getMinColumn());
}
if (tiles.getCoverage().getMaxColumn() != null) {
formatOptions.put("max_column", tiles.getCoverage().getMaxColumn());
}
if (tiles.getCoverage().getMinRow() != null) {
formatOptions.put("min_row", tiles.getCoverage().getMinRow());
}
if (tiles.getCoverage().getMaxRow() != null) {
formatOptions.put("max_row", tiles.getCoverage().getMaxRow());
}
}
if (tiles.getGridSetName() != null) {
formatOptions.put("gridset", tiles.getGridSetName());
}
request.setFormatOptions(formatOptions);
TileEntry e = new TileEntry();
addLayerMetadata(e, tiles);
if (tiles.getGrids() != null) {
mapOutput.addTiles(gpkg, e, request, tiles.getGrids(), layer.getName());
} else {
mapOutput.addTiles(gpkg, e, request, layer.getName());
}
}
}
gpkg.close();
// Add to storage only if it is a temporary file
if(path != null && !remove){
return path;
}else{
return new URL(resources.getOutputResourceUrl(outputName, "application/x-gpkg"));
}
}
private void addLayerMetadata(Entry e, Layer layer) {
e.setDescription(layer.getDescription());
e.setIdentifier(layer.getIdentifier());
}
}