001 package edu.nrao.sss.measure; 002 003 import java.math.BigDecimal; 004 import java.math.RoundingMode; 005 import java.text.ParseException; 006 import java.util.Arrays; 007 import java.util.Comparator; 008 009 import javax.xml.bind.annotation.XmlAccessType; 010 import javax.xml.bind.annotation.XmlAccessorType; 011 import javax.xml.bind.annotation.XmlElement; 012 import javax.xml.bind.annotation.XmlType; 013 014 import static edu.nrao.sss.math.MathUtil.MC_FINAL_CALC; 015 import static edu.nrao.sss.math.MathUtil.MC_INTERM_CALCS; 016 017 import edu.nrao.sss.math.MathUtil; 018 import edu.nrao.sss.util.StringUtil; 019 020 //TODO work on infinity. Make private static +/-INFINITY BigDecimal variables 021 // Whenever a value becomes infinite, use the private statics 022 023 /** 024 * An angle, or measure of arc. 025 * <p> 026 * Positive angles are measured in a counter-clockwise direction; 027 * negative angles in a clockwise direction.</p> 028 * <p> 029 * <b><u>Note About Accuracy</u></b><br/> 030 * This class originally used java's primitive <tt>double</tt> type 031 * for storage and calculation. Certain transformations, though, 032 * led to results that where not accurate enough for many purposes. 033 * Because of that, the internal references to <tt>double</tt> 034 * have been replaced with references to {@link BigDecimal}.</p> 035 * <p> 036 * <b>Version Info:</b> 037 * <table style="margin-left:2em"> 038 * <tr><td>$Revision: 1816 $</td></tr> 039 * <tr><td>$Date: 2008-12-23 10:21:00 -0700 (Tue, 23 Dec 2008) $</td></tr> 040 * <tr><td>$Author: dharland $</td></tr> 041 * </table></p> 042 * 043 * @author David M. Harland 044 * @since 2006-05-23 045 */ 046 @XmlAccessorType(XmlAccessType.NONE) 047 @XmlType(propOrder= {"xmlValue","units"}) 048 public class Angle 049 implements Cloneable, Comparable<Angle>, java.io.Serializable 050 { 051 private static final long serialVersionUID = 1L; 052 053 private static final BigDecimal DEFAULT_VALUE = BigDecimal.ZERO; 054 private static final ArcUnits DEFAULT_UNITS = ArcUnits.DEGREE; 055 056 //Used by equals, hashCode, and compareTo methods 057 //private static ArcUnits STD_UNITS = ArcUnits.ARC_SECOND; 058 059 private static final int PRECISION = MC_FINAL_CALC.getPrecision(); 060 061 private BigDecimal value; 062 private ArcUnits units; 063 064 //=========================================================================== 065 // CONSTRUCTORS 066 //=========================================================================== 067 068 /** Creates a new angle of zero degrees. */ 069 public Angle() 070 { 071 set(DEFAULT_VALUE, DEFAULT_UNITS); 072 } 073 074 /** Creates a new angle of {@code degrees} degrees. */ 075 public Angle(BigDecimal degrees) 076 { 077 set(degrees, ArcUnits.DEGREE); 078 } 079 080 /** Creates a new angle of {@code degrees} degrees. */ 081 public Angle(String degrees) 082 { 083 set(degrees, ArcUnits.DEGREE); 084 } 085 086 /** Creates a new angle with the given magnitude and units. */ 087 public Angle(BigDecimal magnitude, ArcUnits units) 088 { 089 set(magnitude, units); 090 } 091 092 /** Creates a new angle with the given magnitude and units. */ 093 public Angle(String magnitude, ArcUnits units) 094 { 095 set(magnitude, units); 096 } 097 098 /** 099 * Resets this angle so that it is equal to a angle created 100 * via the no-argument constructor. 101 */ 102 public void reset() 103 { 104 set(DEFAULT_VALUE, DEFAULT_UNITS); 105 } 106 107 //=========================================================================== 108 // GETTING & SETTING THE PROPERTIES 109 //=========================================================================== 110 111 /** 112 * Returns the magnitude of this angle. 113 * @return the magnitude of this angle. 114 */ 115 public BigDecimal getValue() 116 { 117 return value; 118 } 119 120 /** 121 * Returns the units of this angle. 122 * @return the units of this angle. 123 */ 124 @XmlElement 125 public ArcUnits getUnits() 126 { 127 return units; 128 } 129 130 /** 131 * Sets the magnitude and units of this angle. 132 * 133 * @param value the new magnitude for this angle. 134 * @param units the new units for this angle. 135 */ 136 public final void set(BigDecimal value, ArcUnits units) 137 { 138 setValue(value); 139 setUnits(units); 140 } 141 142 /** 143 * Sets the magnitude and units of this angle. 144 * 145 * @param value the new magnitude for this angle. 146 * @param units the new units for this angle. 147 */ 148 public final void set(String value, ArcUnits units) 149 { 150 setValue(value); 151 setUnits(units); 152 } 153 154 /** 155 * Sets the magnitude of this angle to {@code newValue}. 156 * <p> 157 * Note that the <tt>units</tt> of this angle are unaffected by 158 * this method.</p> 159 * 160 * @param newValue the new magnitude for this angle. 161 * 162 * @throws NumberFormatException 163 * if {@code newValue} is <i>null</i>. 164 */ 165 public final void setValue(BigDecimal newValue) 166 { 167 if (newValue == null) 168 throw new NumberFormatException("newValue may not be null."); 169 170 setAndRescale(newValue); 171 } 172 173 private void setAndRescale(BigDecimal newValue) 174 { 175 int precision = newValue.precision(); 176 177 if (precision < PRECISION) 178 { 179 int newScale = 180 (newValue.signum() == 0) ? 1 : PRECISION - precision + newValue.scale(); 181 182 value = newValue.setScale(newScale); 183 } 184 else if (precision > PRECISION) 185 { 186 value = newValue.round(MC_FINAL_CALC); 187 } 188 else 189 { 190 value = newValue; 191 } 192 } 193 194 /** 195 * Sets the magnitude of this angle to {@code newValue}. 196 * <p> 197 * Note that the <tt>units</tt> of this angle are unaffected by 198 * this method.</p> 199 * 200 * @param newValue the new magnitude for this angle. 201 * 202 * @throws NumberFormatException 203 * if {@code newValue} is <i>null</i>. 204 */ 205 public final void setValue(String newValue) 206 { 207 if (newValue == null) 208 throw new NumberFormatException("newValue may not be null."); 209 210 BigDecimal newBD; 211 212 newValue = newValue.trim().toLowerCase(); 213 214 if (newValue.equals("infinity") || 215 newValue.equals("+infinity") || newValue.equals("-infinity")) 216 { 217 newBD = MathUtil.getInfiniteValue(newValue.startsWith("-") ? -1 : +1); 218 } 219 else 220 { 221 newBD = new BigDecimal(newValue); 222 } 223 224 setAndRescale(newBD); 225 } 226 227 /** 228 * Sets the units of this angle to {@code newUnits}. 229 * <p> 230 * Note that the <tt>value</tt> of this angle is unaffected by 231 * this method. Contrast this with {@link #convertTo(ArcUnits)}.</p> 232 * 233 * @param newUnits 234 * the new units for this angle. If {@code newUnits} is <i>null</i> 235 * it will be treated as {@link ArcUnits#getDefault()}. 236 */ 237 public final void setUnits(ArcUnits newUnits) 238 { 239 units = (newUnits == null) ? ArcUnits.getDefault() : newUnits; 240 } 241 242 /** 243 * Sets the value and units of this angle based on {@code angleString}. 244 * See {@link #parse(String)} for the expected format of 245 * {@code angleString}. 246 * <p> 247 * If the parsing fails, this angle will be kept in its current 248 * state.</p> 249 * 250 * @param angleString a string that will be converted into 251 * an angle. 252 * 253 * @throws IllegalArgumentException if {@code angleString} is not in 254 * the expected form. 255 */ 256 public void set(String angleString) 257 { 258 if (angleString == null || angleString.equals("")) 259 { 260 this.reset(); 261 } 262 else 263 { 264 ArcUnits oldUnits = units; 265 BigDecimal oldValue = value; 266 267 try { 268 this.parseAngle(angleString); 269 } 270 catch (Exception ex) { 271 set(oldValue, oldUnits); 272 throw new IllegalArgumentException("Could not parse " + angleString, ex); 273 } 274 } 275 } 276 277 //=========================================================================== 278 // HELPERS FOR PERSISTENCE MECHANISMS 279 //=========================================================================== 280 281 //JAXB was having trouble with the overloaded setValue methods. 282 //These methods work around that trouble. 283 @XmlElement(name="value") 284 @SuppressWarnings("unused") 285 private BigDecimal getXmlValue() { return getValue().stripTrailingZeros(); } 286 287 @SuppressWarnings("unused") 288 private void setXmlValue(BigDecimal v) { setValue(v); } 289 290 //=========================================================================== 291 // DERIVED QUERIES 292 //=========================================================================== 293 294 /** 295 * Returns the number of full circles made by this angle. 296 * The value returned is never negative. 297 * <p> 298 * For example, an angle of +60.0° has made zero 299 * full circles, an angle of -400.0° one full circle, 300 * and an angle of +900.0° two full circles.</p> 301 * 302 * @return the number of full circles made by this angle. 303 */ 304 public int getFullCircles() 305 { 306 //Don't use one of the MC_* MathContext objects here. 307 //We used to have MC_FINAL_CALC here and had problems with 308 //radians. We'd get something like 18.9999....999, whereas 309 //the code below will give us 19.0. 310 BigDecimal numberOfCircles = 311 value.abs().divide(units.toFullCircle(), RoundingMode.HALF_UP); 312 313 return numberOfCircles.intValue(); 314 } 315 316 /** 317 * Returns <i>true</i> if the value of this angle is less than zero. 318 * @return <i>true</i> if the value of this angle is less than zero. 319 */ 320 public boolean isNegative() 321 { 322 return value.signum() < 0; 323 } 324 325 /** 326 * Returns <i>true</i> if the value of this angle is greater than zero. 327 * @return <i>true</i> if the value of this angle is greater than zero. 328 */ 329 public boolean isPositive() 330 { 331 return value.signum() > 0; 332 } 333 334 /** 335 * Returns <i>true</i> if this angle is traversed in a clockwise direction. 336 * The convention of this class is that negative angles are considered 337 * to be clockwise. 338 * @return <i>true</i> if this angle is traversed in a clockwise direction. 339 */ 340 public boolean isClockwise() 341 { 342 return isNegative(); 343 } 344 345 /** 346 * Returns <i>true</i> if this angle is in its default state, 347 * no matter how it got there. 348 * <p> 349 * An angle is in its <i>default state</i> if both its value and 350 * its units are the same as those of an angle newly created via 351 * the {@link #Angle() no-argument constructor}. 352 * An angle whose most recent update came via the 353 * {@link #reset() reset} method is also in its default state.</p> 354 * 355 * @return <i>true</i> if this angle is in its default state. 356 */ 357 public boolean isInDefaultState() 358 { 359 return (value == DEFAULT_VALUE) && units.equals(DEFAULT_UNITS); 360 } 361 362 /** 363 * Returns <i>true</i> if the magnitude of this angle approaches infinity. 364 * @return <i>true</i> if the magnitude of this angle approaches infinity. 365 */ 366 public boolean isInfinite() 367 { 368 return MathUtil.doubleValueIsInfinite(value); 369 } 370 371 //=========================================================================== 372 // INDIRECT MANIPULATIONS 373 //=========================================================================== 374 375 /** 376 * Negates the value of this angle. 377 * <p> 378 * For example, if this angle is currently -45.0°, 379 * it will be +45.0° after this call. The negation 380 * here is a simple sign flip. 381 * Contrast this with {@link #reverseDirection()}.</p> 382 * 383 * @return this angle, after the negation. 384 */ 385 public Angle negate() 386 { 387 if (value.signum() != 0) 388 setValue(value.negate()); 389 390 return this; 391 } 392 393 /** 394 * Reverses the direction of this angle. The reversal results in 395 * a sign change, unless this angle has a value of zero. 396 * <p> 397 * For example, if this angle is currently -45.0°, 398 * it will be +315.0° after this call. 399 * Contrast this with {@link #negate()}.</p> 400 * 401 * @return this angle, after the reversal of direction. 402 */ 403 public Angle reverseDirection() 404 { 405 BigDecimal fullCircle = units.toFullCircle(); 406 BigDecimal rotations = new BigDecimal(getFullCircles()); 407 408 if (rotations.signum() > 0) //a non-normalized angle 409 { 410 Angle temp = this.clone(); 411 boolean originallyNegative = temp.isNegative(); 412 temp.normalize(); 413 temp.reverseDirection(); 414 BigDecimal angle = rotations.multiply(fullCircle); 415 if (originallyNegative) 416 setValue(temp.value.add(angle)); 417 else //Can't be zero if rotations > 0, so this is positive 418 setValue(temp.value.subtract(angle)); 419 } 420 else //already normalized 421 { 422 if (value.equals(fullCircle)) 423 { 424 setValue(fullCircle.negate()); 425 } 426 else if (value.signum() != 0) 427 { 428 boolean negate = (value.signum() > 0); 429 BigDecimal absValue = fullCircle.subtract(value.abs()); 430 setValue(negate ? absValue.negate() : absValue); 431 } 432 } 433 434 return this; 435 } 436 437 /** 438 * Normalizes this angle so that its absolute value is less 439 * than that of a full circle. The method does <i>not</i> 440 * change the direction, or sign, of the angle. 441 * <p> 442 * For example, if this angle is currently 765.0°, 443 * it will be 45.0° after this call. 444 * Likewise, if this angle is currently -765.0°, 445 * it will be -45.0° after this call.</p> 446 * 447 * @return this angle, after the normalization. 448 */ 449 public Angle normalize() 450 { 451 int fullCircles = getFullCircles(); 452 453 if (fullCircles > 0) 454 { 455 BigDecimal circleSize = units.toFullCircle(); 456 BigDecimal excess = circleSize.multiply(new BigDecimal(fullCircles)); 457 458 if (value.signum() < 0) 459 excess = excess.negate(); 460 461 setValue(value.subtract(excess)); 462 } 463 464 return this; 465 } 466 467 /** 468 * Converts this angle to a positive normalized angle. 469 * The conversion is done first by normalizing this angle 470 * (see {@link #normalize()}) 471 * and then by creating a positive angle, <i>not</i> by a 472 * simple sign flip, but instead by reversing direction 473 * to complete the circle. 474 * <p> 475 * For example, if this angle is currently -765.0°, 476 * it is first normalized to -45.0°. The result 477 * is then converted to a positive number by traveling the 478 * circle in the opposite direction, for a final result 479 * of +315°.</p> 480 * 481 * @return this angle, after the conversion. 482 */ 483 public Angle convertToPositiveNormal() 484 { 485 this.normalize(); 486 487 if (value.signum() < 0) 488 setValue(value.add(units.toFullCircle())); 489 490 return this; 491 } 492 493 /** 494 * Converts this angle to a negative normalized angle. 495 * The conversion is done first by normalizing this angle 496 * (see {@link #normalize()}) 497 * and then by creating a negative angle <i>not</i> by a 498 * simple sign flip, but instead by reversing direction 499 * to complete the circle. 500 * <p> 501 * For example, if this angle is currently +765.0°, 502 * it is first normalized to +45.0°. The result 503 * is then converted to a negative number by traveling the 504 * circle in the opposite direction, for a final result 505 * of -315°.</p> 506 * 507 * @return this angle, after the conversion. 508 */ 509 public Angle convertToNegativeNormal() 510 { 511 this.normalize(); 512 513 if (value.signum() > 0) 514 setValue(value.subtract(units.toFullCircle())); 515 516 return this; 517 } 518 519 /** 520 * Converts this angle to the normalized value that has the 521 * smaller absolute value. This may involve a reversal of 522 * direction. 523 * <p> 524 * For example, if this angle is currently +920.0°, 525 * it is first normalized to +200.0°. By reversing 526 * direction, we wind up with an angle of -160.0°, 527 * which has a smaller absolute value than +200.0°, 528 * so that is our end result. For the special situations 529 * where the normalized value is either +180.0° 530 * or -180.0°, the original direction is preserved.</p> 531 * 532 * @return this angle, after the conversion. 533 */ 534 public Angle convertToMinAbsValueNormal() 535 { 536 this.normalize(); 537 538 if (value.abs().compareTo(units.toHalfCircle()) > 0) 539 this.reverseDirection(); 540 541 return this; 542 } 543 544 //=========================================================================== 545 // CONVERSION TO, AND EXPRESSION IN, OTHER UNITS 546 //=========================================================================== 547 548 /** 549 * Converts this angle to the new units. 550 * <p> 551 * After this method is complete this angle will have units of 552 * {@code units} and its <tt>value</tt> will have been converted 553 * accordingly.</p> 554 * 555 * @param newUnits the new units for this angle. 556 * If {@code newUnits} is <i>null</i> an 557 * {@code IllegalArgumentException} will be thrown. 558 * 559 * @return this angle. The reason for this return type is to allow 560 * code of this nature: 561 * {@code BigDecimal radians = 562 * myAngle.convertTo(ArcUnits.RADIAN).getValue();} 563 */ 564 public Angle convertTo(ArcUnits newUnits) 565 { 566 if (newUnits == null) 567 throw new IllegalArgumentException("May not convert to NULL units."); 568 569 if (!newUnits.equals(units)) 570 { 571 set(toUnits(newUnits), newUnits); 572 } 573 574 return this; 575 } 576 577 /** 578 * Returns the magnitude of this angle in {@code otherUnits}. 579 * <p> 580 * Note that this method does not alter the state of this angle. 581 * Contrast this with {@link #convertTo(ArcUnits)}.</p> 582 * 583 * @param otherUnits the units in which to express this angle's magnitude. 584 * 585 * @return this angle's value converted to {@code otherUnits}. 586 */ 587 public BigDecimal toUnits(ArcUnits otherUnits) 588 { 589 BigDecimal answer = value; 590 591 if (otherUnits == null) 592 throw new IllegalArgumentException("May not convert to NULL units."); 593 594 //No conversion for zero, infinite, or if no change of units 595 if (!otherUnits.equals(units) && 596 value.compareTo(BigDecimal.ZERO) != 0.0 && !isInfinite()) 597 { 598 answer = units.convertTo(otherUnits, value); 599 } 600 601 return answer; 602 } 603 604 /** 605 * Returns a representation of this angle in degrees, minutes, and seconds. 606 * 607 * @return an array of size three in this order: 608 * <ol start="0"> 609 * <li>An integral number of degrees.</li> 610 * <li>An integral number of arc minutes.</li> 611 * <li>A real number of arc seconds.</li> 612 * </ol> 613 */ 614 public Number[] toDms() 615 { 616 return units.convertToDms(value); 617 } 618 619 /** 620 * Returns a representation of this angle in hours, minutes, and seconds. 621 * 622 * @return an array of size three in this order: 623 * <ol start="0"> 624 * <li>An integral number of hours.</li> 625 * <li>An integral number of minutes.</li> 626 * <li>A real number of seconds.</li> 627 * </ol> 628 */ 629 public Number[] toHms() 630 { 631 return units.convertToHms(value); 632 } 633 634 //=========================================================================== 635 // ARITHMETIC 636 //=========================================================================== 637 638 /** 639 * Adds the {@code other} angle to this one. 640 * @param other an angle to be added to this one. 641 * @return this angle, after the addition. 642 */ 643 public Angle add(Angle other) 644 { 645 //TODO when we work on INFINITY, we need to consider other being infinite 646 // and the result being inf, too 647 if (!isInfinite()) 648 setAndRescale(value.add(other.toUnits(this.units))); 649 650 return this; 651 } 652 653 /** 654 * Subtracts the {@code other} angle from this one. 655 * @param other an angle to be subtracted from this one. 656 * @return this angle, after the subtraction. 657 */ 658 public Angle subtract(Angle other) 659 { 660 //TODO when we work on INFINITY, we need to consider other being infinite 661 // and the result being inf, too 662 if (!isInfinite()) 663 setAndRescale(value.subtract(other.toUnits(this.units))); 664 665 return this; 666 } 667 668 private Angle mathHelper = null; 669 670 /** 671 * Adds the {@code amount}, of the same units as this angle, to this angle. 672 * @param amount the amount to add to this angle. 673 * @return this angle, after the addition. 674 */ 675 public Angle add(BigDecimal amount) 676 { 677 if (mathHelper == null) 678 mathHelper = new Angle(); 679 mathHelper.set(amount, this.units); 680 return add(mathHelper); 681 } 682 683 /** 684 * Adds the {@code amount}, of the same units as this angle, to this angle. 685 * @param amount the amount to add to this angle. 686 * @return this angle, after the addition. 687 */ 688 public Angle add(String amount) 689 { 690 if (mathHelper == null) 691 mathHelper = new Angle(); 692 mathHelper.set(amount, this.units); 693 return add(mathHelper); 694 } 695 696 /** 697 * Subtracts the {@code amount}, of the same units as this angle, from this 698 * angle. 699 * @param amount the amount to subtract from this angle. 700 * @return this angle, after the subtraction. 701 */ 702 public Angle subtract(BigDecimal amount) 703 { 704 if (mathHelper == null) 705 mathHelper = new Angle(); 706 mathHelper.set(amount, this.units); 707 return subtract(mathHelper); 708 } 709 710 /** 711 * Subtracts the {@code amount}, of the same units as this angle, from this 712 * angle. 713 * @param amount the amount to subtract from this angle. 714 * @return this angle, after the subtraction. 715 */ 716 public Angle subtract(String amount) 717 { 718 if (mathHelper == null) 719 mathHelper = new Angle(); 720 mathHelper.set(amount, this.units); 721 return subtract(mathHelper); 722 } 723 724 /** 725 * Multiplies this angle by {@code multiplier}. 726 * 727 * @param multiplier the number by which this angle should be multiplied. 728 * 729 * @return this angle, after the multiplication. 730 */ 731 public Angle multiplyBy(BigDecimal multiplier) 732 { 733 if (!isInfinite()) 734 setAndRescale(value.multiply(multiplier)); 735 736 return this; 737 } 738 739 /** 740 * Multiplies this angle by {@code multiplier}. 741 * 742 * @param multiplier the number by which this angle should be multiplied. 743 * 744 * @return this angle, after the multiplication. 745 */ 746 public Angle multiplyBy(String multiplier) 747 { 748 return multiplyBy(new BigDecimal(multiplier)); 749 } 750 751 /** 752 * Divides this angle by {@code divisor}. 753 * 754 * @param divisor the number by which this angle should be divided. 755 * 756 * @return this angle, after the division. 757 */ 758 public Angle divideBy(BigDecimal divisor) 759 { 760 setAndRescale(value.divide(divisor, MC_INTERM_CALCS)); 761 762 return this; 763 } 764 765 /** 766 * Divides this angle by {@code divisor}. 767 * 768 * @param divisor the number by which this angle should be divided. 769 * 770 * @return this angle, after the division. 771 */ 772 public Angle divideBy(String divisor) 773 { 774 return divideBy(new BigDecimal(divisor)); 775 } 776 777 //=========================================================================== 778 // TRIGONOMETRY 779 //=========================================================================== 780 781 /** 782 * Returns the cosine of this angle. 783 * @return the cosine of this angle. 784 */ 785 public double cosine() 786 { 787 return Math.cos(toUnits(ArcUnits.RADIAN).doubleValue()); 788 } 789 790 /** 791 * Returns the sine of this angle. 792 * @return the sine of this angle. 793 */ 794 public double sine() 795 { 796 return Math.sin(toUnits(ArcUnits.RADIAN).doubleValue()); 797 } 798 799 /** 800 * Returns the tangent of this angle. 801 * @return the tangent of this angle. 802 */ 803 public double tangent() 804 { 805 return Math.tan(toUnits(ArcUnits.RADIAN).doubleValue()); 806 } 807 808 /** 809 * Returns the secant of this angle. 810 * @return the secant of this angle. 811 */ 812 public double secant() 813 { 814 return 1.0 / cosine(); 815 } 816 817 /** 818 * Returns the cosecant of this angle. 819 * @return the cosecant of this angle. 820 */ 821 public double cosecant() 822 { 823 return 1.0 / sine(); 824 } 825 826 /** 827 * Returns the cotangent of this angle. 828 * @return the cotangent of this angle. 829 */ 830 public double cotangent() 831 { 832 return 1.0 / tangent(); 833 } 834 835 /** 836 * Returns a new angle whose cosine is <tt>cosine</tt>. 837 * @param cosine 838 * the value whose arc cosine is to be returned. 839 * @return a new angle whose cosine is <tt>cosine</tt>. 840 */ 841 public static Angle arcCosine(double cosine) 842 { 843 return new Angle(BigDecimal.valueOf(Math.acos(cosine)), 844 ArcUnits.RADIAN); 845 } 846 847 /** 848 * Returns a new angle whose sine is <tt>sine</tt>. 849 * @param sine 850 * the value whose arc sine is to be returned. 851 * @return a new angle whose sine is <tt>sine</tt>. 852 */ 853 public static Angle arcSine(double sine) 854 { 855 return new Angle(BigDecimal.valueOf(Math.asin(sine)), 856 ArcUnits.RADIAN); 857 } 858 859 /** 860 * Returns a new angle whose tangent is <tt>tangent</tt>. 861 * @param tangent 862 * the value whose arc tangent is to be returned. 863 * @return a new angle whose tangent is <tt>tangent</tt>. 864 */ 865 public static Angle arcTangent(double tangent) 866 { 867 return new Angle(BigDecimal.valueOf(Math.atan(tangent)), 868 ArcUnits.RADIAN); 869 } 870 871 //=========================================================================== 872 // PARSING 873 //=========================================================================== 874 875 /** 876 * Returns a new angle based on {@code angleString}. 877 * <p> 878 * <b><u>Valid Formats</u></b><br/> 879 * Let I be the text representation of an integer.<br/> 880 * Let R be the text representation of a real number.<br/> 881 * Let w represent zero or more whitespace characters.<br/> 882 * Let S be a valid {@link ArcUnits units} symbol.<br/> 883 * <br/> 884 * <i>Format One</i>: <tt>wRw</tt>. The given number will be defined to be 885 * in units of {@link ArcUnits#DEGREE degrees}. Examples: 886 * <ul> 887 * <li>12.345</li> 888 * </ul> 889 * <i>Format Two</i>: <tt>wRwSw</tt> 890 * <ul> 891 * <li>12.345d</li> 892 * <li> 12.345 d</li> 893 * <li>1.234 rad</li> 894 * </ul> 895 * <i>Format Three</i>: <tt>wIwSwIwSwRwSw</tt>. The first S must be the symbol 896 * for either {@link ArcUnits#HOUR hours} or {@link ArcUnits#DEGREE degrees}. 897 * The second S must be the symbol for either 898 * {@link ArcUnits#MINUTE minutes} or {@link ArcUnits#ARC_MINUTE arc minutes}. 899 * The final S must be the symbol for either 900 * {@link ArcUnits#SECOND seconds} or {@link ArcUnits#ARC_SECOND arc seconds}. 901 * <ul> 902 * <li>12h34m56.789s</li> 903 * <li>12d 34' 56.789"</li> 904 * </ul> 905 * This format has been updated so that only two of the three number/units 906 * pairs are required. For example, each of the following is valid: 907 * <ul> 908 * <li>12h 34m</li> 909 * <li>12h 56.789s</li> 910 * <li>34m 56.789s</li> 911 * </ul> 912 * </p><p> 913 * <b><u>Special Cases</u></b><br/> 914 * An {@code angleString} of <i>null</i> or <tt>""</tt> (the empty 915 * string) will <i>not</i> result in an {@code IllegalArgumentException}, 916 * but will instead return an angle of zero degrees.</p> 917 * 918 * @param angleString a string that will be converted into 919 * an angle. 920 * 921 * @throws IllegalArgumentException if {@code angleString} is not in 922 * the expected form. 923 */ 924 public static Angle parse(String angleString) 925 { 926 Angle newAngle = new Angle(); 927 928 if ((angleString != null) && !angleString.equals("")) 929 { 930 try { 931 newAngle.parseAngle(angleString); 932 } 933 catch (Exception ex) { 934 throw new IllegalArgumentException("Could not parse '" + angleString + "'.", ex); 935 } 936 } 937 return newAngle; 938 } 939 940 /** 941 * If parsing was successful, this angle's units & value will have been 942 * valued. Otherwise an exception is thrown. 943 */ 944 private void parseAngle(String angleString) 945 { 946 //Eliminate whitespace 947 angleString = angleString.replaceAll("\\s", ""); 948 949 //If successful parsing either hr-min-sec or degrees-min-sec, return 950 if (parseXms(angleString)) 951 return; 952 953 //Now assume we have a number followed (optionally) by a symbol 954 units = null; 955 956 int unitsPos = -1; 957 958 //Sort units by length of symbol, longer symbols before shorter. 959 //This helps w/ discovering which unit is contained in angleString. 960 ArcUnits[] sortedUnits = ArcUnits.values(); 961 Arrays.sort(sortedUnits, 962 new Comparator<ArcUnits>() { 963 public int compare(ArcUnits a, ArcUnits b) { 964 return b.getSymbol().length() - a.getSymbol().length(); 965 } 966 }); 967 968 //Figure out what kind of units we have 969 for (ArcUnits u : sortedUnits) 970 { 971 if (angleString.endsWith(u.getSymbol())) 972 { 973 units = u; 974 unitsPos = angleString.lastIndexOf(u.getSymbol()); 975 break; 976 } 977 } 978 979 //If units, simply parse the number 980 if (units != null) 981 { 982 String numberString = angleString.substring(0, unitsPos); 983 setValue(numberString); 984 } 985 //Otherwise, see if we have just a number. Declare this to be degrees. 986 else 987 { 988 set(angleString, ArcUnits.DEGREE); 989 } 990 } 991 992 /** 993 * Returns true if parsing was successful, false otherwise. 994 * If successful, this angle's units & value will have been set. 995 */ 996 private boolean parseXms(String xmsString) 997 { 998 //Consider as HMS or DMS if it has two or more of the unit symbols. 999 //If it has only one, eg 12.345 h, let normally parsing occur. 1000 int hmsCount = 0; 1001 1002 if (xmsString.contains(ArcUnits.HOUR.getSymbol())) hmsCount++; 1003 if (xmsString.contains(ArcUnits.MINUTE.getSymbol())) hmsCount++; 1004 if (xmsString.contains(ArcUnits.SECOND.getSymbol())) hmsCount++; 1005 1006 int dmsCount = 0; 1007 1008 if (xmsString.contains(ArcUnits.DEGREE.getSymbol())) dmsCount++; 1009 if (xmsString.contains(ArcUnits.ARC_MINUTE.getSymbol())) dmsCount++; 1010 if (xmsString.contains(ArcUnits.ARC_SECOND.getSymbol())) dmsCount++; 1011 1012 boolean isHms = hmsCount > 1; 1013 boolean isDms = dmsCount > 1; 1014 1015 //Quick exit if don't have HMS or DMS, or if have both 1016 if (isHms == isDms) 1017 return false; 1018 1019 //It looks like we have either HMS or DMS. 1020 //Aug 2007: We now allow any 2 or 3 parts for HMS and DMS. 1021 //Eg, "34m 56s" is now acceptable, as are "12h 56s" and "12h 34m". 1022 try 1023 { 1024 //The Integer class, for some odd reason, handles "-" signs, but not "+" 1025 xmsString = xmsString.replaceAll("\\+", ""); 1026 1027 if (isHms) 1028 { 1029 Number[] parts = parseXms(xmsString, ArcUnits.HOUR.getSymbol(), 1030 ArcUnits.MINUTE.getSymbol(), 1031 ArcUnits.SECOND.getSymbol()); 1032 units = ArcUnits.SECOND; 1033 setValue(ArcUnits.convertHmsTo(units, parts[0].intValue(), 1034 parts[1].intValue(), 1035 new BigDecimal(parts[2].toString()))); 1036 } 1037 else //isDms 1038 { 1039 Number[] parts = parseXms(xmsString, ArcUnits.DEGREE.getSymbol(), 1040 ArcUnits.ARC_MINUTE.getSymbol(), 1041 ArcUnits.ARC_SECOND.getSymbol()); 1042 units = ArcUnits.ARC_SECOND; 1043 setValue(ArcUnits.convertDmsTo(units, parts[0].intValue(), 1044 parts[1].intValue(), 1045 new BigDecimal(parts[2].toString()))); 1046 } 1047 } 1048 //Signal any kind of failure with a return value of false; 1049 catch (Exception ex) 1050 { 1051 return false; 1052 } 1053 1054 return true; //success 1055 } 1056 1057 /** 1058 * Expects xmsString to have form: 1059 * INTEGER symbol1 INTEGER minutesSymbol DOUBLE secondsSymbol 1060 * 1061 * As of Aug 2007, we can now have any two or three of the above 1062 * number/symbol pairs. 1063 * 1064 * Throws the following exceptions if xmsString is non-conformant: 1065 * + IndexOutOfBoundsException 1066 * + NumberFormatException 1067 */ 1068 private Number[] parseXms(String xmsString, String symbol1, String minutesSymbol, 1069 String secondsSymbol) 1070 throws ParseException 1071 { 1072 Number[] answer = new Number[3]; 1073 1074 int DorH = 0; //Degrees or Hours 1075 int M = 0; //Arc-minutes or Minutes 1076 BigDecimal S = BigDecimal.ZERO; //Arc-seconds or Seconds 1077 1078 String numberStr = null; 1079 int finalSymbolIndex = -1; 1080 boolean foundPreviousPart = false; 1081 boolean negative = xmsString.contains("-"); 1082 1083 //Get the degrees or HOURS PART, if we have that part 1084 int subStrStart = 0; 1085 int subStrEnd = xmsString.indexOf(symbol1); 1086 1087 if (subStrEnd < 0) 1088 { 1089 foundPreviousPart = false; 1090 } 1091 else //we found either degrees or hours symbol 1092 { 1093 numberStr = xmsString.substring(subStrStart, subStrEnd); 1094 DorH = Math.abs(Integer.parseInt(numberStr)); 1095 finalSymbolIndex = subStrEnd; 1096 foundPreviousPart = true; 1097 } 1098 1099 //Get the MINUTES PART, if we have that part 1100 if (foundPreviousPart) 1101 subStrStart = subStrEnd + symbol1.length(); 1102 //else leave at current value 1103 1104 subStrEnd = xmsString.indexOf(minutesSymbol); 1105 1106 if (subStrEnd < 0) 1107 { 1108 foundPreviousPart = false; 1109 } 1110 else //we found minutes symbol 1111 { 1112 numberStr = xmsString.substring(subStrStart, subStrEnd); 1113 M = Math.abs(Integer.parseInt(numberStr)); 1114 finalSymbolIndex = subStrEnd; 1115 foundPreviousPart = true; 1116 } 1117 1118 //Get the SECONDS PART, if we have that part 1119 if (foundPreviousPart) 1120 subStrStart = subStrEnd + minutesSymbol.length(); 1121 //else leave at current value 1122 1123 subStrEnd = xmsString.indexOf(secondsSymbol); 1124 1125 if (subStrEnd >= 0) //we found seconds symbol 1126 { 1127 numberStr = xmsString.substring(subStrStart, subStrEnd); 1128 finalSymbolIndex = subStrEnd; 1129 S = new BigDecimal(numberStr).abs(); 1130 } 1131 1132 //Ensure that we have no more characters after the position of the last 1133 //units symbol. (Code assumes trailing whitespace was already stripped.) 1134 if (finalSymbolIndex != (xmsString.length()-1)) 1135 throw new ParseException("Found extra characters at end of text '" + 1136 xmsString + "'.", subStrEnd); 1137 1138 //Negate the largest non-zero field 1139 if (negative) 1140 { 1141 if (DorH != 0) 1142 { 1143 DorH = -DorH; 1144 } 1145 else if (M != 0) 1146 { 1147 M = -M; 1148 } 1149 else //M == 0 1150 { 1151 S = S.negate(); 1152 } 1153 } 1154 1155 answer[0] = DorH; 1156 answer[1] = M; 1157 answer[2] = S; 1158 1159 return answer; 1160 } 1161 1162 //=========================================================================== 1163 // UTILITY METHODS 1164 //=========================================================================== 1165 1166 /** Returns a text representation of this angle in its native units. */ 1167 public String toString() 1168 { 1169 return StringUtil.getInstance() 1170 .formatNoScientificNotation(getValue()) + 1171 getUnits().getSymbol(); 1172 } 1173 1174 /** Returns a text representation of this angle in its native units. */ 1175 public String toString(int minFracDigits, int maxFracDigits) 1176 { 1177 return StringUtil.getInstance().formatNoScientificNotation( 1178 getValue(), minFracDigits, maxFracDigits) + 1179 getUnits().getSymbol(); 1180 } 1181 1182 /** 1183 * Returns a text representation of this angle in its native units, using 1184 * an HTML-friendly symbol. 1185 */ 1186 public String toStringHtml(int minFracDigits, int maxFracDigits) 1187 { 1188 return StringUtil.getInstance().formatNoScientificNotation( 1189 getValue(), minFracDigits, maxFracDigits) + 1190 getUnits().getHtmlSymbol(); 1191 } 1192 1193 /** 1194 * Returns a text representation of this angle in 1195 * hours, minutes, and seconds. 1196 */ 1197 public String toStringHms() 1198 { 1199 return toStringHms(0, -1); 1200 } 1201 1202 /** 1203 * Returns a text representation of this angle in 1204 * hours, minutes, and seconds. 1205 * 1206 * @param minFracDigits 1207 * the minimum number of places after the decimal point 1208 * for the seconds field. 1209 * 1210 * @param maxFracDigits 1211 * the maximum number of places after the decimal point 1212 * for the seconds field. If this value is less than zero, 1213 * no rounding or truncating will be performed. 1214 */ 1215 public String toStringHms(int minFracDigits, int maxFracDigits) 1216 { 1217 Number[] hms = toHms(); 1218 1219 int hours = hms[0].intValue(); 1220 int minutes = hms[1].intValue(); 1221 BigDecimal seconds = new BigDecimal(hms[2].toString()); 1222 1223 //Deal with the sign. Only one of {d, m, s} can be negative, 1224 //but it could be any one of them. (If m is < 0, d must be 0; 1225 //if s < 0.0, both d & m must be 0.) 1226 String sign = ""; 1227 1228 if (hours < 0) 1229 { 1230 sign = "-"; 1231 hours = -hours; 1232 } 1233 else if (hours == 0) 1234 { 1235 if (minutes < 0) 1236 { 1237 sign = "-"; 1238 minutes = -minutes; 1239 } 1240 else if (minutes == 0) 1241 { 1242 if (seconds.signum() < 0) 1243 { 1244 sign = "-"; 1245 seconds = seconds.negate(); 1246 } 1247 } 1248 } 1249 1250 //Rollover logic 1251 //We have to worry about things like 59.999 being rounded to 60.0 1252 if (maxFracDigits >= 0) 1253 { 1254 double s = seconds.setScale(maxFracDigits, RoundingMode.HALF_UP).doubleValue(); 1255 1256 if (s == 60.0 || s == -60.0) 1257 { 1258 seconds = BigDecimal.ZERO; 1259 minutes++; 1260 } 1261 1262 if (minutes == 60 || minutes == -60) 1263 { 1264 minutes = 0; 1265 hours++; 1266 } 1267 } 1268 1269 //Formatting logic 1270 StringBuilder buff = new StringBuilder(); 1271 1272 buff.append(sign); 1273 1274 if (hours < 10) 1275 buff.append('0'); 1276 1277 buff.append(hours).append(ArcUnits.HOUR.getSymbol()).append(' '); 1278 1279 if (minutes < 10) 1280 buff.append('0'); 1281 1282 buff.append(minutes).append(ArcUnits.MINUTE.getSymbol()).append(' '); 1283 1284 if (seconds.compareTo(BigDecimal.TEN) < 0) 1285 buff.append('0'); 1286 1287 if (maxFracDigits >= 0) 1288 buff.append(StringUtil.getInstance().formatNoScientificNotation( 1289 seconds, minFracDigits, maxFracDigits)); 1290 else //no rounding or truncation 1291 buff.append(StringUtil.getInstance().formatNoScientificNotation(seconds)); 1292 1293 buff.append(ArcUnits.SECOND.getSymbol()); 1294 1295 return buff.toString(); 1296 } 1297 1298 /** 1299 * Returns a text representation of this angle in 1300 * degrees, arc-minutes, and arc-seconds. 1301 */ 1302 public String toStringDms() 1303 { 1304 return toStringDms(0, -1); 1305 } 1306 1307 /** 1308 * Returns a text representation of this angle in 1309 * degrees, arc-minutes, and arc-seconds. 1310 * 1311 * @param minFracDigits 1312 * the minimum number of places after the decimal point 1313 * for the seconds field. 1314 * 1315 * @param maxFracDigits 1316 * the maximum number of places after the decimal point 1317 * for the seconds field. If this value is less than zero, 1318 * no rounding or truncating will be performed. 1319 */ 1320 public String toStringDms(int minFracDigits, int maxFracDigits) 1321 { 1322 return toStringDms(minFracDigits, maxFracDigits, false); 1323 } 1324 1325 /** 1326 * Returns a text representation of this angle in 1327 * degrees, arc-minutes, and arc-seconds, with HTML-friendly symbols. 1328 * 1329 * @param minFracDigits 1330 * the minimum number of places after the decimal point 1331 * for the seconds field. 1332 * 1333 * @param maxFracDigits 1334 * the maximum number of places after the decimal point 1335 * for the seconds field. If this value is less than zero, 1336 * no rounding or truncating will be performed. 1337 */ 1338 public String toStringDmsHtml(int minFracDigits, int maxFracDigits) 1339 { 1340 return toStringDms(minFracDigits, maxFracDigits, true); 1341 } 1342 1343 private String toStringDms(int minFracDigits, 1344 int maxFracDigits, boolean htmlFriendly) 1345 { 1346 Number[] dms = toDms(); 1347 1348 int degrees = dms[0].intValue(); 1349 int minutes = dms[1].intValue(); 1350 BigDecimal seconds = new BigDecimal(dms[2].toString()); 1351 1352 //Deal with the sign. Only one of {d, m, s} can be negative, 1353 //but it could be any one of them. (If m is < 0, d must be 0; 1354 //if s < 0.0, both d & m must be 0.) 1355 char sign = '+'; 1356 1357 if (degrees < 0) 1358 { 1359 sign = '-'; 1360 degrees = -degrees; 1361 } 1362 else if (degrees == 0) 1363 { 1364 if (minutes < 0) 1365 { 1366 sign = '-'; 1367 minutes = -minutes; 1368 } 1369 else if (minutes == 0) 1370 { 1371 if (seconds.signum() < 0) 1372 { 1373 sign = '-'; 1374 seconds = seconds.negate(); 1375 } 1376 } 1377 } 1378 1379 //Rollover logic 1380 //We have to worry about things like 59.999 being rounded to 60.0 1381 if (maxFracDigits >= 0) 1382 { 1383 double s = seconds.setScale(maxFracDigits, RoundingMode.HALF_UP).doubleValue(); 1384 1385 if (s == 60.0 || s == -60.0) 1386 { 1387 seconds = BigDecimal.ZERO; 1388 minutes++; 1389 } 1390 1391 if (minutes == 60 || minutes == -60) 1392 { 1393 minutes = 0; 1394 degrees++; 1395 } 1396 } 1397 1398 //Formatting logic 1399 StringBuilder buff = new StringBuilder(); 1400 1401 buff.append(sign); 1402 1403 if (degrees < 10) 1404 buff.append('0'); 1405 1406 String symbol = htmlFriendly ? ArcUnits.DEGREE.getHtmlSymbol() 1407 : ArcUnits.DEGREE.getSymbol(); 1408 1409 buff.append(degrees).append(symbol).append(' '); 1410 1411 if (minutes < 10) 1412 buff.append('0'); 1413 1414 symbol = htmlFriendly ? ArcUnits.ARC_MINUTE.getHtmlSymbol() 1415 : ArcUnits.ARC_MINUTE.getSymbol(); 1416 1417 buff.append(minutes).append(symbol).append(' '); 1418 1419 if (seconds.compareTo(BigDecimal.TEN) < 0) 1420 buff.append('0'); 1421 1422 if (maxFracDigits >= 0) 1423 buff.append(StringUtil.getInstance().formatNoScientificNotation( 1424 seconds, minFracDigits, maxFracDigits)); 1425 else //no rounding or truncation 1426 buff.append(StringUtil.getInstance().formatNoScientificNotation(seconds)); 1427 1428 symbol = htmlFriendly ? ArcUnits.ARC_SECOND.getHtmlSymbol() 1429 : ArcUnits.ARC_SECOND.getSymbol(); 1430 1431 buff.append(symbol); 1432 1433 return buff.toString(); 1434 } 1435 1436 /** Returns an angle that is equal to this one. */ 1437 @Override 1438 public Angle clone() 1439 { 1440 //Since this class has only primitive (& immutable) attributes, 1441 //the clone in Object is all we need. 1442 try 1443 { 1444 return (Angle)super.clone(); 1445 } 1446 catch (CloneNotSupportedException ex) 1447 { 1448 //We'll never get here, but just in case... 1449 throw new RuntimeException(ex); 1450 } 1451 } 1452 1453 /** Returns <i>true</i> if {@code o} is equal to this angle. */ 1454 @Override 1455 public boolean equals(Object o) 1456 { 1457 //Quick exit if o is this 1458 if (o == this) 1459 return true; 1460 1461 //Quick exit if o is null 1462 if (o == null) 1463 return false; 1464 1465 //Quick exit if classes are different 1466 if (!o.getClass().equals(this.getClass())) 1467 return false; 1468 1469 Angle other = (Angle)o; 1470 1471 //Treat two infinite values of same sign as equal, 1472 //regardless of actual BigDecimal values 1473 if (isInfinite() && other.isInfinite()) 1474 return value.signum() == other.value.signum(); 1475 1476 //Ignore stored units; equality is based purely on magnitude in std units 1477 return compareTo(other) == 0; 1478 } 1479 1480 /** Returns a hash code value for this angle. */ 1481 @Override 1482 public int hashCode() 1483 { 1484 if (isInfinite()) 1485 return value.signum() > 0 ? "+infinity".hashCode() : "-infinity".hashCode(); 1486 1487 String crude = value.toPlainString() + units.getSymbol(); 1488 return crude.hashCode(); 1489 } 1490 1491 /** Compares this angle with the {@code otherAngle} for order. */ 1492 public int compareTo(Angle otherAngle) 1493 { 1494 //Treat two infinite values of same sign as equal, 1495 //regardless of actual BigDecimal values 1496 if (isInfinite() && otherAngle.isInfinite()) 1497 return value.signum() - otherAngle.value.signum(); 1498 1499 //Avoid doing two unit conversions 1500 return getValue().compareTo(otherAngle.toUnits(units)); 1501 } 1502 1503 /* 1504 //This is here for quick testing. 1505 public static void main(String[] args) 1506 { 1507 double[] angles = {0.0, 60.0, 120.0, 180.0, 300.0, 360.0, 720.0, 765.0, 900.0, 1508 -60.0,-120.0,-180.0,-300.0,-360.0,-720.0,-765.0,-900.0}; 1509 1510 Angle angle = new Angle(); 1511 for (double a : angles) 1512 { 1513 angle.setValue(a); 1514 System.out.println("ANGLE = " + angle); 1515 System.out.println(" positive = " + angle.isPositive()); 1516 System.out.println(" negative = " + angle.isNegative()); 1517 System.out.println(" clockwise = " + angle.isClockwise()); 1518 System.out.println(" rotations = " + angle.getFullCircles()); 1519 System.out.println(" negated = " + angle.negate()); 1520 angle.setValue(a); 1521 System.out.println(" normalized = " + angle.normalize()); 1522 angle.setValue(a); 1523 System.out.println(" reversed = " + angle.reverseDirection()); 1524 angle.setValue(a); 1525 System.out.println(" posNormal = " + angle.convertToPositiveNormal()); 1526 angle.setValue(a); 1527 System.out.println(" negNormal = " + angle.convertToNegativeNormal()); 1528 angle.setValue(a); 1529 System.out.println(" minNormal = " + angle.convertToMinAbsValueNormal()); 1530 angle.setValue(a); 1531 1532 System.out.println(" CONVERSIONS:"); 1533 for (ArcUnits unit : ArcUnits.values()) 1534 System.out.println(" to"+unit.name()+ " = " + angle.toUnits(unit)); 1535 System.out.println(" toDMS = "+angle.toStringDms()); 1536 System.out.println(" toHMS = "+angle.toStringHms()); 1537 1538 System.out.println(" TRIGONOMETRY:"); 1539 System.out.println(" sine = "+angle.sine()); 1540 System.out.println(" cosine = "+angle.cosine()); 1541 System.out.println(" tangent = "+angle.tangent()); 1542 System.out.println(" cosecant = "+angle.cosecant()); 1543 System.out.println(" secant = "+angle.secant()); 1544 System.out.println(" cotangent = "+angle.cotangent()); 1545 1546 System.out.println(); 1547 } 1548 } 1549 */ 1550 /* 1551 public static void main(String[] args) throws Exception 1552 { 1553 JaxbUtility jaxb = JaxbUtility.getSharedInstance(); 1554 jaxb.setLookForDefaultSchema(false); 1555 1556 for (String arg : args) 1557 { 1558 System.out.println("INPUT: " + arg); 1559 1560 Angle a = Angle.parse(arg); 1561 1562 System.out.print("OUTPUT: " + a.toString()); 1563 System.out.print(", " + a.toStringHms()); 1564 System.out.print(", " + a.toStringDms()); 1565 System.out.println(); 1566 1567 System.out.println(); 1568 } 1569 } 1570 */ 1571 /* 1572 public static void main(String args[]) 1573 { 1574 Angle a1 = new Angle(1.2, ArcUnits.RADIAN); 1575 System.out.println("a1 = " + a1); 1576 for (ArcUnits units : ArcUnits.values()) 1577 { 1578 a1.convertTo(units); 1579 System.out.println("a1 = " + a1); 1580 } 1581 1582 System.out.println(); 1583 1584 Angle a2 = new Angle(123.654321, ArcUnits.DEGREE); 1585 System.out.println("a2 = " + a2); 1586 for (ArcUnits units : ArcUnits.values()) 1587 { 1588 a2.convertTo(units); 1589 System.out.println("a2 = " + a2); 1590 } 1591 1592 System.out.println(); 1593 1594 Angle a3 = new Angle(100.0, ArcUnits.SECOND); 1595 System.out.println("a3 = " + a3); 1596 for (ArcUnits units : ArcUnits.values()) 1597 { 1598 a3.convertTo(units); 1599 System.out.println("a3 = " + a3); 1600 } 1601 1602 System.out.println(); 1603 1604 Angle a4 = new Angle(100.0, ArcUnits.SECOND); 1605 System.out.println("a4 = " + a4); 1606 for (ArcUnits units : ArcUnits.values()) 1607 { 1608 System.out.println("a4 = " + a4.toUnits(units)); 1609 } 1610 } 1611 */ 1612 /* 1613 public static void main(String args[]) 1614 { 1615 //Deal w/ proper display when rounding HMS/DMS (ie, no 60 mins or secs) 1616 String[] hmsText = 1617 { 1618 "0h 0m 59.95s", "0h 59m 59.95s", 1619 "-59.95s", "-59m 59.95s" 1620 }; 1621 1622 Angle a; 1623 1624 for (int t=0; t < hmsText.length; t++) 1625 { 1626 a = Angle.parse(hmsText[t]); 1627 System.out.print("text = " + hmsText[t]); 1628 System.out.print(", ...Hms() = " + a.toStringHms()); 1629 System.out.print(", ...Hms(1,1) = " + a.toStringHms(1,1)); 1630 System.out.print(", toString() = " + a.toString()); 1631 System.out.println(); 1632 } 1633 1634 System.out.println(); 1635 1636 String[] dmsText = 1637 { 1638 "0d 0' 59.95\"", "0d 59' 59.95\"", 1639 "-59.95\"", "-59' 59.95\"" 1640 }; 1641 1642 for (int t=0; t < dmsText.length; t++) 1643 { 1644 a = Angle.parse(dmsText[t]); 1645 System.out.print("text = " + dmsText[t]); 1646 System.out.print(", ...Dms() = " + a.toStringDms()); 1647 System.out.print(", ...Dms(1,1) = " + a.toStringDms(1,1)); 1648 System.out.print(", toString() = " + a.toString()); 1649 System.out.println(); 1650 } 1651 } 1652 */ 1653 }