/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2012, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*/
package org.geotoolkit.wps.converters;
import com.vividsolutions.jts.geom.Geometry;
import java.awt.image.RenderedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.List;
import java.util.*;
import java.util.Set;
import javax.xml.bind.JAXBElement;
import net.sf.json.JSONObject;
import org.apache.sis.feature.FeatureTypeExt;
import org.apache.sis.feature.builder.AttributeTypeBuilder;
import org.apache.sis.feature.builder.FeatureTypeBuilder;
import org.apache.sis.feature.builder.PropertyTypeBuilder;
import org.geotoolkit.coverage.grid.GridCoverage2D;
import org.geotoolkit.coverage.io.CoverageIO;
import org.geotoolkit.coverage.io.GridCoverageReader;
import org.geotoolkit.data.FeatureCollection;
import org.geotoolkit.data.FeatureIterator;
import org.apache.sis.geometry.Envelopes;
import org.geotoolkit.geometry.jts.JTS;
import org.geotoolkit.mathml.xml.*;
import org.geotoolkit.nio.IOUtilities;
import org.geotoolkit.nio.ZipUtilities;
import org.geotoolkit.parameter.Parameters;
import org.apache.sis.referencing.CRS;
import org.apache.sis.referencing.IdentifiedObjects;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.ObjectConverters;
import org.apache.sis.util.UnconvertibleObjectException;
import static org.geotoolkit.data.AbstractFileFeatureStoreFactory.PATH;
import org.geotoolkit.data.FeatureStore;
import org.geotoolkit.data.geojson.GeoJSONFeatureStoreFactory;
import org.geotoolkit.data.geojson.binding.GeoJSONGeometry;
import org.geotoolkit.data.geojson.binding.GeoJSONObject;
import org.geotoolkit.data.geojson.utils.GeoJSONParser;
import org.geotoolkit.data.geojson.utils.GeometryUtils;
import org.geotoolkit.data.query.QueryBuilder;
import org.geotoolkit.data.session.Session;
import org.geotoolkit.wps.io.WPSIO;
import org.geotoolkit.wps.xml.ComplexDataType;
import org.geotoolkit.wps.xml.Reference;
import org.opengis.coverage.Coverage;
import org.geotoolkit.util.NamesExt;
import org.opengis.util.GenericName;
import static org.geotoolkit.wps.converters.WPSObjectConverter.ENCODING;
import static org.geotoolkit.wps.converters.WPSObjectConverter.MIME;
import org.geotoolkit.wps.xml.v100.ext.GeoJSONType;
import org.opengis.geometry.Envelope;
import org.opengis.parameter.GeneralParameterDescriptor;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.parameter.ParameterDescriptorGroup;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.metadata.Identifier;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.cs.AxisDirection;
import org.opengis.referencing.cs.CoordinateSystem;
import org.opengis.referencing.cs.CoordinateSystemAxis;
import org.opengis.referencing.operation.TransformException;
import org.opengis.util.FactoryException;
import org.apache.sis.referencing.CommonCRS;
import org.geotoolkit.ows.xml.DomainMetadata;
import org.geotoolkit.wps.xml.WPSXmlFactory;
import org.geotoolkit.storage.DataStores;
import org.opengis.feature.Feature;
import org.opengis.feature.FeatureType;
import org.opengis.feature.Property;
import org.w3c.dom.Node;
/**
*
* @author Quentin Boileau (Geomatys).
*/
public class WPSConvertersUtils {
public static final String OUT_STORAGE_DIR = "OUT_STORAGE_DIR"; //webdav storage directory path
public static final String OUT_STORAGE_URL = "OUT_STORAGE_URL"; //webdav url
public static final String WMS_STORAGE_DIR = "WMS_STORAGE_DIR"; // provider storage directory path
public static final String WMS_STORAGE_ID = "WMS_STORAGE_ID"; // provider name
public static final String WMS_INSTANCE_NAME = "WMS_INSTANCE_NAME"; //WMS instance name
public static final String WMS_INSTANCE_URL = "WMS_INSTANCE_URL"; //WMS instance url
public static final String WMS_LAYER_NAME = "WMS_LAYER_NAME"; //WMS instance url
public static final int FRACTION_DIGITS = 12; // Number of fractions digits to write for floating point numbers
/**
* GML VERSION.
*/
private static final Map<String, String> GML_VERSION = new HashMap<>();
static {
GML_VERSION.put("1.0.0", "3.1.1");
GML_VERSION.put("2.0.0", "3.2.1");
}
/**
* Fix the CRS problem for a Feature or a FeatureCollection
*
* @param dataValue a Feature or a FeatureCollection
* @return the sale Feature/FeatureCollection fixed
* @throws UnconvertibleObjectException
*/
public static Object fixFeature(final Object dataValue) throws FactoryException {
if (dataValue instanceof Feature) {
final Feature featureIN = (Feature) dataValue;
FeatureType ft = featureIN.getType();
fixFeatureType(featureIN, ft);
return featureIN;
}
if (dataValue instanceof FeatureCollection) {
final FeatureCollection featureColl = (FeatureCollection) dataValue;
FeatureType ft = featureColl.getFeatureType();
try (FeatureIterator featureIter = featureColl.iterator()) {
if (featureIter.hasNext()) {
final Feature feature = featureIter.next();
fixFeatureType(feature, ft);
}
}
return featureColl;
}
throw new IllegalArgumentException("Invalid Feature");
}
/**
* Fix a FeatureType in spread the geometry CRS from a feature to the geometry descriptor CRS
*
* @param featureIN feature with geometry used to fix the geometry descriptor
* @param type the featureType to fix
* @throws UnconvertibleObjectException
*/
private static FeatureType fixFeatureType(final Feature featureIN, FeatureType type) throws FactoryException {
final FeatureTypeBuilder ftb = new FeatureTypeBuilder(type);
//Fetch each geometry, get his CRS and
for (PropertyTypeBuilder pt : ftb.properties()) {
if (pt instanceof AttributeTypeBuilder && Geometry.class.isAssignableFrom( ((AttributeTypeBuilder)pt).getValueClass())) {
final String propertyName = pt.getName().toString();
final Geometry propertyGeom = (Geometry) featureIN.getPropertyValue(propertyName);
final CoordinateReferenceSystem extractCRS = JTS.findCoordinateReferenceSystem(propertyGeom);
((AttributeTypeBuilder)pt).setCRS(extractCRS);
}
}
return ftb.build();
}
/**
* Convert a string to a binding class. If the binding class isn't a primitive like Integer, Double, .. we search
* into the converter list if found a match.
*
* @param data string to convert
* @param binding wanted class
* @return converted object
* @throws UnconvertibleObjectException if there is no match found
*/
public static <T> Object convertFromString(final String data, final Class binding) throws UnconvertibleObjectException {
Object convertedData = null; //resulting Object
WPSObjectConverter<? super String, ? extends T> converter;//converter
try {
//try to convert into a primitive type
converter = new WPSObjectConverterAdapter(ObjectConverters.find(String.class, binding));
} catch (UnconvertibleObjectException ex) {
//try to convert with some specified converter
converter = WPSIO.getConverter(binding, WPSIO.IOType.INPUT, WPSIO.FormChoice.LITERAL);
if (converter == null) {
throw new UnconvertibleObjectException("Converter can't be found.");
}
}
convertedData = converter.convert(data, null);
return convertedData;
}
/**
* Convert an object into a String.
*
* @param data
* @return toString object.
*/
public static String convertToString(final Object data) {
String out = null;
if (data != null) {
try {
WPSObjectConverter converter = WPSConverterRegistry.getInstance().getConverter(data.getClass(), String.class);
out = (String) converter.convert(data, null);
} catch (UnconvertibleObjectException ex) {
if (data instanceof CoordinateReferenceSystem) {
out = IdentifiedObjects.getIdentifierOrName((CoordinateReferenceSystem) data);
} else {
out = String.valueOf(data);
}
}
}
return out;
}
/**
* Check that the given parameters map defines an encoding and a mime type.
*
* If the either the encoding or the mime type is null then a default one is
* set based on the default enconding/mime type
*
* @param clazz class needing default parameters
* @param ioType ioType of the class
* @param params parameters map to check
*/
private static void ensureParametersDefined(final Class clazz, final WPSIO.IOType ioType, final Map<String, Object> params) {
ArgumentChecks.ensureNonNull("class", clazz);
ArgumentChecks.ensureNonNull("ioType", ioType);
ArgumentChecks.ensureNonNull("params", params);
if (params.get(MIME) == null)
params.put(MIME, WPSIO.getDefaultMimeType(clazz, ioType));
if (params.get(ENCODING) == null)
params.put(ENCODING, WPSIO.getDefaultEncoding(clazz, ioType));
}
/**
* Get an convert data from a reference for an expected binding
*
* @param wpsVersion
* @param expectedClass
* @param complex
* @return
* @throws UnconvertibleObjectException
*/
public static Object convertFromComplex(final String wpsVersion, final ComplexDataType complex, final Class expectedClass) throws UnconvertibleObjectException {
final String mime = complex.getMimeType();
final String encoding = complex.getEncoding();
final String schema = complex.getSchema();
WPSIO.checkSupportedFormat(expectedClass, WPSIO.IOType.INPUT, mime, encoding, schema);
final Map<String, Object> parameters = new HashMap<>();
parameters.put(WPSObjectConverter.ENCODING, encoding);
parameters.put(WPSObjectConverter.MIME, mime);
parameters.put(WPSObjectConverter.SCHEMA, schema);
parameters.put(WPSObjectConverter.WPSVERSION, wpsVersion);
ensureParametersDefined(expectedClass, WPSIO.IOType.INPUT, parameters);
final List<Object> content = complex.getContent();
//remove white spaces
removeWhiteSpaceFromList(content);
final WPSObjectConverter converter = WPSIO.getConverter(expectedClass, WPSIO.IOType.INPUT, WPSIO.FormChoice.COMPLEX);
if (converter == null) {
throw new UnconvertibleObjectException("Input complex not supported, no converter found.");
}
return converter.convert(complex, parameters);
}
/**
* Get an convert an object int a {@link ComplexDataType complex}.
*
* @param wpsVersion
* @param object
* @param mime
* @param encoding
* @param schema
* @param params
* @return
* @throws UnconvertibleObjectException
*/
public static ComplexDataType convertToComplex(final String wpsVersion, final Object object, final String mime, final String encoding, final String schema,
final Map<String, Object> params) throws UnconvertibleObjectException {
ArgumentChecks.ensureNonNull("Object", object);
WPSIO.checkSupportedFormat(object.getClass(), WPSIO.IOType.INPUT, mime, encoding, schema);
final Map<String, Object> parameters = new HashMap<>();
parameters.put(WPSObjectConverter.TMP_DIR_PATH, params.get(OUT_STORAGE_DIR));
parameters.put(WPSObjectConverter.TMP_DIR_URL, params.get(OUT_STORAGE_URL));
parameters.put(WPSObjectConverter.ENCODING, encoding);
parameters.put(WPSObjectConverter.MIME, mime);
parameters.put(WPSObjectConverter.SCHEMA, schema);
parameters.put(WPSObjectConverter.WPSVERSION, wpsVersion);
parameters.put(WPSObjectConverter.GMLVERSION, GML_VERSION.get(wpsVersion));
ensureParametersDefined(object.getClass(), WPSIO.IOType.OUTPUT, parameters);
final WPSObjectConverter converter = WPSIO.getConverter(object.getClass(), WPSIO.IOType.OUTPUT, WPSIO.FormChoice.COMPLEX);
if (converter == null) {
throw new UnconvertibleObjectException("Output complex not supported, no converter found.");
}
return (ComplexDataType) converter.convert(object, parameters);
}
public static ComplexDataType convertToWMSComplex(String wpsVersion, Object object, String mimeType, String encoding, String schema, Map<String, Object> params)
throws UnconvertibleObjectException {
ArgumentChecks.ensureNonNull("Object", object);
WPSIO.checkSupportedFormat(object.getClass(), WPSIO.IOType.INPUT, mimeType, encoding, schema);
final Map<String, Object> parameters = new HashMap<>();
parameters.put(WPSObjectConverter.TMP_DIR_PATH, params.get(OUT_STORAGE_DIR));
parameters.put(WPSObjectConverter.TMP_DIR_URL, params.get(OUT_STORAGE_URL));
parameters.put(WPSObjectConverter.ENCODING, encoding);
parameters.put(WPSObjectConverter.MIME, mimeType);
parameters.put(WPSObjectConverter.SCHEMA, schema);
parameters.put(WPSObjectConverter.WPSVERSION, wpsVersion);
parameters.put(WPSObjectConverter.GMLVERSION, GML_VERSION.get(wpsVersion));
final ComplexDataType complex = WPSXmlFactory.buildComplexDataType(wpsVersion, encoding, mimeType, schema);
final Map<String,Object> jsonMap = new HashMap<>();
jsonMap.put("url", (String)params.get(WMS_INSTANCE_URL));
jsonMap.put("type", "WMS");
jsonMap.put("version", "1.3.0");
final String layerName = params.get(WMS_LAYER_NAME)+"_"+System.currentTimeMillis();
jsonMap.put("title", layerName);
jsonMap.put("layers", layerName);
final Path coverageFile = Paths.get((String) params.get(WMS_STORAGE_DIR), layerName + ".tiff");
try {
// Set the envelope crs to 4326 because of client request :
final CoordinateReferenceSystem outCRS = CommonCRS.WGS84.geographic();
Envelope env =null;
Integer crsCode = null;
if (object instanceof GridCoverage2D) {
final GridCoverage2D coverage = (GridCoverage2D) object;
CoverageIO.write(coverage, "GEOTIFF", coverageFile);
env = Envelopes.transform(coverage.getEnvelope2D(), outCRS);
crsCode = org.geotoolkit.referencing.IdentifiedObjects.lookupEpsgCode(
coverage.getCoordinateReferenceSystem(), false);
} else if (object instanceof File || object instanceof Path) {
final Path objPath = (object instanceof File) ? ((File) object).toPath() : (Path) object;
final GridCoverageReader reader = CoverageIO.createSimpleReader(objPath);
env = Envelopes.transform(reader.getGridGeometry(0).getEnvelope(), outCRS);
crsCode = org.geotoolkit.referencing.IdentifiedObjects.lookupEpsgCode(
reader.getGridGeometry(0).getCoordinateReferenceSystem(), false);
IOUtilities.copy(objPath, coverageFile, StandardCopyOption.REPLACE_EXISTING);
} else if (object instanceof GridCoverageReader) {
final GridCoverageReader reader = (GridCoverageReader) object;
env = Envelopes.transform(reader.getGridGeometry(0).getEnvelope(), outCRS);
crsCode = org.geotoolkit.referencing.IdentifiedObjects.lookupEpsgCode(
reader.getGridGeometry(0).getCoordinateReferenceSystem(), false);
Object in = reader.getInput();
if(in == null) {
throw new IOException("Input coverage is invalid.");
} else if( in instanceof File || in instanceof Path) {
final Path inPath = (in instanceof File) ? ((File) in).toPath() : (Path) in;
IOUtilities.copy(inPath, coverageFile, StandardCopyOption.REPLACE_EXISTING);
} else if(in instanceof InputStream) {
IOUtilities.writeStream((InputStream) in, coverageFile);
} else {
throw new IOException("Input coverage is invalid.");
}
}
if(crsCode == null) {
crsCode = 3857;
}
final double xMin = env.getLowerCorner().getOrdinate(0);
final double xMax = env.getUpperCorner().getOrdinate(0);
final double yMin = env.getLowerCorner().getOrdinate(1);
final double yMax = env.getUpperCorner().getOrdinate(1);
final Map<String,String> bboxMap = new HashMap<String, String>();
bboxMap.put("bounds", xMin+","+yMin+","+xMax+","+yMax);
bboxMap.put("crs", "EPSG:4326");
jsonMap.put("bbox", bboxMap);
jsonMap.put("srs", "EPSG:" + crsCode);
} catch (TransformException e) {
throw new UnconvertibleObjectException("The geographic envelope of the layer can't be retrieved", e);
} catch (Exception e) {
throw new UnconvertibleObjectException(e.getMessage(), e);
}
final String json = JSONObject.fromObject(jsonMap).toString();
complex.getContent().add(json);
return complex;
}
/**
* Get an convert data from a reference for an expected binding
*
* @param reference
* @param expectedClass
* @return an object
* @throws UnconvertibleObjectException if something went wrong
*/
public static Object convertFromReference(final Reference reference, final Class expectedClass) throws UnconvertibleObjectException {
final String mime = reference.getMimeType();
final String encoding = reference.getEncoding();
final String schema = reference.getSchema();
WPSIO.checkSupportedFormat(expectedClass, WPSIO.IOType.INPUT, mime, encoding, schema);
final Map<String, Object> parameters = new HashMap<String, Object>();
parameters.put(WPSObjectConverter.ENCODING, encoding);
parameters.put(WPSObjectConverter.MIME, mime);
parameters.put(WPSObjectConverter.SCHEMA, schema);
ensureParametersDefined(expectedClass, WPSIO.IOType.INPUT, parameters);
final WPSObjectConverter converter = WPSIO.getConverter(expectedClass, WPSIO.IOType.INPUT, WPSIO.FormChoice.REFERENCE);
if (converter == null) {
throw new UnconvertibleObjectException("Input reference not supported, no converter found.");
}
return converter.convert(reference, parameters);
}
/**
* Get an convert an object int a {@link ReferenceType reference}.
*
* @param version WPS version
* @param object
* @param mime
* @param encoding
* @param schema
* @param params
* @param iotype the io type requested (INPUT/OUTPUT)
* @return an {@link Reference input/output reference}.
* @throws UnconvertibleObjectException
*/
public static Reference convertToReference(final String version, final Object object, final String mime, final String encoding, final String schema,
final Map<String, Object> params, final WPSIO.IOType iotype) throws UnconvertibleObjectException {
ArgumentChecks.ensureNonNull("Object", object);
WPSIO.checkSupportedFormat(object.getClass(), WPSIO.IOType.INPUT, mime, encoding, schema);
final Map<String, Object> parameters = new HashMap<>();
parameters.put(WPSObjectConverter.TMP_DIR_PATH, params.get(OUT_STORAGE_DIR));
parameters.put(WPSObjectConverter.TMP_DIR_URL, params.get(OUT_STORAGE_URL));
parameters.put(WPSObjectConverter.ENCODING, encoding);
parameters.put(WPSObjectConverter.MIME, mime);
parameters.put(WPSObjectConverter.SCHEMA, schema);
parameters.put(WPSObjectConverter.WPSVERSION, version);
parameters.put(WPSObjectConverter.GMLVERSION, GML_VERSION.get(version));
parameters.put(WPSObjectConverter.IOTYPE, iotype.toString());
ensureParametersDefined(object.getClass(), iotype, params);
final WPSObjectConverter converter = WPSIO.getConverter(object.getClass(), WPSIO.IOType.OUTPUT, WPSIO.FormChoice.REFERENCE);
if (converter == null) {
throw new UnconvertibleObjectException("Output complex not supported, no converter found.");
}
return (Reference) converter.convert(object, parameters);
}
/**
* Create the DomaineMetaData object for a literal
*
* @param version WPS version
* @param clazz
* @return
* @throws UnconvertibleObjectException
*/
public static DomainMetadata createDataType(final String version, final Class clazz) {
if (clazz.equals(Double.class)) {
return WPSXmlFactory.buildDomainMetadata(version, "Double", "http://www.w3.org/TR/xmlschema-2/#double");
} else if (clazz.equals(Float.class)) {
return WPSXmlFactory.buildDomainMetadata(version, "Float", "http://www.w3.org/TR/xmlschema-2/#float");
} else if (clazz.equals(Boolean.class)) {
return WPSXmlFactory.buildDomainMetadata(version, "Boolean", "http://www.w3.org/TR/xmlschema-2/#boolean");
} else if (clazz.equals(Integer.class)) {
return WPSXmlFactory.buildDomainMetadata(version, "Integer", "http://www.w3.org/TR/xmlschema-2/#integer");
} else if (clazz.equals(Long.class)) {
return WPSXmlFactory.buildDomainMetadata(version, "Long", "http://www.w3.org/TR/xmlschema-2/#long");
} else if (clazz.equals(String.class) || WPSIO.isSupportedInputClass(clazz) || WPSIO.isSupportedOutputClass(clazz)) {
return WPSXmlFactory.buildDomainMetadata(version, "String", "http://www.w3.org/TR/xmlschema-2/#string");
} else {
return null;
}
}
public static String getDataTypeString(final String version, final Class clazz) {
String ref = createDataType(version, clazz).getReference();
if (ref == null) {
ref = "http://www.w3.org/TR/xmlschema-2/#string";
}
return ref;
}
/**
* Format an INPUT/OUTPUT format for errors messages.
* @param mime
* @param encoding
* @param schema
* @return
*/
public static String dataFormatToString(final String mime, final String encoding, final String schema) {
final StringBuilder builder = new StringBuilder();
final String begin = "[";
final String end = "]";
final String separator = ", ";
builder.append(begin);
builder.append("mimeType=");
builder.append(mime);
builder.append(separator);
builder.append("encoding=");
builder.append(encoding);
builder.append(separator);
builder.append("schema=");
builder.append(schema);
builder.append(end);
return builder.toString();
}
/**
* Extract the fist MathML MTable object.
* @param mathExp
* @return
*/
public static Mtable findMtable(List<Object> mathExp){
for (Object object : mathExp) {
if (object instanceof JAXBElement) {
final JAXBElement element = (JAXBElement) object;
if (element.getValue() instanceof Mtable) {
return (Mtable) element.getValue();
} else if(element.getValue() instanceof Mrow) {
final Mrow mrow = (Mrow) element.getValue();
return findMtable(mrow.getMathExpression());
}
}
}
return null;
}
/**
* Extact rows of an {@link Mtable table}.
* @param table
* @return
*/
public static List<Mtr> getRows(final Mtable table) {
final List<Mtr> rows = new ArrayList<Mtr>();
final List<JAXBElement<?>> jaxbRows = table.getTableRowExpression();
for (JAXBElement<?> jaxbRow : jaxbRows) {
if (jaxbRow.getValue() instanceof Mtr && jaxbRow.getValue() != null) {
rows.add((Mtr) jaxbRow.getValue());
}
}
return rows;
}
/**
* Extact double value of cell of a {@link Mtr row}.
* @param row
* @return
*/
public static double[] getCells (final Mtr row) {
final List<Double> cells = new ArrayList<Double>();
final List<JAXBElement<TableCellExpression>> tableCellExpressionList = row.getTableCellExpression();
for (JAXBElement<TableCellExpression> jAXBElement : tableCellExpressionList) {
final TableCellExpression tableCellExpression = jAXBElement.getValue();
final List<Object> objects = tableCellExpression.getMathExpression();
for (Object object : objects) {
final JAXBElement element = (JAXBElement) object;
if (element.getValue() instanceof Mn && element.getValue() != null) {
final Mn mn = (Mn) element.getValue();
final String value = (String) mn.getContent().get(0);
cells.add(Double.valueOf(value));
}
}
}
final double[] cellsArray = new double[cells.size()];
for (int i = 0; i < cells.size(); i++) {
cellsArray[i] = cells.get(i).doubleValue();
}
return cellsArray;
}
/**
* A function to transform a {@link ParameterDescriptorGroup} into {@link FeatureType}.
*
* This function care about wps constraints, so if our feature contains
* heavy object type as coverages, we replace them with reference type.
*
* @param toConvert The group to convert.
* @return A complex feature type which is the equivalent of the descriptor input.
*/
public static FeatureType descriptorGroupToFeatureType(ParameterDescriptorGroup toConvert) {
final FeatureType ct = FeatureTypeExt.toFeatureType(toConvert);
final FeatureTypeBuilder ftb = new FeatureTypeBuilder(ct);
if(NamesExt.getNamespace(ftb.getName()) == null) {
ftb.setName("constellation-sdi/WS/wps", ftb.getName().tip().toString());
}
final List<PropertyTypeBuilder> properties = ftb.properties();
for(int i = 0 ; i < properties.size(); i++) {
final PropertyTypeBuilder desc = properties.get(i);
if(desc instanceof AttributeTypeBuilder){
final AttributeTypeBuilder atb = (AttributeTypeBuilder) desc;
Class binded = atb.getValueClass();
if(RenderedImage.class.isAssignableFrom(binded) ||
Coverage.class.isAssignableFrom(binded) ||
File.class.isAssignableFrom(binded)) {
atb.setValueClass(URL.class);
}
}
}
return ftb.build();
}
/**
* Convert a feature into a ParameterValueGroup.
*
* This function will try to convert objects if their types doesn't match between feature and parameter.
* Then fill a ParameterValueGroup which contains data of the feature in parameter.
*
* @param version WPS version
* @param toConvert The feature to transform.
* @param toFill The descriptor of the ParameterValueGroup which will be created.
*/
public static void featureToParameterGroup(String version, Feature toConvert, ParameterValueGroup toFill) throws UnconvertibleObjectException {
ArgumentChecks.ensureNonNull("feature", toConvert);
ArgumentChecks.ensureNonNull("ParameterGroup", toFill);
final WPSConverterRegistry registry = WPSConverterRegistry.getInstance();
for (final GeneralParameterDescriptor gpd : toFill.getDescriptor().descriptors()) {
if (gpd instanceof ParameterDescriptor) {
final Property prop = toConvert.getProperty(gpd.getName().getCode());
if (prop == null && gpd.getMinimumOccurs() > 0) {
throw new UnconvertibleObjectException("A mandatory attribute can't be found");
}
final ParameterDescriptor desc = (ParameterDescriptor) gpd;
if (prop.getValue().getClass().isAssignableFrom(desc.getValueClass()) || desc.getValueClass().isAssignableFrom(prop.getValue().getClass())) {
Parameters.getOrCreate(desc, toFill).setValue(prop.getValue());
} else {
if (prop.getValue().getClass().isAssignableFrom(URI.class)) {
Reference type = UriToReference(version, (URI) prop.getValue(), WPSIO.IOType.INPUT, null);
WPSObjectConverter converter = registry.getConverter(type.getClass(), desc.getValueClass());
Parameters.getOrCreate(desc, toFill).setValue(converter.convert(type, null));
}
}
} else if (gpd instanceof ParameterDescriptorGroup) {
final Collection<Feature> propCollection = (Collection<Feature>) toConvert.getPropertyValue(gpd.getName().getCode());
int filledGroups = 0;
for (Feature complex : propCollection) {
ParameterValueGroup childGroup = toFill.addGroup(gpd.getName().getCode());
featureToParameterGroup(version, complex, childGroup);
filledGroups++;
}
if(filledGroups < gpd.getMinimumOccurs()) {
throw new UnconvertibleObjectException("Not enough attributes have been found.");
}
} else {
throw new UnconvertibleObjectException("Parameter type is not managed.");
}
}
}
/**
* Convert an URI into a wps reference.
*
* @param version WPS version
* @param toConvert The source URI.
* @param type The type of reference (input or output), can be null.
* @param mimeType Mime type of the data pointed by the URI, can be null.
*
* @return A reference equivalent to the input URI.
*/
public static Reference UriToReference(String version, URI toConvert, WPSIO.IOType type, String mimeType) {
Reference ref;
if(WPSIO.IOType.INPUT.equals(type)) {
ref = WPSXmlFactory.buildInputReference(version, "UTF-8", mimeType, toConvert.toString());
} else {
ref = WPSXmlFactory.buildOutputReference(version, "UTF-8", mimeType, toConvert.toString());
}
return ref;
}
/**
* Check if a CRS has longitude first or not.
* @param crs
* @return
*/
public static boolean isLongFirst(CoordinateReferenceSystem crs) {
final CoordinateReferenceSystem hcrs = CRS.getHorizontalComponent(crs);
final CoordinateSystem cs = hcrs.getCoordinateSystem();
final CoordinateSystemAxis csa = cs.getAxis(0);
if (csa.getDirection().equals(AxisDirection.NORTH) || csa.getDirection().equals(AxisDirection.SOUTH)) {
return false;
} else {
return true;
}
}
/**
* Extract CRS code like "EPSG:4326".
* @param crs
* @return
*/
public static String encodeCRS(CoordinateReferenceSystem crs) {
final Set<Identifier> identifiers = crs.getIdentifiers();
final Identifier id = !identifiers.isEmpty() ? identifiers.iterator().next() : null;
if (id != null) {
final Collection<Identifier> authIdentifier = (Collection<Identifier>) id.getAuthority().getIdentifiers();
final Identifier authId = !authIdentifier.isEmpty() ? authIdentifier.iterator().next() : null;
return authId.getCode() + ":" + id.getCode();
}
return null;
}
/**
* Unzip an archive which must contain a coverage (1 image + 0-->n metadata files). The decompression is made in a
* temporary directory.
* @param archive The archive to unzip.
* @return The image file of the archive.
* @throws IOException If there's a problem while decompression, or if we can't create a new temporary folder.
*/
public static Path unzipCoverage(Path archive) throws IOException {
Path inputFile = null;
// User should have sent data as a zip archive. What we need to do is unzip it in a folder and identify the image.
if (archive == null || !Files.exists(archive)) {
throw new IOException("No input data have been found.");
}
final Path tmpDir = Files.createTempDirectory(null);
List<Path> inputFiles = ZipUtilities.unzip(archive, tmpDir, null);
// Try to find the image file to treat
// TODO : Change for a better verification. Here we should get a specific parameter with the image file name.
for (Path f : inputFiles) {
String path = f.toAbsolutePath().toString();
if (path.endsWith(".tif") || path.endsWith(".TIF") || path.endsWith(".tiff") || path.endsWith(".TIFF")
|| path.endsWith("jp2") || path.endsWith("JP2")
|| path.endsWith(".png") || path.endsWith(".PNG")
|| path.endsWith(".jpg") || path.endsWith(".JPG")) {
inputFile = f;
break;
}
}
//If list is empty, the source file is not an archive, so we put it as only file for input.
if(inputFile == null && inputFiles.isEmpty()) {
inputFile = archive;
}
return inputFile;
}
/**
* Ensure that an URL points to a file on the filesystem.
*
* If it is a remote file, it will be copied and then an URL to this local
* file will be returned
*
* @param uri uri of a remote or a file on the filesystem
* @return an uri of file on the filesystem
*/
private static final URI makeLocalURL(final URI uri) throws URISyntaxException, IOException {
ArgumentChecks.ensureNonNull("uri", uri);
// This condition detects that the file is not on the filesystem
// based on the java.io.File(URI uri) constructor
String scheme = uri.getScheme();
if ((scheme == null) || !scheme.equalsIgnoreCase("file")) {
final String toStringUrl = uri.toString();
final String extension = toStringUrl.substring(toStringUrl.lastIndexOf("."), toStringUrl.length());
// Create a temporary file
final Path tmpFilePath = Files.createTempFile(UUID.randomUUID().toString(), extension);
// Copy the content of the remote file into the file on the local filesystem
try (InputStream remoteStream = IOUtilities.open(uri)) {
IOUtilities.writeStream(remoteStream, tmpFilePath);
}
return tmpFilePath.toUri();
}
return uri;
}
/**
* Read one feature from a GeoJSON file containing exactly one feature
* @param uri location of the file to read
* @return the read feature
* @throws DataStoreException when there are more than one feature in the file
* or when errors occurs while reading
*/
public static final Feature readFeatureFromJson(final URI uri) throws DataStoreException, URISyntaxException, IOException {
ParameterValueGroup param = GeoJSONFeatureStoreFactory.PARAMETERS_DESCRIPTOR.createValue();
param.parameter(PATH.getName().getCode()).setValue(makeLocalURL(uri));
FeatureStore store = (FeatureStore) DataStores.open(param);
if (store == null)
throw new DataStoreException("No available factory found");
Iterator<GenericName> iterator = store.getNames().iterator();
Session session = store.createSession(false);
int typesNumber = store.getNames().size();
if (typesNumber != 1)
throw new UnconvertibleObjectException("Expected one feature. Found " + typesNumber);
GenericName name = iterator.next();
FeatureCollection featureCollection = session.getFeatureCollection(QueryBuilder.all(name));
if (featureCollection.size() != 1)
throw new DataStoreException("One feature expected. Found " + featureCollection.size());
try (FeatureIterator featureIterator = featureCollection.iterator()) {
return featureIterator.next();
}
}
/**
* Read one feature collection from a GeoJSON file containing one feature collection
* @param url location of the file to read
* @return the read feature collection
* @throws DataStoreException when no feature collection has been found,
* when more than one feature collection has been found or when an error
* occurs while reading the json file
*/
public static final FeatureCollection readFeatureCollectionFromJson(final URI url) throws DataStoreException, URISyntaxException, IOException {
final ParameterValueGroup param = GeoJSONFeatureStoreFactory.PARAMETERS_DESCRIPTOR.createValue();
param.parameter(PATH.getName().getCode()).setValue(makeLocalURL(url));
final FeatureStore store = (FeatureStore) DataStores.open(param);
if (store == null)
throw new DataStoreException("No available factory found");
final Iterator<GenericName> iterator = store.getNames().iterator();
int typesNumber = store.getNames().size();
final Session session = store.createSession(false);
if (typesNumber != 1)
throw new DataStoreException("One feature expected. Found " + typesNumber);
final GenericName name = iterator.next();
return session.getFeatureCollection(QueryBuilder.all(name));
}
/**
* Helper method which extracts the GeoJSONObject from a String.
*
* @param content content string containing a GeoJSONObject
* @return a GeoJSONObject
* @throws java.io.IOException on reading errors
*/
public static GeoJSONObject readGeoJSONObjectsFromString(String content) throws IOException {
ArgumentChecks.ensureNonEmpty("content", content);
// Parse GeoJSON
try (InputStream inputStream = new ByteArrayInputStream(content.getBytes())) {
return GeoJSONParser.parse(inputStream);
}
}
/**
* Convert a GeoJSONGeometry to a Geometry object and take count of the CRS.
*
* If no crs can be found in the json a default crs will be applied.
* The default crs is WGS84
*
* @param jsonGeometry the json geometry to convert
* @return a geometry converted from the provided json
*
* @throws FactoryException when an error occur while decoding a CRS
*/
public static Geometry convertGeoJSONGeometryToGeometry(GeoJSONGeometry jsonGeometry) throws FactoryException, MalformedURLException {
// Check that the json defines a crs, otherwise set a
// default one as indicated by the GeoJSON specification
CoordinateReferenceSystem crs = null;
if (jsonGeometry.getCrs() != null)
crs = jsonGeometry.getCrs().getCRS();
else
crs = CommonCRS.WGS84.geographic();
return GeometryUtils.toJTS(jsonGeometry, crs);
}
/**
* Helper method that encapsulates a String into an XML CDATA section before
* adding it to the content of a ComplexDataType instance.
*
* @param content content to put in a CDATA section
* @param complex complex in which to add the CDATA section
*/
public static void addCDATAToComplex(final String content, final ComplexDataType complex) {
complex.getContent().add(new GeoJSONType(content));
}
/**
* Helper method that clean a list from all Strings containing only whitespace
*
* @param contentList the list to clean
*
*/
public static void removeWhiteSpaceFromList(final List<Object> contentList) {
if (contentList != null) {
final Iterator<Object> ite = contentList.iterator();
while (ite.hasNext()) {
final Object obj = ite.next();
if (obj == null || (obj instanceof String && ((String) obj).trim().isEmpty())) {
ite.remove();
}
}
}
}
/**
* Extract the GeoJSON content of a complex and return it as a String.
*
* Pre-condition : the complex must have exactly one content element
* Pre-condition : the content must be either of the type GeoJSONType, String
* or Node
*
* @param complex the complex to read
* @return the complex content as a String
*/
public static String extractGeoJSONContentAsStringFromComplex(final ComplexDataType complex) {
ArgumentChecks.ensureNonNull("complex", complex);
if (complex.getContent().size() != 1)
throw new UnconvertibleObjectException("Expected a complex with a content size of 1. Actual content size : " + complex.getContent().size());
final Object objContent = complex.getContent().get(0);
if (objContent instanceof GeoJSONType)
return ((GeoJSONType)objContent).getContent();
else if (objContent instanceof String)
return (String) objContent;
else if (objContent instanceof Node)
return ((Node) objContent).getTextContent();
else
throw new UnconvertibleObjectException("Expected content type was "
+ GeoJSONType.class.getName()
+ ", Node or String. Actual content type : "
+ objContent.getClass().getName());
}
/**
* Write a temporary file with the .json extension
* @param fileContent the content to write in the file
* @return a Path to the temporary file created
* @throws IOException when errors occur while writing the file
*/
public static Path writeTempJsonFile(String fileContent) throws IOException {
final Path tmpFilePath = Files.createTempFile(UUID.randomUUID().toString(), ".json");
IOUtilities.writeString(fileContent, tmpFilePath);
return tmpFilePath;
}
}