/* (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.sextante;
import static org.geoserver.wps.sextante.SextanteProcessFactoryConstants.DEFAULT_BOOLEAN_VALUE;
import static org.geoserver.wps.sextante.SextanteProcessFactoryConstants.DEFAULT_NUMERICAL_VALUE;
import static org.geoserver.wps.sextante.SextanteProcessFactoryConstants.DEFAULT_STRING_VALUE;
import static org.geoserver.wps.sextante.SextanteProcessFactoryConstants.FIXED_TABLE_FIXED_NUM_ROWS;
import static org.geoserver.wps.sextante.SextanteProcessFactoryConstants.FIXED_TABLE_NUM_COLS;
import static org.geoserver.wps.sextante.SextanteProcessFactoryConstants.FIXED_TABLE_NUM_ROWS;
import static org.geoserver.wps.sextante.SextanteProcessFactoryConstants.MAX_NUMERICAL_VALUE;
import static org.geoserver.wps.sextante.SextanteProcessFactoryConstants.MIN_NUMERICAL_VALUE;
import static org.geoserver.wps.sextante.SextanteProcessFactoryConstants.MULTIPLE_INPUT_TYPE;
import static org.geoserver.wps.sextante.SextanteProcessFactoryConstants.NUMERICAL_VALUE_TYPE;
import static org.geoserver.wps.sextante.SextanteProcessFactoryConstants.PARAMETER_MANDATORY;
import static org.geoserver.wps.sextante.SextanteProcessFactoryConstants.PARENT_PARAMETER_NAME;
import static org.geoserver.wps.sextante.SextanteProcessFactoryConstants.SHAPE_TYPE;
import java.awt.RenderingHints.Key;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Logger;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.data.Parameter;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.NameImpl;
import org.geotools.process.Process;
import org.geotools.process.ProcessFactory;
import org.geotools.text.Text;
import org.geotools.util.SimpleInternationalString;
import org.geotools.util.logging.Logging;
import org.opengis.feature.type.Name;
import org.opengis.util.InternationalString;
import com.vividsolutions.jts.geom.Envelope;
import es.unex.sextante.additionalInfo.AdditionalInfo;
import es.unex.sextante.additionalInfo.AdditionalInfoBoolean;
import es.unex.sextante.additionalInfo.AdditionalInfoFixedTable;
import es.unex.sextante.additionalInfo.AdditionalInfoMultipleInput;
import es.unex.sextante.additionalInfo.AdditionalInfoNumericalValue;
import es.unex.sextante.additionalInfo.AdditionalInfoRasterLayer;
import es.unex.sextante.additionalInfo.AdditionalInfoString;
import es.unex.sextante.additionalInfo.AdditionalInfoTableField;
import es.unex.sextante.additionalInfo.AdditionalInfoVectorLayer;
import es.unex.sextante.core.GeoAlgorithm;
import es.unex.sextante.core.OutputObjectsSet;
import es.unex.sextante.core.ParametersSet;
import es.unex.sextante.core.Sextante;
import es.unex.sextante.dataObjects.IRasterLayer;
import es.unex.sextante.dataObjects.ITable;
import es.unex.sextante.dataObjects.IVectorLayer;
import es.unex.sextante.exceptions.NullParameterAdditionalInfoException;
import es.unex.sextante.outputs.Output;
import es.unex.sextante.outputs.OutputRasterLayer;
import es.unex.sextante.outputs.OutputTable;
import es.unex.sextante.outputs.OutputText;
import es.unex.sextante.outputs.OutputVectorLayer;
import es.unex.sextante.parameters.ParameterBoolean;
import es.unex.sextante.parameters.ParameterFixedTable;
import es.unex.sextante.parameters.ParameterMultipleInput;
import es.unex.sextante.parameters.ParameterNumericalValue;
import es.unex.sextante.parameters.ParameterRasterLayer;
import es.unex.sextante.parameters.ParameterString;
import es.unex.sextante.parameters.ParameterTableField;
import es.unex.sextante.parameters.ParameterVectorLayer;
/**
* A process factory that wraps a SEXTANTE algorithm and can be used to get information about it and
* create the corresponding process
*
* @author volaya
*
*/
public class SextanteProcessFactory implements ProcessFactory {
public static final String SEXTANTE_NAMESPACE = "sxt";
public static final String SEXTANTE_GRID_ENVELOPE = "gridEnvelope";
public static final String SEXTANTE_GRID_CELL_SIZE = "gridCellSize";
private static final Logger LOGGER = Logging.getLogger(SextanteProcessFactory.class);
private Set<Name> names = new HashSet<Name>();
/**
* Constructs a process factory based on the full SEXTANTE algorithms set
*/
public SextanteProcessFactory() {
Sextante.initialize();
int algorithmsCount = Sextante.getAlgorithmsCount();
LOGGER.info("Sextante loaded; it provides " + algorithmsCount + " algorithms!");
// Register the algorithms loaded.
HashMap<String,HashMap<String,GeoAlgorithm>>algorithmsHash = Sextante.getAlgorithms();
Set<Name> result = new HashSet<Name>();
for (HashMap<String,GeoAlgorithm> itemOb : algorithmsHash.values())
{
for (Entry<String,GeoAlgorithm> entry : itemOb.entrySet())
{
result.add(new NameImpl(SEXTANTE_NAMESPACE, entry.getValue().getCommandLineName()));
}
}
names = Collections.unmodifiableSet(result);
// Register this factory in the singleton Processors manager.
org.geotools.process.Processors.addProcessFactory(this);
}
public InternationalString getTitle() {
return new SimpleInternationalString("Sextante");
}
public Set<Name> getNames() {
return names;
}
void checkName(Name name) {
if(name == null)
throw new NullPointerException("Process name cannot be null");
if(!names.contains(name))
throw new IllegalArgumentException("Unknown process '" + name + "'");
}
/**
* Creates a geotools process which wraps a SEXTANTE geoalgorithm
*
* @param alg
* the SEXTANTE geoalgorithm to wrap
* @throws IllegalArgumentException
*/
public Process create(Name name) throws IllegalArgumentException {
checkName(name);
try {
return new SextanteProcess(Sextante.getAlgorithmFromCommandLineName(name.getLocalPart()));
} catch (Exception e) {
throw new RuntimeException("Error occurred cloning the prototype "
+ "algorithm... this should not happen", e);
}
}
public InternationalString getDescription(Name name) {
checkName(name);
return Text.text(Sextante.getAlgorithmFromCommandLineName(name.getLocalPart()).getName());
}
public InternationalString getTitle(Name name) {
return getDescription(name);
}
public String getName(Name name) {
checkName(name);
GeoAlgorithm algorithm = Sextante.getAlgorithmFromCommandLineName(name.getLocalPart());
String sClass = algorithm.getClass().getName();
int iLast = sClass.lastIndexOf(".");
String sCommandName = sClass.substring(iLast + 1, sClass.length() - "Algorithm".length());
return "Sextante" + sCommandName;
}
public boolean supportsProgress(Name name) {
checkName(name);
GeoAlgorithm algorithm = Sextante.getAlgorithmFromCommandLineName(name.getLocalPart());
return algorithm.isDeterminatedProcess();
}
public String getVersion(Name name) {
checkName(name);
return "1.0.0";
}
public Map<String, Parameter<?>> getParameterInfo(Name name) {
checkName(name);
GeoAlgorithm algorithm = Sextante.getAlgorithmFromCommandLineName(name.getLocalPart());
ParametersSet paramSet = algorithm.getParameters();
Map<String, Parameter<?>> paramInfo = new LinkedHashMap<String, Parameter<?>>();
boolean hasRasterInput = false;
for (int i = 0; i < paramSet.getNumberOfParameters(); i++) {
es.unex.sextante.parameters.Parameter param = paramSet.getParameter(i);
String title = param.getParameterDescription();
String description = title;
try {
String td = param.getParameterAdditionalInfo().getTextDescription();
if(td != null) {
description += " - " + td;
}
// TODO: for numeric data we can specify default value and
// range, that should be useful
} catch(NullParameterAdditionalInfoException e) {
// fine
}
hasRasterInput = hasRasterInput || IRasterLayer.class.isAssignableFrom(param.getParameterClass());
paramInfo.put(param.getParameterName(), new Parameter(param.getParameterName(),
mapToGeoTools(param.getParameterClass()), Text.text(title),
Text.text(description), getAdditionalInfoMap(param)));
}
// check if there is any raster output
boolean hasRasterOutput = false;
OutputObjectsSet ooset = algorithm.getOutputObjects();
for (int i = 0; i < ooset.getOutputObjectsCount(); i++) {
Output output = ooset.getOutput(i);
if (output instanceof OutputRasterLayer) {
hasRasterOutput = true;
break;
}
}
// if there is any input or output raster we also need the user to specify
// the grid structure, though we can get it from the first raster if there
// are raster inputs, meaning in that case we'll grab it from
if(hasRasterInput || hasRasterOutput) {
// create a grid envelope, required only if there is no raster input we can
// get the same info from
if(hasRasterInput) {
paramInfo.put(SEXTANTE_GRID_ENVELOPE, new Parameter(SEXTANTE_GRID_ENVELOPE,
Envelope.class, Text.text("Grid bounds (defaults to the bounds of the inputs)"),
Text.text("The real world coordinates bounding the grid"),
false, 0, 1, null, null));
paramInfo.put(SEXTANTE_GRID_CELL_SIZE, new Parameter(SEXTANTE_GRID_CELL_SIZE,
Double.class, Text.text("Cell size (defaults to the size of the first input)"),
Text.text("The cell size in real world units"),
false, 0, 1, null, null));
} else {
paramInfo.put(SEXTANTE_GRID_ENVELOPE, new Parameter(SEXTANTE_GRID_ENVELOPE,
Envelope.class, Text.text("Grid bounds"),
Text.text("The real world coordinates bounding the grid"),
true, 1, 1, null, null));
paramInfo.put(SEXTANTE_GRID_CELL_SIZE, new Parameter(SEXTANTE_GRID_CELL_SIZE,
Double.class, Text.text("Cell size"),
Text.text("The cell size in real world units"),
true, 1, 1, null, null));
}
}
return paramInfo;
}
/**
* Map Sextante common types into GeoTools common types
*
* @param parameterClass
*
*/
protected Class mapToGeoTools(Class parameterClass) {
if (IVectorLayer.class.isAssignableFrom(parameterClass)) {
return FeatureCollection.class;
} else if (IRasterLayer.class.isAssignableFrom(parameterClass)) {
return GridCoverage2D.class;
} else if (ITable.class.isAssignableFrom(parameterClass)) {
return FeatureCollection.class;
} else {
return parameterClass;
}
}
/**
* Returns a map with additional info about a given parameter. It takes a SEXTANTE parameter and
* produces a map suitable to be added to a GeoTools parameter
*
* @param param
* the parameter to take the additional information from
* @return a Map with additional info about the parameter. Keys used to identify each element
* are defined in {@see SextanteProcessFactoryConstants}
*/
private Map getAdditionalInfoMap(es.unex.sextante.parameters.Parameter param) {
HashMap map = new HashMap();
AdditionalInfo ai;
try {
ai = param.getParameterAdditionalInfo();
} catch (NullParameterAdditionalInfoException e) {
// we return an empty map if could not access the additional information
return map;
}
if (param instanceof ParameterRasterLayer) {
AdditionalInfoRasterLayer airl = (AdditionalInfoRasterLayer) ai;
map.put(PARAMETER_MANDATORY, airl.getIsMandatory());
}
if (param instanceof ParameterVectorLayer) {
AdditionalInfoVectorLayer aivl = (AdditionalInfoVectorLayer) ai;
map.put(PARAMETER_MANDATORY, aivl.getIsMandatory());
map.put(SHAPE_TYPE, aivl.getShapeType());
}
if (param instanceof ParameterVectorLayer) {
AdditionalInfoVectorLayer aiv = (AdditionalInfoVectorLayer) ai;
map.put(PARAMETER_MANDATORY, aiv.getIsMandatory());
}
if (param instanceof ParameterString) {
AdditionalInfoString ais = (AdditionalInfoString) ai;
map.put(DEFAULT_STRING_VALUE, ais.getDefaultString());
}
if (param instanceof ParameterNumericalValue) {
AdditionalInfoNumericalValue ainv = (AdditionalInfoNumericalValue) ai;
map.put(DEFAULT_NUMERICAL_VALUE, ainv.getDefaultValue());
map.put(MAX_NUMERICAL_VALUE, ainv.getMaxValue());
map.put(MIN_NUMERICAL_VALUE, ainv.getMinValue());
map.put(NUMERICAL_VALUE_TYPE, ainv.getType());
}
if (param instanceof ParameterBoolean) {
AdditionalInfoBoolean aib = (AdditionalInfoBoolean) ai;
map.put(DEFAULT_BOOLEAN_VALUE, aib.getDefaultValue());
}
if (param instanceof ParameterMultipleInput) {
AdditionalInfoMultipleInput aimi = (AdditionalInfoMultipleInput) ai;
map.put(MULTIPLE_INPUT_TYPE, aimi.getDataType());
map.put(PARAMETER_MANDATORY, aimi.getIsMandatory());
}
if (param instanceof ParameterFixedTable) {
AdditionalInfoFixedTable aift = (AdditionalInfoFixedTable) ai;
map.put(FIXED_TABLE_NUM_COLS, aift.getColsCount());
map.put(FIXED_TABLE_NUM_ROWS, aift.getRowsCount());
map.put(FIXED_TABLE_FIXED_NUM_ROWS, aift.isNumberOfRowsFixed());
}
if (param instanceof ParameterTableField) {
AdditionalInfoTableField aitf = (AdditionalInfoTableField) ai;
map.put(PARENT_PARAMETER_NAME, aitf.getParentParameterName());
}
return map;
}
public Map<String, Parameter<?>> getResultInfo(Name name, Map<String, Object> inputs)
throws IllegalArgumentException {
checkName(name);
GeoAlgorithm algorithm = Sextante.getAlgorithmFromCommandLineName(name.getLocalPart());
Class outputClass = null;
OutputObjectsSet ooset = algorithm.getOutputObjects();
Map<String, Parameter<?>> outputInfo = new HashMap<String, Parameter<?>>();
for (int i = 0; i < ooset.getOutputObjectsCount(); i++) {
Output output = ooset.getOutput(i);
if (output instanceof OutputVectorLayer) {
outputClass = FeatureCollection.class;
} else if (output instanceof OutputRasterLayer) {
outputClass = GridCoverage2D.class;
} else if (output instanceof OutputTable) {
outputClass = FeatureCollection.class;
} else if (output instanceof OutputText) {
outputClass = String.class;
} else {
throw new IllegalArgumentException("Don't know how to handle output of type" + output.getClass());
}
outputInfo.put(output.getName(), new Parameter(output.getName(), outputClass, Text
.text(output.getDescription()), Text.text(output.getDescription())));
}
return outputInfo;
}
@Override
public String toString() {
return "SextanteFactory";
}
public boolean isAvailable() {
return true;
}
public Map<Key, ?> getImplementationHints() {
return Collections.EMPTY_MAP;
}
}