/**
* Copyright (C) 2008 - 2016 52°North Initiative for Geospatial Open Source
* Software GmbH
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* If the program is linked with libraries which are licensed under one of
* the following licenses, the combination of the program with the linked
* library is not considered a "derivative work" of the program:
*
* • Apache License, version 2.0
* • Apache Software License, version 1.0
* • GNU Lesser General Public License, version 3
* • Mozilla Public License, versions 1.0, 1.1 and 2.0
* • Common Development and Distribution License (CDDL), version 1.0
*
* Therefore the distribution of the program linked with libraries licensed
* under the aforementioned licenses, is permitted by the copyright holders
* if the distribution is compliant with both the GNU General Public
* License version 2 and the aforementioned licenses.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*/
package org.n52.wps.server.sextante;
import java.awt.geom.Point2D;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import net.opengis.wps.x100.InputDescriptionType;
import net.opengis.wps.x100.OutputDescriptionType;
import net.opengis.wps.x100.ProcessDescriptionType;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.data.DataStore;
import org.geotools.data.FeatureSource;
import org.geotools.data.FeatureStore;
import org.geotools.data.collection.CollectionDataStore;
import org.geotools.feature.FeatureCollection;
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.n52.wps.io.data.IData;
import org.n52.wps.io.data.binding.complex.FileDataBinding;
import org.n52.wps.io.data.binding.complex.GTRasterDataBinding;
import org.n52.wps.io.data.binding.complex.GTVectorDataBinding;
import org.n52.wps.io.data.binding.literal.LiteralBooleanBinding;
import org.n52.wps.io.data.binding.literal.LiteralDoubleBinding;
import org.n52.wps.io.data.binding.literal.LiteralIntBinding;
import org.n52.wps.io.data.binding.literal.LiteralStringBinding;
import org.n52.wps.server.IAlgorithm;
import es.unex.sextante.additionalInfo.AdditionalInfoFixedTable;
import es.unex.sextante.additionalInfo.AdditionalInfoMultipleInput;
import es.unex.sextante.additionalInfo.AdditionalInfoRasterLayer;
import es.unex.sextante.additionalInfo.AdditionalInfoSelection;
import es.unex.sextante.additionalInfo.AdditionalInfoVectorLayer;
import es.unex.sextante.core.AnalysisExtent;
import es.unex.sextante.core.GeoAlgorithm;
import es.unex.sextante.core.OutputFactory;
import es.unex.sextante.core.OutputObjectsSet;
import es.unex.sextante.core.ParametersSet;
import es.unex.sextante.core.Sextante;
import es.unex.sextante.dataObjects.ILayer;
import es.unex.sextante.dataObjects.IRasterLayer;
import es.unex.sextante.dataObjects.ITable;
import es.unex.sextante.dataObjects.IVectorLayer;
import es.unex.sextante.exceptions.GeoAlgorithmExecutionException;
import es.unex.sextante.exceptions.NullParameterAdditionalInfoException;
import es.unex.sextante.exceptions.WrongOutputIDException;
import es.unex.sextante.geotools.GTRasterLayer;
import es.unex.sextante.geotools.GTVectorLayer;
import es.unex.sextante.outputs.FileOutputChannel;
import es.unex.sextante.outputs.Output;
import es.unex.sextante.parameters.FixedTableModel;
import es.unex.sextante.parameters.Parameter;
import es.unex.sextante.parameters.ParameterMultipleInput;
import es.unex.sextante.parameters.ParameterRasterLayer;
import es.unex.sextante.parameters.ParameterVectorLayer;
public class GenericSextanteProcessDelegator implements IAlgorithm, SextanteConstants {
private static Logger LOGGER = LoggerFactory.getLogger(GenericSextanteProcessDelegator.class);
private String processID;
private ProcessDescriptionType processDescription;
private List<String> errors;
private GeoAlgorithm sextanteProcess;
public GenericSextanteProcessDelegator(String processID, ProcessDescriptionType processDescriptionType) {
this.processID = processID.replace("Sextante_","");;
errors = new ArrayList<String>();
this.processDescription = processDescriptionType;
sextanteProcess = Sextante.getAlgorithmFromCommandLineName(processID);
}
public ProcessDescriptionType getDescription() {
return processDescription;
}
public List<String> getErrors() {
return errors;
}
public String getWellKnownName() {
return processID;
}
public boolean processDescriptionIsValid() {
return processDescription.validate();
}
public Map<String, IData> run(Map<String, List<IData>> inputData) {
Map<String, IData> resultMap = new HashMap<String, IData>();
/*
* 1. Extract the Sextante Classname out of the processIdentifier.
* It can be assumed that the id is also the classname.
*
*/
try {
GeoAlgorithm sextanteProcess = Sextante.getAlgorithmFromCommandLineName(processID);
/*
* 2. Get the parameters needed from either the processdescription or the object itself
* e.g.
* ParametersSet params = alg.getParameters();
*
*
*/
ParametersSet parameterSet = sextanteProcess.getParameters();
int numberOfParameters = parameterSet.getNumberOfParameters();
for(int i = 0; i <numberOfParameters; i++){
Parameter parameter = parameterSet.getParameter(i);
String parameterName = parameter.getParameterName();
String type = parameter.getParameterTypeName();
/* 3. Wrap the input from layers into a Sextante known format based on the metadata.
* e.g.
* if a vectorlayer is required, do something like:
* GTVectorLayer layer =GTVectorLayer.createLayer(ds, ds.getTypeNames()[0]);
*
* we probably have to refactor the input stuff and add some metadata to know what koind of input is fed in (for instance vector or raster etc)
*/
boolean missingMandatoryParameter = false;
if (parameter instanceof ParameterRasterLayer){
AdditionalInfoRasterLayer ai = (AdditionalInfoRasterLayer) parameter.getParameterAdditionalInfo();
if (ai.getIsMandatory() && (inputData.get(parameterName) == null) ){
missingMandatoryParameter = true;
}
}
else if (parameter instanceof ParameterVectorLayer){
AdditionalInfoVectorLayer ai = (AdditionalInfoVectorLayer) parameter.getParameterAdditionalInfo();
if (ai.getIsMandatory() && (inputData.get(parameterName) == null) ){
missingMandatoryParameter = true;
}
}else if (parameter instanceof ParameterMultipleInput) {
AdditionalInfoMultipleInput ai = (AdditionalInfoMultipleInput) parameter
.getParameterAdditionalInfo();
if (ai.getIsMandatory() && (inputData.get(parameterName) == null) ){
missingMandatoryParameter = true;
}
}
if(missingMandatoryParameter){
LOGGER.error("Missing parameter: " + parameterName);
throw new RuntimeException("Error while executing process " + processID + ". Missing parameter: " + parameterName);
}
if(!(inputData.get(parameterName) == null)){
Object wrappedInput = wrapSextanteInputs(parameter, inputData.get(parameterName), parameterName, type);
if(wrappedInput !=null){
parameter.setParameterValue(wrappedInput);
}
}
}
/* 4. Adjust output grid extent if needed */
if (sextanteProcess.getUserCanDefineAnalysisExtent()){
AnalysisExtent ge = null;
try{
ge = getGridExtent(
(Double)inputData.get( GRID_EXTENT_X_MIN).get(0).getPayload(),
(Double)inputData.get(GRID_EXTENT_X_MAX).get(0).getPayload(),
(Double)inputData.get(GRID_EXTENT_Y_MIN).get(0).getPayload(),
(Double)inputData.get(GRID_EXTENT_Y_MAX).get(0).getPayload(),
(Double)inputData.get(GRID_EXTENT_CELLSIZE).get(0).getPayload());
}
catch(Exception e){}
sextanteProcess.setAnalysisExtent(ge);
}
/* 5. Specify the output
* e.g.
* OutputFactory outputFactory = new GTOutputFactory();
* OutputObjectsSet outputs = alg.getOutputObjects();
* Output contours = outputs.getOutput(LinesToEquispacedPointsAlgorithm.RESULT);
* contours.setFilename("/home/my_user_name/points.shp");
*/
//TODO eventually make the outputfactory dynamic based on the requested output type
//until now, only geotools is supported, which may also supports different formats-->please check.
OutputFactory outputFactory = new N52OutputFactory();
/* 6. Execute
* e.g.
* alg.execute(null, outputFactory);
*/
sextanteProcess.execute(null, outputFactory);
OutputObjectsSet outputs = sextanteProcess.getOutputObjects();
int outputDataCount = outputs.getOutputDataObjectsCount();
for(int i = 0; i<outputDataCount; i++){
Output outputObject = outputs.getOutput(i);
String name = outputObject.getName();
/* 7. Fetch the results
* e.g.
* IVectorLayer result = (IVectorLayer) contours.getOutputObject();
* and Unwrap it into geotools
* e.g.
* FeatureStore fs = (FeatureStore) result.getBaseDataObject();
*/
IData finalResult = unwrapSextanteResults(outputObject);
if(finalResult==null){
throw new RuntimeException("Error while executing process " + processID + ". Sextante Results are null");
}
/* 9. Fill the result hashmap
*/
resultMap.put(name, finalResult);
}
/* 10. return the results
*/
} catch (InstantiationException e) {
LOGGER.error(e.getMessage(), e);
throw new RuntimeException("Error while executing process " + processID + ".");
} catch (IllegalAccessException e) {
LOGGER.error(e.getMessage(), e);
throw new RuntimeException("Error while executing process " + processID + ".");
} catch (ClassNotFoundException e) {
LOGGER.error(e.getMessage(), e);
throw new RuntimeException("Error while executing process " + processID + ". ");
} catch (WrongOutputIDException e) {
LOGGER.error(e.getMessage(), e);
throw new RuntimeException("Error while executing process " + processID + ". ");
} catch (GeoAlgorithmExecutionException e) {
LOGGER.error(e.getMessage(), e);
throw new RuntimeException("Error while executing process " + processID + ".");
} catch (IOException e) {
LOGGER.error(e.getMessage(), e);
throw new RuntimeException("Error while executing process " + processID + ".");
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
throw new RuntimeException("Error while executing process " + processID + ".");
}
return resultMap;
}
private AnalysisExtent getGridExtent(double xMin, double xMax,
double yMin, double yMax,
double cellSize){
AnalysisExtent ge = new AnalysisExtent();
ge.setCellSize(cellSize);
ge.setXRange(xMin, xMax, true);
ge.setYRange(yMin, yMax, true);
return ge;
}
private Object wrapSextanteInputs(Parameter parameter, List<IData> wpsInputParameters , String parameterName, String type) throws IOException, NullParameterAdditionalInfoException {
if(type.equals("Vector Layer") && wpsInputParameters.size() == 1){
IData vectorLayer = wpsInputParameters.get(0);
if(vectorLayer==null){
return null;
}
/* 4. Fill the input parameters with the wps input
* e.g.
* params.getParameter(LinesToEquispacedPointsAlgorithm.LINES).setParameterValue(layer);
* params.getParameter(LinesToEquispacedPointsAlgorithm.DISTANCE).setParameterValue(new Double(5000));
*
*/
return wrapVectorLayer(vectorLayer);
}
else if (type.equals("Raster Layer")&& wpsInputParameters.size() == 1) {
IData rasterLayer = wpsInputParameters.get(0);;
if(rasterLayer==null){
return null;
}
return wrapRasterLayer(rasterLayer);
}else if (type.equals("Numerical Value") || type.equals("String")
|| type.equals("Band") || type.equals("Table Field") && wpsInputParameters.size() == 1){
return wpsInputParameters.get(0).getPayload();
}else if (type.equals("Multiple Input")){
return createMultipleInputArray(parameter, wpsInputParameters);
}else if (type.equals("Selection") && wpsInputParameters.size() == 1){
IData param = wpsInputParameters.get(0);
if(param.getSupportedClass().equals(String.class)){
AdditionalInfoSelection ai = (AdditionalInfoSelection) parameter.getParameterAdditionalInfo();
String[] values = ai.getValues();
for(int i = 0; i<values.length;i++){
if(values[i].equals(param.getPayload())){
return new Integer(i);
}
}
}
/*if(param.getSupportedClass().equals(String.class)){
AdditionalInfoSelection ai = (AdditionalInfoSelection) parameter.getParameterAdditionalInfo();
String[] values = ai.getValues();
for(int i = 0; i<values.length;i++){
if(values[i].equals(param.getPayload())){
return new Integer(i);
}
}
}*/
else{
return null;
}
}else if (type.equals("Boolean") && wpsInputParameters.size() == 1){
IData param = wpsInputParameters.get(0);
if(param == null){
return false;
}
return param.getPayload();
}else if (type.equals("Point") && wpsInputParameters.size() == 1){
IData param = wpsInputParameters.get(0);
if(param == null){
return false;
}
String[] sValue = param.getPayload().toString().split(",");
return new Point2D.Double(Double.parseDouble(sValue[0]),
Double.parseDouble(sValue[1]));
}
else if (type.equals("Fixed Table") && wpsInputParameters.size() == 1){
boolean bIsNumberOfRowsFixed;
int iCols, iRows;
int iCol, iRow;
int iToken = 0;
FixedTableModel tableModel;
IData param = wpsInputParameters.get(0);
if(param==null){
return null;
}
String sValue = param.getPayload().toString();
StringTokenizer st = new StringTokenizer(sValue, ",");
String sToken;
AdditionalInfoFixedTable ai;
ai = (AdditionalInfoFixedTable) parameter.getParameterAdditionalInfo();
iCols = ai.getColsCount();
iRows = (int) (st.countTokens() / iCols) + 1;
bIsNumberOfRowsFixed = ai.isNumberOfRowsFixed();
tableModel = new FixedTableModel(ai.getCols(), iRows, bIsNumberOfRowsFixed);
if (bIsNumberOfRowsFixed){
if (iRows != ai.getRowsCount()){
return null;
}
}
else{
if (st.countTokens() % iCols != 0){
return null;
}
}
while (st.hasMoreTokens()){
iRow = (int) Math.floor(iToken / (double) iCols);
iCol = iToken % iCols;
sToken = st.nextToken().trim();
tableModel.setValueAt(sToken, iRow, iCol);
iToken++;
}
return tableModel;
}
return null;
}
private IData unwrapSextanteResults(Output outputObject) throws Exception {
Object result = outputObject.getOutputObject();
if(result instanceof IVectorLayer){
IVectorLayer vectorLayer = ((IVectorLayer)result);
vectorLayer.open();
FeatureStore<?, ?> fs = (FeatureStore<?, ?>) vectorLayer.getBaseDataObject();
return new GTVectorDataBinding(fs.getFeatures());
}else if (result instanceof IRasterLayer){
IRasterLayer rasterLayer = ((IRasterLayer)result);
GridCoverage coverage = (GridCoverage) rasterLayer.getBaseDataObject();
return new GTRasterDataBinding((GridCoverage2D)coverage);
}else if(result instanceof ITable){
FileOutputChannel outputChannel = (FileOutputChannel) outputObject.getOutputChannel();
File output = new File(outputChannel.getFilename());
return new FileDataBinding(output);
}
//TODO Extend for literal output
return null;
}
private GTRasterLayer wrapRasterLayer(IData rasterLayer) {
if(!(rasterLayer.getPayload() instanceof GridCoverage)){
return null;
}
GridCoverage coverage = (GridCoverage) rasterLayer.getPayload();
GTRasterLayer sextanteRasterLayer = new GTRasterLayer();
sextanteRasterLayer.create(coverage);
return sextanteRasterLayer;
}
private GTVectorLayer wrapVectorLayer(IData vectorLayer) throws IOException {
if(!(vectorLayer.getPayload() instanceof FeatureCollection)){
return null;
}
FeatureCollection<SimpleFeatureType, SimpleFeature> fc = (FeatureCollection<SimpleFeatureType, SimpleFeature>) vectorLayer.getPayload();
DataStore datastore = new CollectionDataStore(fc);
FeatureSource<?, ?> fsource = datastore.getFeatureSource(datastore.getTypeNames()[0]);
GTVectorLayer gtVectorLayer = new GTVectorLayer();
// NoPostprocessingGTVectorLayer gtVectorLayer = new NoPostprocessingGTVectorLayer();
gtVectorLayer.create(fsource);
// GTVectorLayer gtVectorLayer = GTVectorLayer.createLayer(datastore, datastore.getTypeNames()[0]);
// gtVectorLayer.setPostProcessStrategy(new NullStrategy());
gtVectorLayer.setName("VectorLayer");
return gtVectorLayer;
}
private ArrayList<ILayer> createMultipleInputArray(Parameter parameter, List<IData> wpsInputParameters)
throws NullParameterAdditionalInfoException, IOException{
ArrayList<ILayer> list = new ArrayList<ILayer>();
for(IData data : wpsInputParameters){
AdditionalInfoMultipleInput ai = (AdditionalInfoMultipleInput)parameter.getParameterAdditionalInfo();
switch (ai.getDataType()){
case AdditionalInfoMultipleInput.DATA_TYPE_RASTER:
list.add(wrapRasterLayer(data));
break;
case AdditionalInfoMultipleInput.DATA_TYPE_VECTOR_ANY:
list.add(wrapVectorLayer(data));
break;
case AdditionalInfoMultipleInput.DATA_TYPE_VECTOR_LINE:
list.add(wrapVectorLayer(data));
break;
case AdditionalInfoMultipleInput.DATA_TYPE_VECTOR_POLYGON:
list.add(wrapVectorLayer(data));
break;
case AdditionalInfoMultipleInput.DATA_TYPE_VECTOR_POINT:
list.add(wrapVectorLayer(data));
break;
default:
}
}
return list;
}
public Class<?> getInputDataType(String id) {
ParametersSet parameterSet = sextanteProcess.getParameters();
int numberOfParameters = parameterSet.getNumberOfParameters();
for(int i = 0; i <numberOfParameters; i++){
Parameter parameter = parameterSet.getParameter(i);
String parameterName = parameter.getParameterName();
if(!parameterName.equals(id)){
continue;
}
String type = parameter.getParameterTypeName();
if(type.equals("Vector Layer")){
return GTVectorDataBinding.class;
}
else if (type.equals("Raster Layer")) {
return GTRasterDataBinding.class;
}else if (type.equals("Numerical Value")){
return LiteralDoubleBinding.class;
}else if (type.equals("String")){
return LiteralStringBinding.class;
}else if (type.equals("Multiple Input")){
InputDescriptionType[] inputs = processDescription.getDataInputs().getInputArray();
for(InputDescriptionType input : inputs){
if(input.getIdentifier().getStringValue().equals(id)){
if(input.isSetLiteralData()){
String datatype = input.getLiteralData().getDataType().getStringValue();
if(datatype.contains("tring")){
return LiteralStringBinding.class;
}
if(datatype.contains("ollean")){
return LiteralBooleanBinding.class;
}
if(datatype.contains("loat") || datatype.contains("ouble")){
return LiteralDoubleBinding.class;
}
if(datatype.contains("nt")){
return LiteralIntBinding.class;
}
}
if(input.isSetComplexData()){
String mimeType = input.getComplexData().getDefault().getFormat().getMimeType();
if(mimeType.contains("xml") || (mimeType.contains("XML"))){
return GTVectorDataBinding.class;
}else{
return GTRasterDataBinding.class;
}
}
}
}
}else if (type.equals("Selection")){
return LiteralIntBinding.class;
}else if (type.equals("Boolean")){
return LiteralBooleanBinding.class;
}
else if (type.equals("Fixed Table")){
return LiteralStringBinding.class;
}
}
return null;
}
public Class<?> getOutputDataType(String id) {
OutputDescriptionType[] outputs = processDescription.getProcessOutputs().getOutputArray();
for(OutputDescriptionType output : outputs){
if(!output.getIdentifier().getStringValue().equals(id)){
continue;
}
if(output.isSetLiteralOutput()){
String datatype = output.getLiteralOutput().getDataType().getStringValue();
if(datatype.contains("tring")){
return LiteralStringBinding.class;
}
if(datatype.contains("ollean")){
return LiteralBooleanBinding.class;
}
if(datatype.contains("loat") || datatype.contains("ouble")){
return LiteralDoubleBinding.class;
}
if(datatype.contains("nt")){
return LiteralIntBinding.class;
}
}
if(output.isSetComplexOutput()){
String mimeType = output.getComplexOutput().getDefault().getFormat().getMimeType();
if(mimeType.contains("xml") || (mimeType.contains("XML"))){
return GTVectorDataBinding.class;
}else{
return GTRasterDataBinding.class;
}
}
}
return null;
}
}