0001: /*
0002: * GeoTools - OpenSource mapping toolkit
0003: * http://geotools.org
0004: * (C) 2004-2006, GeoTools Project Managment Committee (PMC)
0005: * (C) 2001, Institut de Recherche pour le Développement
0006: *
0007: * This library is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU Lesser General Public
0009: * License as published by the Free Software Foundation;
0010: * version 2.1 of the License.
0011: *
0012: * This library is distributed in the hope that it will be useful,
0013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0015: * Lesser General Public License for more details.
0016: *
0017: * This package contains documentation from OpenGIS specifications.
0018: * OpenGIS consortium's work is fully acknowledged here.
0019: */
0020: package org.geotools.referencing.operation;
0021:
0022: // J2SE dependencies and extensions
0023: import java.util.Map;
0024: import javax.units.NonSI;
0025: import javax.units.SI;
0026: import javax.units.Unit;
0027: import javax.vecmath.SingularMatrixException;
0028:
0029: // OpenGIS dependencies
0030: import org.opengis.parameter.ParameterValueGroup;
0031: import org.opengis.referencing.FactoryException;
0032: import org.opengis.referencing.IdentifiedObject;
0033: import org.opengis.referencing.ReferenceIdentifier;
0034: import org.opengis.referencing.cs.*;
0035: import org.opengis.referencing.crs.*;
0036: import org.opengis.referencing.datum.*;
0037: import org.opengis.referencing.operation.*;
0038:
0039: // Geotools dependencies
0040: import org.geotools.factory.Hints;
0041: import org.geotools.referencing.AbstractIdentifiedObject;
0042: import org.geotools.referencing.crs.DefaultCompoundCRS;
0043: import org.geotools.referencing.crs.DefaultEngineeringCRS;
0044: import org.geotools.referencing.cs.DefaultCartesianCS;
0045: import org.geotools.referencing.cs.DefaultEllipsoidalCS;
0046: import org.geotools.referencing.datum.BursaWolfParameters;
0047: import org.geotools.referencing.datum.DefaultGeodeticDatum;
0048: import org.geotools.referencing.datum.DefaultPrimeMeridian;
0049: import org.geotools.referencing.operation.matrix.XMatrix;
0050: import org.geotools.referencing.operation.matrix.Matrix4;
0051: import org.geotools.referencing.operation.matrix.MatrixFactory;
0052: import org.geotools.referencing.factory.FactoryGroup;
0053: import org.geotools.resources.Utilities;
0054: import org.geotools.resources.i18n.Errors;
0055: import org.geotools.resources.i18n.ErrorKeys;
0056:
0057: /**
0058: * Creates {@linkplain CoordinateOperation coordinate operations}. This factory is capable to find
0059: * coordinate {@linkplain Transformation transformations} or {@linkplain Conversion conversions}
0060: * between two {@linkplain CoordinateReferenceSystem coordinate reference systems}. It delegates
0061: * most of its work to one or many of {@code createOperationStep} methods. Subclasses can
0062: * override those methods in order to extend the factory capability to some more CRS.
0063: *
0064: * @since 2.1
0065: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/operation/DefaultCoordinateOperationFactory.java $
0066: * @version $Id: DefaultCoordinateOperationFactory.java 25755 2007-06-06 11:14:58Z desruisseaux $
0067: * @author Martin Desruisseaux
0068: *
0069: * @tutorial http://docs.codehaus.org/display/GEOTOOLS/Coordinate+Transformation+Services+for+Geotools+2.1
0070: */
0071: public class DefaultCoordinateOperationFactory extends
0072: AbstractCoordinateOperationFactory {
0073: /**
0074: * The priority level for this factory.
0075: */
0076: static final int PRIORITY = NORMAL_PRIORITY;
0077:
0078: /**
0079: * A unit of one millisecond.
0080: */
0081: private static final Unit MILLISECOND = SI.MILLI(SI.SECOND);
0082:
0083: /**
0084: * The operation to use by {@link #createTransformationStep(GeographicCRS,GeographicCRS)} for
0085: * datum shift. This string can have one of the following values:
0086: * <p>
0087: * <ul>
0088: * <li><code>"Abridged_Molodenski"</code> for the abridged Molodenski transformation.</li>
0089: * <li><code>"Molodenski"</code> for the Molodenski transformation.</li>
0090: * <li>{@code null} for performing datum shifts is geocentric coordinates.</li>
0091: * </ul>
0092: */
0093: private final String molodenskiMethod;
0094:
0095: /**
0096: * {@code true} if datum shift are allowed even if no Bursa Wolf parameters is available.
0097: */
0098: private final boolean lenientDatumShift;
0099:
0100: /**
0101: * Constructs a coordinate operation factory using the default factories.
0102: */
0103: public DefaultCoordinateOperationFactory() {
0104: this (null);
0105: }
0106:
0107: /**
0108: * Constructs a coordinate operation factory using the specified hints.
0109: * This constructor recognizes the {@link Hints#CRS_FACTORY CRS}, {@link Hints#CS_FACTORY CS},
0110: * {@link Hints#DATUM_FACTORY DATUM} and {@link Hints#MATH_TRANSFORM_FACTORY MATH_TRANSFORM}
0111: * {@code FACTORY} hints.
0112: *
0113: * @param userHints The hints, or {@code null} if none.
0114: */
0115: public DefaultCoordinateOperationFactory(final Hints userHints) {
0116: this (userHints, PRIORITY);
0117: }
0118:
0119: /**
0120: * Constructs a coordinate operation factory using the specified hints and priority.
0121: * This constructor recognizes the {@link Hints#CRS_FACTORY CRS}, {@link Hints#CS_FACTORY CS},
0122: * {@link Hints#DATUM_FACTORY DATUM} and {@link Hints#MATH_TRANSFORM_FACTORY MATH_TRANSFORM}
0123: * {@code FACTORY} hints.
0124: *
0125: * @param userHints The hints, or {@code null} if none.
0126: * @param priority The priority for this factory, as a number between
0127: * {@link #MINIMUM_PRIORITY MINIMUM_PRIORITY} and
0128: * {@link #MAXIMUM_PRIORITY MAXIMUM_PRIORITY} inclusive.
0129: *
0130: * @since 2.2
0131: */
0132: public DefaultCoordinateOperationFactory(final Hints userHints,
0133: final int priority) {
0134: super (userHints, priority);
0135: //
0136: // Default hints values
0137: //
0138: String molodenskiMethod = "Molodenski"; // Alternative: "Abridged_Molodenski"
0139: boolean lenientDatumShift = false;
0140: //
0141: // Fetchs the user-supplied hints
0142: //
0143: if (userHints != null) {
0144: Object candidate = userHints.get(Hints.DATUM_SHIFT_METHOD);
0145: if (candidate instanceof String) {
0146: molodenskiMethod = (String) candidate;
0147: if (molodenskiMethod.trim().equalsIgnoreCase(
0148: "Geocentric")) {
0149: molodenskiMethod = null;
0150: }
0151: }
0152: candidate = userHints.get(Hints.LENIENT_DATUM_SHIFT);
0153: if (candidate instanceof Boolean) {
0154: lenientDatumShift = ((Boolean) candidate)
0155: .booleanValue();
0156: }
0157: }
0158: //
0159: // Stores the retained hints
0160: //
0161: this .molodenskiMethod = molodenskiMethod;
0162: this .lenientDatumShift = lenientDatumShift;
0163: this .hints.put(Hints.DATUM_SHIFT_METHOD, molodenskiMethod);
0164: this .hints.put(Hints.LENIENT_DATUM_SHIFT, Boolean
0165: .valueOf(lenientDatumShift));
0166: }
0167:
0168: /**
0169: * Returns an operation for conversion or transformation between two coordinate reference
0170: * systems. If an operation exists, it is returned. If more than one operation exists, the
0171: * default is returned. If no operation exists, then the exception is thrown.
0172: * <P>
0173: * The default implementation inspects the CRS and delegates the work to one or
0174: * many {@code createOperationStep(...)} methods. This method fails if no path
0175: * between the CRS is found.
0176: *
0177: * @param sourceCRS Input coordinate reference system.
0178: * @param targetCRS Output coordinate reference system.
0179: * @return A coordinate operation from {@code sourceCRS} to {@code targetCRS}.
0180: * @throws OperationNotFoundException if no operation path was found from {@code sourceCRS}
0181: * to {@code targetCRS}.
0182: * @throws FactoryException if the operation creation failed for some other reason.
0183: */
0184: public CoordinateOperation createOperation(
0185: final CoordinateReferenceSystem sourceCRS,
0186: final CoordinateReferenceSystem targetCRS)
0187: throws OperationNotFoundException, FactoryException {
0188: ensureNonNull("sourceCRS", sourceCRS);
0189: ensureNonNull("targetCRS", targetCRS);
0190: if (equalsIgnoreMetadata(sourceCRS, targetCRS)) {
0191: final int dim = getDimension(sourceCRS);
0192: assert dim == getDimension(targetCRS) : dim;
0193: return createFromAffineTransform(IDENTITY, sourceCRS,
0194: targetCRS, MatrixFactory.create(dim + 1));
0195: } else {
0196: // Query the database (if any) before to try to find the operation by ourself.
0197: final CoordinateOperation candidate = createFromDatabase(
0198: sourceCRS, targetCRS);
0199: if (candidate != null) {
0200: return candidate;
0201: }
0202: }
0203: /////////////////////////////////////////////////////////////////////
0204: //// ////
0205: //// Geographic --> Geographic, Projected or Geocentric ////
0206: //// ////
0207: /////////////////////////////////////////////////////////////////////
0208: if (sourceCRS instanceof GeographicCRS) {
0209: final GeographicCRS source = (GeographicCRS) sourceCRS;
0210: if (targetCRS instanceof GeographicCRS) {
0211: final GeographicCRS target = (GeographicCRS) targetCRS;
0212: return createOperationStep(source, target);
0213: }
0214: if (targetCRS instanceof ProjectedCRS) {
0215: final ProjectedCRS target = (ProjectedCRS) targetCRS;
0216: return createOperationStep(source, target);
0217: }
0218: if (targetCRS instanceof GeocentricCRS) {
0219: final GeocentricCRS target = (GeocentricCRS) targetCRS;
0220: return createOperationStep(source, target);
0221: }
0222: if (targetCRS instanceof VerticalCRS) {
0223: final VerticalCRS target = (VerticalCRS) targetCRS;
0224: return createOperationStep(source, target);
0225: }
0226: }
0227: /////////////////////////////////////////////////////////
0228: //// ////
0229: //// Projected --> Projected or Geographic ////
0230: //// ////
0231: /////////////////////////////////////////////////////////
0232: if (sourceCRS instanceof ProjectedCRS) {
0233: final ProjectedCRS source = (ProjectedCRS) sourceCRS;
0234: if (targetCRS instanceof ProjectedCRS) {
0235: final ProjectedCRS target = (ProjectedCRS) targetCRS;
0236: return createOperationStep(source, target);
0237: }
0238: if (targetCRS instanceof GeographicCRS) {
0239: final GeographicCRS target = (GeographicCRS) targetCRS;
0240: return createOperationStep(source, target);
0241: }
0242: }
0243: //////////////////////////////////////////////////////////
0244: //// ////
0245: //// Geocentric --> Geocentric or Geographic ////
0246: //// ////
0247: //////////////////////////////////////////////////////////
0248: if (sourceCRS instanceof GeocentricCRS) {
0249: final GeocentricCRS source = (GeocentricCRS) sourceCRS;
0250: if (targetCRS instanceof GeocentricCRS) {
0251: final GeocentricCRS target = (GeocentricCRS) targetCRS;
0252: return createOperationStep(source, target);
0253: }
0254: if (targetCRS instanceof GeographicCRS) {
0255: final GeographicCRS target = (GeographicCRS) targetCRS;
0256: return createOperationStep(source, target);
0257: }
0258: }
0259: /////////////////////////////////////////
0260: //// ////
0261: //// Vertical --> Vertical ////
0262: //// ////
0263: /////////////////////////////////////////
0264: if (sourceCRS instanceof VerticalCRS) {
0265: final VerticalCRS source = (VerticalCRS) sourceCRS;
0266: if (targetCRS instanceof VerticalCRS) {
0267: final VerticalCRS target = (VerticalCRS) targetCRS;
0268: return createOperationStep(source, target);
0269: }
0270: }
0271: /////////////////////////////////////////
0272: //// ////
0273: //// Temporal --> Temporal ////
0274: //// ////
0275: /////////////////////////////////////////
0276: if (sourceCRS instanceof TemporalCRS) {
0277: final TemporalCRS source = (TemporalCRS) sourceCRS;
0278: if (targetCRS instanceof TemporalCRS) {
0279: final TemporalCRS target = (TemporalCRS) targetCRS;
0280: return createOperationStep(source, target);
0281: }
0282: }
0283: //////////////////////////////////////////////////////////////////
0284: //// ////
0285: //// Any coordinate reference system --> Derived CRS ////
0286: //// ////
0287: //////////////////////////////////////////////////////////////////
0288: if (targetCRS instanceof GeneralDerivedCRS) {
0289: // Note: this code is identical to 'createOperationStep(GeographicCRS, ProjectedCRS)'
0290: // except that the later invokes directly the right method for 'step1' instead
0291: // of invoking 'createOperation' recursively.
0292: final GeneralDerivedCRS target = (GeneralDerivedCRS) targetCRS;
0293: final CoordinateReferenceSystem base = target.getBaseCRS();
0294: final CoordinateOperation step1 = createOperation(
0295: sourceCRS, base);
0296: final CoordinateOperation step2 = target
0297: .getConversionFromBase();
0298: return concatenate(step1, step2);
0299: }
0300: //////////////////////////////////////////////////////////////////
0301: //// ////
0302: //// Derived CRS --> Any coordinate reference system ////
0303: //// ////
0304: //////////////////////////////////////////////////////////////////
0305: if (sourceCRS instanceof GeneralDerivedCRS) {
0306: // Note: this code is identical to 'createOperationStep(ProjectedCRS, GeographicCRS)'
0307: // except that the later invokes directly the right method for 'step2' instead
0308: // of invoking 'createOperation' recursively.
0309: final GeneralDerivedCRS source = (GeneralDerivedCRS) sourceCRS;
0310: final CoordinateReferenceSystem base = source.getBaseCRS();
0311: final CoordinateOperation step2 = createOperation(base,
0312: targetCRS);
0313: CoordinateOperation step1 = source.getConversionFromBase();
0314: MathTransform transform = step1.getMathTransform();
0315: try {
0316: transform = transform.inverse();
0317: } catch (NoninvertibleTransformException exception) {
0318: throw new OperationNotFoundException(getErrorMessage(
0319: sourceCRS, base), exception);
0320: }
0321: step1 = createFromMathTransform(INVERSE_OPERATION,
0322: sourceCRS, base, transform);
0323: return concatenate(step1, step2);
0324: }
0325: ////////////////////////////////////////////
0326: //// ////
0327: //// Compound --> various CRS ////
0328: //// ////
0329: ////////////////////////////////////////////
0330: if (sourceCRS instanceof CompoundCRS) {
0331: final CompoundCRS source = (CompoundCRS) sourceCRS;
0332: if (targetCRS instanceof CompoundCRS) {
0333: final CompoundCRS target = (CompoundCRS) targetCRS;
0334: return createOperationStep(source, target);
0335: }
0336: if (targetCRS instanceof SingleCRS) {
0337: final SingleCRS target = (SingleCRS) targetCRS;
0338: return createOperationStep(source, target);
0339: }
0340: }
0341: if (targetCRS instanceof CompoundCRS) {
0342: final CompoundCRS target = (CompoundCRS) targetCRS;
0343: if (sourceCRS instanceof SingleCRS) {
0344: final SingleCRS source = (SingleCRS) sourceCRS;
0345: return createOperationStep(source, target);
0346: }
0347: }
0348: /////////////////////////////////////////
0349: //// ////
0350: //// Generic --> various CS ////
0351: //// Various CS --> Generic ////
0352: //// ////
0353: /////////////////////////////////////////
0354: if (sourceCRS == DefaultEngineeringCRS.GENERIC_2D
0355: || targetCRS == DefaultEngineeringCRS.GENERIC_2D
0356: || sourceCRS == DefaultEngineeringCRS.GENERIC_3D
0357: || targetCRS == DefaultEngineeringCRS.GENERIC_3D) {
0358: final int dimSource = getDimension(sourceCRS);
0359: final int dimTarget = getDimension(targetCRS);
0360: if (dimTarget == dimSource) {
0361: final Matrix matrix = MatrixFactory.create(
0362: dimTarget + 1, dimSource + 1);
0363: return createFromAffineTransform(IDENTITY, sourceCRS,
0364: targetCRS, matrix);
0365: }
0366: }
0367: throw new OperationNotFoundException(getErrorMessage(sourceCRS,
0368: targetCRS));
0369: }
0370:
0371: /**
0372: * Returns an operation using a particular method for conversion or transformation
0373: * between two coordinate reference systems.
0374: * If the operation exists on the implementation, then it is returned.
0375: * If the operation does not exist on the implementation, then the implementation has the option
0376: * of inferring the operation from the argument objects.
0377: * If for whatever reason the specified operation will not be returned, then the exception is
0378: * thrown.
0379: *
0380: * @param sourceCRS Input coordinate reference system.
0381: * @param targetCRS Output coordinate reference system.
0382: * @param method the algorithmic method for conversion or transformation
0383: * @throws OperationNotFoundException if no operation path was found from {@code sourceCRS}
0384: * to {@code targetCRS}.
0385: * @throws FactoryException if the operation creation failed for some other reason.
0386: *
0387: * @deprecated Current implementation ignore the {@code method} argument.
0388: */
0389: public CoordinateOperation createOperation(
0390: final CoordinateReferenceSystem sourceCRS,
0391: final CoordinateReferenceSystem targetCRS,
0392: final OperationMethod method)
0393: throws OperationNotFoundException, FactoryException {
0394: return createOperation(sourceCRS, targetCRS);
0395: }
0396:
0397: /////////////////////////////////////////////////////////////////////////////////
0398: /////////////////////////////////////////////////////////////////////////////////
0399: //////////// ////////////
0400: //////////// N O R M A L I Z A T I O N S ////////////
0401: //////////// ////////////
0402: /////////////////////////////////////////////////////////////////////////////////
0403: /////////////////////////////////////////////////////////////////////////////////
0404:
0405: /**
0406: * Makes sure that the specified geocentric CRS uses standard axis,
0407: * prime meridian and the specified datum.
0408: * If {@code crs} already meets all those conditions, then it is
0409: * returned unchanged. Otherwise, a new normalized geocentric CRS is
0410: * created and returned.
0411: *
0412: * @param crs The geocentric coordinate reference system to normalize.
0413: * @param datum The expected datum.
0414: * @return The normalized coordinate reference system.
0415: * @throws FactoryException if the construction of a new CRS was needed but failed.
0416: */
0417: private GeocentricCRS normalize(final GeocentricCRS crs,
0418: final GeodeticDatum datum) throws FactoryException {
0419: final CartesianCS STANDARD = DefaultCartesianCS.GEOCENTRIC;
0420: final GeodeticDatum candidate = (GeodeticDatum) crs.getDatum();
0421: // TODO: Remove cast once we are allowed to compile against J2SE 1.5.
0422: if (equalsIgnorePrimeMeridian(candidate, datum)) {
0423: if (getGreenwichLongitude(candidate.getPrimeMeridian()) == getGreenwichLongitude(datum
0424: .getPrimeMeridian())) {
0425: if (hasStandardAxis(crs.getCoordinateSystem(), STANDARD)) {
0426: return crs;
0427: }
0428: }
0429: }
0430: final CRSFactory crsFactory = getFactoryGroup().getCRSFactory();
0431: return crsFactory.createGeocentricCRS(getTemporaryName(crs),
0432: datum, STANDARD);
0433: }
0434:
0435: /**
0436: * Makes sure that the specified geographic CRS uses standard axis (longitude and latitude in
0437: * decimal degrees). Optionally, this method can also make sure that the CRS use the Greenwich
0438: * prime meridian. Other datum properties are left unchanged. If {@code crs} already meets all
0439: * those conditions, then it is returned unchanged. Otherwise, a new normalized geographic CRS
0440: * is created and returned.
0441: *
0442: * @param crs The geographic coordinate reference system to normalize.
0443: * @param forceGreenwich {@code true} for forcing the Greenwich prime meridian.
0444: * @return The normalized coordinate reference system.
0445: * @throws FactoryException if the construction of a new CRS was needed but failed.
0446: */
0447: private GeographicCRS normalize(final GeographicCRS crs,
0448: final boolean forceGreenwich) throws FactoryException {
0449: // TODO: remove cast once we will be allowed to compile for J2SE 1.5.
0450: GeodeticDatum datum = (GeodeticDatum) crs.getDatum();
0451: final EllipsoidalCS cs = (EllipsoidalCS) crs
0452: .getCoordinateSystem();
0453: final EllipsoidalCS STANDARD = (cs.getDimension() <= 2) ? DefaultEllipsoidalCS.GEODETIC_2D
0454: : DefaultEllipsoidalCS.GEODETIC_3D;
0455: if (forceGreenwich
0456: && getGreenwichLongitude(datum.getPrimeMeridian()) != 0) {
0457: datum = new TemporaryDatum(datum);
0458: } else if (hasStandardAxis(cs, STANDARD)) {
0459: return crs;
0460: }
0461: /*
0462: * The specified geographic coordinate system doesn't use standard axis
0463: * (EAST, NORTH) or the greenwich meridian. Create a new one meeting those criterions.
0464: */
0465: final CRSFactory crsFactory = getFactoryGroup().getCRSFactory();
0466: return crsFactory.createGeographicCRS(getTemporaryName(crs),
0467: datum, STANDARD);
0468: }
0469:
0470: /**
0471: * A datum identical to the specified datum except for the prime meridian, which is replaced
0472: * by Greenwich. This datum is processed in a special way by {@link #equalsIgnorePrimeMeridian}.
0473: */
0474: private static final class TemporaryDatum extends
0475: DefaultGeodeticDatum {
0476: /** The wrapped datum. */
0477: private final GeodeticDatum datum;
0478:
0479: /** Wrap the specified datum. */
0480: public TemporaryDatum(final GeodeticDatum datum) {
0481: super (getTemporaryName(datum), datum.getEllipsoid(),
0482: DefaultPrimeMeridian.GREENWICH);
0483: this .datum = datum;
0484: }
0485:
0486: /** Unwrap the datum. */
0487: public static GeodeticDatum unwrap(GeodeticDatum datum) {
0488: while (datum instanceof TemporaryDatum) {
0489: datum = ((TemporaryDatum) datum).datum;
0490: }
0491: return datum;
0492: }
0493:
0494: /** Compares this datum with the specified object for equality. */
0495: public boolean equals(final AbstractIdentifiedObject object,
0496: final boolean compareMetadata) {
0497: if (super .equals(object, compareMetadata)) {
0498: final GeodeticDatum other = ((TemporaryDatum) object).datum;
0499: return compareMetadata ? datum.equals(other)
0500: : equalsIgnoreMetadata(datum, other);
0501: }
0502: return false;
0503: }
0504: }
0505:
0506: /**
0507: * Returns {@code true} if the specified coordinate system
0508: * use standard axis and units.
0509: *
0510: * @param crs The coordinate system to test.
0511: * @param standard The coordinate system that defines the standard. Usually
0512: * {@link DefaultEllipsoidalCS#GEODETIC_2D} or
0513: * {@link DefaultCartesianCS#PROJECTED}.
0514: */
0515: private static boolean hasStandardAxis(final CoordinateSystem cs,
0516: final CoordinateSystem standard) {
0517: final int dimension = standard.getDimension();
0518: if (cs.getDimension() != dimension) {
0519: return false;
0520: }
0521: for (int i = 0; i < dimension; i++) {
0522: final CoordinateSystemAxis a1 = cs.getAxis(i);
0523: final CoordinateSystemAxis a2 = standard.getAxis(i);
0524: if (!a1.getDirection().equals(a2.getDirection())
0525: || !a1.getUnit().equals(a2.getUnit())) {
0526: return false;
0527: }
0528: }
0529: return true;
0530: }
0531:
0532: /////////////////////////////////////////////////////////////////////////////////
0533: /////////////////////////////////////////////////////////////////////////////////
0534: //////////// ////////////
0535: //////////// A X I S O R I E N T A T I O N S ////////////
0536: //////////// ////////////
0537: /////////////////////////////////////////////////////////////////////////////////
0538: /////////////////////////////////////////////////////////////////////////////////
0539:
0540: /**
0541: * Returns an affine transform between two ellipsoidal coordinate systems. Only
0542: * units, axis order (e.g. transforming from (NORTH,WEST) to (EAST,NORTH)) and
0543: * prime meridian are taken in account. Other attributes (especially the datum)
0544: * must be checked before invoking this method.
0545: *
0546: * @param sourceCS The source coordinate system.
0547: * @param targetCS The target coordinate system.
0548: * @param sourcePM The source prime meridian.
0549: * @param targetPM The target prime meridian.
0550: * @return The transformation from {@code sourceCS} to {@code targetCS} as
0551: * an affine transform. Only axis orientation, units and prime meridian are
0552: * taken in account.
0553: * @throws OperationNotFoundException If the affine transform can't be constructed.
0554: */
0555: private Matrix swapAndScaleAxis(final EllipsoidalCS sourceCS,
0556: final EllipsoidalCS targetCS, final PrimeMeridian sourcePM,
0557: final PrimeMeridian targetPM)
0558: throws OperationNotFoundException {
0559: final Matrix matrix = swapAndScaleAxis(sourceCS, targetCS);
0560: for (int i = targetCS.getDimension(); --i >= 0;) {
0561: final CoordinateSystemAxis axis = targetCS.getAxis(i);
0562: final AxisDirection direction = axis.getDirection();
0563: if (AxisDirection.EAST.equals(direction.absolute())) {
0564: /*
0565: * A longitude ordinate has been found (i.e. the axis is oriented toward EAST or
0566: * WEST). Compute the amount of angle to add to the source longitude in order to
0567: * get the destination longitude. This amount is measured in units of the target
0568: * axis. The affine transform is then updated in order to take this rotation in
0569: * account. Note that the resulting longitude may be outside the usual [-180..180°]
0570: * range.
0571: */
0572: final Unit unit = axis.getUnit();
0573: final double sourceLongitude = getGreenwichLongitude(
0574: sourcePM, unit);
0575: final double targetLongitude = getGreenwichLongitude(
0576: targetPM, unit);
0577: final int lastMatrixColumn = matrix.getNumCol() - 1;
0578: double rotate = sourceLongitude - targetLongitude;
0579: if (AxisDirection.WEST.equals(direction)) {
0580: rotate = -rotate;
0581: }
0582: rotate += matrix.getElement(i, lastMatrixColumn);
0583: matrix.setElement(i, lastMatrixColumn, rotate);
0584: }
0585: }
0586: return matrix;
0587: }
0588:
0589: /**
0590: * Returns the longitude value relative to the Greenwich Meridian,
0591: * expressed in the specified units.
0592: */
0593: private static double getGreenwichLongitude(final PrimeMeridian pm,
0594: final Unit unit) {
0595: return pm.getAngularUnit().getConverterTo(unit).convert(
0596: pm.getGreenwichLongitude());
0597: }
0598:
0599: /**
0600: * Returns the longitude value relative to the Greenwich Meridian, expressed in decimal degrees.
0601: */
0602: private static double getGreenwichLongitude(final PrimeMeridian pm) {
0603: return getGreenwichLongitude(pm, NonSI.DEGREE_ANGLE);
0604: }
0605:
0606: /**
0607: * Returns a conversion from a source to target projected CRS, if this conversion
0608: * is representable as an affine transform. More specifically, if all projection
0609: * parameters are identical except the following ones:
0610: * <BR>
0611: * <UL>
0612: * <LI>{@link org.geotools.referencing.operation.projection.MapProjection.AbstractProvider#SCALE_FACTOR scale_factor}</LI>
0613: * <LI>{@link org.geotools.referencing.operation.projection.MapProjection.AbstractProvider#FALSE_EASTING false_easting}</LI>
0614: * <LI>{@link org.geotools.referencing.operation.projection.MapProjection.AbstractProvider#FALSE_NORTHING false_northing}</LI>
0615: * </UL>
0616: *
0617: * <P>Then the conversion between two projected CRS can sometime be represented as a linear
0618: * conversion. For example if only false easting/northing differ, than the coordinate conversion
0619: * is simply a translation. If no linear conversion has been found between the two CRS, then
0620: * this method returns {@code null}.</P>
0621: *
0622: * @param sourceCRS The source coordinate reference system.
0623: * @param targetCRS The target coordinate reference system.
0624: * @return The conversion from {@code sourceCRS} to {@code targetCRS} as an
0625: * affine transform, or {@code null} if no linear transform has been found.
0626: *
0627: * @todo Delete and replace by a static import when we
0628: * will be allowed to compile against J2SE 1.5.
0629: */
0630: private static Matrix createLinearConversion(
0631: final ProjectedCRS sourceCRS, final ProjectedCRS targetCRS) {
0632: return ProjectionAnalyzer.createLinearConversion(sourceCRS,
0633: targetCRS, 1E-10);
0634: }
0635:
0636: /////////////////////////////////////////////////////////////////////////////////
0637: /////////////////////////////////////////////////////////////////////////////////
0638: //////////// ////////////
0639: //////////// T R A N S F O R M A T I O N S S T E P S ////////////
0640: //////////// ////////////
0641: /////////////////////////////////////////////////////////////////////////////////
0642: /////////////////////////////////////////////////////////////////////////////////
0643:
0644: /**
0645: * Creates an operation between two temporal coordinate reference systems.
0646: * The default implementation checks if both CRS use the same datum, and
0647: * then adjusts for axis direction, units and epoch.
0648: *
0649: * @param sourceCRS Input coordinate reference system.
0650: * @param targetCRS Output coordinate reference system.
0651: * @return A coordinate operation from {@code sourceCRS} to {@code targetCRS}.
0652: * @throws FactoryException If the operation can't be constructed.
0653: */
0654: protected CoordinateOperation createOperationStep(
0655: final TemporalCRS sourceCRS, final TemporalCRS targetCRS)
0656: throws FactoryException {
0657: // TODO: remove cast once we will be allowed to compile for J2SE 1.5.
0658: final TemporalDatum sourceDatum = (TemporalDatum) sourceCRS
0659: .getDatum();
0660: final TemporalDatum targetDatum = (TemporalDatum) targetCRS
0661: .getDatum();
0662: if (!equalsIgnoreMetadata(sourceDatum, targetDatum)) {
0663: throw new OperationNotFoundException(getErrorMessage(
0664: sourceDatum, targetDatum));
0665: }
0666: /*
0667: * Compute the epoch shift. The epoch is the time "0" in a particular coordinate
0668: * reference system. For example, the epoch for java.util.Date object is january 1,
0669: * 1970 at 00:00 UTC. We compute how much to add to a time in 'sourceCRS' in order
0670: * to get a time in 'targetCRS'. This "epoch shift" is in units of 'targetCRS'.
0671: */
0672: // TODO: remove cast once we will be allowed to compile for J2SE 1.5.
0673: final TimeCS sourceCS = (TimeCS) sourceCRS
0674: .getCoordinateSystem();
0675: final TimeCS targetCS = (TimeCS) targetCRS
0676: .getCoordinateSystem();
0677: final Unit targetUnit = targetCS.getAxis(0).getUnit();
0678: double epochShift = sourceDatum.getOrigin().getTime()
0679: - targetDatum.getOrigin().getTime();
0680: epochShift = MILLISECOND.getConverterTo(targetUnit).convert(
0681: epochShift);
0682: /*
0683: * Check axis orientation. The method 'swapAndScaleAxis' should returns a matrix
0684: * of size 2x2. The element at index (0,0) may be 1 if sourceCRS and targetCRS axis
0685: * are in the same direction, or -1 if there are in opposite direction (e.g.
0686: * "PAST" vs "FUTURE"). This number may be something else than -1 or +1 if a unit
0687: * conversion was applied too, for example 60 if time in 'sourceCRS' was in hours
0688: * while time in 'targetCRS' was in minutes.
0689: *
0690: * The "epoch shift" previously computed is a translation.
0691: * Consequently, it is added to element (0,1).
0692: */
0693: final Matrix matrix = swapAndScaleAxis(sourceCS, targetCS);
0694: final int translationColumn = matrix.getNumCol() - 1;
0695: if (translationColumn >= 0) { // Paranoiac check: should always be 1.
0696: final double translation = matrix.getElement(0,
0697: translationColumn);
0698: matrix.setElement(0, translationColumn, translation
0699: + epochShift);
0700: }
0701: return createFromAffineTransform(AXIS_CHANGES, sourceCRS,
0702: targetCRS, matrix);
0703: }
0704:
0705: /**
0706: * Creates an operation between two vertical coordinate reference systems.
0707: * The default implementation checks if both CRS use the same datum, and
0708: * then adjusts for axis direction and units.
0709: *
0710: * @param sourceCRS Input coordinate reference system.
0711: * @param targetCRS Output coordinate reference system.
0712: * @return A coordinate operation from {@code sourceCRS} to {@code targetCRS}.
0713: * @throws FactoryException If the operation can't be constructed.
0714: */
0715: protected CoordinateOperation createOperationStep(
0716: final VerticalCRS sourceCRS, final VerticalCRS targetCRS)
0717: throws FactoryException {
0718: // TODO: remove cast once we will be allowed to compile for J2SE 1.5.
0719: final VerticalDatum sourceDatum = (VerticalDatum) sourceCRS
0720: .getDatum();
0721: final VerticalDatum targetDatum = (VerticalDatum) targetCRS
0722: .getDatum();
0723: if (!equalsIgnoreMetadata(sourceDatum, targetDatum)) {
0724: throw new OperationNotFoundException(getErrorMessage(
0725: sourceDatum, targetDatum));
0726: }
0727: // TODO: remove cast once we will be allowed to compile for J2SE 1.5.
0728: final VerticalCS sourceCS = (VerticalCS) sourceCRS
0729: .getCoordinateSystem();
0730: final VerticalCS targetCS = (VerticalCS) targetCRS
0731: .getCoordinateSystem();
0732: final Matrix matrix = swapAndScaleAxis(sourceCS, targetCS);
0733: return createFromAffineTransform(AXIS_CHANGES, sourceCRS,
0734: targetCRS, matrix);
0735: }
0736:
0737: /**
0738: * Creates an operation between a geographic and a vertical coordinate reference systems.
0739: * The default implementation accepts the conversion only if the geographic CRS is a tri
0740: * dimensional one and the vertical CRS is for {@linkplain VerticalDatumType#ELLIPSOIDAL
0741: * height above the ellipsoid}. More elaborated operation, like transformation from
0742: * ellipsoidal to geoidal height, should be implemented here.
0743: *
0744: * @param sourceCRS Input coordinate reference system.
0745: * @param targetCRS Output coordinate reference system.
0746: * @return A coordinate operation from {@code sourceCRS} to {@code targetCRS}.
0747: * @throws FactoryException If the operation can't be constructed.
0748: *
0749: * @todo Implement GEOT-352 here.
0750: */
0751: protected CoordinateOperation createOperationStep(
0752: final GeographicCRS sourceCRS, final VerticalCRS targetCRS)
0753: throws FactoryException {
0754: // TODO: remove cast when we will be allowed to compile for J2SE 1.5.
0755: if (VerticalDatumType.ELLIPSOIDAL
0756: .equals(((VerticalDatum) targetCRS.getDatum())
0757: .getVerticalDatumType())) {
0758: final Matrix matrix = swapAndScaleAxis(sourceCRS
0759: .getCoordinateSystem(), targetCRS
0760: .getCoordinateSystem());
0761: return createFromAffineTransform(AXIS_CHANGES, sourceCRS,
0762: targetCRS, matrix);
0763: }
0764: throw new OperationNotFoundException(getErrorMessage(sourceCRS,
0765: targetCRS));
0766: }
0767:
0768: /**
0769: * Creates an operation between two geographic coordinate reference systems. The default
0770: * implementation can adjust axis order and orientation (e.g. transforming from
0771: * {@code (NORTH,WEST)} to {@code (EAST,NORTH)}), performs units conversion
0772: * and apply datum shifts if needed.
0773: *
0774: * @param sourceCRS Input coordinate reference system.
0775: * @param targetCRS Output coordinate reference system.
0776: * @return A coordinate operation from {@code sourceCRS} to {@code targetCRS}.
0777: * @throws FactoryException If the operation can't be constructed.
0778: *
0779: * @todo When rotating the prime meridian, we should ensure that
0780: * transformed longitudes stay in the range [-180..+180°].
0781: */
0782: protected CoordinateOperation createOperationStep(
0783: final GeographicCRS sourceCRS, final GeographicCRS targetCRS)
0784: throws FactoryException {
0785: // TODO: remove cast once we will be allowed to compile for J2SE 1.5.
0786: final EllipsoidalCS sourceCS = (EllipsoidalCS) sourceCRS
0787: .getCoordinateSystem();
0788: final EllipsoidalCS targetCS = (EllipsoidalCS) targetCRS
0789: .getCoordinateSystem();
0790: final GeodeticDatum sourceDatum = (GeodeticDatum) sourceCRS
0791: .getDatum();
0792: final GeodeticDatum targetDatum = (GeodeticDatum) targetCRS
0793: .getDatum();
0794: final PrimeMeridian sourcePM = sourceDatum.getPrimeMeridian();
0795: final PrimeMeridian targetPM = targetDatum.getPrimeMeridian();
0796: if (equalsIgnorePrimeMeridian(sourceDatum, targetDatum)) {
0797: /*
0798: * If both geographic CRS use the same datum, then there is no need for a datum shift.
0799: * Just swap axis order, and rotate the longitude coordinate if prime meridians are
0800: * different. Note: this special block is mandatory for avoiding never-ending loop,
0801: * since it is invoked by 'createOperationStep(GeocentricCRS...)'.
0802: *
0803: * TODO: We should ensure that longitude is in range [-180..+180°].
0804: */
0805: final Matrix matrix = swapAndScaleAxis(sourceCS, targetCS,
0806: sourcePM, targetPM);
0807: return createFromAffineTransform(AXIS_CHANGES, sourceCRS,
0808: targetCRS, matrix);
0809: }
0810: /*
0811: * The two geographic CRS use different datum. If Molodenski transformations
0812: * are allowed, try them first. Note that is some case if the datum shift can't
0813: * be performed in a single Molodenski transformation step (i.e. if we need to
0814: * go through at least one intermediate datum), then we will use the geocentric
0815: * transform below instead: it allows to concatenates many Bursa Wolf parameters
0816: * in a single affine transform.
0817: */
0818: if (molodenskiMethod != null) {
0819: ReferenceIdentifier identifier = DATUM_SHIFT;
0820: BursaWolfParameters bursaWolf = null;
0821: if (sourceDatum instanceof DefaultGeodeticDatum) {
0822: bursaWolf = ((DefaultGeodeticDatum) sourceDatum)
0823: .getBursaWolfParameters(targetDatum);
0824: }
0825: if (bursaWolf == null) {
0826: /*
0827: * No direct path found. Try the more expensive matrix calculation, and
0828: * see if we can retrofit the result in a BursaWolfParameters object.
0829: */
0830: final Matrix shift = DefaultGeodeticDatum
0831: .getAffineTransform(sourceDatum, targetDatum);
0832: if (shift != null)
0833: try {
0834: bursaWolf = new BursaWolfParameters(targetDatum);
0835: bursaWolf.setAffineTransform(shift, 1E-4);
0836: } catch (IllegalArgumentException ignore) {
0837: /*
0838: * A matrix exists, but we are unable to retrofit it as a set of Bursa-Wolf
0839: * parameters. Do NOT set the 'bursaWolf' variable: it must stay null, which
0840: * means to perform the datum shift using geocentric coordinates.
0841: */
0842: }
0843: else if (lenientDatumShift) {
0844: /*
0845: * No BursaWolf parameters available. No affine transform to be applied in
0846: * geocentric coordinates are available neither (the "shift" matrix above),
0847: * so performing a geocentric transformation will not help. But the user wants
0848: * us to perform the datum shift anyway. We will notify the user through
0849: * positional accuracy, which is set indirectly through ELLIPSOID_SHIFT.
0850: */
0851: bursaWolf = new BursaWolfParameters(targetDatum);
0852: identifier = ELLIPSOID_SHIFT;
0853: }
0854: }
0855: /*
0856: * Applies the Molodenski transformation now. Note: in current parameters, we can't
0857: * specify a different input and output dimension. However, our Molodenski transform
0858: * allows that. We should expand the parameters block for this case (TODO).
0859: */
0860: if (bursaWolf != null && bursaWolf.isTranslation()) {
0861: final Ellipsoid sourceEllipsoid = sourceDatum
0862: .getEllipsoid();
0863: final Ellipsoid targetEllipsoid = targetDatum
0864: .getEllipsoid();
0865: if (bursaWolf.isIdentity()
0866: && equalsIgnoreMetadata(sourceEllipsoid,
0867: targetEllipsoid)) {
0868: final Matrix matrix = swapAndScaleAxis(sourceCS,
0869: targetCS, sourcePM, targetPM);
0870: return createFromAffineTransform(identifier,
0871: sourceCRS, targetCRS, matrix);
0872: }
0873: final int sourceDim = getDimension(sourceCRS);
0874: final int targetDim = getDimension(targetCRS);
0875: final ParameterValueGroup parameters;
0876: parameters = getMathTransformFactory()
0877: .getDefaultParameters(molodenskiMethod);
0878: parameters.parameter("src_semi_major").setValue(
0879: sourceEllipsoid.getSemiMajorAxis());
0880: parameters.parameter("src_semi_minor").setValue(
0881: sourceEllipsoid.getSemiMinorAxis());
0882: parameters.parameter("tgt_semi_major").setValue(
0883: targetEllipsoid.getSemiMajorAxis());
0884: parameters.parameter("tgt_semi_minor").setValue(
0885: targetEllipsoid.getSemiMinorAxis());
0886: parameters.parameter("dx").setValue(bursaWolf.dx);
0887: parameters.parameter("dy").setValue(bursaWolf.dy);
0888: parameters.parameter("dz").setValue(bursaWolf.dz);
0889: parameters.parameter("dim").setValue(sourceDim);
0890: if (sourceDim == targetDim) {
0891: final CoordinateOperation step1, step2, step3;
0892: final GeographicCRS normSourceCRS = normalize(
0893: sourceCRS, true);
0894: final GeographicCRS normTargetCRS = normalize(
0895: targetCRS, true);
0896: step1 = createOperationStep(sourceCRS,
0897: normSourceCRS);
0898: step2 = createFromParameters(identifier,
0899: normSourceCRS, normTargetCRS, parameters);
0900: step3 = createOperationStep(normTargetCRS,
0901: targetCRS);
0902: return concatenate(step1, step2, step3);
0903: } else {
0904: // TODO: Need some way to pass 'targetDim' to Molodenski.
0905: // Fallback on geocentric transformations for now.
0906: }
0907: }
0908: }
0909: /*
0910: * If the two geographic CRS use different datum, transform from the
0911: * source to target datum through the geocentric coordinate system.
0912: * The transformation chain is:
0913: *
0914: * source geographic CRS -->
0915: * geocentric CRS with a preference for datum using Greenwich meridian -->
0916: * target geographic CRS
0917: */
0918: final CartesianCS STANDARD = DefaultCartesianCS.GEOCENTRIC;
0919: final GeocentricCRS stepCRS;
0920: final CRSFactory crsFactory = getFactoryGroup().getCRSFactory();
0921: if (getGreenwichLongitude(targetPM) == 0) {
0922: stepCRS = crsFactory.createGeocentricCRS(
0923: getTemporaryName(targetCRS), targetDatum, STANDARD);
0924: } else {
0925: stepCRS = crsFactory.createGeocentricCRS(
0926: getTemporaryName(sourceCRS), sourceDatum, STANDARD);
0927: }
0928: final CoordinateOperation step1 = createOperationStep(
0929: sourceCRS, stepCRS);
0930: final CoordinateOperation step2 = createOperationStep(stepCRS,
0931: targetCRS);
0932: return concatenate(step1, step2);
0933: }
0934:
0935: /**
0936: * Creates an operation between two projected coordinate reference systems.
0937: * The default implementation can adjust axis order and orientation. It also
0938: * performs units conversion if it is the only extra change needed. Otherwise,
0939: * it performs three steps:
0940: *
0941: * <ul>
0942: * <li>Unproject from {@code sourceCRS} to its base
0943: * {@linkplain GeographicCRS geographic CRS}.</li>
0944: * <li>Convert the source to target base geographic CRS.</li>
0945: * <li>Project from the base {@linkplain GeographicCRS geographic CRS}
0946: * to the {@code targetCRS}.</li>
0947: * </ul>
0948: *
0949: * @param sourceCRS Input coordinate reference system.
0950: * @param targetCRS Output coordinate reference system.
0951: * @return A coordinate operation from {@code sourceCRS} to {@code targetCRS}.
0952: * @throws FactoryException If the operation can't be constructed.
0953: */
0954: protected CoordinateOperation createOperationStep(
0955: final ProjectedCRS sourceCRS, final ProjectedCRS targetCRS)
0956: throws FactoryException {
0957: /*
0958: * First, check if a linear path exists from sourceCRS to targetCRS.
0959: * If both projected CRS use the same projection and the same horizontal datum,
0960: * then only axis orientation and units may have been changed. We do not need
0961: * to perform the tedious ProjectedCRS --> GeographicCRS --> ProjectedCRS chain.
0962: * We can apply a much shorter conversion using only an affine transform.
0963: *
0964: * This shorter path is essential for proper working of
0965: * createOperationStep(GeographicCRS,ProjectedCRS).
0966: */
0967: final Matrix linear = createLinearConversion(sourceCRS,
0968: targetCRS);
0969: if (linear != null) {
0970: return createFromAffineTransform(AXIS_CHANGES, sourceCRS,
0971: targetCRS, linear);
0972: }
0973: /*
0974: * Apply the transformation in 3 steps (the 3 arrows below):
0975: *
0976: * source projected CRS --(unproject)-->
0977: * source geographic CRS --------------->
0978: * target geographic CRS ---(project)--->
0979: * target projected CRS
0980: */
0981: // TODO: remove cast once we will be allowed to compile for J2SE 1.5.
0982: final GeographicCRS sourceGeo = (GeographicCRS) sourceCRS
0983: .getBaseCRS();
0984: final GeographicCRS targetGeo = (GeographicCRS) targetCRS
0985: .getBaseCRS();
0986: CoordinateOperation step1, step2, step3;
0987: step1 = tryDB(sourceCRS, sourceGeo);
0988: if (step1 == null)
0989: step1 = createOperationStep(sourceCRS, sourceGeo);
0990: step2 = tryDB(sourceGeo, targetGeo);
0991: if (step2 == null)
0992: step2 = createOperationStep(sourceGeo, targetGeo);
0993: step3 = tryDB(targetGeo, targetCRS);
0994: if (step3 == null)
0995: step3 = createOperationStep(targetGeo, targetCRS);
0996: return concatenate(step1, step2, step3);
0997: }
0998:
0999: /**
1000: * Creates an operation from a geographic to a projected coordinate reference system.
1001: * The default implementation constructs the following operation chain:
1002: *
1003: * <blockquote><pre>
1004: * sourceCRS → {@linkplain ProjectedCRS#getBaseCRS baseCRS} → targetCRS
1005: * </pre></blockquote>
1006: *
1007: * where the conversion from {@code baseCRS} to {@code targetCRS} is obtained
1008: * from <code>targetCRS.{@linkplain ProjectedCRS#getConversionFromBase
1009: * getConversionFromBase()}</code>.
1010: *
1011: * @param sourceCRS Input coordinate reference system.
1012: * @param targetCRS Output coordinate reference system.
1013: * @return A coordinate operation from {@code sourceCRS} to {@code targetCRS}.
1014: * @throws FactoryException If the operation can't be constructed.
1015: */
1016: protected CoordinateOperation createOperationStep(
1017: final GeographicCRS sourceCRS, final ProjectedCRS targetCRS)
1018: throws FactoryException {
1019: // TODO: remove cast once we will be allowed to compile for J2SE 1.5.
1020: GeographicCRS base = (GeographicCRS) targetCRS.getBaseCRS();
1021: CoordinateOperation step2 = targetCRS.getConversionFromBase();
1022: CoordinateOperation step1 = tryDB(sourceCRS, base);
1023: if (step1 == null) {
1024: step1 = createOperationStep(sourceCRS, base);
1025: }
1026: return concatenate(step1, step2);
1027: }
1028:
1029: /**
1030: * Creates an operation from a projected to a geographic coordinate reference system.
1031: * The default implementation constructs the following operation chain:
1032: *
1033: * <blockquote><pre>
1034: * sourceCRS → {@linkplain ProjectedCRS#getBaseCRS baseCRS} → targetCRS
1035: * </pre></blockquote>
1036: *
1037: * where the conversion from {@code sourceCRS} to {@code baseCRS} is obtained
1038: * from the inverse of
1039: * <code>sourceCRS.{@linkplain ProjectedCRS#getConversionFromBase
1040: * getConversionFromBase()}</code>.
1041: *
1042: * @param sourceCRS Input coordinate reference system.
1043: * @param targetCRS Output coordinate reference system.
1044: * @return A coordinate operation from {@code sourceCRS} to {@code targetCRS}.
1045: * @throws FactoryException If the operation can't be constructed.
1046: *
1047: * @todo Provides a non-null method.
1048: */
1049: protected CoordinateOperation createOperationStep(
1050: final ProjectedCRS sourceCRS, final GeographicCRS targetCRS)
1051: throws FactoryException {
1052: // TODO: remove cast once we will be allowed to compile for J2SE 1.5.
1053: final GeographicCRS base = (GeographicCRS) sourceCRS
1054: .getBaseCRS();
1055: CoordinateOperation step1 = sourceCRS.getConversionFromBase();
1056: CoordinateOperation step2 = tryDB(base, targetCRS);
1057: if (step2 == null) {
1058: step2 = createOperationStep(base, targetCRS);
1059: }
1060: MathTransform transform = step1.getMathTransform();
1061: try {
1062: transform = transform.inverse();
1063: } catch (NoninvertibleTransformException exception) {
1064: throw new OperationNotFoundException(getErrorMessage(
1065: sourceCRS, base), exception);
1066: }
1067: step1 = createFromMathTransform(INVERSE_OPERATION, sourceCRS,
1068: base, transform);
1069: return concatenate(step1, step2);
1070: }
1071:
1072: /**
1073: * Creates an operation between two geocentric coordinate reference systems.
1074: * The default implementation can adjust for axis order and orientation,
1075: * performs units conversion and apply Bursa Wolf transformation if needed.
1076: *
1077: * @param sourceCRS Input coordinate reference system.
1078: * @param targetCRS Output coordinate reference system.
1079: * @return A coordinate operation from {@code sourceCRS} to {@code targetCRS}.
1080: * @throws FactoryException If the operation can't be constructed.
1081: *
1082: * @todo Rotation of prime meridian not yet implemented.
1083: * @todo Transformation version set to "(unknow)". We should search this information somewhere.
1084: */
1085: protected CoordinateOperation createOperationStep(
1086: final GeocentricCRS sourceCRS, final GeocentricCRS targetCRS)
1087: throws FactoryException {
1088: // TODO: remove cast once we will be allowed to compile for J2SE 1.5.
1089: final GeodeticDatum sourceDatum = (GeodeticDatum) sourceCRS
1090: .getDatum();
1091: final GeodeticDatum targetDatum = (GeodeticDatum) targetCRS
1092: .getDatum();
1093: final CoordinateSystem sourceCS = sourceCRS
1094: .getCoordinateSystem();
1095: final CoordinateSystem targetCS = targetCRS
1096: .getCoordinateSystem();
1097: final double sourcePM, targetPM;
1098: sourcePM = getGreenwichLongitude(sourceDatum.getPrimeMeridian());
1099: targetPM = getGreenwichLongitude(targetDatum.getPrimeMeridian());
1100: if (equalsIgnorePrimeMeridian(sourceDatum, targetDatum)) {
1101: if (sourcePM == targetPM) {
1102: /*
1103: * If both CRS use the same datum and the same prime meridian,
1104: * then the transformation is probably just axis swap or unit
1105: * conversions.
1106: */
1107: final Matrix matrix = swapAndScaleAxis(sourceCS,
1108: targetCS);
1109: return createFromAffineTransform(AXIS_CHANGES,
1110: sourceCRS, targetCRS, matrix);
1111: }
1112: // Prime meridians are differents. Performs the full transformation.
1113: }
1114: if (sourcePM != targetPM) {
1115: throw new OperationNotFoundException(
1116: "Rotation of prime meridian not yet implemented");
1117: }
1118: /*
1119: * Transform between differents ellipsoids using Bursa Wolf parameters.
1120: * The Bursa Wolf parameters are used with "standard" geocentric CS, i.e.
1121: * with x axis towards the prime meridian, y axis towards East and z axis
1122: * toward North. The following steps are applied:
1123: *
1124: * source CRS -->
1125: * standard CRS with source datum -->
1126: * standard CRS with target datum -->
1127: * target CRS
1128: */
1129: final CartesianCS STANDARD = DefaultCartesianCS.GEOCENTRIC;
1130: final XMatrix matrix;
1131: ReferenceIdentifier identifier = DATUM_SHIFT;
1132: try {
1133: Matrix datumShift = DefaultGeodeticDatum
1134: .getAffineTransform(TemporaryDatum
1135: .unwrap(sourceDatum), TemporaryDatum
1136: .unwrap(targetDatum));
1137: if (datumShift == null) {
1138: if (lenientDatumShift) {
1139: datumShift = new Matrix4(); // Identity transform.
1140: identifier = ELLIPSOID_SHIFT;
1141: } else {
1142: throw new OperationNotFoundException(
1143: Errors
1144: .format(ErrorKeys.BURSA_WOLF_PARAMETERS_REQUIRED));
1145: }
1146: }
1147: final Matrix normalizeSource = swapAndScaleAxis(sourceCS,
1148: STANDARD);
1149: final Matrix normalizeTarget = swapAndScaleAxis(STANDARD,
1150: targetCS);
1151: /*
1152: * Since all steps are matrix, we can multiply them into a single matrix operation.
1153: * Note: XMatrix.multiply(XMatrix) is equivalents to AffineTransform.concatenate(...):
1154: * First transform by the supplied transform and then transform the result
1155: * by the original transform.
1156: *
1157: * We compute: matrix = normalizeTarget * datumShift * normalizeSource
1158: */
1159: matrix = new Matrix4(normalizeTarget);
1160: matrix.multiply(datumShift);
1161: matrix.multiply(normalizeSource);
1162: } catch (SingularMatrixException cause) {
1163: throw new OperationNotFoundException(getErrorMessage(
1164: sourceDatum, targetDatum), cause);
1165: }
1166: return createFromAffineTransform(identifier, sourceCRS,
1167: targetCRS, matrix);
1168: }
1169:
1170: /**
1171: * Creates an operation from a geographic to a geocentric coordinate reference systems.
1172: * If the source CRS doesn't have a vertical axis, height above the ellipsoid will be
1173: * assumed equals to zero everywhere. The default implementation uses the
1174: * {@code "Ellipsoid_To_Geocentric"} math transform.
1175: *
1176: * @param sourceCRS Input coordinate reference system.
1177: * @param targetCRS Output coordinate reference system.
1178: * @return A coordinate operation from {@code sourceCRS} to {@code targetCRS}.
1179: * @throws FactoryException If the operation can't be constructed.
1180: */
1181: protected CoordinateOperation createOperationStep(
1182: final GeographicCRS sourceCRS, final GeocentricCRS targetCRS)
1183: throws FactoryException {
1184: /*
1185: * This transformation is a 3 steps process:
1186: *
1187: * source geographic CRS -->
1188: * normalized geographic CRS -->
1189: * normalized geocentric CRS -->
1190: * target geocentric CRS
1191: *
1192: * "Normalized" means that axis point toward standards direction (East, North, etc.),
1193: * units are metres or decimal degrees, prime meridian is Greenwich and height is measured
1194: * above the ellipsoid. However, the horizontal datum is preserved.
1195: */
1196: // TODO: remove cast once we will be allowed to compile for J2SE 1.5.
1197: final GeographicCRS normSourceCRS = normalize(sourceCRS, true);
1198: final GeodeticDatum datum = (GeodeticDatum) normSourceCRS
1199: .getDatum();
1200: final GeocentricCRS normTargetCRS = normalize(targetCRS, datum);
1201: final Ellipsoid ellipsoid = datum.getEllipsoid();
1202: final Unit unit = ellipsoid.getAxisUnit();
1203: final MathTransform transform;
1204: final ParameterValueGroup param;
1205: param = getMathTransformFactory().getDefaultParameters(
1206: "Ellipsoid_To_Geocentric");
1207: param.parameter("semi_major").setValue(
1208: ellipsoid.getSemiMajorAxis(), unit);
1209: param.parameter("semi_minor").setValue(
1210: ellipsoid.getSemiMinorAxis(), unit);
1211: param.parameter("dim").setValue(getDimension(normSourceCRS));
1212:
1213: final CoordinateOperation step1, step2, step3;
1214: step1 = createOperationStep(sourceCRS, normSourceCRS);
1215: step2 = createFromParameters(GEOCENTRIC_CONVERSION,
1216: normSourceCRS, normTargetCRS, param);
1217: step3 = createOperationStep(normTargetCRS, targetCRS);
1218: return concatenate(step1, step2, step3);
1219: }
1220:
1221: /**
1222: * Creates an operation from a geocentric to a geographic coordinate reference systems.
1223: * The default implementation use the <code>"Geocentric_To_Ellipsoid"</code> math transform.
1224: *
1225: * @param sourceCRS Input coordinate reference system.
1226: * @param targetCRS Output coordinate reference system.
1227: * @return A coordinate operation from {@code sourceCRS} to {@code targetCRS}.
1228: * @throws FactoryException If the operation can't be constructed.
1229: */
1230: protected CoordinateOperation createOperationStep(
1231: final GeocentricCRS sourceCRS, final GeographicCRS targetCRS)
1232: throws FactoryException {
1233: // TODO: remove cast once we will be allowed to compile for J2SE 1.5.
1234: final GeographicCRS normTargetCRS = normalize(targetCRS, true);
1235: final GeodeticDatum datum = (GeodeticDatum) normTargetCRS
1236: .getDatum();
1237: final GeocentricCRS normSourceCRS = normalize(sourceCRS, datum);
1238: final Ellipsoid ellipsoid = datum.getEllipsoid();
1239: final Unit unit = ellipsoid.getAxisUnit();
1240: final MathTransform transform;
1241: final ParameterValueGroup param;
1242: param = getMathTransformFactory().getDefaultParameters(
1243: "Geocentric_To_Ellipsoid");
1244: param.parameter("semi_major").setValue(
1245: ellipsoid.getSemiMajorAxis(), unit);
1246: param.parameter("semi_minor").setValue(
1247: ellipsoid.getSemiMinorAxis(), unit);
1248: param.parameter("dim").setValue(getDimension(normTargetCRS));
1249:
1250: final CoordinateOperation step1, step2, step3;
1251: step1 = createOperationStep(sourceCRS, normSourceCRS);
1252: step2 = createFromParameters(GEOCENTRIC_CONVERSION,
1253: normSourceCRS, normTargetCRS, param);
1254: step3 = createOperationStep(normTargetCRS, targetCRS);
1255: return concatenate(step1, step2, step3);
1256: }
1257:
1258: /**
1259: * Creates an operation from a compound to a single coordinate reference systems.
1260: *
1261: * @param sourceCRS Input coordinate reference system.
1262: * @param targetCRS Output coordinate reference system.
1263: * @return A coordinate operation from {@code sourceCRS} to {@code targetCRS}.
1264: * @throws FactoryException If the operation can't be constructed.
1265: *
1266: * @todo (GEOT-401) This method work for some simple cases (e.g. no datum change), and give up
1267: * otherwise. Before to give up at the end of this method, we should try the following:
1268: * <ul>
1269: * <li>Maybe {@code sourceCRS} uses a non-ellipsoidal height. We should replace
1270: * the non-ellipsoidal height by an ellipsoidal one, create a transformation step
1271: * for that (to be concatenated), and then try again this operation step.</li>
1272: *
1273: * <li>Maybe {@code sourceCRS} contains some extra axis, like a temporal CRS.
1274: * We should revisit this code in other to lets supplemental ordinates to be
1275: * pass through or removed.</li>
1276: * </ul>
1277: */
1278: protected CoordinateOperation createOperationStep(
1279: final CompoundCRS sourceCRS, final SingleCRS targetCRS)
1280: throws FactoryException {
1281: final SingleCRS[] sources = DefaultCompoundCRS
1282: .getSingleCRS(sourceCRS);
1283: if (sources.length == 1) {
1284: return createOperation(sources[0], targetCRS);
1285: }
1286: if (!needsGeodetic3D(sources, targetCRS)) {
1287: // No need for a datum change (see 'needGeodetic3D' javadoc).
1288: final SingleCRS[] targets = new SingleCRS[] { targetCRS };
1289: return createOperationStep(sourceCRS, sources, targetCRS,
1290: targets);
1291: }
1292: /*
1293: * There is a change of datum. It may be a vertical datum change (for example from
1294: * ellipsoidal to geoidal height), in which case geographic coordinates are usually
1295: * needed. It may also be a geodetic datum change, in which case the height is part
1296: * of computation. Try to convert the source CRS into a 3D-geodetic CRS.
1297: */
1298: final CoordinateReferenceSystem source3D = getFactoryGroup()
1299: .toGeodetic3D(sourceCRS);
1300: if (source3D != sourceCRS) {
1301: return createOperation(source3D, targetCRS);
1302: }
1303: /*
1304: * TODO: Search for non-ellipsoidal height, and lets supplemental axis (e.g. time)
1305: * pass through. See javadoc comments above.
1306: */
1307: throw new OperationNotFoundException(getErrorMessage(sourceCRS,
1308: targetCRS));
1309: }
1310:
1311: /**
1312: * Creates an operation from a single to a compound coordinate reference system.
1313: *
1314: * @param sourceCRS Input coordinate reference system.
1315: * @param targetCRS Output coordinate reference system.
1316: * @return A coordinate operation from {@code sourceCRS} to {@code targetCRS}.
1317: * @throws FactoryException If the operation can't be constructed.
1318: */
1319: protected CoordinateOperation createOperationStep(
1320: final SingleCRS sourceCRS, final CompoundCRS targetCRS)
1321: throws FactoryException {
1322: final SingleCRS[] targets = DefaultCompoundCRS
1323: .getSingleCRS(targetCRS);
1324: if (targets.length == 1) {
1325: return createOperation(sourceCRS, targets[0]);
1326: }
1327: /*
1328: * This method has almost no chance to succeed (we can't invent ordinate values!) unless
1329: * 'sourceCRS' is a 3D-geodetic CRS and 'targetCRS' is a 2D + 1D one. Test for this case.
1330: * Otherwise, the 'createOperationStep' invocation will throws the appropriate exception.
1331: */
1332: final CoordinateReferenceSystem target3D = getFactoryGroup()
1333: .toGeodetic3D(targetCRS);
1334: if (target3D != targetCRS) {
1335: return createOperation(sourceCRS, target3D);
1336: }
1337: final SingleCRS[] sources = new SingleCRS[] { sourceCRS };
1338: return createOperationStep(sourceCRS, sources, targetCRS,
1339: targets);
1340: }
1341:
1342: /**
1343: * Creates an operation between two compound coordinate reference systems.
1344: *
1345: * @param sourceCRS Input coordinate reference system.
1346: * @param targetCRS Output coordinate reference system.
1347: * @return A coordinate operation from {@code sourceCRS} to {@code targetCRS}.
1348: * @throws FactoryException If the operation can't be constructed.
1349: */
1350: protected CoordinateOperation createOperationStep(
1351: final CompoundCRS sourceCRS, final CompoundCRS targetCRS)
1352: throws FactoryException {
1353: final SingleCRS[] sources = DefaultCompoundCRS
1354: .getSingleCRS(sourceCRS);
1355: final SingleCRS[] targets = DefaultCompoundCRS
1356: .getSingleCRS(targetCRS);
1357: if (targets.length == 1) {
1358: return createOperation(sourceCRS, targets[0]);
1359: }
1360: if (sources.length == 1) { // After 'targets' because more likely to fails to transform.
1361: return createOperation(sources[0], targetCRS);
1362: }
1363: /*
1364: * If the source CRS contains both a geodetic and a vertical CRS, then we can process
1365: * only if there is no datum change. If at least one of those CRS appears in the target
1366: * CRS with a different datum, then the datum shift must be applied on the horizontal and
1367: * vertical components together.
1368: */
1369: for (int i = 0; i < targets.length; i++) {
1370: if (needsGeodetic3D(sources, targets[i])) {
1371: final FactoryGroup factories = getFactoryGroup();
1372: final CoordinateReferenceSystem source3D = factories
1373: .toGeodetic3D(sourceCRS);
1374: final CoordinateReferenceSystem target3D = factories
1375: .toGeodetic3D(targetCRS);
1376: if (source3D != sourceCRS || target3D != targetCRS) {
1377: return createOperation(source3D, target3D);
1378: }
1379: /*
1380: * TODO: Search for non-ellipsoidal height, and lets supplemental axis pass through.
1381: * See javadoc comments for createOperation(CompoundCRS, SingleCRS).
1382: */
1383: throw new OperationNotFoundException(getErrorMessage(
1384: sourceCRS, targetCRS));
1385: }
1386: }
1387: // No need for a datum change (see 'needGeodetic3D' javadoc).
1388: return createOperationStep(sourceCRS, sources, targetCRS,
1389: targets);
1390: }
1391:
1392: /**
1393: * Implementation of transformation step on compound CRS.
1394: *
1395: * <strong>NOTE:</strong>
1396: * If there is a horizontal (geographic or projected) CRS together with a vertical CRS,
1397: * then we can't performs the transformation since the vertical value has an impact on
1398: * the horizontal value, and this impact is not taken in account if the horizontal and
1399: * vertical components are not together in a 3D geographic CRS. This case occurs when
1400: * the vertical CRS is not a height above the ellipsoid. It must be checked by the
1401: * caller before this method is invoked.
1402: *
1403: * @param sourceCRS Input coordinate reference system.
1404: * @param sources The source CRS components.
1405: * @param targetCRS Output coordinate reference system.
1406: * @param targets The target CRS components.
1407: * @return A coordinate operation from {@code sourceCRS} to {@code targetCRS}.
1408: * @throws FactoryException If the operation can't be constructed.
1409: */
1410: private CoordinateOperation createOperationStep(
1411: final CoordinateReferenceSystem sourceCRS,
1412: final SingleCRS[] sources,
1413: final CoordinateReferenceSystem targetCRS,
1414: final SingleCRS[] targets) throws FactoryException {
1415: /*
1416: * Try to find operations from source CRSs to target CRSs. All pairwise combinaisons are
1417: * tried, but the preference is given to CRS in the same order (source[0] with target[0],
1418: * source[1] with target[1], etc.). Operations found are stored in 'steps', but are not
1419: * yet given to pass through transforms. We need to know first if some ordinate values
1420: * need reordering (for matching the order of target CRS) if any ordinates reordering and
1421: * source ordinates drops are required.
1422: */
1423: final CoordinateReferenceSystem[] ordered = new CoordinateReferenceSystem[targets.length];
1424: final CoordinateOperation[] steps = new CoordinateOperation[targets.length];
1425: final boolean[] done = new boolean[sources.length];
1426: final int[] indices = new int[getDimension(sourceCRS)];
1427: int count = 0, dimensions = 0;
1428: search: for (int j = 0; j < targets.length; j++) {
1429: int lower, upper = 0;
1430: final CoordinateReferenceSystem target = targets[j];
1431: OperationNotFoundException cause = null;
1432: for (int i = 0; i < sources.length; i++) {
1433: final CoordinateReferenceSystem source = sources[i];
1434: lower = upper;
1435: upper += getDimension(source);
1436: if (done[i])
1437: continue;
1438: try {
1439: steps[count] = createOperation(source, target);
1440: } catch (OperationNotFoundException exception) {
1441: // No operation path for this pair.
1442: // Search for an other pair.
1443: if (cause == null || i == j) {
1444: cause = exception;
1445: }
1446: continue;
1447: }
1448: ordered[count++] = source;
1449: while (lower < upper) {
1450: indices[dimensions++] = lower++;
1451: }
1452: done[i] = true;
1453: continue search;
1454: }
1455: /*
1456: * No source CRS was found for current target CRS.
1457: * Consequently, we can't get a transformation path.
1458: */
1459: throw new OperationNotFoundException(getErrorMessage(
1460: sourceCRS, targetCRS), cause);
1461: }
1462: /*
1463: * A transformation has been found for every source and target CRS pairs.
1464: * Some reordering of ordinate values may be needed. Prepare it now as an
1465: * affine transform. This transform also drop source dimensions not used
1466: * for any target coordinates.
1467: */
1468: assert count == targets.length : count;
1469: while (count != 0
1470: && steps[--count].getMathTransform().isIdentity())
1471: ;
1472: final FactoryGroup factories = getFactoryGroup();
1473: CoordinateOperation operation = null;
1474: CoordinateReferenceSystem sourceStepCRS = sourceCRS;
1475: final XMatrix select = MatrixFactory.create(dimensions + 1,
1476: indices.length + 1);
1477: select.setZero();
1478: select.setElement(dimensions, indices.length, 1);
1479: for (int j = 0; j < dimensions; j++) {
1480: select.setElement(j, indices[j], 1);
1481: }
1482: if (!select.isIdentity()) {
1483: if (ordered.length == 1) {
1484: sourceStepCRS = ordered[0];
1485: } else {
1486: sourceStepCRS = factories.getCRSFactory()
1487: .createCompoundCRS(getTemporaryName(sourceCRS),
1488: ordered);
1489: }
1490: operation = createFromAffineTransform(AXIS_CHANGES,
1491: sourceCRS, sourceStepCRS, select);
1492: }
1493: /*
1494: * Now creates the pass through transforms for each transformation steps found above.
1495: * We get (or construct temporary) source and target CRS for this step. They will be
1496: * given to the constructor of the pass through operation, after the construction of
1497: * pass through transform.
1498: */
1499: int lower, upper = 0;
1500: for (int i = 0; i < targets.length; i++) {
1501: CoordinateOperation step = steps[i];
1502: final Map properties = AbstractIdentifiedObject
1503: .getProperties(step);
1504: final CoordinateReferenceSystem source = ordered[i];
1505: final CoordinateReferenceSystem target = targets[i];
1506: final CoordinateReferenceSystem targetStepCRS;
1507: ordered[i] = target; // Used for the construction of targetStepCRS.
1508: MathTransform mt = step.getMathTransform();
1509: if (i >= count) {
1510: targetStepCRS = targetCRS;
1511: } else if (mt.isIdentity()) {
1512: targetStepCRS = sourceStepCRS;
1513: } else if (ordered.length == 1) {
1514: targetStepCRS = ordered[0];
1515: } else {
1516: targetStepCRS = factories.getCRSFactory()
1517: .createCompoundCRS(getTemporaryName(target),
1518: ordered);
1519: }
1520: lower = upper;
1521: upper += getDimension(source);
1522: if (lower != 0 || upper != dimensions) {
1523: /*
1524: * Constructs the pass through transform only if there is at least one ordinate to
1525: * pass. Actually, the code below would give an acceptable result even if this check
1526: * was not performed, except for creation of intermediate objects.
1527: */
1528: if (!(step instanceof Operation)) {
1529: final MathTransform stepMT = step
1530: .getMathTransform();
1531: step = DefaultOperation.create(
1532: AbstractIdentifiedObject
1533: .getProperties(step), step
1534: .getSourceCRS(), step
1535: .getTargetCRS(), stepMT,
1536: new DefaultOperationMethod(stepMT), step
1537: .getClass());
1538: }
1539: mt = getMathTransformFactory()
1540: .createPassThroughTransform(lower, mt,
1541: dimensions - upper);
1542: step = new DefaultPassThroughOperation(properties,
1543: sourceStepCRS, targetStepCRS, (Operation) step,
1544: mt);
1545: }
1546: operation = (operation == null) ? step : concatenate(
1547: operation, step);
1548: sourceStepCRS = targetStepCRS;
1549: }
1550: assert upper == dimensions : upper;
1551: return operation;
1552: }
1553:
1554: /**
1555: * Returns {@code true} if a transformation path from {@code sourceCRS} to
1556: * {@code targetCRS} is likely to requires a tri-dimensional geodetic CRS as an
1557: * intermediate step. More specifically, this method returns {@code false} if at
1558: * least one of the following conditions is meet:
1559: *
1560: * <ul>
1561: * <li>The target datum is not a vertical or geodetic one (the two datum that must work
1562: * together). Consequently, a potential datum change is not the caller's business.
1563: * It will be handled by the generic method above.</li>
1564: *
1565: * <li>The target datum is vertical or geodetic, but there is no datum change. It is
1566: * better to not try to create 3D-geodetic CRS, since they are more difficult to
1567: * separate in the generic method above. An exception to this rule occurs when
1568: * the target datum is used in a three-dimensional CRS.</li>
1569: *
1570: * <li>A datum change is required, but source CRS doesn't have both a geodetic
1571: * and a vertical CRS, so we can't apply a 3D datum shift anyway.</li>
1572: * </ul>
1573: */
1574: private static boolean needsGeodetic3D(final SingleCRS[] sourceCRS,
1575: final SingleCRS targetCRS) {
1576: final boolean targetGeodetic;
1577: final Datum targetDatum = targetCRS.getDatum();
1578: if (targetDatum instanceof GeodeticDatum) {
1579: targetGeodetic = true;
1580: } else if (targetDatum instanceof VerticalDatum) {
1581: targetGeodetic = false;
1582: } else {
1583: return false;
1584: }
1585: boolean horizontal = false;
1586: boolean vertical = false;
1587: boolean shift = false;
1588: for (int i = 0; i < sourceCRS.length; i++) {
1589: final Datum sourceDatum = sourceCRS[i].getDatum();
1590: final boolean sourceGeodetic;
1591: if (sourceDatum instanceof GeodeticDatum) {
1592: horizontal = true;
1593: sourceGeodetic = true;
1594: } else if (sourceDatum instanceof VerticalDatum) {
1595: vertical = true;
1596: sourceGeodetic = false;
1597: } else {
1598: continue;
1599: }
1600: if (!shift && sourceGeodetic == targetGeodetic) {
1601: shift = !equalsIgnoreMetadata(sourceDatum, targetDatum);
1602: assert Utilities.sameInterfaces(sourceDatum.getClass(),
1603: targetDatum.getClass(), Datum.class);
1604: }
1605: }
1606: return horizontal
1607: && vertical
1608: && (shift || targetCRS.getCoordinateSystem()
1609: .getDimension() >= 3);
1610: }
1611:
1612: /////////////////////////////////////////////////////////////////////////////////
1613: /////////////////////////////////////////////////////////////////////////////////
1614: //////////// ////////////
1615: //////////// M I S C E L L A N E O U S ////////////
1616: //////////// ////////////
1617: /////////////////////////////////////////////////////////////////////////////////
1618: /////////////////////////////////////////////////////////////////////////////////
1619:
1620: /**
1621: * Compares the specified datum for equality, except the prime meridian.
1622: *
1623: * @param object1 The first object to compare (may be null).
1624: * @param object2 The second object to compare (may be null).
1625: * @return {@code true} if both objects are equals.
1626: */
1627: private static boolean equalsIgnorePrimeMeridian(
1628: GeodeticDatum object1, GeodeticDatum object2) {
1629: object1 = TemporaryDatum.unwrap(object1);
1630: object2 = TemporaryDatum.unwrap(object2);
1631: if (equalsIgnoreMetadata(object1.getEllipsoid(), object2
1632: .getEllipsoid())) {
1633: return nameMatches(object1, object2.getName().getCode())
1634: || nameMatches(object2, object1.getName().getCode());
1635: }
1636: return false;
1637: }
1638:
1639: /**
1640: * Returns {@code true} if either the primary name or at least
1641: * one alias matches the specified string.
1642: *
1643: * @param object The object to check.
1644: * @param name The name.
1645: * @return {@code true} if the primary name of at least one alias
1646: * matches the specified {@code name}.
1647: *
1648: * @todo Delete and replace by a static import when we
1649: * will be allowed to compile against J2SE 1.5.
1650: */
1651: private static boolean nameMatches(final IdentifiedObject object,
1652: final String name) {
1653: return AbstractIdentifiedObject.nameMatches(object, name);
1654: }
1655:
1656: /**
1657: * Tries to get a coordinate operation from a database (typically EPSG). The exact behavior
1658: * depends on the {@link AuthorityBackedFactory} implementation (the most typical subclass),
1659: * but usually the database query is degelated to some instance of
1660: * {@link org.opengis.referencing.operation.CoordinateOperationAuthorityFactory}.
1661: * If no coordinate operation was found in the database, then this method returns {@code null}.
1662: */
1663: private final CoordinateOperation tryDB(final SingleCRS sourceCRS,
1664: final SingleCRS targetCRS) {
1665: return (sourceCRS == targetCRS) ? null : createFromDatabase(
1666: sourceCRS, targetCRS);
1667: }
1668:
1669: /**
1670: * If the coordinate operation is explicitly defined in some database (typically EPSG),
1671: * returns it. Otherwise (if there is no database, or if the database doesn't contains
1672: * an explicit operation from {@code sourceCRS} to {@code targetCRS}, or if this method
1673: * failed to create an operation from the database), returns {@code null}.
1674: * <p>
1675: * The default implementation always returns {@code null}, since there is no database
1676: * connected to a {@code DefaultCoordinateOperationFactory} instance. In other words,
1677: * the default implementation is "standalone": it tries to figure out transformation
1678: * paths by itself. Subclasses should override this method if they can fetch a more
1679: * accurate operation from some database. The mean subclass doing so is
1680: * {@link AuthorityBackedFactory}.
1681: * <p>
1682: * This method is invoked by <code>{@linkplain #createOperation createOperation}(sourceCRS,
1683: * targetCRS)</code> before to try to figure out a transformation path by itself. It is also
1684: * invoked by various {@code createOperationStep(...)} methods when an intermediate CRS was
1685: * obtained by {@link GeneralDerivedCRS#getBaseCRS()} (this case occurs especially during
1686: * {@linkplain GeographicCRS geographic} from/to {@linkplain ProjectedCRS projected} CRS
1687: * operations). This method is <strong>not</strong> invoked for synthetic CRS generated by
1688: * {@code createOperationStep(...)}, since those temporary CRS are not expected to exist
1689: * in a database.
1690: *
1691: * @param sourceCRS Input coordinate reference system.
1692: * @param targetCRS Output coordinate reference system.
1693: * @return A coordinate operation from {@code sourceCRS} to {@code targetCRS} if and only if
1694: * one is explicitly defined in some underlying database, or {@code null} otherwise.
1695: *
1696: * @since 2.3
1697: */
1698: protected CoordinateOperation createFromDatabase(
1699: final CoordinateReferenceSystem sourceCRS,
1700: final CoordinateReferenceSystem targetCRS) {
1701: return null;
1702: }
1703: }
|