Source Code Cross Referenced for DefaultCoordinateOperationFactory.java in  » GIS » GeoTools-2.4.1 » org » geotools » referencing » operation » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » GIS » GeoTools 2.4.1 » org.geotools.referencing.operation 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


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  &rarr;  {@linkplain ProjectedCRS#getBaseCRS baseCRS}  &rarr;  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  &rarr;  {@linkplain ProjectedCRS#getBaseCRS baseCRS}  &rarr;  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:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.