/*
* Copyright (c) 2012 Data Harmonisation Panel
*
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution. If not, see <http://www.gnu.org/licenses/>.
*
* Contributors:
* HUMBOLDT EU Integrated Project #030962
* Data Harmonisation Panel <http://www.dhpanel.eu>
*/
package eu.esdihumboldt.hale.ui.views.styledmap.util;
import java.util.HashMap;
import java.util.Map;
import org.geotools.geometry.DirectPosition2D;
import org.geotools.referencing.CRS;
import org.opengis.geometry.DirectPosition;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.cs.AxisDirection;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import de.fhg.igd.geom.BoundingBox;
import de.fhg.igd.geom.Point3D;
/**
* Geotools based CRS converter.
*
* @author Simon Templer
*/
public class CRSConverter {
/**
* Thread local direct position initialized with a {@link DirectPosition2D}.
*/
public static class ThreadLocalDirectPosition2D extends ThreadLocal<DirectPosition> {
@Override
protected DirectPosition initialValue() {
return new DirectPosition2D();
}
}
/**
* Create a CRS converter between the given coordinate reference systems.
*
* @param source the source CRS
* @param target the target CRS
* @return the CRS converter
* @throws FactoryException if creating the transformer fails
*/
public synchronized static CRSConverter getConverter(CoordinateReferenceSystem source,
CoordinateReferenceSystem target) throws FactoryException {
CRSConverter converter = null;
// try retrieving converter from map
Map<CoordinateReferenceSystem, CRSConverter> sourceConverters = converters.get(source);
if (sourceConverters != null) {
converter = sourceConverters.get(target);
}
if (converter == null) {
// create and store the converter
converter = new CRSConverter(source, target);
if (sourceConverters == null) {
sourceConverters = new HashMap<CoordinateReferenceSystem, CRSConverter>();
converters.put(source, sourceConverters);
}
sourceConverters.put(target, converter);
}
return converter;
}
/**
* Source CRS mapped to target CRS mapped to converter
*/
private static final Map<CoordinateReferenceSystem, Map<CoordinateReferenceSystem, CRSConverter>> converters = new HashMap<CoordinateReferenceSystem, Map<CoordinateReferenceSystem, CRSConverter>>();
private final CoordinateReferenceSystem source;
private final CoordinateReferenceSystem target;
private final boolean initialFlip;
private final boolean finalFlip;
private final MathTransform math;
/*
* temporary positions to lower the impact on GC
*/
private final ThreadLocal<DirectPosition> _tempPos2A = new ThreadLocalDirectPosition2D();
private final ThreadLocal<DirectPosition> _tempPos2B = new ThreadLocalDirectPosition2D();
/**
* Create a CRS converter between the given coordinate reference systems.
*
* @param source the source CRS
* @param target the target CRS
* @throws FactoryException if creating the transformer fails
*/
private CRSConverter(CoordinateReferenceSystem source, CoordinateReferenceSystem target)
throws FactoryException {
this.source = source;
this.target = target;
// this.math = CRS.findMathTransform(source, target);
// XXX lenient mode to find transformation also if Bursa Wolf parameters
// are not present (ok for display according to Geotools)
// http://docs.geotools.org/latest/userguide/faq.html#q-bursa-wolf-parameters-required
// XXX does this error only occur for CRS related to shapefiles?
this.math = CRS.findMathTransform(source, target, true);
/*
* XXX do not flip the coordinates - the math transformation should
* handle it correctly, because it is based on the CRS definitions
*/
this.initialFlip = false; // flipCRS(source);
this.finalFlip = false; // flipCRS(target);
}
/**
* Convert a bounding box.
*
* @param bb the bounding box in the source CRS
* @return the bounding box in the target CRS
* @throws TransformException if the conversion fails
*/
public BoundingBox convert(BoundingBox bb) throws TransformException {
Point3D targetCorners[] = { null, null };
targetCorners[0] = convert(bb.getMinX(), bb.getMinY(), bb.getMinZ());
targetCorners[1] = convert(bb.getMaxX(), bb.getMaxY(), bb.getMaxZ());
return new BoundingBox(//
targetCorners[0].getX(), //
targetCorners[0].getY(), //
targetCorners[0].getZ(), //
targetCorners[1].getX(), //
targetCorners[1].getY(), //
targetCorners[1].getZ());
}
/**
* This method checks if the the CoordinateSystem is in the way we expected,
* or if we have to flip the coordinates.
*
* @param crs The CRS to be checked.
* @return True, if we have to flip the coordinates
*/
@SuppressWarnings("unused")
private boolean flipCRS(CoordinateReferenceSystem crs) {
if (crs.getCoordinateSystem().getDimension() == 2) {
AxisDirection direction = crs.getCoordinateSystem().getAxis(0).getDirection();
if (direction.equals(AxisDirection.NORTH) || direction.equals(AxisDirection.UP)) {
return true;
}
}
return false;
}
/**
* This method converts the given coordinates and returns them as a Point3D.
*
* @param x the x ordinate
* @param y the y ordinate
* @param z the z ordinate
* @return The converted coordinates as a Point3D.
* @throws TransformException if the coordinate transformation fails
*/
public Point3D convert(double x, double y, double z) throws TransformException {
DirectPosition position = _tempPos2A.get();
if (this.initialFlip) {
position.setOrdinate(0, y);
position.setOrdinate(1, x);
}
else {
position.setOrdinate(0, x);
position.setOrdinate(1, y);
}
DirectPosition targetPosition = _tempPos2B.get();
math.transform(position, targetPosition);
return createPoint3D(targetPosition.getOrdinate(0), targetPosition.getOrdinate(1), z);
}
/**
* This method creates a Point3D from the given coordinates.
*
* @param x The X axis value of the coordinate.
* @param y The Y axis value of the coordinate.
* @param z The Z axis value of the coordinate.
* @return A Point3D from the given coordinates.
*/
private Point3D createPoint3D(double x, double y, double z) {
if (finalFlip) {
return new Point3D(y, x, z);
}
return new Point3D(x, y, z);
}
/**
* @return the source coordinate reference system
*/
public CoordinateReferenceSystem getSource() {
return source;
}
/**
* @return the target coordinate reference system
*/
public CoordinateReferenceSystem getTarget() {
return target;
}
}