001 package edu.nrao.sss.measure; 002 003 import java.math.BigDecimal; 004 import java.math.RoundingMode; 005 006 import static edu.nrao.sss.math.MathUtil.MC_FINAL_CALC; 007 import static edu.nrao.sss.math.MathUtil.MC_INTERM_CALCS; 008 009 import edu.nrao.sss.util.EnumerationUtility; 010 import edu.nrao.sss.util.Symbolic; 011 012 /** 013 * Units of arc. 014 * <p> 015 * <b><u>Table of Units</u></b></p> 016 * <p style="margin-left:1.5em"> 017 * <table border="1" cellspacing="0"> 018 * <tr BGCOLOR="#EEEEFF"><th>Element</th> <th>Name<sup><i>1</i></sup></th> 019 * <th>Symbol<sup><i>1</i></sup></th> <th>HTML Symbol<sup><i>2</i></sup></th> 020 * <th>Units per Circle</th></tr> 021 * <tr><td>DEGREE</td><td>DEGREE</td> 022 * <td align="center">d <sup><i>3</i></sup></td> 023 * <td align="center">&#x00B0; (°)</td> 024 * <td align="right">360.0</td></tr> 025 * <tr><td>RADIAN</td><td>RADIAN</td> 026 * <td align="center">rad</td> 027 * <td align="center">rad</td> 028 * <td align="right">2π</td></tr> 029 * <tr><td>PERCENT</td><td>PERCENT</td> 030 * <td align="center">%</td> 031 * <td align="center">&#x0025; (%)</td> 032 * <td align="right">100.0</td></tr> 033 * <tr><td>HOUR</td><td>HOUR</td> 034 * <td align="center">h</td> 035 * <td align="center">h</td> 036 * <td align="right">24.0</td></tr> 037 * <tr><td>MINUTE</td><td>MINUTE</td> 038 * <td align="center">m</td> 039 * <td align="center">m</td> 040 * <td align="right">1,440.0</td></tr> 041 * <tr><td>SECOND</td><td>SECOND</td> 042 * <td align="center">s</td> 043 * <td align="center">s</td> 044 * <td align="right">86,400.0</td></tr> 045 * <tr><td>ARC_MINUTE</td><td>ARC_MINUTE</td> 046 * <td align="center">'</td> 047 * <td align="center">&#x0027; (')</td> 048 * <td align="right">21,600.0</td></tr> 049 * <tr><td>ARC_SECOND</td><td>ARC_SECOND</td> 050 * <td align="center">"</td> 051 * <td align="center">&quot; (")</td> 052 * <td align="right">1,296,000.0</td></tr> 053 * <tr><td>MILLI_ARC_SECOND</td><td>MILLI_ARC_SECOND</td> 054 * <td align="center">mas</td> 055 * <td align="center">mas</td> 056 * <td align="right">1,296,000,000.0</td></tr> 057 * </table> 058 * <sup><b>1</b></sup>The names in this column may be sent to 059 * {@link #fromString(String)}. Note that the names are 060 * not case sensitive.<br/> 061 * <sup><b>2</b></sup>The symbols for arc min and arc sec are sometimes 062 * troublesome in HTML (and in other arenas, for that matter). These 063 * particular symbols are placed in ampersand form.<br/> 064 * <sup><b>3</b></sup>The original plan was to use '°' as the symbol, 065 * but 'd' is an easier identifier for users to furnish.</p> 066 * <p> 067 * <b><u>Table of Conversion Factors</u><sup>4</sup></b></p> 068 * <p style="margin-left:1.5em"> 069 * <table border="1" cellspacing="0"> 070 * <tr BGCOLOR="#EEEEFF"><th>Element</th><th>d</th><th>rad</th><th>%</th><th>h</th><th>m</th><th>s</th><th>'</th><th>"</th><th>mas</th></tr> 071 * <tr><td>DEGREE</td><td align="right">1.0</td><td align="right">0.01745329251994329444444444444444444</td><td align="right">0.2777777777777777777777777777777778</td><td align="right">0.06666666666666666666666666666666667</td><td align="right">4.0</td><td align="right">240.0</td><td align="right">60.0</td><td align="right">3600.0</td><td align="right">3600000.0</td></tr> 072 * <tr><td>RADIAN</td><td align="right">57.29577951308232522583526558752712</td><td align="right">1.0</td><td align="right">15.91549430918953478495424044097976</td><td align="right">3.819718634205488348389017705835142</td><td align="right">229.1831180523293009033410623501085</td><td align="right">13750.98708313975805420046374100651</td><td align="right">3437.746770784939513550115935251627</td><td align="right">206264.8062470963708130069561150976</td><td align="right">206264806.2470963708130069561150976</td></tr> 073 * <tr><td>PERCENT</td><td align="right">3.6</td><td align="right">0.06283185307179586</td><td align="right">1.0</td><td align="right">0.24</td><td align="right">14.4</td><td align="right">864.0</td><td align="right">216.0</td><td align="right">12960.0</td><td align="right">12960000.0</td></tr> 074 * <tr><td>HOUR</td><td align="right">15.0</td><td align="right">0.2617993877991494166666666666666667</td><td align="right">4.166666666666666666666666666666667</td><td align="right">1.0</td><td align="right">60.0</td><td align="right">3600.0</td><td align="right">900.0</td><td align="right">54000.0</td><td align="right">54000000.0</td></tr> 075 * <tr><td>MINUTE</td><td align="right">0.25</td><td align="right">0.004363323129985823611111111111111111</td><td align="right">0.06944444444444444444444444444444444</td><td align="right">0.01666666666666666666666666666666667</td><td align="right">1.0</td><td align="right">60.0</td><td align="right">15.0</td><td align="right">900.0</td><td align="right">900000.0</td></tr> 076 * <tr><td>SECOND</td><td align="right">0.004166666666666666666666666666666667</td><td align="right">0.00007272205216643039351851851851851852</td><td align="right">0.001157407407407407407407407407407407</td><td align="right">0.0002777777777777777777777777777777778</td><td align="right">0.01666666666666666666666666666666667</td><td align="right">1.0</td><td align="right">0.25</td><td align="right">15.0</td><td align="right">15000.0</td></tr> 077 * <tr><td>ARC_MINUTE</td><td align="right">0.01666666666666666666666666666666667</td><td align="right">0.0002908882086657215740740740740740741</td><td align="right">0.004629629629629629629629629629629630</td><td align="right">0.001111111111111111111111111111111111</td><td align="right">0.06666666666666666666666666666666667</td><td align="right">4.0</td><td align="right">1.0</td><td align="right">60.0</td><td align="right">60000.0</td></tr> 078 * <tr><td>ARC_SECOND</td><td align="right">0.0002777777777777777777777777777777778</td><td align="right">0.000004848136811095359567901234567901235</td><td align="right">0.00007716049382716049382716049382716049</td><td align="right">0.00001851851851851851851851851851851852</td><td align="right">0.001111111111111111111111111111111111</td><td align="right">0.06666666666666666666666666666666667</td><td align="right">0.01666666666666666666666666666666667</td><td align="right">1.0</td><td align="right">1000.0</td></tr> 079 * <tr><td>MILLI_ARC_SECOND</td><td align="right">2.777777777777777777777777777777778E-7</td><td align="right">4.848136811095359567901234567901235E-9</td><td align="right">7.716049382716049382716049382716049E-8</td><td align="right">1.851851851851851851851851851851852E-8</td><td align="right">0.000001111111111111111111111111111111111</td><td align="right">0.00006666666666666666666666666666666667</td><td align="right">0.00001666666666666666666666666666666667</td><td align="right">0.001</td><td align="right">1.0</td></tr> 080 * </table> 081 * <sup><b>4</b></sup>This table was generated from the conversion logic of this class.<br/> 082 * </p> 083 * <p> 084 * <b>Version Info:</b> 085 * <table style="margin-left:2em"> 086 * <tr><td>$Revision: 1586 $</td></tr> 087 * <tr><td>$Date: 2008-10-01 10:38:49 -0600 (Wed, 01 Oct 2008) $</td></tr> 088 * <tr><td>$Author: dharland $</td></tr> 089 * </table></p> 090 * 091 * @author David M. Harland 092 * @since 2006-04-12 093 */ 094 public enum ArcUnits 095 implements Symbolic 096 { 097 /** 098 * A degree. There are 360 degrees in a full circle. 099 */ 100 DEGREE("360.0", "d", "°"), 101 102 /** 103 * A radian. There are 2π radians in a full circle. 104 */ 105 RADIAN("6.2831853071795864769252867665590057683943387987502", "rad", "rad"), 106 107 /** 108 * Percent of a full circle. A full circle is 100%. 109 */ 110 PERCENT("100.0", "%", "%"), 111 112 /** 113 * An hour angle. There are 24 hours in a full circle. 114 */ 115 HOUR("24.0", "h", "h"), 116 117 /** 118 * One sixtieth of an hour angle. 119 * Contrast this unit of measure with {@link #ARC_MINUTE}. 120 */ 121 MINUTE("1440.0", "m", "m"), 122 123 /** 124 * One sixtieth of a minute. 125 * Contrast this unit of measure with {@link #ARC_SECOND}. 126 */ 127 SECOND("86400.0", "s", "s"), 128 129 /** 130 * One sixtieth of a degree. 131 * Contrast this unit of measure with {@link #MINUTE}. 132 */ 133 ARC_MINUTE("21600.0", "'", "'"), 134 135 /** 136 * One sixtieth of an arc minute. 137 * Contrast this unit of measure with {@link #SECOND}. 138 */ 139 ARC_SECOND("1296000.0", "\"", """), 140 141 /** 142 * One thousandth of an arc second. 143 */ 144 MILLI_ARC_SECOND("1296000000.0", "mas", "mas"); 145 146 private static final int PRECISION = MC_FINAL_CALC.getPrecision(); 147 148 private BigDecimal completeCircle; 149 private String symbol; 150 private String htmlSymbol; 151 152 private ArcUnits(String completeCircle, String symbol, String htmlSymbol) 153 { 154 this.symbol = symbol; 155 this.htmlSymbol = htmlSymbol; 156 this.completeCircle = new BigDecimal(completeCircle); 157 158 //It turns out that the PRECISION constant is zero when we're 159 //in this constructor, so we make the call directly. 160 //(See http://forums.java.net/jive/thread.jspa?threadID=40585&tstart=0) 161 this.completeCircle = 162 this.completeCircle.setScale(MC_FINAL_CALC.getPrecision(), 163 RoundingMode.HALF_UP); 164 } 165 166 /** Returns <i>false</i> -- these symbols are <i>not</i> case sensitive. */ 167 public boolean symbolsAreCaseSensitive() { return false; } 168 169 /** 170 * Returns the symbol for this unit. 171 * For example, the symbol for {@code DEGREE} is <i>d</i>. 172 * 173 * @return the symbol for this unit. 174 */ 175 public String getSymbol() 176 { 177 return symbol; 178 } 179 180 /** 181 * Returns a symbol for this unit that may be more appropriate 182 * for use in HTML than the main symbol. This is particularly 183 * true for {@link #ARC_MINUTE} and {@link #ARC_SECOND}. For 184 * most units, the HTML symbol is the same as the main symbol. 185 * 186 * @return a symbol for this unit that is HTML-friendly. 187 */ 188 public String getHtmlSymbol() 189 { 190 return htmlSymbol; 191 } 192 193 /** 194 * Returns the number of these units in a full circle. 195 * 196 * @return the number of these units in a full circle. 197 */ 198 public BigDecimal toFullCircle() 199 { 200 return completeCircle; 201 } 202 203 /** 204 * Returns the number of these units in one half of a circle. 205 * This is useful for calculating supplementarty angles. 206 * 207 * @return the number of these units in one half of a circle. 208 */ 209 public BigDecimal toHalfCircle() 210 { 211 final BigDecimal oneHalf = new BigDecimal("0.5"); 212 return completeCircle.multiply(oneHalf); 213 } 214 215 /** 216 * Returns the number of these units in one quarter a circle. 217 * This is useful for calculating complementarty angles. 218 * 219 * @return the number of these units in one quarter a circle. 220 */ 221 public BigDecimal toQuarterCircle() 222 { 223 final BigDecimal oneQtr = new BigDecimal("0.25"); 224 return completeCircle.multiply(oneQtr); 225 } 226 227 /** 228 * Returns a factor for converting from this unit to {@code otherUnits}. 229 * 230 * @param otherUnits the unit to which conversion is desired. 231 * 232 * @return a factor for converting from this unit to {@code otherUnits}. 233 */ 234 public BigDecimal toUnits(ArcUnits otherUnits) 235 { 236 return convertTo(otherUnits, BigDecimal.ONE); 237 } 238 239 /** 240 * Non-public method for use by this and other classes in pkg. 241 * Tries to keep all calcs in BigDecimal form, but if that fails, 242 * reverts to java primitives. 243 */ 244 BigDecimal convertTo(ArcUnits otherUnits, BigDecimal value) 245 { 246 BigDecimal answer = value; 247 248 //Convert only if we have different units 249 if (!otherUnits.equals(this)) 250 { 251 try //to use BigDecimal, for better accuracy, but... 252 { 253 if (value.scale() < PRECISION) 254 value = value.setScale(PRECISION); 255 256 BigDecimal ratio = 257 otherUnits.completeCircle.divide(this.completeCircle, 258 MC_INTERM_CALCS); 259 answer = value.multiply(ratio, MC_FINAL_CALC); 260 } 261 catch (ArithmeticException ex) 262 { 263 //...if it fails, use java primitives 264 double ratio = otherUnits.completeCircle.doubleValue() / 265 this.completeCircle.doubleValue(); 266 267 answer = BigDecimal.valueOf(value.doubleValue() * ratio); 268 } 269 } 270 271 return answer.round(MC_FINAL_CALC); 272 } 273 274 /** 275 * Converts {@code value}, expressed in this unit, to degrees-minutes-seconds. 276 * 277 * @param value the quantity, in this unit, to be converted to DMS. 278 * 279 * @return an array of size three in this order: 280 * <ol start="0"> 281 * <li>An integral number of degrees.</li> 282 * <li>An integral number of arc minutes.</li> 283 * <li>A real number of arc seconds.</li> 284 * </ol> 285 */ 286 public Number[] convertToDms(BigDecimal value) 287 { 288 Number[] arc = new Number[3]; 289 290 //Calculate total seconds of arc 291 BigDecimal arcSeconds = convertTo(ARC_SECOND, value); 292 293 //Work with positive numbers until last step 294 boolean negate = false; 295 if (arcSeconds.signum() < 0) 296 { 297 negate = true; 298 arcSeconds = arcSeconds.negate(); 299 } 300 301 //Get the normalized arc seconds (eg, range [0.0-60.0)) 302 BigDecimal factor = ARC_MINUTE.toUnits(ARC_SECOND); 303 BigDecimal[] intAndRem = arcSeconds.divideAndRemainder(factor, 304 MC_FINAL_CALC); 305 arc[2] = intAndRem[1]; 306 307 //Get the normalized integral arc minutes (eg, range [0-59]) 308 factor = DEGREE.toUnits(ARC_MINUTE); 309 intAndRem = intAndRem[0].divideAndRemainder(factor, MC_FINAL_CALC); 310 arc[1] = intAndRem[1]; 311 312 //Get the integral arc degrees 313 arc[0] = intAndRem[0]; 314 315 //Handle negation 316 if (negate) 317 negateForConvertToXms(arc); 318 319 return arc; 320 } 321 322 /** 323 * Converts {@code value}, expressed in this unit, to hours-minutes-seconds. 324 * 325 * @param value the quantity, in this unit, to be converted to DMS. 326 * 327 * @return an array of size three in this order: 328 * <ol start="0"> 329 * <li>An integral number of degrees.</li> 330 * <li>An integral number of minutes.</li> 331 * <li>A real number of seconds.</li> 332 * </ol> 333 */ 334 public Number[] convertToHms(BigDecimal value) 335 { 336 Number[] arc = new Number[3]; 337 338 //Calculate total angle-seconds 339 BigDecimal seconds = convertTo(SECOND, value); 340 341 //Work with positive numbers until last step 342 boolean negate = false; 343 if (seconds.signum() < 0) 344 { 345 negate = true; 346 seconds = seconds.negate(); 347 } 348 349 //Get the normalized angle-seconds (eg, range [0.0-60.0)) 350 BigDecimal factor = MINUTE.toUnits(SECOND); 351 BigDecimal[] intAndRem = seconds.divideAndRemainder(factor, 352 MC_FINAL_CALC); 353 arc[2] = intAndRem[1]; 354 355 //Get the normalized integral angle-minutes (eg, range [0-59]) 356 factor = HOUR.toUnits(MINUTE); 357 intAndRem = intAndRem[0].divideAndRemainder(factor, MC_FINAL_CALC); 358 arc[1] = intAndRem[1]; 359 360 //Get the integral angle-hours 361 arc[0] = intAndRem[0]; 362 363 //Handle negation 364 if (negate) 365 negateForConvertToXms(arc); 366 367 return arc; 368 } 369 370 /** Handles negation of H/D, M, S for convertToH/Dms methods. */ 371 private void negateForConvertToXms(Number[] arc) 372 { 373 //Only one of H/D, M, S can be negative 374 if (arc[0].intValue() > 0) { 375 arc[0] = new Integer(-arc[0].intValue()); 376 } 377 else if (arc[1].intValue() > 0) { 378 arc[1] = new Integer(-arc[1].intValue()); 379 } 380 else { 381 arc[2] = new BigDecimal(arc[2].toString()).negate(); 382 } 383 } 384 385 /** 386 * Converts from degrees-minutes-seconds to {@code otherUnits}. 387 * <p> 388 * At most, only one of {@code degrees}, {@code minutes}, or {@code seconds} 389 * may be negative. Further more, if one of these is negative, the higher 390 * units must all be zero. E.g., in order for {@code seconds} to be negative 391 * both {@code degrees} and {@code minutes} must be zero. If these conditions 392 * are not met, an {@code IllegalArgumentException} is thrown.</p> 393 * 394 * @param otherUnits the units in which the value is returned. 395 * @param degrees the whole number of degrees of arc. 396 * @param minutes the whole number of minutes of arc. The normal 397 * range for this value is [0-59]. 398 * @param seconds the whole and fraction number of seconds of arc. 399 * The normal range for this value is [0.0-60.0). 400 * @return the value of {@code degrees, minutes, seconds} converted 401 * to {@code otherUnits}. 402 * 403 * @throws IllegalArgumentException if the rules about negative parameters 404 * described above are violated. 405 */ 406 public static BigDecimal convertDmsTo(ArcUnits otherUnits, int degrees, 407 int minutes, BigDecimal seconds) 408 { 409 //Determine if negation is needed and do some parameter validation 410 boolean negate = 411 convertXmsToUnitsNeedsNegation(degrees, minutes, seconds, "degrees"); 412 413 //Work with positive numbers until last step 414 if (negate) 415 { 416 if (seconds.signum() < 0) seconds = seconds.negate(); 417 else if (minutes < 0) minutes = -minutes; 418 else if (degrees < 0) degrees = -degrees; 419 } 420 421 BigDecimal secondsM = ARC_MINUTE.convertTo(ARC_SECOND, new BigDecimal(minutes)); 422 BigDecimal secondsD = DEGREE.convertTo(ARC_SECOND, new BigDecimal(degrees)); 423 424 BigDecimal totalSeconds = secondsD.add(secondsM).add(seconds); 425 426 BigDecimal result = ARC_SECOND.convertTo(otherUnits, totalSeconds); 427 428 if (negate) 429 result = result.negate(); 430 431 return result; 432 } 433 434 /** 435 * Converts from hours-minutes-seconds to {@code otherUnits}. 436 * <p> 437 * At most, only one of {@code hours}, {@code minutes}, or {@code seconds} 438 * may be negative. Further more, if one of these is negative, the higher 439 * units must all be zero. E.g., in order for {@code seconds} to be negative 440 * both {@code hours} and {@code minutes} must be zero. If these conditions 441 * are not met, an {@code IllegalArgumentException} is thrown.</p> 442 * 443 * @param otherUnits the units in which the value is returned. 444 * @param hours the whole number of angle hours. 445 * @param minutes the whole number of angle minutes. The normal 446 * range for this value is [0-59]. 447 * @param seconds the whole and fraction number of angle seconds. 448 * The normal range for this value is [0.0-60.0). 449 * @return the value of {@code hours, minutes, seconds} converted 450 * to {@code otherUnits}. 451 * 452 * @throws IllegalArgumentException if the rules about negative parameters 453 * described above are violated. 454 */ 455 public static BigDecimal convertHmsTo(ArcUnits otherUnits, int hours, 456 int minutes, BigDecimal seconds) 457 { 458 //Determine if negation is needed and do some parameter validation 459 boolean negate = 460 convertXmsToUnitsNeedsNegation(hours, minutes, seconds, "hours"); 461 462 //Work with positive numbers until last step 463 if (negate) 464 { 465 if (seconds.signum() < 0) seconds = seconds.negate(); 466 else if (minutes < 0) minutes = -minutes; 467 else if (hours < 0) hours = -hours; 468 } 469 470 BigDecimal secondsM = MINUTE.convertTo(SECOND, new BigDecimal(minutes)); 471 BigDecimal secondsH = HOUR.convertTo(SECOND, new BigDecimal(hours)); 472 473 BigDecimal totalSeconds = secondsH.add(secondsM).add(seconds); 474 475 BigDecimal result = SECOND.convertTo(otherUnits, totalSeconds); 476 477 if (negate) 478 result = result.negate(); 479 480 return result; 481 } 482 483 /** Returns true if x or m or s is negative. Peforms some validation. */ 484 private static boolean convertXmsToUnitsNeedsNegation(int x, int m, BigDecimal s, 485 String type) 486 { 487 boolean negate = false; 488 489 //If seconds are negative, minutes and hours/degrees must be zero 490 if (s.signum() < 0) 491 { 492 if ((x != 0) || (m != 0)) 493 throw new IllegalArgumentException( 494 "If seconds < 0.0, " + type + " and minutes must be 0."); 495 496 negate = true; 497 } 498 //If minutes are negative, hours/degrees must be zero 499 else if (m < 0) 500 { 501 if (x != 0) 502 throw new IllegalArgumentException( 503 "If minutes < 0.0, " + type + " must be 0."); 504 505 negate = true; 506 } 507 else if (x < 0) 508 { 509 negate = true; 510 } 511 512 return negate; 513 } 514 515 /** 516 * Returns a default unit of arc. 517 * @return a default unit of arc. 518 */ 519 public static ArcUnits getDefault() 520 { 521 return DEGREE; 522 } 523 524 /** 525 * Returns a text representation of this enumeration constant. 526 * @return a text representation of this enumeration constant. 527 */ 528 public String toString() 529 { 530 return EnumerationUtility.getSharedInstance().enumToString(this); 531 } 532 533 /** 534 * Returns the arc units represented by {@code text}. 535 * <p> 536 * For details about the transformation, see 537 * {@link EnumerationUtility#enumFromString(Class, String)}.</p> 538 * 539 * @param text a text representation of a unit of arc. 540 * 541 * @return the arc units represented by {@code text}. 542 */ 543 public static ArcUnits fromString(String text) 544 { 545 return EnumerationUtility.getSharedInstance() 546 .enumFromString(ArcUnits.class, text); 547 } 548 549 //Here for testing only; builds HTML table for use in javadoc class comments. 550 /* 551 private static String toHtmlTable() 552 { 553 StringBuilder table = new StringBuilder(); 554 555 //Column headers 556 table.append(" * <table border=\"1\" cellspacing=\"0\">\n"); 557 table.append(" * <tr BGCOLOR=\"#EEEEFF\"><th>Element</th>"); 558 for (ArcUnits u : ArcUnits.values()) 559 table.append("<th>").append(u.getSymbol()).append("</th>"); 560 table.append("</tr>\n"); 561 562 //Data rows 563 for (ArcUnits from : ArcUnits.values()) 564 { 565 table.append(" * <tr><td>").append(from.name()).append("</td>"); 566 567 for (ArcUnits to : ArcUnits.values()) 568 table.append("<td align=\"right\">").append(from.convertTo(to)).append("</td>"); 569 570 table.append("</tr>\n"); 571 } 572 573 //End table 574 table.append(" * </table>"); 575 576 return table.toString(); 577 } 578 579 public static void main(String[] args) 580 { 581 String htmlTable = ArcUnits.toHtmlTable(); 582 583 System.out.println(htmlTable); 584 } 585 */ 586 }