001/* 002 * JScience - Java(TM) Tools and Libraries for the Advancement of Sciences. 003 * Copyright (C) 2006 - JScience (http://jscience.org/) 004 * All rights reserved. 005 * 006 * Permission to use, copy, modify, and distribute this software is 007 * freely granted, provided that this notice is preserved. 008 */ 009package org.jscience.geography.coordinates; 010 011import static org.jscience.geography.coordinates.crs.ReferenceEllipsoid.WGS84; 012 013import javax.measure.Measure; 014import javax.measure.converter.UnitConverter; 015import javax.measure.quantity.*; 016import javax.measure.unit.*; 017import static javax.measure.unit.SI.*; 018import static javax.measure.unit.NonSI.*; 019 020import javolution.context.ObjectFactory; 021import javolution.xml.XMLFormat; 022import javolution.xml.stream.XMLStreamException; 023 024import org.jscience.geography.coordinates.crs.*; 025import org.opengis.referencing.cs.CoordinateSystem; 026 027/** 028 * This class represents the {@link ProjectedCRS projected} 029 * Universal Transverse Mercator (UTM) coordinates onto the WGS84 ellipsoid. 030 * 031 * <p>The UTM system is limited to values between -80 and +84 degrees latitude. 032 * Values beyond these limits (i.e., the polar regions) are projected 033 * using the Universal Polar Stereographic (UPS) projection. Although 034 * mathematically distinct, the two projections are represented identically. 035 * This class returns correct results for both UTM and UPS projections. 036 * 037 * The conversion routines for this class were derived from formulas described 038 * in the 039 * <a href="http://earth-info.nga.mil/GandG/publications/tm8358.2/TM8358_2.pdf"> 040 * Defense Mapping Agency Technical Manual 8358.2.</a> 041 * 042 * @author Paul D. Anderson 043 * @version 3.0, February 25, 2006 044 * @see <a href="http://en.wikipedia.org/wiki/Universal_Transverse_Mercator_coordinate_system"> 045 * Wikipedia: Universal Transverse Mercator Coordinate System</a> 046 */ 047public final class UTM extends Coordinates<ProjectedCRS<?>> { 048 049 /** 050 * The UTM scale factor. This the exact scale factor only on a pair of 051 * lines lying either side of the central meridian, but the effect is to 052 * reduce overall distortion within the UTM zone to less than one part per 053 * thousand. 054 */ 055 public static final double UTM_SCALE_FACTOR = 0.9996; 056 057 /** 058 * The UTM "false easting" value. This quantity is added to the true 059 * easting to avoid using negative numbers in the coordinates. 060 */ 061 public static Measure<Integer, Length> UTM_FALSE_EASTING = Measure.valueOf( 062 500000, METRE); 063 064 /** 065 * The UTM "false northing" value. This quantity is added to the true 066 * northing for coordinates <b>in the southern hemisphere only</b> 067 * to avoid using negative numbers in the coordinates. 068 */ 069 public static Measure<Integer, Length> UTM_FALSE_NORTHING = Measure 070 .valueOf(10000000, METRE); 071 072 /** 073 * The northern limit of the UTM grid. Beyond this limit the distortion 074 * introduced by the transverse Mercator projection is impractically 075 * large, and the UPS grid is used instead. 076 */ 077 public static final Measure<Integer, Angle> UTM_NORTHERN_LIMIT = Measure 078 .valueOf(84, DEGREE_ANGLE); 079 080 /** 081 * The southern limit of the UTM grid. Beyond this limit the distortion 082 * introduced by the transverse Mercator projection is impractically 083 * large, and the UPS grid is used instead. 084 */ 085 public static final Measure<Integer, Angle> UTM_SOUTHERN_LIMIT = Measure 086 .valueOf(-80, DEGREE_ANGLE); 087 088 /** 089 * The UPS scale factor. 090 */ 091 public static final double UPS_SCALE_FACTOR = 0.994; 092 093 /** 094 * The UPS "false easting" value. This quantity is added to the true 095 * easting to avoid using negative numbers in the coordinates. 096 */ 097 public static Measure<Integer, Length> UPS_FALSE_EASTING = Measure.valueOf( 098 2000000, METRE); 099 100 /** 101 * The UPS "false northing" value. This quantity is added to the true 102 * northing to avoid using negative numbers in the coordinates. 103 * The UPS system, unlike the UTM system, always includes the false northing. 104 */ 105 public static Measure<Integer, Length> UPS_FALSE_NORTHING = Measure 106 .valueOf(2000000, METRE); 107 108 /* 109 * NOTE: The calculations in this class use power series expansions. 110 * The naming convention is to include the power in the name 111 * of the term, so that the square of K0 is 'K02', the cube 112 * is 'K03', etc. 113 */ 114 private static final double K0 = UTM_SCALE_FACTOR; 115 116 private static final double K02 = K0 * K0; 117 118 private static final double K03 = K02 * K0; 119 120 private static final double K04 = K03 * K0; 121 122 private static final double K05 = K04 * K0; 123 124 private static final double K06 = K05 * K0; 125 126 private static final double K07 = K06 * K0; 127 128 private static final double K08 = K07 * K0; 129 130 /** 131 * Holds the coordinate reference system for all instances of this class. 132 */ 133 public static final ProjectedCRS<UTM> CRS = new ProjectedCRS<UTM>() { 134 135 private final double NORTHERN_LIMIT_IN_DEGREES = UTM_NORTHERN_LIMIT 136 .doubleValue(NonSI.DEGREE_ANGLE); 137 138 private final double SOUTHERN_LIMIT_IN_DEGREES = UTM_SOUTHERN_LIMIT 139 .doubleValue(NonSI.DEGREE_ANGLE); 140 141 @Override 142 protected UTM coordinatesOf(AbsolutePosition position) { 143 LatLong latLong = LatLong.valueOf(position.latitudeWGS84 144 .doubleValue(SI.RADIAN), position.longitudeWGS84 145 .doubleValue(SI.RADIAN), SI.RADIAN); 146 // UTM or UPS? 147 final double latitude = position.latitudeWGS84 148 .doubleValue(NonSI.DEGREE_ANGLE); 149 if (latitude > SOUTHERN_LIMIT_IN_DEGREES 150 && latitude < NORTHERN_LIMIT_IN_DEGREES) { 151 return latLongToUtm(latLong, WGS84); 152 } else { 153 return latLongToUps(latLong, WGS84); 154 } 155 } 156 157 @Override 158 protected AbsolutePosition positionOf(UTM coordinates, 159 AbsolutePosition position) { 160 final LatLong latLong; 161 if (coordinates.latitudeZone() < 'C' 162 || coordinates.latitudeZone() > 'X') { 163 latLong = upsToLatLong(coordinates, WGS84); 164 } else { 165 latLong = utmToLatLong(coordinates, WGS84); 166 } 167 position.latitudeWGS84 = Measure.valueOf(latLong 168 .latitudeValue(SI.RADIAN), SI.RADIAN); 169 position.longitudeWGS84 = Measure.valueOf(latLong 170 .longitudeValue(SI.RADIAN), SI.RADIAN); 171 return position; 172 } 173 174 @Override 175 public CoordinateSystem getCoordinateSystem() { 176 return ProjectedCRS.EASTING_NORTHING_CS; 177 } 178 }; 179 180 /** 181 * Holds the longitude zone identifier. 182 */ 183 private int _longitudeZone; 184 185 /** 186 * Holds the latitude zone identifier. 187 */ 188 private char _latitudeZone; 189 190 /** 191 * Holds the easting in meters. 192 */ 193 private double _easting; 194 195 /** 196 * Holds the northing in meters. 197 */ 198 private double _northing; 199 200 /** 201 * Returns the projected UTM position corresponding to the specified 202 * coordinates. 203 * 204 * @param longitudeZone the longitude zone number. 205 * @param latitudeZone the longitude zone character. 206 * @param easting the easting value stated in the specified unit. 207 * @param northing the northing value stated in the specified unit. 208 * @param unit the easting/northing length unit. 209 * @return the corresponding surface position. 210 */ 211 public static UTM valueOf(int longitudeZone, char latitudeZone, 212 double easting, double northing, Unit<Length> unit) { 213 UTM utm = FACTORY.object(); 214 utm._longitudeZone = longitudeZone; 215 utm._latitudeZone = latitudeZone; 216 if (unit == METRE) { 217 utm._easting = easting; 218 utm._northing = northing; 219 } else { 220 UnitConverter toMeter = unit.getConverterTo(METRE); 221 utm._easting = toMeter.convert(easting); 222 utm._northing = toMeter.convert(northing); 223 } 224 return utm; 225 } 226 227 private static final ObjectFactory<UTM> FACTORY = new ObjectFactory<UTM>() { 228 229 @Override 230 protected UTM create() { 231 return new UTM(); 232 } 233 }; 234 235 private UTM() { 236 } 237 238 /** 239 * Returns the longitude zone identifier. 240 * 241 * @return the longitude zone number. 242 */ 243 public final int longitudeZone() { 244 return _longitudeZone; 245 } 246 247 /** 248 * Returns the latitude zone identifier. 249 * 250 * @return the latitude zone character. 251 */ 252 public final char latitudeZone() { 253 return _latitudeZone; 254 } 255 256 /** 257 * Returns the projected distance of the position from the central meridian. 258 * 259 * @param unit the length unit of the easting to return. 260 * @return the easting stated in the specified unit. 261 */ 262 public final double eastingValue(Unit<Length> unit) { 263 return unit.equals(METRE) ? _easting : METRE.getConverterTo(unit) 264 .convert(_easting); 265 } 266 267 /** 268 * Returns the projected distance of the point from the equator. 269 * 270 * @param unit the length unit of the northing to return. 271 * @return the northing stated in the specified unit. 272 */ 273 public final double northingValue(Unit<Length> unit) { 274 return unit.equals(METRE) ? _northing : METRE.getConverterTo(unit) 275 .convert(_northing); 276 } 277 278 @Override 279 public ProjectedCRS<UTM> getCoordinateReferenceSystem() { 280 return CRS; 281 } 282 283 // OpenGIS Interface. 284 public int getDimension() { 285 return 2; 286 } 287 288 // OpenGIS Interface. 289 public double getOrdinate(int dimension) throws IndexOutOfBoundsException { 290 if (dimension == 0) { 291 Unit<?> u = ProjectedCRS.EASTING_NORTHING_CS.getAxis(0).getUnit(); 292 return METRE.getConverterTo(u).convert(_easting); 293 } else if (dimension == 1) { 294 Unit<?> u = ProjectedCRS.EASTING_NORTHING_CS.getAxis(1).getUnit(); 295 return METRE.getConverterTo(u).convert(_northing); 296 } else { 297 throw new IndexOutOfBoundsException(); 298 } 299 } 300 301 /** 302 * Returns true if the position indicated by the coordinates is 303 * north of the northern limit of the UTM grid (84 degrees). 304 * 305 * @param latLong The coordinates. 306 * @return True if the latitude is greater than 84 degrees. 307 */ 308 public static boolean isNorthPolar(final LatLong latLong) { 309 return latLong.latitudeValue(DEGREE_ANGLE) > 84.0; 310 } 311 312 /** 313 * Returns true if the position indicated by the coordinates is 314 * south of the southern limit of the UTM grid (-80 degrees). 315 * 316 * @param latLong The coordinates. 317 * @return True if the latitude is less than -80 degrees. 318 */ 319 public static boolean isSouthPolar(final LatLong latLong) { 320 return latLong.latitudeValue(DEGREE_ANGLE) < -80.0; 321 } 322 323 /** 324 * Returns the UTM/UPS latitude zone identifier for the specified coordinates. 325 * 326 * @param latLong The coordinates. 327 * @return the latitude zone character. 328 */ 329 public static char getLatitudeZone(final LatLong latLong) { 330 if (isNorthPolar(latLong)) { 331 if (latLong.longitudeValue(RADIAN) < 0) { 332 return 'Y'; 333 } else { 334 return 'Z'; 335 } 336 } 337 if (isSouthPolar(latLong)) { 338 if (latLong.longitudeValue(RADIAN) < 0) { 339 return 'A'; 340 } else { 341 return 'B'; 342 } 343 } 344 final int degreesLatitude = (int) latLong.latitudeValue(DEGREE_ANGLE); 345 char zone = (char) ((degreesLatitude + 80) / 8 + 'C'); 346 if (zone > 'H') { 347 zone++; 348 } 349 if (zone > 'N') { 350 zone++; 351 } 352 if (zone > 'X') { 353 zone = 'X'; 354 } 355 return zone; 356 } 357 358 /** 359 * Returns the UTM/UPS longitude zone number for the specified 360 * coordinates. 361 * 362 * @param latLong The coordinates. 363 * @return the longitude zone number. 364 */ 365 public static int getLongitudeZone(LatLong latLong) { 366 367 final double degreesLongitude = latLong.longitudeValue(DEGREE_ANGLE); 368 369 // UPS longitude zones 370 if (isNorthPolar(latLong) || isSouthPolar(latLong)) { 371 if (degreesLongitude < 0.0) { 372 return 30; 373 } else { 374 return 31; 375 } 376 } 377 378 final char latitudeZone = getLatitudeZone(latLong); 379 // X latitude exceptions 380 if (latitudeZone == 'X' && degreesLongitude > 0.0 381 && degreesLongitude < 42.0) { 382 if (degreesLongitude < 9.0) { 383 return 31; 384 } 385 if (degreesLongitude < 21.0) { 386 return 33; 387 } 388 if (degreesLongitude < 33.0) { 389 return 35; 390 } else { 391 return 37; 392 } 393 } 394 // V latitude exceptions 395 if (latitudeZone == 'V' && degreesLongitude > 0.0 396 && degreesLongitude < 12.0) { 397 if (degreesLongitude < 3.0) { 398 return 31; 399 } else { 400 return 32; 401 } 402 } 403 404 return (int) ((degreesLongitude + 180) / 6) + 1; 405 } 406 407 /** 408 * Returns the central meridian (in radians) for the specified 409 * UTM/UPS zone. 410 * @param longitudeZone The UTM/UPS longitude zone number. 411 * @param latitudeZone The UTM/UPS latitude zone character. 412 * @return The central meridian for the specified zone. 413 */ 414 public static double getCentralMeridian(int longitudeZone, char latitudeZone) { 415 // polar zones 416 if (latitudeZone < 'C' || latitudeZone > 'X') { 417 return 0.0; 418 } 419 // X latitude zone exceptions 420 if (latitudeZone == 'X' && longitudeZone > 31 && longitudeZone <= 37) { 421 return Math.toRadians((longitudeZone - 1) * 6 - 180 + 4.5); 422 } 423 // V latitude zone exceptions 424 if (longitudeZone == 'V') { 425 if (latitudeZone == 31) { 426 return Math.toRadians(1.5); 427 } else if (latitudeZone == 32) { 428 return Math.toRadians(7.5); 429 } 430 } 431 return Math.toRadians((longitudeZone - 1) * 6 - 180 + 3); 432 } 433 434 /** 435 * Converts latitude/longitude coordinates to UTM coordinates based on 436 * the specified reference ellipsoid. 437 * 438 * @param latLong The latitude/longitude coordinates. 439 * @param ellipsoid The reference ellipsoid. 440 * @return The UTM coordinates. 441 */ 442 public static UTM latLongToUtm(LatLong latLong, ReferenceEllipsoid ellipsoid) { 443 final char latitudeZone = getLatitudeZone(latLong); 444 final int longitudeZone = getLongitudeZone(latLong); 445 446 final double phi = latLong.latitudeValue(RADIAN); 447 448 final double cosPhi = Math.cos(phi); 449 final double cos2Phi = cosPhi * cosPhi; 450 final double cos3Phi = cos2Phi * cosPhi; 451 final double cos4Phi = cos3Phi * cosPhi; 452 final double cos5Phi = cos4Phi * cosPhi; 453 final double cos6Phi = cos5Phi * cosPhi; 454 final double cos7Phi = cos6Phi * cosPhi; 455 final double cos8Phi = cos7Phi * cosPhi; 456 457 final double tanPhi = Math.tan(phi); 458 final double tan2Phi = tanPhi * tanPhi; 459 final double tan4Phi = tan2Phi * tan2Phi; 460 final double tan6Phi = tan4Phi * tan2Phi; 461 462 final double eb2 = ellipsoid.getSecondEccentricitySquared(); 463 final double eb4 = eb2 * eb2; 464 final double eb6 = eb4 * eb2; 465 final double eb8 = eb6 * eb2; 466 467 final double e2c2 = eb2 * cos2Phi; 468 final double e4c4 = eb4 * cos4Phi; 469 final double e6c6 = eb6 * cos6Phi; 470 final double e8c8 = eb8 * cos8Phi; 471 472 final double t2e2c2 = tan2Phi * e2c2; 473 final double t2e4c4 = tan2Phi * e4c4; 474 final double t2e6c6 = tan2Phi * e6c6; 475 final double t2e8c8 = tan2Phi * e8c8; 476 477 final double nu = ellipsoid.verticalRadiusOfCurvature(phi); 478 final double kn1 = K0 * nu * Math.sin(phi); 479 final double t1 = K0 * ellipsoid.meridionalArc(phi); 480 final double t2 = kn1 * cosPhi / 2.0; 481 final double t3 = (kn1 * cos3Phi / 24.0) 482 * (5.0 - tan2Phi + 9.0 * e2c2 + 4.0 * e4c4); 483 final double t4 = (kn1 * cos5Phi / 720.0) 484 * (61.0 - 58.0 * tan2Phi + tan4Phi + 270.0 * e2c2 - 330.0 485 * t2e2c2 + 445.0 * e4c4 - 680.0 * t2e4c4 + 324.0 * e6c6 486 - 600.0 * t2e6c6 + 88.0 * e8c8 - 192.0 * t2e8c8); 487 final double t5 = (kn1 * cos7Phi / 40320.0) 488 * (1385.0 - 3111.0 * tan2Phi + 543.0 * tan4Phi - tan6Phi); 489 490 final double kn2 = K0 * nu; 491 final double t6 = kn2 * cosPhi; 492 final double t7 = (kn2 * cos3Phi / 6.0) * (1.0 - tan2Phi + e2c2); 493 final double t8 = (kn2 * cos5Phi / 120.0) 494 * (5.0 - 18.0 * tan2Phi + tan4Phi + 14.0 * e2c2 - 58.0 * t2e2c2 495 + 13.0 * e4c4 - 64.0 * t2e4c4 + 4.0 * e6c6 - 24.0 * t2e6c6); 496 final double t9 = (kn2 * cos7Phi / 50.40) 497 * (61.0 - 479.0 * tan2Phi + 179.0 * tan4Phi - tan6Phi); 498 499 final double lambda = latLong.longitudeValue(RADIAN); 500 final double lambda0 = getCentralMeridian(longitudeZone, latitudeZone); 501 final double dL = lambda - lambda0; 502 final double dL2 = dL * dL; 503 final double dL3 = dL2 * dL; 504 final double dL4 = dL3 * dL; 505 final double dL5 = dL4 * dL; 506 final double dL6 = dL5 * dL; 507 final double dL7 = dL6 * dL; 508 final double dL8 = dL7 * dL; 509 510 final double falseNorthing; 511 if ((phi < 0.0)) { 512 // southern hemisphere -- add false northing 513 falseNorthing = UTM_FALSE_NORTHING.doubleValue(METRE); 514 } else { 515 // northern hemisphere -- no false northing 516 falseNorthing = 0.0; 517 } 518 final double falseEasting = UTM_FALSE_EASTING.doubleValue(METRE); 519 final double northing = falseNorthing + t1 + dL2 * t2 + dL4 * t3 + dL6 520 * t4 + dL8 * t5; 521 final double easting = falseEasting + dL * t6 + dL3 * t7 + dL5 * t8 522 + dL7 * t9; 523 524 return UTM.valueOf(longitudeZone, latitudeZone, easting, northing, 525 METRE); 526 } 527 528 /** 529 * Converts latitude/longitude coordinates to UPS coordinates based on 530 * the specified reference ellipsoid. 531 * 532 * @param latLong The latitude/longitude coordinates. 533 * @param ellipsoid The reference ellipsoid. 534 * @return The UPS coordinates. 535 */ 536 public static UTM latLongToUps(LatLong latLong, ReferenceEllipsoid ellipsoid) { 537 538 final char latitudeZone = getLatitudeZone(latLong); 539 final int longitudeZone = getLongitudeZone(latLong); 540 541 final double latitude = latLong.latitudeValue(RADIAN); 542 final double sign = Math.signum(latitude); 543 final double phi = Math.abs(latitude); 544 final double lambda = latLong.longitudeValue(RADIAN); 545 546 final double a = ellipsoid.getSemimajorAxis().doubleValue(METRE); 547 final double e = ellipsoid.getEccentricity(); 548 final double e2 = ellipsoid.getEccentricitySquared(); 549 550 final double c0 = ((2.0 * a) / Math.sqrt(1.0 - e2)) 551 * Math.pow((1.0 - e) / (1.0 + e), e / 2.0); 552 final double eSinPhi = e * Math.sin(phi); 553 final double tz = Math.pow((1 + eSinPhi) / (1 - eSinPhi), e / 2.0) 554 * Math.tan(Math.PI / 4.0 - phi / 2.0); 555 final double radius = UPS_SCALE_FACTOR * c0 * tz; 556 final double falseNorthing = UPS_FALSE_NORTHING.doubleValue(METRE); 557 final double northing; 558 if (sign > 0) { 559 northing = falseNorthing - radius * Math.cos(lambda); 560 } else { 561 northing = falseNorthing + radius * Math.cos(lambda); 562 } 563 final double falseEasting = UPS_FALSE_EASTING.doubleValue(METRE); 564 final double easting = falseEasting + radius * Math.sin(lambda); 565 566 return UTM.valueOf(longitudeZone, latitudeZone, easting, northing, 567 METRE); 568 } 569 570 /** 571 * Converts the UTM coordinates to latitude/longitude coordinates, 572 * based on the specified reference ellipsoid. 573 * 574 * @param utm The UTM coordinates. 575 * @param ellipsoid The reference ellipsoid. 576 * @return The latitude/longitude coordinates. 577 */ 578 public static LatLong utmToLatLong(UTM utm, ReferenceEllipsoid ellipsoid) { 579 final double northing; 580 if ((utm.latitudeZone() < 'N')) { 581 // southern hemisphere 582 northing = utm._northing - UTM_FALSE_NORTHING.doubleValue(METRE); 583 } else { 584 // northern hemisphere 585 northing = utm._northing; 586 } 587 588 // footpoint latitude 589 final double arc0 = northing / K0; 590 double rho = ellipsoid.meridionalRadiusOfCurvature(0.0); 591 double phi = arc0 / rho; 592 for (int i = 0; i < 5; i++) { 593 double arc = ellipsoid.meridionalArc(phi); 594 rho = ellipsoid.meridionalRadiusOfCurvature(phi); 595 double diff = (arc0 - arc) / rho; 596 if (Math.abs(diff) < Math.ulp(phi)) { 597 break; 598 } 599 phi += diff; 600 } 601 602 final double cosPhi = Math.cos(phi); 603 final double cos2Phi = cosPhi * cosPhi; 604 final double cos3Phi = cos2Phi * cosPhi; 605 final double cos4Phi = cos3Phi * cosPhi; 606 final double cos5Phi = cos4Phi * cosPhi; 607 final double cos6Phi = cos5Phi * cosPhi; 608 final double cos7Phi = cos6Phi * cosPhi; 609 final double cos8Phi = cos7Phi * cosPhi; 610 611 final double tanPhi = Math.tan(phi); 612 final double tan2Phi = tanPhi * tanPhi; 613 final double tan4Phi = tan2Phi * tan2Phi; 614 final double tan6Phi = tan4Phi * tan2Phi; 615 616 final double eb2 = ellipsoid.getSecondEccentricitySquared(); 617 final double eb4 = eb2 * eb2; 618 final double eb6 = eb4 * eb2; 619 final double eb8 = eb6 * eb2; 620 final double e2c2 = eb2 * cos2Phi; 621 final double e4c4 = eb4 * cos4Phi; 622 final double e6c6 = eb6 * cos6Phi; 623 final double e8c8 = eb8 * cos8Phi; 624 625 final double t2e2c2 = tan2Phi * e2c2; 626 final double t2e4c4 = tan2Phi * e4c4; 627 final double t2e6c6 = tan2Phi * e6c6; 628 final double t2e8c8 = tan2Phi * e8c8; 629 final double t4e2c2 = tan4Phi * e2c2; 630 final double t4e4c4 = tan4Phi * e4c4; 631 632 final double nu = ellipsoid.verticalRadiusOfCurvature(phi); 633 final double nu2 = nu * nu; 634 final double nu3 = nu2 * nu; 635 final double nu5 = nu3 * nu2; 636 final double nu7 = nu5 * nu2; 637 638 final double lambda0 = getCentralMeridian(utm.longitudeZone(), utm 639 .latitudeZone()); 640 final double dE = utm._easting - UTM_FALSE_EASTING.doubleValue(METRE); 641 final double dE2 = dE * dE; 642 final double dE3 = dE2 * dE; 643 final double dE4 = dE3 * dE; 644 final double dE5 = dE4 * dE; 645 final double dE6 = dE5 * dE; 646 final double dE7 = dE6 * dE; 647 final double dE8 = dE7 * dE; 648 649 final double t10 = tanPhi / (2.0 * rho * nu * K02); 650 final double t11 = tanPhi / (24.0 * rho * nu3 * K04) 651 * (5.0 + 3.0 * tan2Phi + e2c2 - 9.0 * t2e2c2 - 4.0 * e4c4); 652 final double t12 = tanPhi 653 / (720.0 * rho * nu5 * K06) 654 * (61.0 + 90.0 * tan2Phi + 45.0 * tan4Phi + 46.0 * e2c2 - 252.0 655 * t2e2c2 - 90.0 * t4e2c2 - 3.0 * e4c4 - 66.0 * t2e4c4 656 + 225.0 * t4e4c4 + 100.0 * e6c6 + 84.0 * t2e6c6 + 88.0 657 * e8c8 - 192.0 * t2e8c8); 658 final double t13 = tanPhi 659 / (40320.0 * rho * nu7 * K08) 660 * (1385.0 + 3633.0 * tan2Phi + 4095.0 * tan4Phi + 1575.0 * tan6Phi); 661 final double t14 = 1.0 / (cosPhi * nu * K0); 662 final double t15 = 1.0 / (6.0 * cosPhi * nu3 * K03) 663 * (1.0 + 2.0 * tan2Phi + e2c2); 664 final double t16 = 1.0 665 / (120.0 * cosPhi * nu5 * K05) 666 * (5.0 + 28.0 * tan2Phi + 24.0 * tan4Phi + 6.0 * e2c2 + 8.0 667 * t2e2c2 - 3.0 * e4c4 + 4.0 * t2e4c4 - 4.0 * e6c6 + 24.0 * t2e6c6); 668 final double t17 = 1.0 / (5040.0 * cosPhi * nu7 * K07) 669 * (61.0 + 662.0 * tan2Phi + 1320.0 * tan4Phi + 720.0 * tan6Phi); 670 671 final double latitude = phi - dE2 * t10 + dE4 * t11 - dE6 * t12 + dE8 672 * t13; 673 final double longitude = lambda0 + dE * t14 - dE3 * t15 + dE5 * t16 674 - dE7 * t17; 675 return LatLong.valueOf(latitude, longitude, RADIAN); 676 } 677 678 /** 679 * Converts the UPS coordinates to latitude/longitude coordinates, 680 * based on the specified reference ellipsoid. 681 * 682 * @param ups The UPS coordinates. 683 * @param ellipsoid The reference ellipsoid. 684 * @return The latitude/longitude coordinates. 685 */ 686 public static LatLong upsToLatLong(UTM ups, ReferenceEllipsoid ellipsoid) { 687 final boolean northernHemisphere = ups.latitudeZone() > 'N'; 688 final double dN = ups.northingValue(METRE) 689 - UPS_FALSE_NORTHING.doubleValue(METRE); 690 final double dE = ups.eastingValue(METRE) 691 - UPS_FALSE_EASTING.doubleValue(METRE); 692 // check for zeroes (the poles) 693 if (dE == 0.0 && dN == 0.0) { 694 if (northernHemisphere) { 695 return LatLong.valueOf(90.0, 0.0, DEGREE_ANGLE); 696 } else { 697 return LatLong.valueOf(-90.0, 0.0, DEGREE_ANGLE); 698 } 699 } 700 // compute longitude 701 final double longitude; 702 if (northernHemisphere) { 703 longitude = Math.atan2(dE, -dN); 704 } else { 705 longitude = Math.atan2(dE, dN); 706 } 707 708 // compute latitude 709 final double a = ellipsoid.getSemimajorAxis().doubleValue(METRE); 710 final double e = ellipsoid.getEccentricity(); 711 final double e2 = ellipsoid.getEccentricitySquared(); 712 final double e4 = e2 * e2; 713 final double e6 = e4 * e2; 714 final double e8 = e6 * e2; 715 final double aBar = e2 / 2.0 + 5.0 * e4 / 24.0 + e6 / 12.0 + 13 * e8 716 / 360.0; 717 final double bBar = 7.0 * e4 / 48.0 + 29.0 * e6 / 240.0 + 811.0 * e8 718 / 11520.0; 719 final double cBar = 7.0 * e6 / 120.0 + 81.0 * e8 / 1120.0; 720 final double dBar = 4279 * e8 / 161280.0; 721 final double c0 = ((2.0 * a) / Math.sqrt(1.0 - e2)) 722 * Math.pow((1.0 - e) / (1.0 + e), e / 2.0); 723 final double r; 724 if (dE == 0.0) { 725 r = dN; 726 } else if (dN == 0.0) { 727 r = dE; 728 } else if (dN < dE) { 729 r = dE / Math.sin(longitude); 730 } else { 731 r = dN / Math.cos(longitude); 732 } 733 final double radius = Math.abs(r); 734 735 final double chi = (Math.PI / 2.0) - 2.0 736 * Math.atan2(radius, UPS_SCALE_FACTOR * c0); 737 final double phi = chi + aBar * Math.sin(2.0 * chi) + bBar 738 * Math.sin(4.0 * chi) + cBar * Math.sin(6.0 * chi) + dBar 739 * Math.sin(8.0 * chi); 740 final double latitude; 741 if (northernHemisphere) { 742 latitude = phi; 743 } else { 744 latitude = -phi; 745 } 746 return LatLong.valueOf(latitude, longitude, RADIAN); 747 } 748 749 @Override 750 public UTM copy() { 751 return UTM.valueOf(_longitudeZone, _latitudeZone, _easting, _northing, 752 METRE); 753 } 754 755 // Default serialization. 756 // 757 758 static final XMLFormat<UTM> XML = new XMLFormat<UTM>(UTM.class) { 759 760 @Override 761 public UTM newInstance(Class<UTM> cls, InputElement xml) 762 throws XMLStreamException { 763 return FACTORY.object(); 764 } 765 766 public void write(UTM utm, OutputElement xml) throws XMLStreamException { 767 xml.setAttribute("latitudeZone", utm._latitudeZone); 768 xml.setAttribute("longitudeZone", utm._longitudeZone); 769 xml.setAttribute("easting", utm._easting); 770 xml.setAttribute("northing", utm._northing); 771 } 772 773 public void read(InputElement xml, UTM UTM) throws XMLStreamException { 774 UTM._latitudeZone = xml.getAttribute("latitudeZone", ' '); 775 UTM._longitudeZone = xml.getAttribute("longitudeZone", 0); 776 UTM._easting = xml.getAttribute("easting", 0.0); 777 UTM._northing = xml.getAttribute("northing", 0.0); 778 } 779 }; 780 781 private static final long serialVersionUID = 1L; 782 783}