/**
* H2GIS is a library that brings spatial support to the H2 Database Engine
* <http://www.h2database.com>. H2GIS is developed by CNRS
* <http://www.cnrs.fr/>.
*
* This code is part of the H2GIS project. H2GIS 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;
* version 3.0 of the License.
*
* H2GIS 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 <http://www.gnu.org/licenses/>.
*
*
* For more information, please consult: <http://www.h2gis.org/>
* or contact directly: info_at_h2gis.org
*/
package org.h2gis.functions.spatial.crs;
import com.vividsolutions.jts.geom.*;
import org.cts.CRSFactory;
import org.cts.IllegalCoordinateException;
import org.cts.crs.CRSException;
import org.cts.crs.CoordinateReferenceSystem;
import org.cts.crs.GeodeticCRS;
import org.cts.op.CoordinateOperation;
import org.cts.op.CoordinateOperationFactory;
import org.h2gis.api.AbstractFunction;
import org.h2gis.api.ScalarFunction;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* This class is used to transform a geometry from one CRS to another.
* Only integer codes available in the spatial_ref_sys table are allowed.
* The default source CRS is the input geometry's internal CRS.
*
* @author Erwan Bocher
* @author Adam Gouge
*/
public class ST_Transform extends AbstractFunction implements ScalarFunction {
private static CRSFactory crsf;
private static SpatialRefRegistry srr = new SpatialRefRegistry();
private static Map<EPSGTuple, CoordinateOperation> copPool = new CopCache(5);
/**
* Constructor
*/
public ST_Transform() {
addProperty(PROP_REMARKS, "Transform a geometry from one CRS to another " +
"using integer codes from the SPATIAL_REF_SYS table.");
}
@Override
public String getJavaStaticMethod() {
return "ST_Transform";
}
/**
* Returns a new geometry transformed to the SRID referenced by the integer
* parameter available in the spatial_ref_sys table
* @param connection
* @param geom
* @param codeEpsg
* @return
* @throws SQLException
*/
public static Geometry ST_Transform(Connection connection, Geometry geom, Integer codeEpsg) throws SQLException {
if (geom == null) {
return null;
}
if (codeEpsg == null) {
throw new IllegalArgumentException("The SRID code cannot be null.");
}
if (crsf == null) {
crsf = new CRSFactory();
//Activate the CRSFactory and the internal H2 spatial_ref_sys registry to
// manage Coordinate Reference Systems.
crsf.getRegistryManager().addRegistry(srr);
}
srr.setConnection(connection);
try {
int inputSRID = geom.getSRID();
if (inputSRID == 0) {
throw new SQLException("Cannot find a CRS");
} else {
CoordinateReferenceSystem inputCRS = crsf.getCRS(srr.getRegistryName() + ":" + String.valueOf(inputSRID));
CoordinateReferenceSystem targetCRS = crsf.getCRS(srr.getRegistryName() + ":" + String.valueOf(codeEpsg));
if (inputCRS.equals(targetCRS)) {
return geom;
}
EPSGTuple epsg = new EPSGTuple(inputSRID, codeEpsg);
CoordinateOperation op = copPool.get(epsg);
if (op != null) {
Geometry outPutGeom = (Geometry) geom.clone();
outPutGeom.apply(new CRSTransformFilter(op));
outPutGeom.setSRID(codeEpsg);
return outPutGeom;
} else {
if (inputCRS instanceof GeodeticCRS && targetCRS instanceof GeodeticCRS) {
List<CoordinateOperation> ops = CoordinateOperationFactory
.createCoordinateOperations((GeodeticCRS) inputCRS, (GeodeticCRS) targetCRS);
if (!ops.isEmpty()) {
op = ops.get(0);
Geometry outPutGeom = (Geometry) geom.clone();
outPutGeom.apply(new CRSTransformFilter(op));
copPool.put(epsg, op);
outPutGeom.setSRID(codeEpsg);
return outPutGeom;
}
} else {
throw new SQLException("The transformation from "
+ inputCRS + " to " + codeEpsg + " is not yet supported.");
}
}
}
} catch (CRSException ex) {
throw new SQLException("Cannot create the CRS", ex);
} finally {
srr.setConnection(null);
}
return null;
}
/**
* This method is used to apply a {@link CoordinateOperation} to a geometry.
* The transformation loops on each coordinate.
*/
public static class CRSTransformFilter implements CoordinateFilter{
private final CoordinateOperation coordinateOperation;
public CRSTransformFilter(final CoordinateOperation coordinateOperation){
this.coordinateOperation=coordinateOperation;
}
@Override
public void filter(Coordinate coord) {
try {
if (Double.isNaN(coord.z)) {
coord.z = 0;
}
double[] xyz = coordinateOperation
.transform(new double[]{coord.x, coord.y, coord.z});
coord.x = xyz[0];
coord.y = xyz[1];
if (xyz.length > 2) {
coord.z = xyz[2];
} else {
coord.z = Double.NaN;
}
} catch (IllegalCoordinateException ice) {
throw new RuntimeException("Cannot transform the coordinate" + coord.toString(), ice);
}
}
}
/**
* A simple cache to manage {@link CoordinateOperation}
*/
public static class CopCache extends LinkedHashMap<EPSGTuple, CoordinateOperation> {
private final int limit;
public CopCache(int limit) {
super(16, 0.75f, true);
this.limit = limit;
}
@Override
protected boolean removeEldestEntry(Map.Entry<EPSGTuple, CoordinateOperation> eldest) {
return size() > limit;
}
}
}