001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/model/csct/ct/TransverseMercatorProjection.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 This library is free software; you can redistribute it and/or 012 modify it under the terms of the GNU Lesser General Public 013 License as published by the Free Software Foundation; either 014 version 2.1 of the License, or (at your option) any later version. 015 016 This library is distributed in the hope that it will be useful, 017 but WITHOUT ANY WARRANTY; without even the implied warranty of 018 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 019 Lesser General Public License for more details. 020 021 You should have received a copy of the GNU Lesser General Public 022 License along with this library; if not, write to the Free Software 023 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 024 025 Contact: 026 027 Andreas Poth 028 lat/lon GmbH 029 Aennchenstr. 19 030 53115 Bonn 031 Germany 032 E-Mail: poth@lat-lon.de 033 034 Klaus Greve 035 Department of Geography 036 University of Bonn 037 Meckenheimer Allee 166 038 53115 Bonn 039 Germany 040 E-Mail: klaus.greve@uni-bonn.de 041 042 043 ---------------------------------------------------------------------------*/ 044 package org.deegree.model.csct.ct; 045 046 import java.awt.geom.Point2D; 047 import java.util.ArrayList; 048 049 import org.deegree.model.csct.cs.Projection; 050 import org.deegree.model.csct.pt.Latitude; 051 import org.deegree.model.csct.resources.css.ResourceKeys; 052 import org.deegree.model.csct.resources.css.Resources; 053 054 /** 055 * Projections de Mercator tranverses Universelle et Modifi�e. Il s'agit de la projection 056 * Mercator cylindrique, mais dans lequel le cylindre a subit une rotation de 90�. Au lieu 057 * d'�tre tangeant � l'�quateur (ou � une autre latitude standard), le cylindre de la 058 * projection tranverse est tangeant � un m�ridien central. Les d�formation deviennent 059 * de plus en plus importantes � mesure que l'on s'�loigne du m�ridien central. Cette 060 * projection est appropri�e pour les r�gions qui s'�tendent d'avantage dans le sens 061 * nord-sud que dans le sens est-ouest. 062 * 063 * R�f�rence: John P. Snyder (Map Projections - A Working Manual, 064 * U.S. Geological Survey Professional Paper 1395, 1987) 065 * 066 * @version 1.0 067 * @author Andr� Gosselin 068 * @author Martin Desruisseaux 069 */ 070 final class TransverseMercatorProjection extends CylindricalProjection { 071 /* 072 * Constants used to calculate {@link #en0}, {@link #en1}, 073 * {@link #en2}, {@link #en3}, {@link #en4}. 074 */ 075 private static final double C00 = 1.0, C02 = 0.25, C04 = 0.046875, C06 = 0.01953125, 076 C08 = 0.01068115234375, C22 = 0.75, C44 = 0.46875, 077 C46 = 0.01302083333333333333, C48 = 0.00712076822916666666, 078 C66 = 0.36458333333333333333, C68 = 0.00569661458333333333, 079 C88 = 0.3076171875; 080 081 /* 082 * Contants used for the forward and inverse transform for the eliptical 083 * case of the Transverse Mercator. 084 */ 085 private static final double FC1 = 1.00000000000000000000000, // 1/1 086 FC2 = 0.50000000000000000000000, // 1/2 087 FC3 = 0.16666666666666666666666, // 1/6 088 FC4 = 0.08333333333333333333333, // 1/12 089 FC5 = 0.05000000000000000000000, // 1/20 090 FC6 = 0.03333333333333333333333, // 1/30 091 FC7 = 0.02380952380952380952380, // 1/42 092 FC8 = 0.01785714285714285714285; // 1/56 093 094 /** 095 * Relative precisions. 096 */ 097 private static final double EPS10 = 1e-10; 098 099 /** 100 * Relative precisions. 101 */ 102 private static final double EPS11 = 1e-11; 103 104 /** 105 * scale factor for semi mayor axis 106 */ 107 private double scale_factor = 1.0; 108 109 /** 110 * Global scale factor. Value <code>ak0</code> 111 * is equals to <code>{@link #a}*k0</code>. 112 */ 113 private final double ak0; 114 115 /** 116 * Constant needed for the <code>mlfn<code> method. 117 * Setup at construction time. 118 */ 119 private final double en0, en1, en2, en3, en4; 120 121 /** 122 * Variante de l'eccentricit�, calcul�e par 123 * <code>e'� = (a�-b�)/b� = es/(1-es)</code>. 124 */ 125 private final double esp; 126 127 /** 128 * indicates if the projection should be performed for the north hemisphere 129 * (1) or the south hemisphere (-1) 130 */ 131 private int hemisphere = 1; 132 133 /** 134 * Construct a new map projection from the suplied parameters. 135 * Projection will default to Universal Transverse Mercator (UTM). 136 * 137 * @param parameters The parameter values in standard units. 138 * @throws MissingParameterException if a mandatory parameter is missing. 139 */ 140 protected TransverseMercatorProjection( final Projection parameters ) 141 throws MissingParameterException { 142 this( parameters, false ); 143 } // Default to UTM. 144 145 /** 146 * Construct a new map projection from the suplied parameters. 147 * 148 * @param parameters The parameter values in standard units. 149 * @param modified <code>true</code> for MTM, <code>false</code> for UTM. 150 * @throws MissingParameterException if a mandatory parameter is missing. 151 */ 152 protected TransverseMercatorProjection( final Projection parameters, final boolean modified ) 153 throws MissingParameterException { 154 155 super( parameters ); 156 157 String[] param = parameters.getParameters().getParameterListDescriptor().getParamNames(); 158 ArrayList params = new ArrayList(); 159 160 for ( int i = 0; i < param.length; i++ ) { 161 params.add( param[i] ); 162 } 163 164 hemisphere = (int) parameters.getValue( "hemisphere", 1 ); 165 166 scale_factor = 1.0; 167 168 if ( params.contains( "scale_factor" ) ) { 169 scale_factor = parameters.getParameters().getDoubleParameter( "scale_factor" ); 170 } 171 172 ak0 = a * scale_factor; 173 174 double t; 175 esp = ( ( a * a ) / ( b * b ) ) - 1.0; 176 en0 = C00 - ( es * ( C02 + es * ( C04 + es * ( C06 + es * C08 ) ) ) ); 177 en1 = es * ( C22 - es * ( C04 + es * ( C06 + es * C08 ) ) ); 178 en2 = ( t = es * es ) * ( C44 - es * ( C46 + es * C48 ) ); 179 en3 = ( t *= es ) * ( C66 - es * C68 ); 180 en4 = t * es * C88; 181 } 182 183 /** 184 * Returns a human readable name localized for the specified locale. 185 */ 186 public String getName() { 187 return "TransverseMercatorProjection"; 188 } //Resources.getResources(locale).getString(key); 189 190 /** 191 * Calcule la distance meridionale sur un 192 * ellipso�de � la latitude <code>phi</code>. 193 */ 194 private final double mlfn( final double phi, double sphi, double cphi ) { 195 cphi *= sphi; 196 sphi *= sphi; 197 return ( en0 * phi ) - ( cphi * ( en1 + sphi * ( en2 + sphi * ( en3 + sphi * en4 ) ) ) ); 198 } 199 200 /** 201 * Transforms the specified (<var>x</var>,<var>y</var>) coordinate 202 * and stores the result in <code>ptDst</code>. 203 */ 204 protected Point2D transform( double x, double y, final Point2D ptDst ) 205 throws TransformException { 206 207 if ( Math.abs( y ) > ( Math.PI / 2.0 - EPS ) ) { 208 throw new TransformException( Resources.format( ResourceKeys.ERROR_POLE_PROJECTION_$1, 209 new Latitude( Math.toDegrees( y ) ) ) ); 210 } 211 212 x -= centralMeridian; 213 y -= centralLatitude; 214 y *= hemisphere; 215 216 double sinphi = Math.sin( y ); 217 double cosphi = Math.cos( y ); 218 219 if ( isSpherical ) { 220 // Spherical model. 221 double b = cosphi * Math.sin( x ); 222 223 if ( Math.abs( Math.abs( b ) - 1.0 ) <= EPS10 ) { 224 throw new TransformException( 225 Resources.format( ResourceKeys.ERROR_VALUE_TEND_TOWARD_INFINITY ) ); 226 } 227 228 double yy = ( cosphi * Math.cos( x ) ) / Math.sqrt( 1.0 - ( b * b ) ); 229 x = ( 0.5 * ak0 * Math.log( ( 1.0 + b ) / ( 1.0 - b ) ) ) + false_easting;/* 8-1 */ 230 231 if ( ( b = Math.abs( yy ) ) >= 1.0 ) { 232 if ( ( b - 1.0 ) > EPS10 ) { 233 throw new TransformException( 234 Resources.format( ResourceKeys.ERROR_VALUE_TEND_TOWARD_INFINITY ) ); 235 } 236 yy = 0.0; 237 238 } else { 239 yy = Math.acos( yy ); 240 } 241 242 if ( y < 0 ) { 243 yy = -yy; 244 } 245 246 y = ak0 * yy; 247 } else { 248 249 // Ellipsoidal model. 250 double t = ( Math.abs( cosphi ) > EPS10 ) ? ( sinphi / cosphi ) : 0; 251 t *= t; 252 253 double al = cosphi * x; 254 double als = al * al; 255 al /= Math.sqrt( 1.0 - ( es * sinphi * sinphi ) ); 256 257 double n = esp * cosphi * cosphi; 258 259 /* NOTE: meridinal distance at central latitude is always 0 */ 260 y = ( ak0 * ( mlfn( y, sinphi, cosphi ) + sinphi 261 * al 262 * x 263 * FC2 264 * ( 1.0 + FC4 265 * als 266 * ( 5.0 - t 267 + ( n * ( 9.0 + 4.0 * n ) ) + FC6 268 * als 269 * ( 61.0 270 + ( t * ( t - 58.0 ) ) 271 + ( n * ( 270.0 - 330.0 * t ) ) + FC8 272 * als 273 * ( 1385.0 + t 274 * ( t 275 * ( 543.0 - t ) - 3111.0 ) ) ) ) ) ) ); 276 y += false_northing; 277 278 x = ( ak0 * al * ( FC1 + FC3 279 * als 280 * ( 1.0 - t + n + FC5 281 * als 282 * ( 5.0 + ( t * ( t - 18.0 ) ) 283 + ( n * ( 14.0 - 58.0 * t ) ) + FC7 284 * als 285 * ( 61.0 + t 286 * ( t 287 * ( 179.0 - t ) - 479.0 ) ) ) ) ) ); 288 x += false_easting; 289 } 290 291 if ( ptDst != null ) { 292 ptDst.setLocation( x, y ); 293 return ptDst; 294 } 295 return new Point2D.Double( x, y ); 296 297 } 298 299 /** 300 * Transforms the specified (<var>x</var>,<var>y</var>) coordinate 301 * and stores the result in <code>ptDst</code>. 302 */ 303 protected Point2D inverseTransform( double x, double y, final Point2D ptDst ) 304 throws TransformException { 305 x -= false_easting; 306 y -= false_northing; 307 y *= hemisphere; 308 //x = x - ( (int)( x / 1000000 ) * 1000000.0 ); 309 310 if ( isSpherical ) { 311 // Spherical model. 312 double t = Math.exp( x / ak0 ); 313 double d = 0.5 * ( t - 1 / t ); 314 t = Math.cos( y / ak0 ); 315 316 double phi = Math.asin( Math.sqrt( ( 1.0 - t * t ) / ( 1.0 + d * d ) ) ); 317 y = ( y < 0.0 ) ? ( -phi ) : phi; 318 x = ( Math.abs( d ) > EPS10 || Math.abs( t ) > EPS10 ) ? ( Math.atan2( d, t ) + centralMeridian ) 319 : centralMeridian; 320 } else { 321 // Ellipsoidal projection. 322 final double y_ak0 = y / ak0; 323 final double k = 1.0 - es; 324 double phi = y_ak0; 325 326 for ( int i = 20; true; ) // rarely goes over 5 iterations 327 { 328 if ( ( --i ) < 0 ) { 329 throw new TransformException( 330 Resources.format( ResourceKeys.ERROR_NO_CONVERGENCE ) ); 331 } 332 333 final double s = Math.sin( phi ); 334 double t = 1.0 - ( es * ( s * s ) ); 335 t = ( mlfn( phi, s, Math.cos( phi ) ) - y_ak0 ) / ( k * t * Math.sqrt( t ) ); 336 phi -= t; 337 338 if ( Math.abs( t ) < EPS11 ) { 339 break; 340 } 341 } 342 343 if ( Math.abs( phi ) >= ( Math.PI / 2.0 ) ) { 344 y = ( y < 0.0 ) ? ( -( Math.PI / 2.0 ) ) : ( Math.PI / 2.0 ); 345 x = centralMeridian; 346 } else { 347 double sinphi = Math.sin( phi ); 348 double cosphi = Math.cos( phi ); 349 double t = ( Math.abs( cosphi ) > EPS10 ) ? ( sinphi / cosphi ) : 0.0; 350 double n = esp * cosphi * cosphi; 351 double con = 1.0 - ( es * sinphi * sinphi ); 352 double d = ( x * Math.sqrt( con ) ) / ak0; 353 con *= t; 354 t *= t; 355 356 double ds = d * d; 357 y = phi 358 - ( ( con * ds / ( 1.0 - es ) ) * FC2 * ( 1.0 - ds 359 * FC4 360 * ( 5.0 361 + ( t * ( 3.0 - 9.0 * n ) ) 362 + ( n * ( 1.0 - 4 * n ) ) - ds 363 * FC6 364 * ( 61.0 365 + ( t * ( 90.0 - ( 252.0 * n ) + 45.0 * t ) ) 366 + ( 46.0 * n ) - ds 367 * FC8 368 * ( 1385.0 + t 369 * ( 3633.0 + t 370 * ( 4095.0 + 1574.0 * t ) ) ) ) ) ) ) 371 + centralLatitude; 372 373 x = ( ( d * ( FC1 - ds 374 * FC3 375 * ( 1.0 + ( 2.0 * t ) + n - ds 376 * FC5 377 * ( 5.0 378 + ( t * ( 28.0 + ( 24 * t ) + 8.0 * n ) ) 379 + ( 6.0 * n ) - ds 380 * FC7 381 * ( 61.0 + t 382 * ( 662.0 + t 383 * ( 1320.0 + 720.0 * t ) ) ) ) ) ) ) / cosphi ) 384 + centralMeridian; 385 } 386 } 387 388 if ( ptDst != null ) { 389 ptDst.setLocation( x, y ); 390 return ptDst; 391 } 392 return new Point2D.Double( x, y ); 393 394 } 395 396 /** 397 * Returns a hash value for this projection. 398 */ 399 public int hashCode() { 400 final long code = Double.doubleToLongBits( false_easting ); 401 return ( (int) code ^ (int) ( code >>> 32 ) ) + ( 37 * super.hashCode() ); 402 } 403 404 /** 405 * Compares the specified object with 406 * this map projection for equality. 407 */ 408 public boolean equals( final Object object ) { 409 if ( object == this ) { 410 return true; // Slight optimization 411 } 412 413 if ( super.equals( object ) ) { 414 final TransverseMercatorProjection that = (TransverseMercatorProjection) object; 415 return ( Double.doubleToLongBits( this.false_easting ) == Double.doubleToLongBits( that.false_easting ) ) 416 && ( Double.doubleToLongBits( this.ak0 ) == Double.doubleToLongBits( that.ak0 ) ); 417 } 418 419 return false; 420 } 421 422 /** 423 * Impl�mentation de la partie entre crochets 424 * de la cha�ne retourn�e par {@link #toString()}. 425 */ 426 void toString( final StringBuffer buffer ) { 427 super.toString( buffer ); 428 } 429 430 /** 431 * Informations about a {@link TransverseMercatorProjection}. 432 * 433 * @version 1.0 434 * @author Martin Desruisseaux 435 */ 436 static final class Provider extends MapProjection.Provider { 437 /** 438 * <code>true</code> for Modified Mercator Projection (MTM), or 439 * <code>false</code> for Universal Mercator Projection (UTM). 440 */ 441 private final boolean modified; 442 443 /** 444 * Construct a new registration. 445 * 446 * @param modified <code>true</code> for Modified Mercator Projection (MTM), 447 * or <code>false</code> for Universal Mercator Projection (UTM). 448 */ 449 public Provider( final boolean modified ) { 450 super( modified ? "Modified_Transverse_Mercator" : "Transverse_Mercator", 451 modified ? ResourceKeys.MTM_PROJECTION : ResourceKeys.UTM_PROJECTION ); 452 this.modified = modified; 453 } 454 455 /** 456 * Create a new map projection. 457 */ 458 protected Object create( final Projection parameters ) { 459 return new TransverseMercatorProjection( parameters, modified ); 460 } 461 } 462 }