1 9 package org.jscience.geography.coordinates; 10 11 import static org.jscience.geography.coordinates.crs.ReferenceEllipsoid.WGS84; 12 13 import javax.measure.converters.UnitConverter; 14 import javax.measure.quantities.*; 15 import javax.measure.units.*; 16 17 import org.jscience.geography.coordinates.crs.*; 18 import org.opengis.referencing.cs.CoordinateSystem; 19 20 40 public class UTM extends Coordinates<ProjectedCRS> { 41 42 48 public static final double UTM_SCALE_FACTOR = 0.9996; 49 50 54 public static Quantity<Length> UTM_FALSE_EASTING = new Scalar<Length>( 55 500000.0, SI.METER); 56 57 62 public static Quantity<Length> UTM_FALSE_NORTHING = new Scalar<Length>( 63 10000000.0, SI.METER); 64 65 70 public static final Quantity<Angle> UTM_NORTHERN_LIMIT = new Scalar<Angle>( 71 84.0, NonSI.DEGREE_ANGLE); 72 73 78 public static final Quantity<Angle> UTM_SOUTHERN_LIMIT = new Scalar<Angle>( 79 -80.0, NonSI.DEGREE_ANGLE); 80 81 84 public static final double UPS_SCALE_FACTOR = 0.994; 85 86 90 public static Quantity<Length> UPS_FALSE_EASTING = new Scalar<Length>( 91 2000000.0, SI.METER); 92 93 98 public static Quantity<Length> UPS_FALSE_NORTHING = new Scalar<Length>( 99 2000000.0, SI.METER); 100 101 107 private static final double K0 = UTM_SCALE_FACTOR; 108 109 private static final double K02 = K0 * K0; 110 111 private static final double K03 = K02 * K0; 112 113 private static final double K04 = K03 * K0; 114 115 private static final double K05 = K04 * K0; 116 117 private static final double K06 = K05 * K0; 118 119 private static final double K07 = K06 * K0; 120 121 private static final double K08 = K07 * K0; 122 123 126 public static final ProjectedCRS<UTM> CRS = new ProjectedCRS<UTM>() { 127 128 private final double NORTHERN_LIMIT_IN_DEGREES = UTM_NORTHERN_LIMIT 129 .doubleValue(NonSI.DEGREE_ANGLE); 130 131 private final double SOUTHERN_LIMIT_IN_DEGREES = UTM_SOUTHERN_LIMIT 132 .doubleValue(NonSI.DEGREE_ANGLE); 133 134 @Override 135 protected UTM coordinatesOf(AbsolutePosition position) { 136 LatLong latLong = LatLong.valueOf(position.latitudeWGS84 137 .doubleValue(SI.RADIAN), position.longitudeWGS84 138 .doubleValue(SI.RADIAN), SI.RADIAN); 139 final double latitude = position.latitudeWGS84 141 .doubleValue(NonSI.DEGREE_ANGLE); 142 if (latitude > SOUTHERN_LIMIT_IN_DEGREES 143 && latitude < NORTHERN_LIMIT_IN_DEGREES) { 144 return latLongToUtm(latLong, WGS84); 145 } else { 146 return latLongToUps(latLong, WGS84); 147 } 148 } 149 150 @Override 151 protected AbsolutePosition positionOf(UTM coordinates, 152 AbsolutePosition position) { 153 final LatLong latLong; 154 if (coordinates.latitudeZone() < 'C' 155 || coordinates.latitudeZone() > 'X') { 156 latLong = upsToLatLong(coordinates, WGS84); 157 } else { 158 latLong = utmToLatLong(coordinates, WGS84); 159 } 160 position.latitudeWGS84 = new Scalar<Angle>(latLong 161 .latitudeValue(SI.RADIAN), SI.RADIAN); 162 position.longitudeWGS84 = new Scalar<Angle>(latLong 163 .longitudeValue(SI.RADIAN), SI.RADIAN); 164 return position; 165 } 166 167 @Override 168 public CoordinateSystem getCoordinateSystem() { 169 return ProjectedCRS.EASTING_NORTHING_CS; 170 } 171 }; 172 173 176 private int _longitudeZone; 177 178 181 private char _latitudeZone; 182 183 186 private double _eastingInMeter; 187 188 191 private double _northingInMeter; 192 193 204 public static UTM valueOf(int longitudeZone, char latitudeZone, 205 double easting, double northing, Unit<Length> unit) { 206 return new UTM(longitudeZone, latitudeZone, easting, northing, unit); 207 } 208 209 219 public UTM(int longitudeZone, char latitudeZone, double easting, 220 double northing, Unit<Length> unit) { 221 _longitudeZone = longitudeZone; 222 _latitudeZone = latitudeZone; 223 if (unit == SI.METER) { 224 _eastingInMeter = easting; 225 _northingInMeter = northing; 226 } else { 227 UnitConverter toMeter = unit.getConverterTo(SI.METER); 228 _eastingInMeter = toMeter.convert(easting); 229 _northingInMeter = toMeter.convert(northing); 230 } 231 } 232 233 238 public final int longitudeZone() { 239 return _longitudeZone; 240 } 241 242 247 public final char latitudeZone() { 248 return _latitudeZone; 249 } 250 251 257 public final double eastingValue(Unit<Length> unit) { 258 return unit.equals(SI.METER) ? _eastingInMeter : SI.METER 259 .getConverterTo(unit).convert(_eastingInMeter); 260 } 261 262 268 public final double northingValue(Unit<Length> unit) { 269 return unit.equals(SI.METER) ? _northingInMeter : SI.METER 270 .getConverterTo(unit).convert(_northingInMeter); 271 } 272 273 @Override 274 public ProjectedCRS<UTM> getCoordinateReferenceSystem() { 275 return CRS; 276 } 277 278 public int getDimension() { 280 return 2; 281 } 282 283 public double getOrdinate(int dimension) throws IndexOutOfBoundsException { 285 if (dimension == 0) { 286 Unit u = ProjectedCRS.EASTING_NORTHING_CS.getAxis(0).getUnit(); 287 return SI.METER.getConverterTo(u).convert(_eastingInMeter); 288 } else if (dimension == 1) { 289 Unit u = ProjectedCRS.EASTING_NORTHING_CS.getAxis(1).getUnit(); 290 return SI.METER.getConverterTo(u).convert(_northingInMeter); 291 } else { 292 throw new IndexOutOfBoundsException (); 293 } 294 } 295 296 303 public static boolean isNorthPolar(final LatLong latLong) { 304 return latLong.latitudeValue(NonSI.DEGREE_ANGLE) > 84.0; 305 } 306 307 314 public static boolean isSouthPolar(final LatLong latLong) { 315 return latLong.latitudeValue(NonSI.DEGREE_ANGLE) < -80.0; 316 } 317 318 324 public static char getLatitudeZone(final LatLong latLong) { 325 if (isNorthPolar(latLong)) { 326 if (latLong.longitudeValue(SI.RADIAN) < 0) { 327 return 'Y'; 328 } else { 329 return 'Z'; 330 } 331 } 332 if (isSouthPolar(latLong)) { 333 if (latLong.longitudeValue(SI.RADIAN) < 0) { 334 return 'A'; 335 } else { 336 return 'B'; 337 } 338 } 339 final int degreesLatitude = (int) latLong 340 .latitudeValue(NonSI.DEGREE_ANGLE); 341 char zone = (char) ((degreesLatitude + 80) / 8 + 'C'); 342 if (zone > 'H') { 343 zone++; 344 } 345 if (zone > 'N') { 346 zone++; 347 } 348 if (zone > 'X') { 349 zone = 'X'; 350 } 351 return zone; 352 } 353 354 361 public static int getLongitudeZone(LatLong latLong) { 362 363 final double degreesLongitude = latLong 364 .longitudeValue(NonSI.DEGREE_ANGLE); 365 366 if (isNorthPolar(latLong) || isSouthPolar(latLong)) { 368 if (degreesLongitude < 0.0) { 369 return 30; 370 } else { 371 return 31; 372 } 373 } 374 375 final char latitudeZone = getLatitudeZone(latLong); 376 if (latitudeZone == 'X' && degreesLongitude > 0.0 378 && degreesLongitude < 42.0) { 379 if (degreesLongitude < 9.0) { 380 return 31; 381 } 382 if (degreesLongitude < 21.0) { 383 return 33; 384 } 385 if (degreesLongitude < 33.0) { 386 return 35; 387 } else { 388 return 37; 389 } 390 } 391 if (latitudeZone == 'V' && degreesLongitude > 0.0 393 && degreesLongitude < 12.0) { 394 if (degreesLongitude < 3.0) { 395 return 31; 396 } else { 397 return 32; 398 } 399 } 400 401 return ((int) degreesLongitude + 180) / 6 + 1; 402 } 403 404 411 public static double getCentralMeridian(int longitudeZone, char latitudeZone) { 412 if (latitudeZone < 'C' || latitudeZone > 'X') { 414 return 0.0; 415 } 416 if (latitudeZone == 'X' && longitudeZone > 31 && longitudeZone <= 37) { 418 return Math.toRadians((longitudeZone - 1) * 6 - 180 + 4.5); 419 } 420 if (longitudeZone == 'V') { 422 if (latitudeZone == 31) { 423 return Math.toRadians(1.5); 424 } else if (latitudeZone == 32) { 425 return Math.toRadians(7.5); 426 } 427 } 428 return Math.toRadians((longitudeZone - 1) * 6 - 180 + 3); 429 } 430 431 439 public static UTM latLongToUtm(LatLong latLong, ReferenceEllipsoid ellipsoid) { 440 final char latitudeZone = getLatitudeZone(latLong); 441 final int longitudeZone = getLongitudeZone(latLong); 442 443 final double phi = latLong.latitudeValue(SI.RADIAN); 444 445 final double cosPhi = Math.cos(phi); 446 final double cos2Phi = cosPhi * cosPhi; 447 final double cos3Phi = cos2Phi * cosPhi; 448 final double cos4Phi = cos3Phi * cosPhi; 449 final double cos5Phi = cos4Phi * cosPhi; 450 final double cos6Phi = cos5Phi * cosPhi; 451 final double cos7Phi = cos6Phi * cosPhi; 452 final double cos8Phi = cos7Phi * cosPhi; 453 454 final double tanPhi = Math.tan(phi); 455 final double tan2Phi = tanPhi * tanPhi; 456 final double tan4Phi = tan2Phi * tan2Phi; 457 final double tan6Phi = tan4Phi * tan2Phi; 458 459 final double eb2 = ellipsoid.getSecondEccentricitySquared(); 460 final double eb4 = eb2 * eb2; 461 final double eb6 = eb4 * eb2; 462 final double eb8 = eb6 * eb2; 463 464 final double e2c2 = eb2 * cos2Phi; 465 final double e4c4 = eb4 * cos4Phi; 466 final double e6c6 = eb6 * cos6Phi; 467 final double e8c8 = eb8 * cos8Phi; 468 469 final double t2e2c2 = tan2Phi * e2c2; 470 final double t2e4c4 = tan2Phi * e4c4; 471 final double t2e6c6 = tan2Phi * e6c6; 472 final double t2e8c8 = tan2Phi * e8c8; 473 474 final double nu = ellipsoid.verticalRadiusOfCurvature(phi); 475 final double kn1 = K0 * nu * Math.sin(phi); 476 final double t1 = K0 * ellipsoid.meridionalArc(phi); 477 final double t2 = kn1 * cosPhi / 2.0; 478 final double t3 = (kn1 * cos3Phi / 24.0) 479 * (5.0 - tan2Phi + 9.0 * e2c2 + 4.0 * e4c4); 480 final double t4 = (kn1 * cos5Phi / 720.0) 481 * (61.0 - 58.0 * tan2Phi + tan4Phi + 270.0 * e2c2 - 330.0 482 * t2e2c2 + 445.0 * e4c4 - 680.0 * t2e4c4 + 324.0 * e6c6 483 - 600.0 * t2e6c6 + 88.0 * e8c8 - 192.0 * t2e8c8); 484 final double t5 = (kn1 * cos7Phi / 40320.0) 485 * (1385.0 - 3111.0 * tan2Phi + 543.0 * tan4Phi - tan6Phi); 486 487 final double kn2 = K0 * nu; 488 final double t6 = kn2 * cosPhi; 489 final double t7 = (kn2 * cos3Phi / 6.0) * (1.0 - tan2Phi + e2c2); 490 final double t8 = (kn2 * cos5Phi / 120.0) 491 * (5.0 - 18.0 * tan2Phi + tan4Phi + 14.0 * e2c2 - 58.0 * t2e2c2 492 + 13.0 * e4c4 - 64.0 * t2e4c4 + 4.0 * e6c6 - 24.0 * t2e6c6); 493 final double t9 = (kn2 * cos7Phi / 50.40) 494 * (61.0 - 479.0 * tan2Phi + 179.0 * tan4Phi - tan6Phi); 495 496 final double lambda = latLong.longitudeValue(SI.RADIAN); 497 final double lambda0 = getCentralMeridian(longitudeZone, latitudeZone); 498 final double dL = lambda - lambda0; 499 final double dL2 = dL * dL; 500 final double dL3 = dL2 * dL; 501 final double dL4 = dL3 * dL; 502 final double dL5 = dL4 * dL; 503 final double dL6 = dL5 * dL; 504 final double dL7 = dL6 * dL; 505 final double dL8 = dL7 * dL; 506 507 final double falseNorthing; 508 if ((phi < 0.0)) { 509 falseNorthing = UTM_FALSE_NORTHING.doubleValue(SI.METER); 511 } else { 512 falseNorthing = 0.0; 514 } 515 final double falseEasting = UTM_FALSE_EASTING.doubleValue(SI.METER); 516 final double northing = falseNorthing + t1 + dL2 * t2 + dL4 * t3 + dL6 517 * t4 + dL8 * t5; 518 final double easting = falseEasting + dL * t6 + dL3 * t7 + dL5 * t8 519 + dL7 * t9; 520 521 return UTM.valueOf(longitudeZone, latitudeZone, easting, northing, 522 SI.METER); 523 } 524 525 533 public static UTM latLongToUps(LatLong latLong, ReferenceEllipsoid ellipsoid) { 534 535 final char latitudeZone = getLatitudeZone(latLong); 536 final int longitudeZone = getLongitudeZone(latLong); 537 538 final double latitude = latLong.latitudeValue(SI.RADIAN); 539 final double sign = Math.signum(latitude); 540 final double phi = Math.abs(latitude); 541 final double lambda = latLong.longitudeValue(SI.RADIAN); 542 543 final double a = ellipsoid.getSemimajorAxis().doubleValue(SI.METER); 544 final double e = ellipsoid.getEccentricity(); 545 final double e2 = ellipsoid.getEccentricitySquared(); 546 547 final double c0 = ((2.0 * a) / Math.sqrt(1.0 - e2)) 548 * Math.pow((1.0 - e) / (1.0 + e), e / 2.0); 549 final double eSinPhi = e * Math.sin(phi); 550 final double tz = Math.pow((1 + eSinPhi) / (1 - eSinPhi), e / 2.0) 551 * Math.tan(Math.PI / 4.0 - phi / 2.0); 552 final double radius = UPS_SCALE_FACTOR * c0 * tz; 553 final double falseNorthing = UPS_FALSE_NORTHING.doubleValue(SI.METER); 554 final double northing; 555 if (sign > 0) { 556 northing = falseNorthing - radius * Math.cos(lambda); 557 } else { 558 northing = falseNorthing + radius * Math.cos(lambda); 559 } 560 final double falseEasting = UPS_FALSE_EASTING.doubleValue(SI.METER); 561 final double easting = falseEasting + radius * Math.sin(lambda); 562 563 return UTM.valueOf(longitudeZone, latitudeZone, easting, northing, 564 SI.METER); 565 } 566 567 575 public static LatLong utmToLatLong(UTM utm, ReferenceEllipsoid ellipsoid) { 576 final double northing; 577 if ((utm.latitudeZone() < 'N')) { 578 northing = utm._northingInMeter 580 - UTM_FALSE_NORTHING.doubleValue(SI.METER); 581 } else { 582 northing = utm._northingInMeter; 584 } 585 586 final double arc0 = northing / K0; 588 double rho = ellipsoid.meridionalRadiusOfCurvature(0.0); 589 double phi = arc0 / rho; 590 for (int i = 0; i < 5; i++) { 591 double arc = ellipsoid.meridionalArc(phi); 592 rho = ellipsoid.meridionalRadiusOfCurvature(phi); 593 double diff = (arc0 - arc) / rho; 594 if (Math.abs(diff) < Math.ulp(phi)) { 595 break; 596 } 597 phi += diff; 598 } 599 600 final double cosPhi = Math.cos(phi); 601 final double cos2Phi = cosPhi * cosPhi; 602 final double cos3Phi = cos2Phi * cosPhi; 603 final double cos4Phi = cos3Phi * cosPhi; 604 final double cos5Phi = cos4Phi * cosPhi; 605 final double cos6Phi = cos5Phi * cosPhi; 606 final double cos7Phi = cos6Phi * cosPhi; 607 final double cos8Phi = cos7Phi * cosPhi; 608 609 final double tanPhi = Math.tan(phi); 610 final double tan2Phi = tanPhi * tanPhi; 611 final double tan4Phi = tan2Phi * tan2Phi; 612 final double tan6Phi = tan4Phi * tan2Phi; 613 614 final double eb2 = ellipsoid.getSecondEccentricitySquared(); 615 final double eb4 = eb2 * eb2; 616 final double eb6 = eb4 * eb2; 617 final double eb8 = eb6 * eb2; 618 final double e2c2 = eb2 * cos2Phi; 619 final double e4c4 = eb4 * cos4Phi; 620 final double e6c6 = eb6 * cos6Phi; 621 final double e8c8 = eb8 * cos8Phi; 622 623 final double t2e2c2 = tan2Phi * e2c2; 624 final double t2e4c4 = tan2Phi * e4c4; 625 final double t2e6c6 = tan2Phi * e6c6; 626 final double t2e8c8 = tan2Phi * e8c8; 627 final double t4e2c2 = tan4Phi * e2c2; 628 final double t4e4c4 = tan4Phi * e4c4; 629 630 final double nu = ellipsoid.verticalRadiusOfCurvature(phi); 631 final double nu2 = nu * nu; 632 final double nu3 = nu2 * nu; 633 final double nu5 = nu3 * nu2; 634 final double nu7 = nu5 * nu2; 635 636 final double lambda0 = getCentralMeridian(utm.longitudeZone(), utm 637 .latitudeZone()); 638 final double dE = utm._eastingInMeter 639 - UTM_FALSE_EASTING.doubleValue(SI.METER); 640 final double dE2 = dE * dE; 641 final double dE3 = dE2 * dE; 642 final double dE4 = dE3 * dE; 643 final double dE5 = dE4 * dE; 644 final double dE6 = dE5 * dE; 645 final double dE7 = dE6 * dE; 646 final double dE8 = dE7 * dE; 647 648 final double t10 = tanPhi / (2.0 * rho * nu * K02); 649 final double t11 = tanPhi / (24.0 * rho * nu3 * K04) 650 * (5.0 + 3.0 * tan2Phi + e2c2 - 9.0 * t2e2c2 - 4.0 * e4c4); 651 final double t12 = tanPhi 652 / (720.0 * rho * nu5 * K06) 653 * (61.0 + 90.0 * tan2Phi + 45.0 * tan4Phi + 46.0 * e2c2 - 252.0 654 * t2e2c2 - 90.0 * t4e2c2 - 3.0 * e4c4 - 66.0 * t2e4c4 655 + 225.0 * t4e4c4 + 100.0 * e6c6 + 84.0 * t2e6c6 + 88.0 656 * e8c8 - 192.0 * t2e8c8); 657 final double t13 = tanPhi 658 / (40320.0 * rho * nu7 * K08) 659 * (1385.0 + 3633.0 * tan2Phi + 4095.0 * tan4Phi + 1575.0 * tan6Phi); 660 final double t14 = 1.0 / (cosPhi * nu * K0); 661 final double t15 = 1.0 / (6.0 * cosPhi * nu3 * K03) 662 * (1.0 + 2.0 * tan2Phi + e2c2); 663 final double t16 = 1.0 664 / (120.0 * cosPhi * nu5 * K05) 665 * (5.0 + 28.0 * tan2Phi + 24.0 * tan4Phi + 6.0 * e2c2 + 8.0 666 * t2e2c2 - 3.0 * e4c4 + 4.0 * t2e4c4 - 4.0 * e6c6 + 24.0 * t2e6c6); 667 final double t17 = 1.0 / (5040.0 * cosPhi * nu7 * K07) 668 * (61.0 + 662.0 * tan2Phi + 1320.0 * tan4Phi + 720.0 * tan6Phi); 669 670 final double latitude = phi - dE2 * t10 + dE4 * t11 - dE6 * t12 + dE8 671 * t13; 672 final double longitude = lambda0 + dE * t14 - dE3 * t15 + dE5 * t16 673 - dE7 * t17; 674 return LatLong.valueOf(latitude, longitude, SI.RADIAN); 675 } 676 677 685 public static LatLong upsToLatLong(UTM ups, ReferenceEllipsoid ellipsoid) { 686 final boolean northernHemisphere = ups.latitudeZone() > 'N'; 687 final double dN = ups.northingValue(SI.METER) 688 - UPS_FALSE_NORTHING.doubleValue(SI.METER); 689 final double dE = ups.eastingValue(SI.METER) 690 - UPS_FALSE_EASTING.doubleValue(SI.METER); 691 if (dE == 0.0 && dN == 0.0) { 693 if (northernHemisphere) { 694 return LatLong.valueOf(90.0, 0.0, NonSI.DEGREE_ANGLE); 695 } else { 696 return LatLong.valueOf(-90.0, 0.0, NonSI.DEGREE_ANGLE); 697 } 698 } 699 final double longitude; 701 if (northernHemisphere) { 702 longitude = Math.atan2(dE, -dN); 703 } else { 704 longitude = Math.atan2(dE, dN); 705 } 706 707 final double a = ellipsoid.getSemimajorAxis().doubleValue(SI.METER); 709 final double e = ellipsoid.getEccentricity(); 710 final double e2 = ellipsoid.getEccentricitySquared(); 711 final double e4 = e2 * e2; 712 final double e6 = e4 * e2; 713 final double e8 = e6 * e2; 714 final double aBar = e2 / 2.0 + 5.0 * e4 / 24.0 + e6 / 12.0 + 13 * e8 715 / 360.0; 716 final double bBar = 7.0 * e4 / 48.0 + 29.0 * e6 / 240.0 + 811.0 * e8 717 / 11520.0; 718 final double cBar = 7.0 * e6 / 120.0 + 81.0 * e8 / 1120.0; 719 final double dBar = 4279 * e8 / 161280.0; 720 final double c0 = ((2.0 * a) / Math.sqrt(1.0 - e2)) 721 * Math.pow((1.0 - e) / (1.0 + e), e / 2.0); 722 final double r; 723 if (dE == 0.0) { 724 r = dN; 725 } else if (dN == 0.0) { 726 r = dE; 727 } else if (dN < dE) { 728 r = dE / Math.sin(longitude); 729 } else { 730 r = dN / Math.cos(longitude); 731 } 732 final double radius = Math.abs(r); 733 734 final double chi = (Math.PI / 2.0) - 2.0 735 * Math.atan2(radius, UPS_SCALE_FACTOR * c0); 736 final double phi = chi + aBar * Math.sin(2.0 * chi) + bBar 737 * Math.sin(4.0 * chi) + cBar * Math.sin(6.0 * chi) + dBar 738 * Math.sin(8.0 * chi); 739 final double latitude; 740 if (northernHemisphere) { 741 latitude = phi; 742 } else { 743 latitude = -phi; 744 } 745 return LatLong.valueOf(latitude, longitude, SI.RADIAN); 746 } 747 748 } | Popular Tags |