001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/model/csct/ct/CoordinateTransformationFactory.java $ 002 /*---------------- FILE HEADER ------------------------------------------ 003 004 This file is part of deegree. 005 Copyright (C) 2001 by: 006 EXSE, Department of Geography, University of Bonn 007 http://www.giub.uni-bonn.de/exse/ 008 lat/lon GmbH 009 http://www.lat-lon.de 010 011 It has been implemented within SEAGIS - An OpenSource implementation of OpenGIS specification 012 (C) 2001, Institut de Recherche pour le D�veloppement (http://sourceforge.net/projects/seagis/) 013 SEAGIS Contacts: Surveillance de l'Environnement Assist�e par Satellite 014 Institut de Recherche pour le D�veloppement / US-Espace 015 mailto:seasnet@teledetection.fr 016 017 018 This library is free software; you can redistribute it and/or 019 modify it under the terms of the GNU Lesser General Public 020 License as published by the Free Software Foundation; either 021 version 2.1 of the License, or (at your option) any later version. 022 023 This library is distributed in the hope that it will be useful, 024 but WITHOUT ANY WARRANTY; without even the implied warranty of 025 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 026 Lesser General Public License for more details. 027 028 You should have received a copy of the GNU Lesser General Public 029 License along with this library; if not, write to the Free Software 030 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 031 032 Contact: 033 034 Andreas Poth 035 lat/lon GmbH 036 Aennchenstr. 19 037 53115 Bonn 038 Germany 039 E-Mail: poth@lat-lon.de 040 041 Klaus Greve 042 Department of Geography 043 University of Bonn 044 Meckenheimer Allee 166 045 53115 Bonn 046 Germany 047 E-Mail: klaus.greve@uni-bonn.de 048 049 050 ---------------------------------------------------------------------------*/ 051 package org.deegree.model.csct.ct; 052 053 // OpenGIS dependencies 054 import javax.media.jai.ParameterList; 055 import javax.vecmath.SingularMatrixException; 056 057 import org.deegree.model.csct.cs.AxisOrientation; 058 import org.deegree.model.csct.cs.CompoundCoordinateSystem; 059 import org.deegree.model.csct.cs.CoordinateSystem; 060 import org.deegree.model.csct.cs.Ellipsoid; 061 import org.deegree.model.csct.cs.GeocentricCoordinateSystem; 062 import org.deegree.model.csct.cs.GeographicCoordinateSystem; 063 import org.deegree.model.csct.cs.HorizontalCoordinateSystem; 064 import org.deegree.model.csct.cs.HorizontalDatum; 065 import org.deegree.model.csct.cs.PrimeMeridian; 066 import org.deegree.model.csct.cs.ProjectedCoordinateSystem; 067 import org.deegree.model.csct.cs.Projection; 068 import org.deegree.model.csct.cs.TemporalCoordinateSystem; 069 import org.deegree.model.csct.cs.VerticalCoordinateSystem; 070 import org.deegree.model.csct.cs.VerticalDatum; 071 import org.deegree.model.csct.cs.WGS84ConversionInfo; 072 import org.deegree.model.csct.pt.Dimensioned; 073 import org.deegree.model.csct.pt.Matrix; 074 import org.deegree.model.csct.resources.OpenGIS; 075 import org.deegree.model.csct.resources.Utilities; 076 import org.deegree.model.csct.resources.css.ResourceKeys; 077 import org.deegree.model.csct.resources.css.Resources; 078 import org.deegree.model.csct.units.Unit; 079 080 /** 081 * Creates coordinate transformations. 082 * 083 * @version 1.0 084 * @author OpenGIS (www.opengis.org) 085 * @author Martin Desruisseaux 086 * 087 * @see org.opengis.ct.CT_CoordinateTransformationFactory 088 */ 089 public class CoordinateTransformationFactory { 090 /** 091 * The default coordinate transformation factory. 092 * Will be constructed only when first needed. 093 */ 094 private static CoordinateTransformationFactory DEFAULT; 095 096 /** 097 * Number for temporary created objects. This number is 098 * incremented each time {@link #getTemporaryName} is 099 * invoked. 100 */ 101 private static volatile int temporaryID; 102 103 /** 104 * The underlying math transform factory. 105 */ 106 private final MathTransformFactory factory; 107 108 /** 109 * Construct a coordinate transformation factory. 110 * 111 * @param factory The math transform factory to use. 112 */ 113 public CoordinateTransformationFactory( final MathTransformFactory factory ) { 114 this.factory = factory; 115 } 116 117 /** 118 * Returns the default coordinate transformation factory. 119 */ 120 public static synchronized CoordinateTransformationFactory getDefault() { 121 if ( DEFAULT == null ) { 122 DEFAULT = new CoordinateTransformationFactory( MathTransformFactory.getDefault() ); 123 } 124 return DEFAULT; 125 } 126 127 /** 128 * Returns the underlying math transform factory. This factory 129 * is used for constructing {@link MathTransform} objects for 130 * all {@link CoordinateTransformation}. 131 */ 132 public final MathTransformFactory getMathTransformFactory() { 133 return factory; 134 } 135 136 /** 137 * Creates a transformation between two coordinate systems. 138 * This method will examine the coordinate systems in order to construct a 139 * transformation between them. This method may fail if no path between the 140 * coordinate systems is found. 141 * 142 * @param sourceCS Input coordinate system. 143 * @param targetCS Output coordinate system. 144 * @return A coordinate transformation from <code>sourceCS</code> to <code>targetCS</code>. 145 * @throws CannotCreateTransformException if no transformation path has been found. 146 * 147 */ 148 public CoordinateTransformation createFromCoordinateSystems( final CoordinateSystem sourceCS, 149 final CoordinateSystem targetCS ) 150 throws CannotCreateTransformException { 151 ///////////////////////////////////////////////////////////////////// 152 //// //// 153 //// Geographic --> Geographic, Projected or Geocentric //// 154 //// //// 155 ///////////////////////////////////////////////////////////////////// 156 if ( sourceCS instanceof GeographicCoordinateSystem ) { 157 final GeographicCoordinateSystem source = (GeographicCoordinateSystem) sourceCS; 158 if ( targetCS instanceof GeographicCoordinateSystem ) { 159 return createTransformationStep( source, (GeographicCoordinateSystem) targetCS ); 160 } 161 if ( targetCS instanceof ProjectedCoordinateSystem ) { 162 return createTransformationStep( source, (ProjectedCoordinateSystem) targetCS ); 163 } 164 if ( targetCS instanceof GeocentricCoordinateSystem ) { 165 return createTransformationStep( source, (GeocentricCoordinateSystem) targetCS ); 166 } 167 } 168 ///////////////////////////////////////////////////////////////////// 169 //// //// 170 //// Projected --> Projected, Geographic or Geocentric //// 171 //// //// 172 ///////////////////////////////////////////////////////////////////// 173 if ( sourceCS instanceof ProjectedCoordinateSystem ) { 174 final ProjectedCoordinateSystem source = (ProjectedCoordinateSystem) sourceCS; 175 if ( targetCS instanceof ProjectedCoordinateSystem ) { 176 return createTransformationStep( source, (ProjectedCoordinateSystem) targetCS ); 177 } 178 if ( targetCS instanceof GeographicCoordinateSystem ) { 179 return createTransformationStep( source, (GeographicCoordinateSystem) targetCS ); 180 } 181 if ( targetCS instanceof GeocentricCoordinateSystem ) { 182 return createTransformationStep( source, (GeocentricCoordinateSystem) targetCS ); 183 } 184 } 185 ///////////////////////////////////////////////////////////////////// 186 //// //// 187 //// Geocentric --> Geocentric, Horizontal or Compound //// 188 //// //// 189 ///////////////////////////////////////////////////////////////////// 190 if ( sourceCS instanceof GeocentricCoordinateSystem ) { 191 final GeocentricCoordinateSystem source = (GeocentricCoordinateSystem) sourceCS; 192 if ( targetCS instanceof GeocentricCoordinateSystem ) { 193 return createTransformationStep( source, (GeocentricCoordinateSystem) targetCS ); 194 } 195 try { 196 return createFromCoordinateSystems( targetCS, sourceCS ).inverse(); 197 } catch ( TransformException exception ) { 198 final CannotCreateTransformException e = new CannotCreateTransformException( 199 sourceCS, 200 targetCS ); 201 e.initCause( exception ); 202 throw e; 203 } 204 } 205 ///////////////////////////////////////// 206 //// //// 207 //// Vertical --> Vertical //// 208 //// //// 209 ///////////////////////////////////////// 210 if ( sourceCS instanceof VerticalCoordinateSystem ) { 211 final VerticalCoordinateSystem source = (VerticalCoordinateSystem) sourceCS; 212 if ( targetCS instanceof VerticalCoordinateSystem ) { 213 return createTransformationStep( source, (VerticalCoordinateSystem) targetCS ); 214 } 215 } 216 ///////////////////////////////////////// 217 //// //// 218 //// Temporal --> Temporal //// 219 //// //// 220 ///////////////////////////////////////// 221 if ( sourceCS instanceof TemporalCoordinateSystem ) { 222 final TemporalCoordinateSystem source = (TemporalCoordinateSystem) sourceCS; 223 if ( targetCS instanceof TemporalCoordinateSystem ) { 224 return createTransformationStep( source, (TemporalCoordinateSystem) targetCS ); 225 } 226 } 227 /////////////////////////////////////////// 228 //// //// 229 //// Compound --> various CS //// 230 //// //// 231 /////////////////////////////////////////// 232 if ( sourceCS instanceof CompoundCoordinateSystem ) { 233 final CompoundCoordinateSystem source = (CompoundCoordinateSystem) sourceCS; 234 if ( targetCS instanceof CompoundCoordinateSystem ) { 235 return createTransformationStep( source, (CompoundCoordinateSystem) targetCS ); 236 } 237 if ( targetCS instanceof GeocentricCoordinateSystem ) { 238 return createTransformationStep( source, (GeocentricCoordinateSystem) targetCS ); 239 } 240 /* 241 * Try a loosely transformation. For example, the source CS may be 242 * a geographic + vertical coordinate systems, will the target CS 243 * may be only the geographic part. The code below will try to 244 * discart one or more dimension. 245 */ 246 final CoordinateSystem headSourceCS = source.getHeadCS(); 247 final CoordinateSystem tailSourceCS = source.getTailCS(); 248 final int dimHeadCS = headSourceCS.getDimension(); 249 final int dimSource = source.getDimension(); 250 CoordinateTransformation step2; 251 int lower, upper; 252 try { 253 lower = 0; 254 upper = dimHeadCS; 255 step2 = createFromCoordinateSystems( headSourceCS, targetCS ); 256 } catch ( CannotCreateTransformException exception ) { 257 /* 258 * If we can't construct a transformation from the head CS, 259 * then try a transformation from the tail CS. If this step 260 * fails also, then the head CS will be taken as the raison 261 * for the failure. 262 */ 263 try { 264 lower = dimHeadCS; 265 upper = dimSource; 266 step2 = createFromCoordinateSystems( tailSourceCS, targetCS ); 267 } catch ( CannotCreateTransformException ignore ) { 268 CannotCreateTransformException e = new CannotCreateTransformException( 269 sourceCS, 270 targetCS ); 271 e.initCause( exception ); 272 throw e; 273 } 274 } 275 /* 276 * A coordinate transformation from the head or tail part of 'sourceCS' 277 * has been succesfully contructed. Now, build a matrix transform that 278 * will select only the corresponding ordinates from input arrays, and 279 * pass them to the transform. 280 */ 281 final MathTransform step1 = factory.createSubMathTransform( 282 lower, 283 upper, 284 factory.createIdentityTransform( dimSource ) ); 285 final MathTransform transform = factory.createConcatenatedTransform( 286 step1, 287 step2.getMathTransform() ); 288 return createFromMathTransform( sourceCS, targetCS, step2.getTransformType(), transform ); 289 } 290 throw new CannotCreateTransformException( sourceCS, targetCS ); 291 } 292 293 /** 294 * Creates a transformation between two temporal coordinate systems. This 295 * method is automatically invoked by {@link #createFromCoordinateSystems 296 * createFromCoordinateSystems(...)}. The default implementation checks if 297 * both coordinate systems use the same datum, and then adjusts for axis 298 * orientation, units and epoch. 299 * 300 * @param sourceCS Input coordinate system. 301 * @param targetCS Output coordinate system. 302 * @return A coordinate transformation from <code>sourceCS</code> to <code>targetCS</code>. 303 * @throws CannotCreateTransformException if no transformation path has been found. 304 */ 305 protected CoordinateTransformation createTransformationStep( 306 final TemporalCoordinateSystem sourceCS, 307 final TemporalCoordinateSystem targetCS ) 308 throws CannotCreateTransformException { 309 if ( Utilities.equals( sourceCS.getTemporalDatum(), targetCS.getTemporalDatum() ) ) { 310 // Compute the epoch shift 311 double epochShift = sourceCS.getEpoch().getTime() - targetCS.getEpoch().getTime(); 312 epochShift = targetCS.getUnits( 0 ).convert( epochShift / ( 24 * 60 * 60 * 1000 ), 313 Unit.DAY ); 314 315 // Get the affine transform, add the epoch 316 // shift and returns the resulting transform. 317 final Matrix matrix = swapAndScaleAxis( sourceCS, targetCS ); 318 final int translationColumn = matrix.getNumCol() - 1; 319 if ( translationColumn >= 0 ) // Paranoiac check: should always be 1. 320 { 321 final double translation = matrix.getElement( 0, translationColumn ); 322 matrix.setElement( 0, translationColumn, translation + epochShift ); 323 } 324 final MathTransform transform = factory.createAffineTransform( matrix ); 325 return createFromMathTransform( sourceCS, targetCS, TransformType.CONVERSION, transform ); 326 } 327 throw new CannotCreateTransformException( sourceCS, targetCS ); 328 } 329 330 /** 331 * Creates a transformation between two vertical coordinate systems. This 332 * method is automatically invoked by {@link #createFromCoordinateSystems 333 * createFromCoordinateSystems(...)}. The default implementation checks if 334 * both coordinate systems use the same datum, and then adjusts for axis 335 * orientation and units. 336 * 337 * @param sourceCS Input coordinate system. 338 * @param targetCS Output coordinate system. 339 * @return A coordinate transformation from <code>sourceCS</code> to <code>targetCS</code>. 340 * @throws CannotCreateTransformException if no transformation path has been found. 341 */ 342 protected CoordinateTransformation createTransformationStep( 343 final VerticalCoordinateSystem sourceCS, 344 final VerticalCoordinateSystem targetCS ) 345 throws CannotCreateTransformException { 346 if ( Utilities.equals( sourceCS.getVerticalDatum(), targetCS.getVerticalDatum() ) ) { 347 final Matrix matrix = swapAndScaleAxis( sourceCS, targetCS ); 348 final MathTransform transform = factory.createAffineTransform( matrix ); 349 return createFromMathTransform( sourceCS, targetCS, TransformType.CONVERSION, transform ); 350 } 351 throw new CannotCreateTransformException( sourceCS, targetCS ); 352 } 353 354 /** 355 * Creates a transformation between two geographic coordinate systems. This 356 * method is automatically invoked by {@link #createFromCoordinateSystems 357 * createFromCoordinateSystems(...)}. The default implementation can adjust 358 * axis order and orientation (e.g. transforming from <code>(NORTH,WEST)</code> 359 * to <code>(EAST,NORTH)</code>), performs units conversion and apply Bursa Wolf 360 * transformation if needed. 361 * 362 * @param sourceCS Input coordinate system. 363 * @param targetCS Output coordinate system. 364 * @return A coordinate transformation from <code>sourceCS</code> to <code>targetCS</code>. 365 * @throws CannotCreateTransformException if no transformation path has been found. 366 */ 367 protected CoordinateTransformation createTransformationStep( 368 final GeographicCoordinateSystem sourceCS, 369 final GeographicCoordinateSystem targetCS ) 370 throws CannotCreateTransformException { 371 final HorizontalDatum sourceDatum = sourceCS.getHorizontalDatum(); 372 final HorizontalDatum targetDatum = targetCS.getHorizontalDatum(); 373 final Ellipsoid sourceEllipsoid = sourceDatum.getEllipsoid(); 374 final Ellipsoid targetEllipsoid = targetDatum.getEllipsoid(); 375 if ( !Utilities.equals( sourceEllipsoid, targetEllipsoid ) ) 376 try { 377 /* 378 * If the two geographic coordinate systems use differents ellipsoid, 379 * convert from the source to target ellipsoid through the geocentric 380 * coordinate system. 381 */ 382 final String name = getTemporaryName(); 383 final GeocentricCoordinateSystem gcs1 = new GeocentricCoordinateSystem( name, 384 sourceDatum ); 385 final GeocentricCoordinateSystem gcs3 = new GeocentricCoordinateSystem( name, 386 targetDatum ); 387 CoordinateTransformation step1 = createTransformationStep( sourceCS, gcs1 ); 388 CoordinateTransformation step2 = createTransformationStep( gcs1, gcs3 ); 389 CoordinateTransformation step3 = createTransformationStep( targetCS, gcs3 ).inverse(); 390 return concatenate( step1, step2, step3 ); 391 392 } catch ( TransformException exception ) { 393 CannotCreateTransformException e = new CannotCreateTransformException( sourceCS, 394 targetCS ); 395 throw e; 396 } 397 /* 398 * Swap axis order, and rotate the longitude 399 * coordinate if prime meridians are different. 400 */ 401 final Matrix matrix = swapAndScaleGeoAxis( sourceCS, targetCS ); 402 // TODO: We should ensure that longitude is in range [-180..+180�]. 403 final MathTransform transform = factory.createAffineTransform( matrix ); 404 return createFromMathTransform( sourceCS, targetCS, TransformType.CONVERSION, transform ); 405 } 406 407 /** 408 * Creates a transformation between two projected coordinate systems. This 409 * method is automatically invoked by {@link #createFromCoordinateSystems 410 * createFromCoordinateSystems(...)}. The default implementation can adjust 411 * axis order and orientation. It also performs units conversion if it 412 * is the only extra change needed. Otherwise, it performs three steps: 413 * 414 * <ol> 415 * <li>Unproject <code>sourceCS</code>.</li> 416 * <li>Transform from <code>sourceCS.geographicCS</code> to <code>targetCS.geographicCS</code>.</li> 417 * <li>Project <code>targetCS</code>.</li> 418 * </ol> 419 * 420 * @param sourceCS Input coordinate system. 421 * @param targetCS Output coordinate system. 422 * @return A coordinate transformation from <code>sourceCS</code> to <code>targetCS</code>. 423 * @throws CannotCreateTransformException if no transformation path has been found. 424 */ 425 protected CoordinateTransformation createTransformationStep( 426 final ProjectedCoordinateSystem sourceCS, 427 final ProjectedCoordinateSystem targetCS ) 428 throws CannotCreateTransformException { 429 if ( Utilities.equals( sourceCS.getProjection(), targetCS.getProjection() ) 430 && Utilities.equals( sourceCS.getHorizontalDatum(), targetCS.getHorizontalDatum() ) ) { 431 // This special case is necessary for createTransformationStep(GeographicCS,ProjectedCS). 432 // 'swapAndScaleAxis(...) takes care of axis orientation and units. Datum and projection 433 // have just been checked above. Prime meridien is not checked (TODO: should we do???) 434 final Matrix matrix = swapAndScaleAxis( sourceCS, targetCS ); 435 final MathTransform transform = factory.createAffineTransform( matrix ); 436 return createFromMathTransform( sourceCS, targetCS, TransformType.CONVERSION, transform ); 437 } 438 final GeographicCoordinateSystem sourceGeo = sourceCS.getGeographicCoordinateSystem(); 439 final GeographicCoordinateSystem targetGeo = targetCS.getGeographicCoordinateSystem(); 440 final CoordinateTransformation step1 = createTransformationStep( sourceCS, sourceGeo ); 441 final CoordinateTransformation step2 = createTransformationStep( sourceGeo, targetGeo ); 442 final CoordinateTransformation step3 = createTransformationStep( targetGeo, targetCS ); 443 return concatenate( step1, step2, step3 ); 444 } 445 446 /** 447 * Creates a transformation between a geographic and a projected coordinate systems. 448 * This method is automatically invoked by {@link #createFromCoordinateSystems 449 * createFromCoordinateSystems(...)}. 450 * 451 * @param sourceCS Input coordinate system. 452 * @param targetCS Output coordinate system. 453 * @return A coordinate transformation from <code>sourceCS</code> to <code>targetCS</code>. 454 * @throws CannotCreateTransformException if no transformation path has been found. 455 */ 456 protected CoordinateTransformation createTransformationStep( 457 final GeographicCoordinateSystem sourceCS, 458 final ProjectedCoordinateSystem targetCS ) 459 throws CannotCreateTransformException { 460 461 final ProjectedCoordinateSystem stepProjCS = normalize( targetCS ); 462 final GeographicCoordinateSystem stepGeoCS = stepProjCS.getGeographicCoordinateSystem(); 463 464 final Projection projection = stepProjCS.getProjection(); 465 final MathTransform mapProjection = factory.createParameterizedTransform( projection ); 466 // should perform a datum shift 467 final CoordinateTransformation step1 = createTransformationStep( sourceCS, stepGeoCS ); 468 final CoordinateTransformation step2 = createFromMathTransform( stepGeoCS, stepProjCS, 469 TransformType.CONVERSION, 470 mapProjection ); 471 472 // TODO 473 // this is possibly an error because stepProjCS and targetCS 474 // are identical 475 //final CoordinateTransformation step3 = createTransformationStep( stepProjCS, targetCS ); 476 //return concatenate( step1, step2, step3 ); 477 // it seems it must look like this 478 return concatenate( step1, step2, null ); 479 } 480 481 /** 482 * Creates a transformation between a projected and a geographic coordinate systems. 483 * This method is automatically invoked by {@link #createFromCoordinateSystems 484 * createFromCoordinateSystems(...)}. The default implementation returns 485 * <code>{@link #createTransformationStep(GeographicCoordinateSystem, ProjectedCoordinateSystem) 486 * createTransformationStep}(targetCS, sourceCS).{@link MathTransform#inverse() inverse()}</code>. 487 * 488 * @param sourceCS Input coordinate system. 489 * @param targetCS Output coordinate system. 490 * @return A coordinate transformation from <code>sourceCS</code> to <code>targetCS</code>. 491 * @throws CannotCreateTransformException if no transformation path has been found. 492 */ 493 protected CoordinateTransformation createTransformationStep( 494 final ProjectedCoordinateSystem sourceCS, 495 final GeographicCoordinateSystem targetCS ) 496 throws CannotCreateTransformException { 497 try { 498 return createTransformationStep( targetCS, sourceCS ).inverse(); 499 } catch ( NoninvertibleTransformException exception ) { 500 final CannotCreateTransformException e = new CannotCreateTransformException( sourceCS, 501 targetCS ); 502 throw e; 503 } 504 } 505 506 /** 507 * Creates a transformation between two geocentric coordinate systems. This 508 * method is automatically invoked by {@link #createFromCoordinateSystems 509 * createFromCoordinateSystems(...)}. The default implementation can adjust 510 * for axis order and orientation, adjust for prime meridian, performs units 511 * conversion and apply Bursa Wolf transformation if needed. 512 * 513 * @param sourceCS Input coordinate system. 514 * @param targetCS Output coordinate system. 515 * @return A coordinate transformation from <code>sourceCS</code> to <code>targetCS</code>. 516 * @throws CannotCreateTransformException if no transformation path has been found. 517 */ 518 protected CoordinateTransformation createTransformationStep( 519 final GeocentricCoordinateSystem sourceCS, 520 final GeocentricCoordinateSystem targetCS ) 521 throws CannotCreateTransformException { 522 final HorizontalDatum sourceHD = sourceCS.getHorizontalDatum(); 523 final HorizontalDatum targetHD = targetCS.getHorizontalDatum(); 524 if ( Utilities.equals( sourceHD, targetHD ) ) { 525 if ( Utilities.equals( sourceCS.getPrimeMeridian(), targetCS.getPrimeMeridian() ) ) { 526 final Matrix matrix = swapAndScaleAxis( sourceCS, targetCS ); 527 final MathTransform transform = factory.createAffineTransform( matrix ); 528 return createFromMathTransform( sourceCS, targetCS, TransformType.CONVERSION, 529 transform ); 530 } 531 // If prime meridians are not the same, performs the full transformation. 532 } 533 if ( !PrimeMeridian.GREENWICH.equals( sourceCS.getPrimeMeridian() ) 534 || !PrimeMeridian.GREENWICH.equals( targetCS.getPrimeMeridian() ) ) { 535 throw new CannotCreateTransformException( 536 "Rotation of prime meridian not yet implemented" ); 537 } 538 // Transform between differents ellipsoids 539 // using Bursa Wolf parameters. 540 final Matrix step1 = swapAndScaleAxis( sourceCS, GeocentricCoordinateSystem.DEFAULT ); 541 final Matrix step2 = getWGS84Parameters( sourceHD ); 542 final Matrix step3 = getWGS84Parameters( targetHD ); 543 final Matrix step4 = swapAndScaleAxis( GeocentricCoordinateSystem.DEFAULT, targetCS ); 544 545 if ( step2 != null && step3 != null ) 546 try { 547 // Note: GMatrix.mul(GMatrix) is equivalents to AffineTransform.concatenate(...): 548 // First transform by the supplied transform and then transform the result 549 // by the original transform. 550 step3.invert(); // Invert in place. 551 step4.mul( step3 ); // step4 = step4*step3 552 step4.mul( step2 ); // step4 = step4*step3*step2 553 step4.mul( step1 ); // step4 = step4*step3*step2*step1 554 final MathTransform transform = factory.createAffineTransform( step4 ); 555 return createFromMathTransform( sourceCS, targetCS, TransformType.CONVERSION, 556 transform ); 557 } catch ( SingularMatrixException exception ) { 558 final CannotCreateTransformException e = new CannotCreateTransformException( 559 sourceCS, 560 targetCS ); 561 throw e; 562 } 563 throw new CannotCreateTransformException( 564 Resources.format( ResourceKeys.BURSA_WOLF_PARAMETERS_REQUIRED ) ); 565 } 566 567 /** 568 * Creates a transformation between a geographic and a geocentric coordinate systems. 569 * Since the source coordinate systems doesn't have a vertical axis, height above the 570 * ellipsoid is assumed equals to zero everywhere. This method is automatically invoked 571 * by {@link #createFromCoordinateSystems createFromCoordinateSystems(...)}. 572 * 573 * @param sourceCS Input geographic coordinate system. 574 * @param targetCS Output coordinate system. 575 * @return A coordinate transformation from <code>sourceCS</code> to <code>targetCS</code>. 576 * @throws CannotCreateTransformException if no transformation path has been found. 577 */ 578 protected CoordinateTransformation createTransformationStep(final GeographicCoordinateSystem sourceCS, 579 final GeocentricCoordinateSystem targetCS ) 580 throws CannotCreateTransformException { 581 if ( !PrimeMeridian.GREENWICH.equals( targetCS.getPrimeMeridian() ) ) { 582 throw new CannotCreateTransformException( "Rotation of prime meridian not yet implemented" ); 583 } 584 final MathTransform step1 = 585 factory.createAffineTransform( swapAndScaleGeoAxis( sourceCS, GeographicCoordinateSystem.WGS84 ) ); 586 final MathTransform step2 = getGeocentricTransform( "Ellipsoid_To_Geocentric", 2, 587 sourceCS.getHorizontalDatum().getEllipsoid() ); 588 return createFromMathTransform( sourceCS, targetCS, TransformType.CONVERSION, 589 factory.createConcatenatedTransform( step1, step2 ) ); 590 } 591 592 /** 593 * Creates a transformation between a projected and a geocentric coordinate systems. 594 * This method is automatically invoked by {@link #createFromCoordinateSystems 595 * createFromCoordinateSystems(...)}. This method doesn't need to be public since 596 * its decomposition in two step should be general enough. 597 * 598 * @param sourceCS Input projected coordinate system. 599 * @param targetCS Output coordinate system. 600 * @return A coordinate transformation from <code>sourceCS</code> to <code>targetCS</code>. 601 * @throws CannotCreateTransformException if no transformation path has been found. 602 */ 603 private CoordinateTransformation createTransformationStep( 604 final ProjectedCoordinateSystem sourceCS, 605 final GeocentricCoordinateSystem targetCS ) 606 throws CannotCreateTransformException { 607 final GeographicCoordinateSystem sourceGCS = sourceCS.getGeographicCoordinateSystem(); 608 final CoordinateTransformation step1 = createTransformationStep( sourceCS, sourceGCS ); 609 final CoordinateTransformation step2 = createTransformationStep( sourceGCS, targetCS ); 610 return concatenate( step1, step2 ); 611 } 612 613 /** 614 * Creates a transformation between a compound and a geocentric coordinate systems. 615 * The compound coordinate system <strong>must</strong> be an aggregate of the two 616 * following coordinate systems: 617 * 618 * <ul> 619 * <li>The head must be an instance of {@link HorizontalCoordinateSystem}</li> 620 * <li>The tail must be an instance of {@link VerticalCoordinateSystem}</li> 621 * </ul> 622 * 623 * This method is automatically invoked by {@link #createFromCoordinateSystems 624 * createFromCoordinateSystems(...)}. 625 * 626 * @param sourceCS Input compound coordinate system. 627 * @param targetCS Output coordinate system. 628 * @return A coordinate transformation from <code>sourceCS</code> to <code>targetCS</code>. 629 * @throws CannotCreateTransformException if no transformation path has been found. 630 */ 631 protected CoordinateTransformation createTransformationStep( 632 final CompoundCoordinateSystem sourceCS, 633 final GeocentricCoordinateSystem targetCS ) 634 throws CannotCreateTransformException { 635 /* 636 * Construct a temporary transformation from 'sourceCS' to a standard coordinate 637 * system which is an agregate of a geographic and a vertical coordinate system. 638 * The horizontal datum is preserved, but other properties (vertical datum, axis 639 * order, units, prime meridian...) are "standardized". 640 */ 641 final String name = getTemporaryName(); 642 final HorizontalDatum datum = OpenGIS.getHorizontalDatum( sourceCS ); 643 final CompoundCoordinateSystem stdCS = 644 new CompoundCoordinateSystem( name, new GeographicCoordinateSystem( name, datum ), 645 new VerticalCoordinateSystem( name, VerticalDatum.ELLIPSOIDAL ) ); 646 final CoordinateTransformation step1 = createTransformationStep( sourceCS, stdCS ); 647 /* 648 * Construct the next steps: conversion to the geocentric coordinate 649 * system, and then conversion to the requested coordinate system. 650 * In summary, the 3 conversions steps are: 651 * 652 * sourceCS --> standardized geographic CS --> standardized geocentric CS --> targetCS 653 */ 654 final MathTransform transform = getGeocentricTransform( "Ellipsoid_To_Geocentric", 3, 655 datum.getEllipsoid() ); 656 final CoordinateTransformation step2 = createFromMathTransform( stdCS, 657 GeocentricCoordinateSystem.DEFAULT, 658 TransformType.CONVERSION, 659 transform ); 660 final CoordinateTransformation step3 = createTransformationStep( GeocentricCoordinateSystem.DEFAULT, 661 targetCS ); 662 return concatenate( step1, step2, step3 ); 663 } 664 665 /** 666 * Creates a transformation between two compound coordinate systems. This 667 * method is automatically invoked by {@link #createFromCoordinateSystems 668 * createFromCoordinateSystems(...)}. 669 * 670 * @param sourceCS Input coordinate system. 671 * @param targetCS Output coordinate system. 672 * @return A coordinate transformation from <code>sourceCS</code> to <code>targetCS</code>. 673 * @throws CannotCreateTransformException if no transformation path has been found. 674 */ 675 protected CoordinateTransformation createTransformationStep( 676 final CompoundCoordinateSystem sourceCS, 677 final CompoundCoordinateSystem targetCS ) 678 throws CannotCreateTransformException { 679 final CoordinateSystem headSourceCS = sourceCS.getHeadCS(); 680 final CoordinateSystem tailSourceCS = sourceCS.getTailCS(); 681 final CoordinateSystem headTargetCS = targetCS.getHeadCS(); 682 final CoordinateSystem tailTargetCS = targetCS.getTailCS(); 683 if ( tailSourceCS.equivalents( tailTargetCS ) ) { 684 final CoordinateTransformation tr = createFromCoordinateSystems( headSourceCS, 685 headTargetCS ); 686 final MathTransform transform = factory.createPassThroughTransform( 687 0, 688 tr.getMathTransform(), 689 tailSourceCS.getDimension() ); 690 return createFromMathTransform( sourceCS, targetCS, tr.getTransformType(), transform ); 691 } 692 if ( headSourceCS.equivalents( headTargetCS ) ) { 693 final CoordinateTransformation tr = createFromCoordinateSystems( tailSourceCS, 694 tailTargetCS ); 695 final MathTransform transform = factory.createPassThroughTransform( 696 headSourceCS.getDimension(), 697 tr.getMathTransform(), 698 0 ); 699 return createFromMathTransform( sourceCS, targetCS, tr.getTransformType(), transform ); 700 } 701 // TODO: implement others CompoundCoordinateSystem cases. 702 // We could do it in a more general way be creating 703 // and using a 'CompoundTransform' class instead of 704 // of 'PassThroughTransform'. PassThroughTransform 705 // is really a special case of a 'CompoundTransform' 706 // where the head transform is the identity transform. 707 throw new CannotCreateTransformException( sourceCS, targetCS ); 708 } 709 710 //////////////////////////////////////////////////////////////////////// 711 //////////// //////////// 712 //////////// HELPER METHODS (private) //////////// 713 //////////// //////////// 714 //////////////////////////////////////////////////////////////////////// 715 716 /** 717 * Returns the WGS84 parameters as an affine transform, 718 * or <code>null</code> if not available. 719 */ 720 private static Matrix getWGS84Parameters( final HorizontalDatum datum ) { 721 final WGS84ConversionInfo info = datum.getWGS84Parameters(); 722 if ( info != null ) { 723 return info.getAffineTransform(); 724 } 725 if ( Ellipsoid.WGS84.equals( datum.getEllipsoid() ) ) { 726 return new Matrix( 4 ); // Identity matrix 727 } 728 return null; 729 } 730 731 /** 732 * Returns a transform between a geocentric 733 * coordinate system and an ellipsoid. 734 * 735 * @param classification either "Ellipsoid_To_Geocentric" or "Geocentric_To_Ellipsoid". 736 * @param dimGeoCS Dimension of the geographic coordinate system (2 or 3). 737 * @param ellipsoid The ellipsoid. 738 * @return The transformation. 739 */ 740 private MathTransform getGeocentricTransform( final String classification, final int dimGeoCS, 741 final Ellipsoid ellipsoid ) { 742 final Unit unit = ellipsoid.getAxisUnit(); 743 ParameterList param = factory.getMathTransformProvider( classification ).getParameterList(); 744 param = param.setParameter( "semi_major", Unit.METRE.convert( ellipsoid.getSemiMajorAxis(), 745 unit ) ); 746 param = param.setParameter( "semi_minor", Unit.METRE.convert( ellipsoid.getSemiMinorAxis(), 747 unit ) ); 748 try { 749 param = param.setParameter( "dim_geoCS", dimGeoCS ); 750 } catch ( IllegalArgumentException exception ) { 751 // The "dim_geoCS" is a custom argument needed for our SEAGIS 752 // implementation. It is not part of OpenGIS's specification. 753 // But if the required dimension is not 3, we can't finish 754 // the operation (TODO: What should we do? Open question...) 755 if ( dimGeoCS != 3 ) { 756 throw exception; 757 } 758 } 759 return factory.createParameterizedTransform( classification, param ); 760 } 761 762 /** 763 * Concatenate two transformation steps. 764 * 765 * @param step1 The first step, or <code>null</code> for the identity transform. 766 * @param step2 The second step, or <code>null</code> for the identity transform. 767 * @return A concatenated transform, or <code>null</code> if all arguments was nul. 768 */ 769 private CoordinateTransformation concatenate( final CoordinateTransformation step1, 770 final CoordinateTransformation step2 ) { 771 if ( step1 == null ) 772 return step2; 773 if ( step2 == null ) 774 return step1; 775 if ( !step1.getTargetCS().equivalents( step2.getSourceCS() ) ) { 776 throw new IllegalArgumentException( String.valueOf( step1 ) ); 777 } 778 final MathTransform step = factory.createConcatenatedTransform( step1.getMathTransform(), 779 step2.getMathTransform() ); 780 final TransformType type = step1.getTransformType().concatenate( step2.getTransformType() ); 781 782 return createFromMathTransform( step1.getSourceCS(), step2.getTargetCS(), type, step ); 783 } 784 785 /** 786 * Concatenate three transformation steps. 787 * 788 * @param step1 The first step, or <code>null</code> for the identity transform. 789 * @param step2 The second step, or <code>null</code> for the identity transform. 790 * @param step3 The third step, or <code>null</code> for the identity transform. 791 * @return A concatenated transform, or <code>null</code> if all arguments was nul. 792 */ 793 private CoordinateTransformation concatenate( final CoordinateTransformation step1, 794 final CoordinateTransformation step2, 795 final CoordinateTransformation step3 ) { 796 if ( step1 == null ) 797 return concatenate( step2, step3 ); 798 if ( step2 == null ) 799 return concatenate( step1, step3 ); 800 if ( step3 == null ) { 801 return concatenate( step1, step2 ); 802 } 803 if ( !step1.getTargetCS().equivalents( step2.getSourceCS() ) ) { 804 // Current message is for debugging purpose only. 805 throw new IllegalArgumentException( String.valueOf( step1 ) ); 806 } 807 if ( !step2.getTargetCS().equivalents( step3.getSourceCS() ) ) { 808 // Current message is for debugging purpose only. 809 throw new IllegalArgumentException( String.valueOf( step3 ) ); 810 } 811 final MathTransform step = factory.createConcatenatedTransform( 812 step1.getMathTransform(), 813 factory.createConcatenatedTransform( 814 step2.getMathTransform(), 815 step3.getMathTransform() ) ); 816 final TransformType type = step1.getTransformType().concatenate( 817 step2.getTransformType().concatenate( 818 step3.getTransformType() ) ); 819 return createFromMathTransform( step1.getSourceCS(), step3.getTargetCS(), type, step ); 820 } 821 822 /** 823 * Create a coordinate transform from a math transform. 824 * If the specified math transform is already a coordinate transform, and if source 825 * and target coordinate systems match, then <code>transform</code> is returned with 826 * no change. Otherwise, a new coordinate transform is created. 827 * 828 * @param sourceCS The source coordinate system. 829 * @param targetCS The destination coordinate system. 830 * @param type The transform type. 831 * @param transform The math transform. 832 * @return A coordinate transform using the specified math transform. 833 */ 834 private static CoordinateTransformation createFromMathTransform( 835 final CoordinateSystem sourceCS, 836 final CoordinateSystem targetCS, 837 final TransformType type, 838 final MathTransform transform ) { 839 if ( transform instanceof CoordinateTransformation ) { 840 final CoordinateTransformation ct = (CoordinateTransformation) transform; 841 if ( Utilities.equals( ct.getSourceCS(), sourceCS ) 842 && Utilities.equals( ct.getTargetCS(), targetCS ) ) { 843 return ct; 844 } 845 } 846 return (CoordinateTransformation) MathTransformFactory.pool.intern( new CoordinateTransformation( 847 null, 848 sourceCS, 849 targetCS, 850 type, 851 transform ) ); 852 } 853 854 /** 855 * Returns an affine transform between two coordinate systems. Only units and 856 * axis order (e.g. transforming from (NORTH,WEST) to (EAST,NORTH)) are taken 857 * in account. Other attributes (especially the datum) must be checked before 858 * invoking this method. 859 * 860 * @param sourceCS The source coordinate system. If <code>null</code>, then 861 * (x,y,z,t) axis order is assumed. 862 * @param targetCS The target coordinate system. If <code>null</code>, then 863 * (x,y,z,t) axis order is assumed. 864 */ 865 private Matrix swapAndScaleAxis( final CoordinateSystem sourceCS, 866 final CoordinateSystem targetCS ) 867 throws CannotCreateTransformException { 868 final AxisOrientation[] sourceAxis = getAxisOrientations( sourceCS, targetCS ); 869 final AxisOrientation[] targetAxis = getAxisOrientations( targetCS, sourceCS ); 870 final Matrix matrix; 871 try { 872 matrix = Matrix.createAffineTransform( sourceAxis, targetAxis ); 873 } catch ( RuntimeException exception ) { 874 final CannotCreateTransformException e = new CannotCreateTransformException( sourceCS, 875 targetCS ); 876 throw e; 877 } 878 // Convert units (Optimized case where the conversion 879 // can be applied right into the AffineTransform). 880 final int dimension = matrix.getNumRow() - 1; 881 for ( int i = 0; i < dimension; i++ ) { 882 // TODO: check if units conversion is really linear. 883 final Unit sourceUnit = sourceCS.getUnits( i ); 884 final Unit targetUnit = targetCS.getUnits( i ); 885 final double offset = targetUnit.convert( 0, sourceUnit ); 886 final double scale = targetUnit.convert( 1, sourceUnit ) - offset; 887 matrix.setElement( i, i, scale * matrix.getElement( i, i ) ); 888 matrix.setElement( i, dimension, scale * matrix.getElement( i, dimension ) + offset ); 889 } 890 return matrix; 891 } 892 893 /** 894 * Returns an affine transform between two geographic coordinate systems. Only 895 * units, axis order (e.g. transforming from (NORTH,WEST) to (EAST,NORTH)) and 896 * prime meridian are taken in account. Other attributes (especially the datum) 897 * must be checked before invoking this method. 898 * 899 * @param sourceCS The source coordinate system. 900 * @param targetCS The target coordinate system. 901 */ 902 private Matrix swapAndScaleGeoAxis( final GeographicCoordinateSystem sourceCS, 903 final GeographicCoordinateSystem targetCS ) 904 throws CannotCreateTransformException { 905 final Matrix matrix = swapAndScaleAxis( sourceCS, targetCS ); 906 for ( int i = targetCS.getDimension(); --i >= 0; ) { 907 // Find longitude ordinate, and apply a rotation if prime meridian are different. 908 final AxisOrientation orientation = targetCS.getAxis( i ).orientation; 909 if ( AxisOrientation.EAST.equals( orientation.absolute() ) ) { 910 final Unit unit = targetCS.getUnits( i ); 911 final double sourceLongitude = sourceCS.getPrimeMeridian().getLongitude( unit ); 912 final double targetLongitude = targetCS.getPrimeMeridian().getLongitude( unit ); 913 final int lastMatrixColumn = matrix.getNumCol() - 1; 914 double rotate = targetLongitude - sourceLongitude; 915 if ( AxisOrientation.WEST.equals( orientation ) ) 916 rotate = -rotate; 917 matrix.setElement( i, lastMatrixColumn, matrix.getElement( i, lastMatrixColumn ) 918 - rotate ); 919 } 920 } 921 return matrix; 922 } 923 924 /** 925 * Returns the axis orientation for the specified coordinate system. 926 * If <code>cs</code> is <code>null</code>, then an array of length 927 * <code>dim.getDimension()</code> is created and filled with 928 * <code>(x,y,z,t)</code> axis orientations. 929 */ 930 private static AxisOrientation[] getAxisOrientations( final CoordinateSystem cs, 931 final Dimensioned dim ) { 932 final AxisOrientation[] axis; 933 if ( cs != null ) { 934 axis = new AxisOrientation[cs.getDimension()]; 935 for ( int i = 0; i < axis.length; i++ ) 936 axis[i] = cs.getAxis( i ).orientation; 937 } else { 938 axis = new AxisOrientation[dim.getDimension()]; 939 switch ( axis.length ) { 940 default: 941 for ( int i = axis.length; --i >= 4; ) 942 axis[i] = AxisOrientation.OTHER; // fall through 943 case 4: 944 axis[3] = AxisOrientation.FUTURE; // fall through 945 case 3: 946 axis[2] = AxisOrientation.UP; // fall through 947 case 2: 948 axis[1] = AxisOrientation.NORTH; // fall through 949 case 1: 950 axis[0] = AxisOrientation.EAST; // fall through 951 case 0: 952 break; 953 } 954 } 955 return axis; 956 } 957 958 /** 959 * Makes sure that the specified {@link GeographicCoordinateSystem} use standard axis 960 * (longitude and latitude in degrees), Greenwich prime meridian and an ellipsoid 961 * matching projection's parameters. If <code>cs</code> already meets all those 962 * conditions, then it is returned unchanged. Otherwise, a new normalized geographic 963 * coordinate system is created and returned. 964 */ 965 private static GeographicCoordinateSystem normalize( GeographicCoordinateSystem cs, 966 final Projection projection ) { 967 HorizontalDatum datum = cs.getHorizontalDatum(); 968 Ellipsoid ellipsoid = datum.getEllipsoid(); 969 final String name = getTemporaryName(); 970 final double semiMajorEll = ellipsoid.getSemiMajorAxis(); 971 final double semiMinorEll = ellipsoid.getSemiMinorAxis(); 972 final double semiMajorPrj = projection.getValue( "semi_major", semiMajorEll ); 973 final double semiMinorPrj = projection.getValue( "semi_minor", semiMinorEll ); 974 if ( !hasStandardAxis( cs, Unit.DEGREE ) 975 || cs.getPrimeMeridian().getLongitude( Unit.DEGREE ) != 0 ) { 976 cs = null; // Signal that it needs to be reconstructed. 977 } 978 if ( semiMajorEll != semiMajorPrj || semiMinorEll != semiMinorPrj ) { 979 // Those objects are temporary. We assume it is not 980 // a big deal if their name are not very explicit... 981 ellipsoid = new Ellipsoid( name, semiMajorPrj, semiMinorPrj, Unit.METRE ); 982 datum = new HorizontalDatum( name, ellipsoid ); 983 cs = null; // Signal that it needs to be reconstructed. 984 } 985 if ( cs != null ) { 986 return cs; 987 } 988 return new GeographicCoordinateSystem( name, datum ); 989 } 990 991 /** 992 * Makes sure that a {@link ProjectedCoordinateSystem} use standard axis 993 * (x and y in metres) and a normalized {@link GeographicCoordinateSystem}. 994 * If <code>cs</code> already meets all those conditions, then it is 995 * returned unchanged. Otherwise, a new normalized projected coordinate 996 * system is created and returned. 997 */ 998 private static ProjectedCoordinateSystem normalize( final ProjectedCoordinateSystem cs ) { 999 final Projection projection = cs.getProjection(); 1000 final GeographicCoordinateSystem geoCS = cs.getGeographicCoordinateSystem(); 1001 final GeographicCoordinateSystem normalizedGeoCS = normalize( geoCS, projection ); 1002 1003 if ( hasStandardAxis( cs, Unit.METRE ) && normalizedGeoCS == geoCS ) { 1004 return cs; 1005 } 1006 return new ProjectedCoordinateSystem( getTemporaryName(), normalizedGeoCS, projection ); 1007 } 1008 1009 /** 1010 * Returns <code>true</code> if the specified coordinate 1011 * system use standard axis and standard units. 1012 * 1013 * @param cs The coordinate system to test. 1014 * @paral unit The standard units. 1015 */ 1016 private static boolean hasStandardAxis( final HorizontalCoordinateSystem cs, final Unit unit ) { 1017 return cs.getDimension() == 2 /* Just a paranoiac check */ 1018 && unit.equals( cs.getUnits( 0 ) ) && unit.equals( cs.getUnits( 1 ) ) 1019 && AxisOrientation.EAST.equals( cs.getAxis( 0 ).orientation ) 1020 && AxisOrientation.NORTH.equals( cs.getAxis( 1 ).orientation ); 1021 } 1022 1023 /** 1024 * Returns a temporary name for generated objects. The first object 1025 * has a name like "Temporary-1", the second is "Temporary-2", etc. 1026 * 1027 */ 1028 private static String getTemporaryName() { 1029 return "Temporary-" + ( ++temporaryID ); 1030 } 1031 1032 }