1 // © 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 3 /* 4 ******************************************************************************* 5 * Copyright (C) 1996-2011, International Business Machines Corporation and * 6 * others. All Rights Reserved. * 7 ******************************************************************************* 8 */ 9 10 package com.ibm.icu.impl; 11 12 import java.util.Date; 13 import java.util.TimeZone; 14 15 /** 16 * <code>CalendarAstronomer</code> is a class that can perform the calculations to 17 * determine the positions of the sun and moon, the time of sunrise and 18 * sunset, and other astronomy-related data. The calculations it performs 19 * are in some cases quite complicated, and this utility class saves you 20 * the trouble of worrying about them. 21 * <p> 22 * The measurement of time is a very important part of astronomy. Because 23 * astronomical bodies are constantly in motion, observations are only valid 24 * at a given moment in time. Accordingly, each <code>CalendarAstronomer</code> 25 * object has a <code>time</code> property that determines the date 26 * and time for which its calculations are performed. You can set and 27 * retrieve this property with {@link #setDate setDate}, {@link #getDate getDate} 28 * and related methods. 29 * <p> 30 * Almost all of the calculations performed by this class, or by any 31 * astronomer, are approximations to various degrees of accuracy. The 32 * calculations in this class are mostly modelled after those described 33 * in the book 34 * <a href="http://www.amazon.com/exec/obidos/ISBN=0521356997" target="_top"> 35 * Practical Astronomy With Your Calculator</a>, by Peter J. 36 * Duffett-Smith, Cambridge University Press, 1990. This is an excellent 37 * book, and if you want a greater understanding of how these calculations 38 * are performed it a very good, readable starting point. 39 * <p> 40 * <strong>WARNING:</strong> This class is very early in its development, and 41 * it is highly likely that its API will change to some degree in the future. 42 * At the moment, it basically does just enough to support {@link com.ibm.icu.util.IslamicCalendar} 43 * and {@link com.ibm.icu.util.ChineseCalendar}. 44 * 45 * @author Laura Werner 46 * @author Alan Liu 47 * @internal 48 */ 49 public class CalendarAstronomer { 50 51 //------------------------------------------------------------------------- 52 // Astronomical constants 53 //------------------------------------------------------------------------- 54 55 /** 56 * The number of standard hours in one sidereal day. 57 * Approximately 24.93. 58 * @internal 59 */ 60 public static final double SIDEREAL_DAY = 23.93446960027; 61 62 /** 63 * The number of sidereal hours in one mean solar day. 64 * Approximately 24.07. 65 * @internal 66 */ 67 public static final double SOLAR_DAY = 24.065709816; 68 69 /** 70 * The average number of solar days from one new moon to the next. This is the time 71 * it takes for the moon to return the same ecliptic longitude as the sun. 72 * It is longer than the sidereal month because the sun's longitude increases 73 * during the year due to the revolution of the earth around the sun. 74 * Approximately 29.53. 75 * 76 * @see #SIDEREAL_MONTH 77 * @internal 78 */ 79 public static final double SYNODIC_MONTH = 29.530588853; 80 81 /** 82 * The average number of days it takes 83 * for the moon to return to the same ecliptic longitude relative to the 84 * stellar background. This is referred to as the sidereal month. 85 * It is shorter than the synodic month due to 86 * the revolution of the earth around the sun. 87 * Approximately 27.32. 88 * 89 * @see #SYNODIC_MONTH 90 * @internal 91 */ 92 public static final double SIDEREAL_MONTH = 27.32166; 93 94 /** 95 * The average number number of days between successive vernal equinoxes. 96 * Due to the precession of the earth's 97 * axis, this is not precisely the same as the sidereal year. 98 * Approximately 365.24 99 * 100 * @see #SIDEREAL_YEAR 101 * @internal 102 */ 103 public static final double TROPICAL_YEAR = 365.242191; 104 105 /** 106 * The average number of days it takes 107 * for the sun to return to the same position against the fixed stellar 108 * background. This is the duration of one orbit of the earth about the sun 109 * as it would appear to an outside observer. 110 * Due to the precession of the earth's 111 * axis, this is not precisely the same as the tropical year. 112 * Approximately 365.25. 113 * 114 * @see #TROPICAL_YEAR 115 * @internal 116 */ 117 public static final double SIDEREAL_YEAR = 365.25636; 118 119 //------------------------------------------------------------------------- 120 // Time-related constants 121 //------------------------------------------------------------------------- 122 123 /** 124 * The number of milliseconds in one second. 125 * @internal 126 */ 127 public static final int SECOND_MS = 1000; 128 129 /** 130 * The number of milliseconds in one minute. 131 * @internal 132 */ 133 public static final int MINUTE_MS = 60*SECOND_MS; 134 135 /** 136 * The number of milliseconds in one hour. 137 * @internal 138 */ 139 public static final int HOUR_MS = 60*MINUTE_MS; 140 141 /** 142 * The number of milliseconds in one day. 143 * @internal 144 */ 145 public static final long DAY_MS = 24*HOUR_MS; 146 147 /** 148 * The start of the julian day numbering scheme used by astronomers, which 149 * is 1/1/4713 BC (Julian), 12:00 GMT. This is given as the number of milliseconds 150 * since 1/1/1970 AD (Gregorian), a negative number. 151 * Note that julian day numbers and 152 * the Julian calendar are <em>not</em> the same thing. Also note that 153 * julian days start at <em>noon</em>, not midnight. 154 * @internal 155 */ 156 public static final long JULIAN_EPOCH_MS = -210866760000000L; 157 158 // static { 159 // Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT")); 160 // cal.clear(); 161 // cal.set(cal.ERA, 0); 162 // cal.set(cal.YEAR, 4713); 163 // cal.set(cal.MONTH, cal.JANUARY); 164 // cal.set(cal.DATE, 1); 165 // cal.set(cal.HOUR_OF_DAY, 12); 166 // System.out.println("1.5 Jan 4713 BC = " + cal.getTime().getTime()); 167 168 // cal.clear(); 169 // cal.set(cal.YEAR, 2000); 170 // cal.set(cal.MONTH, cal.JANUARY); 171 // cal.set(cal.DATE, 1); 172 // cal.add(cal.DATE, -1); 173 // System.out.println("0.0 Jan 2000 = " + cal.getTime().getTime()); 174 // } 175 176 /** 177 * Milliseconds value for 0.0 January 2000 AD. 178 */ 179 static final long EPOCH_2000_MS = 946598400000L; 180 181 //------------------------------------------------------------------------- 182 // Assorted private data used for conversions 183 //------------------------------------------------------------------------- 184 185 // My own copies of these so compilers are more likely to optimize them away 186 static private final double PI = 3.14159265358979323846; 187 static private final double PI2 = PI * 2.0; 188 189 static private final double RAD_HOUR = 12 / PI; // radians -> hours 190 static private final double DEG_RAD = PI / 180; // degrees -> radians 191 static private final double RAD_DEG = 180 / PI; // radians -> degrees 192 193 //------------------------------------------------------------------------- 194 // Constructors 195 //------------------------------------------------------------------------- 196 197 /** 198 * Construct a new <code>CalendarAstronomer</code> object that is initialized to 199 * the current date and time. 200 * @internal 201 */ CalendarAstronomer()202 public CalendarAstronomer() { 203 this(System.currentTimeMillis()); 204 } 205 206 /** 207 * Construct a new <code>CalendarAstronomer</code> object that is initialized to 208 * the specified date and time. 209 * @internal 210 */ CalendarAstronomer(Date d)211 public CalendarAstronomer(Date d) { 212 this(d.getTime()); 213 } 214 215 /** 216 * Construct a new <code>CalendarAstronomer</code> object that is initialized to 217 * the specified time. The time is expressed as a number of milliseconds since 218 * January 1, 1970 AD (Gregorian). 219 * 220 * @see java.util.Date#getTime() 221 * @internal 222 */ CalendarAstronomer(long aTime)223 public CalendarAstronomer(long aTime) { 224 time = aTime; 225 } 226 227 /** 228 * Construct a new <code>CalendarAstronomer</code> object with the given 229 * latitude and longitude. The object's time is set to the current 230 * date and time. 231 * <p> 232 * @param longitude The desired longitude, in <em>degrees</em> east of 233 * the Greenwich meridian. 234 * 235 * @param latitude The desired latitude, in <em>degrees</em>. Positive 236 * values signify North, negative South. 237 * 238 * @see java.util.Date#getTime() 239 * @internal 240 */ CalendarAstronomer(double longitude, double latitude)241 public CalendarAstronomer(double longitude, double latitude) { 242 this(); 243 fLongitude = normPI(longitude * DEG_RAD); 244 fLatitude = normPI(latitude * DEG_RAD); 245 fGmtOffset = (long)(fLongitude * 24 * HOUR_MS / PI2); 246 } 247 248 249 //------------------------------------------------------------------------- 250 // Time and date getters and setters 251 //------------------------------------------------------------------------- 252 253 /** 254 * Set the current date and time of this <code>CalendarAstronomer</code> object. All 255 * astronomical calculations are performed based on this time setting. 256 * 257 * @param aTime the date and time, expressed as the number of milliseconds since 258 * 1/1/1970 0:00 GMT (Gregorian). 259 * 260 * @see #setDate 261 * @see #getTime 262 * @internal 263 */ setTime(long aTime)264 public void setTime(long aTime) { 265 time = aTime; 266 clearCache(); 267 } 268 269 /** 270 * Set the current date and time of this <code>CalendarAstronomer</code> object. All 271 * astronomical calculations are performed based on this time setting. 272 * 273 * @param date the time and date, expressed as a <code>Date</code> object. 274 * 275 * @see #setTime 276 * @see #getDate 277 * @internal 278 */ setDate(Date date)279 public void setDate(Date date) { 280 setTime(date.getTime()); 281 } 282 283 /** 284 * Set the current date and time of this <code>CalendarAstronomer</code> object. All 285 * astronomical calculations are performed based on this time setting. 286 * 287 * @param jdn the desired time, expressed as a "julian day number", 288 * which is the number of elapsed days since 289 * 1/1/4713 BC (Julian), 12:00 GMT. Note that julian day 290 * numbers start at <em>noon</em>. To get the jdn for 291 * the corresponding midnight, subtract 0.5. 292 * 293 * @see #getJulianDay 294 * @see #JULIAN_EPOCH_MS 295 * @internal 296 */ setJulianDay(double jdn)297 public void setJulianDay(double jdn) { 298 time = (long)(jdn * DAY_MS) + JULIAN_EPOCH_MS; 299 clearCache(); 300 julianDay = jdn; 301 } 302 303 /** 304 * Get the current time of this <code>CalendarAstronomer</code> object, 305 * represented as the number of milliseconds since 306 * 1/1/1970 AD 0:00 GMT (Gregorian). 307 * 308 * @see #setTime 309 * @see #getDate 310 * @internal 311 */ getTime()312 public long getTime() { 313 return time; 314 } 315 316 /** 317 * Get the current time of this <code>CalendarAstronomer</code> object, 318 * represented as a <code>Date</code> object. 319 * 320 * @see #setDate 321 * @see #getTime 322 * @internal 323 */ getDate()324 public Date getDate() { 325 return new Date(time); 326 } 327 328 /** 329 * Get the current time of this <code>CalendarAstronomer</code> object, 330 * expressed as a "julian day number", which is the number of elapsed 331 * days since 1/1/4713 BC (Julian), 12:00 GMT. 332 * 333 * @see #setJulianDay 334 * @see #JULIAN_EPOCH_MS 335 * @internal 336 */ getJulianDay()337 public double getJulianDay() { 338 if (julianDay == INVALID) { 339 julianDay = (double)(time - JULIAN_EPOCH_MS) / (double)DAY_MS; 340 } 341 return julianDay; 342 } 343 344 /** 345 * Return this object's time expressed in julian centuries: 346 * the number of centuries after 1/1/1900 AD, 12:00 GMT 347 * 348 * @see #getJulianDay 349 * @internal 350 */ getJulianCentury()351 public double getJulianCentury() { 352 if (julianCentury == INVALID) { 353 julianCentury = (getJulianDay() - 2415020.0) / 36525; 354 } 355 return julianCentury; 356 } 357 358 /** 359 * Returns the current Greenwich sidereal time, measured in hours 360 * @internal 361 */ getGreenwichSidereal()362 public double getGreenwichSidereal() { 363 if (siderealTime == INVALID) { 364 // See page 86 of "Practial Astronomy with your Calculator", 365 // by Peter Duffet-Smith, for details on the algorithm. 366 367 double UT = normalize((double)time/HOUR_MS, 24); 368 369 siderealTime = normalize(getSiderealOffset() + UT*1.002737909, 24); 370 } 371 return siderealTime; 372 } 373 getSiderealOffset()374 private double getSiderealOffset() { 375 if (siderealT0 == INVALID) { 376 double JD = Math.floor(getJulianDay() - 0.5) + 0.5; 377 double S = JD - 2451545.0; 378 double T = S / 36525.0; 379 siderealT0 = normalize(6.697374558 + 2400.051336*T + 0.000025862*T*T, 24); 380 } 381 return siderealT0; 382 } 383 384 /** 385 * Returns the current local sidereal time, measured in hours 386 * @internal 387 */ getLocalSidereal()388 public double getLocalSidereal() { 389 return normalize(getGreenwichSidereal() + (double)fGmtOffset/HOUR_MS, 24); 390 } 391 392 /** 393 * Converts local sidereal time to Universal Time. 394 * 395 * @param lst The Local Sidereal Time, in hours since sidereal midnight 396 * on this object's current date. 397 * 398 * @return The corresponding Universal Time, in milliseconds since 399 * 1 Jan 1970, GMT. 400 */ lstToUT(double lst)401 private long lstToUT(double lst) { 402 // Convert to local mean time 403 double lt = normalize((lst - getSiderealOffset()) * 0.9972695663, 24); 404 405 // Then find local midnight on this day 406 long base = DAY_MS * ((time + fGmtOffset)/DAY_MS) - fGmtOffset; 407 408 //out(" lt =" + lt + " hours"); 409 //out(" base=" + new Date(base)); 410 411 return base + (long)(lt * HOUR_MS); 412 } 413 414 415 //------------------------------------------------------------------------- 416 // Coordinate transformations, all based on the current time of this object 417 //------------------------------------------------------------------------- 418 419 /** 420 * Convert from ecliptic to equatorial coordinates. 421 * 422 * @param ecliptic A point in the sky in ecliptic coordinates. 423 * @return The corresponding point in equatorial coordinates. 424 * @internal 425 */ eclipticToEquatorial(Ecliptic ecliptic)426 public final Equatorial eclipticToEquatorial(Ecliptic ecliptic) 427 { 428 return eclipticToEquatorial(ecliptic.longitude, ecliptic.latitude); 429 } 430 431 /** 432 * Convert from ecliptic to equatorial coordinates. 433 * 434 * @param eclipLong The ecliptic longitude 435 * @param eclipLat The ecliptic latitude 436 * 437 * @return The corresponding point in equatorial coordinates. 438 * @internal 439 */ eclipticToEquatorial(double eclipLong, double eclipLat)440 public final Equatorial eclipticToEquatorial(double eclipLong, double eclipLat) 441 { 442 // See page 42 of "Practial Astronomy with your Calculator", 443 // by Peter Duffet-Smith, for details on the algorithm. 444 445 double obliq = eclipticObliquity(); 446 double sinE = Math.sin(obliq); 447 double cosE = Math.cos(obliq); 448 449 double sinL = Math.sin(eclipLong); 450 double cosL = Math.cos(eclipLong); 451 452 double sinB = Math.sin(eclipLat); 453 double cosB = Math.cos(eclipLat); 454 double tanB = Math.tan(eclipLat); 455 456 return new Equatorial(Math.atan2(sinL*cosE - tanB*sinE, cosL), 457 Math.asin(sinB*cosE + cosB*sinE*sinL) ); 458 } 459 460 /** 461 * Convert from ecliptic longitude to equatorial coordinates. 462 * 463 * @param eclipLong The ecliptic longitude 464 * 465 * @return The corresponding point in equatorial coordinates. 466 * @internal 467 */ eclipticToEquatorial(double eclipLong)468 public final Equatorial eclipticToEquatorial(double eclipLong) 469 { 470 return eclipticToEquatorial(eclipLong, 0); // TODO: optimize 471 } 472 473 /** 474 * @internal 475 */ eclipticToHorizon(double eclipLong)476 public Horizon eclipticToHorizon(double eclipLong) 477 { 478 Equatorial equatorial = eclipticToEquatorial(eclipLong); 479 480 double H = getLocalSidereal()*PI/12 - equatorial.ascension; // Hour-angle 481 482 double sinH = Math.sin(H); 483 double cosH = Math.cos(H); 484 double sinD = Math.sin(equatorial.declination); 485 double cosD = Math.cos(equatorial.declination); 486 double sinL = Math.sin(fLatitude); 487 double cosL = Math.cos(fLatitude); 488 489 double altitude = Math.asin(sinD*sinL + cosD*cosL*cosH); 490 double azimuth = Math.atan2(-cosD*cosL*sinH, sinD - sinL * Math.sin(altitude)); 491 492 return new Horizon(azimuth, altitude); 493 } 494 495 496 //------------------------------------------------------------------------- 497 // The Sun 498 //------------------------------------------------------------------------- 499 500 // 501 // Parameters of the Sun's orbit as of the epoch Jan 0.0 1990 502 // Angles are in radians (after multiplying by PI/180) 503 // 504 static final double JD_EPOCH = 2447891.5; // Julian day of epoch 505 506 static final double SUN_ETA_G = 279.403303 * PI/180; // Ecliptic longitude at epoch 507 static final double SUN_OMEGA_G = 282.768422 * PI/180; // Ecliptic longitude of perigee 508 static final double SUN_E = 0.016713; // Eccentricity of orbit 509 //double sunR0 = 1.495585e8; // Semi-major axis in KM 510 //double sunTheta0 = 0.533128 * PI/180; // Angular diameter at R0 511 512 // The following three methods, which compute the sun parameters 513 // given above for an arbitrary epoch (whatever time the object is 514 // set to), make only a small difference as compared to using the 515 // above constants. E.g., Sunset times might differ by ~12 516 // seconds. Furthermore, the eta-g computation is befuddled by 517 // Duffet-Smith's incorrect coefficients (p.86). I've corrected 518 // the first-order coefficient but the others may be off too - no 519 // way of knowing without consulting another source. 520 521 // /** 522 // * Return the sun's ecliptic longitude at perigee for the current time. 523 // * See Duffett-Smith, p. 86. 524 // * @return radians 525 // */ 526 // private double getSunOmegaG() { 527 // double T = getJulianCentury(); 528 // return (281.2208444 + (1.719175 + 0.000452778*T)*T) * DEG_RAD; 529 // } 530 531 // /** 532 // * Return the sun's ecliptic longitude for the current time. 533 // * See Duffett-Smith, p. 86. 534 // * @return radians 535 // */ 536 // private double getSunEtaG() { 537 // double T = getJulianCentury(); 538 // //return (279.6966778 + (36000.76892 + 0.0003025*T)*T) * DEG_RAD; 539 // // 540 // // The above line is from Duffett-Smith, and yields manifestly wrong 541 // // results. The below constant is derived empirically to match the 542 // // constant he gives for the 1990 EPOCH. 543 // // 544 // return (279.6966778 + (-0.3262541582718024 + 0.0003025*T)*T) * DEG_RAD; 545 // } 546 547 // /** 548 // * Return the sun's eccentricity of orbit for the current time. 549 // * See Duffett-Smith, p. 86. 550 // * @return double 551 // */ 552 // private double getSunE() { 553 // double T = getJulianCentury(); 554 // return 0.01675104 - (0.0000418 + 0.000000126*T)*T; 555 // } 556 557 /** 558 * The longitude of the sun at the time specified by this object. 559 * The longitude is measured in radians along the ecliptic 560 * from the "first point of Aries," the point at which the ecliptic 561 * crosses the earth's equatorial plane at the vernal equinox. 562 * <p> 563 * Currently, this method uses an approximation of the two-body Kepler's 564 * equation for the earth and the sun. It does not take into account the 565 * perturbations caused by the other planets, the moon, etc. 566 * @internal 567 */ getSunLongitude()568 public double getSunLongitude() 569 { 570 // See page 86 of "Practial Astronomy with your Calculator", 571 // by Peter Duffet-Smith, for details on the algorithm. 572 573 if (sunLongitude == INVALID) { 574 double[] result = getSunLongitude(getJulianDay()); 575 sunLongitude = result[0]; 576 meanAnomalySun = result[1]; 577 } 578 return sunLongitude; 579 } 580 581 /** 582 * TODO Make this public when the entire class is package-private. 583 */ getSunLongitude(double julian)584 /*public*/ double[] getSunLongitude(double julian) 585 { 586 // See page 86 of "Practial Astronomy with your Calculator", 587 // by Peter Duffet-Smith, for details on the algorithm. 588 589 double day = julian - JD_EPOCH; // Days since epoch 590 591 // Find the angular distance the sun in a fictitious 592 // circular orbit has travelled since the epoch. 593 double epochAngle = norm2PI(PI2/TROPICAL_YEAR*day); 594 595 // The epoch wasn't at the sun's perigee; find the angular distance 596 // since perigee, which is called the "mean anomaly" 597 double meanAnomaly = norm2PI(epochAngle + SUN_ETA_G - SUN_OMEGA_G); 598 599 // Now find the "true anomaly", e.g. the real solar longitude 600 // by solving Kepler's equation for an elliptical orbit 601 // NOTE: The 3rd ed. of the book lists omega_g and eta_g in different 602 // equations; omega_g is to be correct. 603 return new double[] { 604 norm2PI(trueAnomaly(meanAnomaly, SUN_E) + SUN_OMEGA_G), 605 meanAnomaly 606 }; 607 } 608 609 /** 610 * The position of the sun at this object's current date and time, 611 * in equatorial coordinates. 612 * @internal 613 */ getSunPosition()614 public Equatorial getSunPosition() { 615 return eclipticToEquatorial(getSunLongitude(), 0); 616 } 617 618 private static class SolarLongitude { 619 double value; SolarLongitude(double val)620 SolarLongitude(double val) { value = val; } 621 } 622 623 /** 624 * Constant representing the vernal equinox. 625 * For use with {@link #getSunTime(SolarLongitude, boolean) getSunTime}. 626 * Note: In this case, "vernal" refers to the northern hemisphere's seasons. 627 * @internal 628 */ 629 public static final SolarLongitude VERNAL_EQUINOX = new SolarLongitude(0); 630 631 /** 632 * Constant representing the summer solstice. 633 * For use with {@link #getSunTime(SolarLongitude, boolean) getSunTime}. 634 * Note: In this case, "summer" refers to the northern hemisphere's seasons. 635 * @internal 636 */ 637 public static final SolarLongitude SUMMER_SOLSTICE = new SolarLongitude(PI/2); 638 639 /** 640 * Constant representing the autumnal equinox. 641 * For use with {@link #getSunTime(SolarLongitude, boolean) getSunTime}. 642 * Note: In this case, "autumn" refers to the northern hemisphere's seasons. 643 * @internal 644 */ 645 public static final SolarLongitude AUTUMN_EQUINOX = new SolarLongitude(PI); 646 647 /** 648 * Constant representing the winter solstice. 649 * For use with {@link #getSunTime(SolarLongitude, boolean) getSunTime}. 650 * Note: In this case, "winter" refers to the northern hemisphere's seasons. 651 * @internal 652 */ 653 public static final SolarLongitude WINTER_SOLSTICE = new SolarLongitude((PI*3)/2); 654 655 /** 656 * Find the next time at which the sun's ecliptic longitude will have 657 * the desired value. 658 * @internal 659 */ getSunTime(double desired, boolean next)660 public long getSunTime(double desired, boolean next) 661 { 662 return timeOfAngle( new AngleFunc() { @Override 663 public double eval() { return getSunLongitude(); } }, 664 desired, 665 TROPICAL_YEAR, 666 MINUTE_MS, 667 next); 668 } 669 670 /** 671 * Find the next time at which the sun's ecliptic longitude will have 672 * the desired value. 673 * @internal 674 */ 675 public long getSunTime(SolarLongitude desired, boolean next) { 676 return getSunTime(desired.value, next); 677 } 678 679 /** 680 * Returns the time (GMT) of sunrise or sunset on the local date to which 681 * this calendar is currently set. 682 * 683 * NOTE: This method only works well if this object is set to a 684 * time near local noon. Because of variations between the local 685 * official time zone and the geographic longitude, the 686 * computation can flop over into an adjacent day if this object 687 * is set to a time near local midnight. 688 * 689 * @internal 690 */ 691 public long getSunRiseSet(boolean rise) { 692 long t0 = time; 693 694 // Make a rough guess: 6am or 6pm local time on the current day 695 long noon = ((time + fGmtOffset)/DAY_MS)*DAY_MS - fGmtOffset + 12*HOUR_MS; 696 697 setTime(noon + (rise ? -6L : 6L) * HOUR_MS); 698 699 long t = riseOrSet(new CoordFunc() { 700 @Override 701 public Equatorial eval() { return getSunPosition(); } 702 }, 703 rise, 704 .533 * DEG_RAD, // Angular Diameter 705 34 /60.0 * DEG_RAD, // Refraction correction 706 MINUTE_MS / 12); // Desired accuracy 707 708 setTime(t0); 709 return t; 710 } 711 712 // Commented out - currently unused. ICU 2.6, Alan 713 // //------------------------------------------------------------------------- 714 // // Alternate Sun Rise/Set 715 // // See Duffett-Smith p.93 716 // //------------------------------------------------------------------------- 717 // 718 // // This yields worse results (as compared to USNO data) than getSunRiseSet(). 719 // /** 720 // * TODO Make this public when the entire class is package-private. 721 // */ 722 // /*public*/ long getSunRiseSet2(boolean rise) { 723 // // 1. Calculate coordinates of the sun's center for midnight 724 // double jd = Math.floor(getJulianDay() - 0.5) + 0.5; 725 // double[] sl = getSunLongitude(jd); 726 // double lambda1 = sl[0]; 727 // Equatorial pos1 = eclipticToEquatorial(lambda1, 0); 728 // 729 // // 2. Add ... to lambda to get position 24 hours later 730 // double lambda2 = lambda1 + 0.985647*DEG_RAD; 731 // Equatorial pos2 = eclipticToEquatorial(lambda2, 0); 732 // 733 // // 3. Calculate LSTs of rising and setting for these two positions 734 // double tanL = Math.tan(fLatitude); 735 // double H = Math.acos(-tanL * Math.tan(pos1.declination)); 736 // double lst1r = (PI2 + pos1.ascension - H) * 24 / PI2; 737 // double lst1s = (pos1.ascension + H) * 24 / PI2; 738 // H = Math.acos(-tanL * Math.tan(pos2.declination)); 739 // double lst2r = (PI2-H + pos2.ascension ) * 24 / PI2; 740 // double lst2s = (H + pos2.ascension ) * 24 / PI2; 741 // if (lst1r > 24) lst1r -= 24; 742 // if (lst1s > 24) lst1s -= 24; 743 // if (lst2r > 24) lst2r -= 24; 744 // if (lst2s > 24) lst2s -= 24; 745 // 746 // // 4. Convert LSTs to GSTs. If GST1 > GST2, add 24 to GST2. 747 // double gst1r = lstToGst(lst1r); 748 // double gst1s = lstToGst(lst1s); 749 // double gst2r = lstToGst(lst2r); 750 // double gst2s = lstToGst(lst2s); 751 // if (gst1r > gst2r) gst2r += 24; 752 // if (gst1s > gst2s) gst2s += 24; 753 // 754 // // 5. Calculate GST at 0h UT of this date 755 // double t00 = utToGst(0); 756 // 757 // // 6. Calculate GST at 0h on the observer's longitude 758 // double offset = Math.round(fLongitude*12/PI); // p.95 step 6; he _rounds_ to nearest 15 deg. 759 // double t00p = t00 - offset*1.002737909; 760 // if (t00p < 0) t00p += 24; // do NOT normalize 761 // 762 // // 7. Adjust 763 // if (gst1r < t00p) { 764 // gst1r += 24; 765 // gst2r += 24; 766 // } 767 // if (gst1s < t00p) { 768 // gst1s += 24; 769 // gst2s += 24; 770 // } 771 // 772 // // 8. 773 // double gstr = (24.07*gst1r-t00*(gst2r-gst1r))/(24.07+gst1r-gst2r); 774 // double gsts = (24.07*gst1s-t00*(gst2s-gst1s))/(24.07+gst1s-gst2s); 775 // 776 // // 9. Correct for parallax, refraction, and sun's diameter 777 // double dec = (pos1.declination + pos2.declination) / 2; 778 // double psi = Math.acos(Math.sin(fLatitude) / Math.cos(dec)); 779 // double x = 0.830725 * DEG_RAD; // parallax+refraction+diameter 780 // double y = Math.asin(Math.sin(x) / Math.sin(psi)) * RAD_DEG; 781 // double delta_t = 240 * y / Math.cos(dec) / 3600; // hours 782 // 783 // // 10. Add correction to GSTs, subtract from GSTr 784 // gstr -= delta_t; 785 // gsts += delta_t; 786 // 787 // // 11. Convert GST to UT and then to local civil time 788 // double ut = gstToUt(rise ? gstr : gsts); 789 // //System.out.println((rise?"rise=":"set=") + ut + ", delta_t=" + delta_t); 790 // long midnight = DAY_MS * (time / DAY_MS); // Find UT midnight on this day 791 // return midnight + (long) (ut * 3600000); 792 // } 793 794 // Commented out - currently unused. ICU 2.6, Alan 795 // /** 796 // * Convert local sidereal time to Greenwich sidereal time. 797 // * Section 15. Duffett-Smith p.21 798 // * @param lst in hours (0..24) 799 // * @return GST in hours (0..24) 800 // */ 801 // double lstToGst(double lst) { 802 // double delta = fLongitude * 24 / PI2; 803 // return normalize(lst - delta, 24); 804 // } 805 806 // Commented out - currently unused. ICU 2.6, Alan 807 // /** 808 // * Convert UT to GST on this date. 809 // * Section 12. Duffett-Smith p.17 810 // * @param ut in hours 811 // * @return GST in hours 812 // */ 813 // double utToGst(double ut) { 814 // return normalize(getT0() + ut*1.002737909, 24); 815 // } 816 817 // Commented out - currently unused. ICU 2.6, Alan 818 // /** 819 // * Convert GST to UT on this date. 820 // * Section 13. Duffett-Smith p.18 821 // * @param gst in hours 822 // * @return UT in hours 823 // */ 824 // double gstToUt(double gst) { 825 // return normalize(gst - getT0(), 24) * 0.9972695663; 826 // } 827 828 // Commented out - currently unused. ICU 2.6, Alan 829 // double getT0() { 830 // // Common computation for UT <=> GST 831 // 832 // // Find JD for 0h UT 833 // double jd = Math.floor(getJulianDay() - 0.5) + 0.5; 834 // 835 // double s = jd - 2451545.0; 836 // double t = s / 36525.0; 837 // double t0 = 6.697374558 + (2400.051336 + 0.000025862*t)*t; 838 // return t0; 839 // } 840 841 // Commented out - currently unused. ICU 2.6, Alan 842 // //------------------------------------------------------------------------- 843 // // Alternate Sun Rise/Set 844 // // See sci.astro FAQ 845 // // http://www.faqs.org/faqs/astronomy/faq/part3/section-5.html 846 // //------------------------------------------------------------------------- 847 // 848 // // Note: This method appears to produce inferior accuracy as 849 // // compared to getSunRiseSet(). 850 // 851 // /** 852 // * TODO Make this public when the entire class is package-private. 853 // */ 854 // /*public*/ long getSunRiseSet3(boolean rise) { 855 // 856 // // Compute day number for 0.0 Jan 2000 epoch 857 // double d = (double)(time - EPOCH_2000_MS) / DAY_MS; 858 // 859 // // Now compute the Local Sidereal Time, LST: 860 // // 861 // double LST = 98.9818 + 0.985647352 * d + /*UT*15 + long*/ 862 // fLongitude*RAD_DEG; 863 // // 864 // // (east long. positive). Note that LST is here expressed in degrees, 865 // // where 15 degrees corresponds to one hour. Since LST really is an angle, 866 // // it's convenient to use one unit---degrees---throughout. 867 // 868 // // COMPUTING THE SUN'S POSITION 869 // // ---------------------------- 870 // // 871 // // To be able to compute the Sun's rise/set times, you need to be able to 872 // // compute the Sun's position at any time. First compute the "day 873 // // number" d as outlined above, for the desired moment. Next compute: 874 // // 875 // double oblecl = 23.4393 - 3.563E-7 * d; 876 // // 877 // double w = 282.9404 + 4.70935E-5 * d; 878 // double M = 356.0470 + 0.9856002585 * d; 879 // double e = 0.016709 - 1.151E-9 * d; 880 // // 881 // // This is the obliquity of the ecliptic, plus some of the elements of 882 // // the Sun's apparent orbit (i.e., really the Earth's orbit): w = 883 // // argument of perihelion, M = mean anomaly, e = eccentricity. 884 // // Semi-major axis is here assumed to be exactly 1.0 (while not strictly 885 // // true, this is still an accurate approximation). Next compute E, the 886 // // eccentric anomaly: 887 // // 888 // double E = M + e*(180/PI) * Math.sin(M*DEG_RAD) * ( 1.0 + e*Math.cos(M*DEG_RAD) ); 889 // // 890 // // where E and M are in degrees. This is it---no further iterations are 891 // // needed because we know e has a sufficiently small value. Next compute 892 // // the true anomaly, v, and the distance, r: 893 // // 894 // /* r * cos(v) = */ double A = Math.cos(E*DEG_RAD) - e; 895 // /* r * sin(v) = */ double B = Math.sqrt(1 - e*e) * Math.sin(E*DEG_RAD); 896 // // 897 // // and 898 // // 899 // // r = sqrt( A*A + B*B ) 900 // double v = Math.atan2( B, A )*RAD_DEG; 901 // // 902 // // The Sun's true longitude, slon, can now be computed: 903 // // 904 // double slon = v + w; 905 // // 906 // // Since the Sun is always at the ecliptic (or at least very very close to 907 // // it), we can use simplified formulae to convert slon (the Sun's ecliptic 908 // // longitude) to sRA and sDec (the Sun's RA and Dec): 909 // // 910 // // sin(slon) * cos(oblecl) 911 // // tan(sRA) = ------------------------- 912 // // cos(slon) 913 // // 914 // // sin(sDec) = sin(oblecl) * sin(slon) 915 // // 916 // // As was the case when computing az, the Azimuth, if possible use an 917 // // atan2() function to compute sRA. 918 // 919 // double sRA = Math.atan2(Math.sin(slon*DEG_RAD) * Math.cos(oblecl*DEG_RAD), Math.cos(slon*DEG_RAD))*RAD_DEG; 920 // 921 // double sin_sDec = Math.sin(oblecl*DEG_RAD) * Math.sin(slon*DEG_RAD); 922 // double sDec = Math.asin(sin_sDec)*RAD_DEG; 923 // 924 // // COMPUTING RISE AND SET TIMES 925 // // ---------------------------- 926 // // 927 // // To compute when an object rises or sets, you must compute when it 928 // // passes the meridian and the HA of rise/set. Then the rise time is 929 // // the meridian time minus HA for rise/set, and the set time is the 930 // // meridian time plus the HA for rise/set. 931 // // 932 // // To find the meridian time, compute the Local Sidereal Time at 0h local 933 // // time (or 0h UT if you prefer to work in UT) as outlined above---name 934 // // that quantity LST0. The Meridian Time, MT, will now be: 935 // // 936 // // MT = RA - LST0 937 // double MT = normalize(sRA - LST, 360); 938 // // 939 // // where "RA" is the object's Right Ascension (in degrees!). If negative, 940 // // add 360 deg to MT. If the object is the Sun, leave the time as it is, 941 // // but if it's stellar, multiply MT by 365.2422/366.2422, to convert from 942 // // sidereal to solar time. Now, compute HA for rise/set, name that 943 // // quantity HA0: 944 // // 945 // // sin(h0) - sin(lat) * sin(Dec) 946 // // cos(HA0) = --------------------------------- 947 // // cos(lat) * cos(Dec) 948 // // 949 // // where h0 is the altitude selected to represent rise/set. For a purely 950 // // mathematical horizon, set h0 = 0 and simplify to: 951 // // 952 // // cos(HA0) = - tan(lat) * tan(Dec) 953 // // 954 // // If you want to account for refraction on the atmosphere, set h0 = -35/60 955 // // degrees (-35 arc minutes), and if you want to compute the rise/set times 956 // // for the Sun's upper limb, set h0 = -50/60 (-50 arc minutes). 957 // // 958 // double h0 = -50/60 * DEG_RAD; 959 // 960 // double HA0 = Math.acos( 961 // (Math.sin(h0) - Math.sin(fLatitude) * sin_sDec) / 962 // (Math.cos(fLatitude) * Math.cos(sDec*DEG_RAD)))*RAD_DEG; 963 // 964 // // When HA0 has been computed, leave it as it is for the Sun but multiply 965 // // by 365.2422/366.2422 for stellar objects, to convert from sidereal to 966 // // solar time. Finally compute: 967 // // 968 // // Rise time = MT - HA0 969 // // Set time = MT + HA0 970 // // 971 // // convert the times from degrees to hours by dividing by 15. 972 // // 973 // // If you'd like to check that your calculations are accurate or just 974 // // need a quick result, check the USNO's Sun or Moon Rise/Set Table, 975 // // <URL:http://aa.usno.navy.mil/AA/data/docs/RS_OneYear.html>. 976 // 977 // double result = MT + (rise ? -HA0 : HA0); // in degrees 978 // 979 // // Find UT midnight on this day 980 // long midnight = DAY_MS * (time / DAY_MS); 981 // 982 // return midnight + (long) (result * 3600000 / 15); 983 // } 984 985 //------------------------------------------------------------------------- 986 // The Moon 987 //------------------------------------------------------------------------- 988 989 static final double moonL0 = 318.351648 * PI/180; // Mean long. at epoch 990 static final double moonP0 = 36.340410 * PI/180; // Mean long. of perigee 991 static final double moonN0 = 318.510107 * PI/180; // Mean long. of node 992 static final double moonI = 5.145366 * PI/180; // Inclination of orbit 993 static final double moonE = 0.054900; // Eccentricity of orbit 994 995 // These aren't used right now 996 static final double moonA = 3.84401e5; // semi-major axis (km) 997 static final double moonT0 = 0.5181 * PI/180; // Angular size at distance A 998 static final double moonPi = 0.9507 * PI/180; // Parallax at distance A 999 1000 /** 1001 * The position of the moon at the time set on this 1002 * object, in equatorial coordinates. 1003 * @internal 1004 */ 1005 public Equatorial getMoonPosition() 1006 { 1007 // 1008 // See page 142 of "Practial Astronomy with your Calculator", 1009 // by Peter Duffet-Smith, for details on the algorithm. 1010 // 1011 if (moonPosition == null) { 1012 // Calculate the solar longitude. Has the side effect of 1013 // filling in "meanAnomalySun" as well. 1014 double sunLong = getSunLongitude(); 1015 1016 // 1017 // Find the # of days since the epoch of our orbital parameters. 1018 // TODO: Convert the time of day portion into ephemeris time 1019 // 1020 double day = getJulianDay() - JD_EPOCH; // Days since epoch 1021 1022 // Calculate the mean longitude and anomaly of the moon, based on 1023 // a circular orbit. Similar to the corresponding solar calculation. 1024 double meanLongitude = norm2PI(13.1763966*PI/180*day + moonL0); 1025 double meanAnomalyMoon = norm2PI(meanLongitude - 0.1114041*PI/180 * day - moonP0); 1026 1027 // 1028 // Calculate the following corrections: 1029 // Evection: the sun's gravity affects the moon's eccentricity 1030 // Annual Eqn: variation in the effect due to earth-sun distance 1031 // A3: correction factor (for ???) 1032 // 1033 double evection = 1.2739*PI/180 * Math.sin(2 * (meanLongitude - sunLong) 1034 - meanAnomalyMoon); 1035 double annual = 0.1858*PI/180 * Math.sin(meanAnomalySun); 1036 double a3 = 0.3700*PI/180 * Math.sin(meanAnomalySun); 1037 1038 meanAnomalyMoon += evection - annual - a3; 1039 1040 // 1041 // More correction factors: 1042 // center equation of the center correction 1043 // a4 yet another error correction (???) 1044 // 1045 // TODO: Skip the equation of the center correction and solve Kepler's eqn? 1046 // 1047 double center = 6.2886*PI/180 * Math.sin(meanAnomalyMoon); 1048 double a4 = 0.2140*PI/180 * Math.sin(2 * meanAnomalyMoon); 1049 1050 // Now find the moon's corrected longitude 1051 moonLongitude = meanLongitude + evection + center - annual + a4; 1052 1053 // 1054 // And finally, find the variation, caused by the fact that the sun's 1055 // gravitational pull on the moon varies depending on which side of 1056 // the earth the moon is on 1057 // 1058 double variation = 0.6583*PI/180 * Math.sin(2*(moonLongitude - sunLong)); 1059 1060 moonLongitude += variation; 1061 1062 // 1063 // What we've calculated so far is the moon's longitude in the plane 1064 // of its own orbit. Now map to the ecliptic to get the latitude 1065 // and longitude. First we need to find the longitude of the ascending 1066 // node, the position on the ecliptic where it is crossed by the moon's 1067 // orbit as it crosses from the southern to the northern hemisphere. 1068 // 1069 double nodeLongitude = norm2PI(moonN0 - 0.0529539*PI/180 * day); 1070 1071 nodeLongitude -= 0.16*PI/180 * Math.sin(meanAnomalySun); 1072 1073 double y = Math.sin(moonLongitude - nodeLongitude); 1074 double x = Math.cos(moonLongitude - nodeLongitude); 1075 1076 moonEclipLong = Math.atan2(y*Math.cos(moonI), x) + nodeLongitude; 1077 double moonEclipLat = Math.asin(y * Math.sin(moonI)); 1078 1079 moonPosition = eclipticToEquatorial(moonEclipLong, moonEclipLat); 1080 } 1081 return moonPosition; 1082 } 1083 1084 /** 1085 * The "age" of the moon at the time specified in this object. 1086 * This is really the angle between the 1087 * current ecliptic longitudes of the sun and the moon, 1088 * measured in radians. 1089 * 1090 * @see #getMoonPhase 1091 * @internal 1092 */ 1093 public double getMoonAge() { 1094 // See page 147 of "Practial Astronomy with your Calculator", 1095 // by Peter Duffet-Smith, for details on the algorithm. 1096 // 1097 // Force the moon's position to be calculated. We're going to use 1098 // some the intermediate results cached during that calculation. 1099 // 1100 getMoonPosition(); 1101 1102 return norm2PI(moonEclipLong - sunLongitude); 1103 } 1104 1105 /** 1106 * Calculate the phase of the moon at the time set in this object. 1107 * The returned phase is a <code>double</code> in the range 1108 * <code>0 <= phase < 1</code>, interpreted as follows: 1109 * <ul> 1110 * <li>0.00: New moon 1111 * <li>0.25: First quarter 1112 * <li>0.50: Full moon 1113 * <li>0.75: Last quarter 1114 * </ul> 1115 * 1116 * @see #getMoonAge 1117 * @internal 1118 */ 1119 public double getMoonPhase() { 1120 // See page 147 of "Practial Astronomy with your Calculator", 1121 // by Peter Duffet-Smith, for details on the algorithm. 1122 return 0.5 * (1 - Math.cos(getMoonAge())); 1123 } 1124 1125 private static class MoonAge { 1126 double value; 1127 MoonAge(double val) { value = val; } 1128 } 1129 1130 /** 1131 * Constant representing a new moon. 1132 * For use with {@link #getMoonTime(MoonAge, boolean) getMoonTime} 1133 * @internal 1134 */ 1135 public static final MoonAge NEW_MOON = new MoonAge(0); 1136 1137 /** 1138 * Constant representing the moon's first quarter. 1139 * For use with {@link #getMoonTime(MoonAge, boolean) getMoonTime} 1140 * @internal 1141 */ 1142 public static final MoonAge FIRST_QUARTER = new MoonAge(PI/2); 1143 1144 /** 1145 * Constant representing a full moon. 1146 * For use with {@link #getMoonTime(MoonAge, boolean) getMoonTime} 1147 * @internal 1148 */ 1149 public static final MoonAge FULL_MOON = new MoonAge(PI); 1150 1151 /** 1152 * Constant representing the moon's last quarter. 1153 * For use with {@link #getMoonTime(MoonAge, boolean) getMoonTime} 1154 * @internal 1155 */ 1156 public static final MoonAge LAST_QUARTER = new MoonAge((PI*3)/2); 1157 1158 /** 1159 * Find the next or previous time at which the Moon's ecliptic 1160 * longitude will have the desired value. 1161 * <p> 1162 * @param desired The desired longitude. 1163 * @param next <tt>true</tt> if the next occurrance of the phase 1164 * is desired, <tt>false</tt> for the previous occurrance. 1165 * @internal 1166 */ 1167 public long getMoonTime(double desired, boolean next) 1168 { 1169 return timeOfAngle( new AngleFunc() { 1170 @Override 1171 public double eval() { return getMoonAge(); } }, 1172 desired, 1173 SYNODIC_MONTH, 1174 MINUTE_MS, 1175 next); 1176 } 1177 1178 /** 1179 * Find the next or previous time at which the moon will be in the 1180 * desired phase. 1181 * <p> 1182 * @param desired The desired phase of the moon. 1183 * @param next <tt>true</tt> if the next occurrance of the phase 1184 * is desired, <tt>false</tt> for the previous occurrance. 1185 * @internal 1186 */ 1187 public long getMoonTime(MoonAge desired, boolean next) { 1188 return getMoonTime(desired.value, next); 1189 } 1190 1191 /** 1192 * Returns the time (GMT) of sunrise or sunset on the local date to which 1193 * this calendar is currently set. 1194 * @internal 1195 */ 1196 public long getMoonRiseSet(boolean rise) 1197 { 1198 return riseOrSet(new CoordFunc() { 1199 @Override 1200 public Equatorial eval() { return getMoonPosition(); } 1201 }, 1202 rise, 1203 .533 * DEG_RAD, // Angular Diameter 1204 34 /60.0 * DEG_RAD, // Refraction correction 1205 MINUTE_MS); // Desired accuracy 1206 } 1207 1208 //------------------------------------------------------------------------- 1209 // Interpolation methods for finding the time at which a given event occurs 1210 //------------------------------------------------------------------------- 1211 1212 private interface AngleFunc { 1213 public double eval(); 1214 } 1215 1216 private long timeOfAngle(AngleFunc func, double desired, 1217 double periodDays, long epsilon, boolean next) 1218 { 1219 // Find the value of the function at the current time 1220 double lastAngle = func.eval(); 1221 1222 // Find out how far we are from the desired angle 1223 double deltaAngle = norm2PI(desired - lastAngle) ; 1224 1225 // Using the average period, estimate the next (or previous) time at 1226 // which the desired angle occurs. 1227 double deltaT = (deltaAngle + (next ? 0 : -PI2)) * (periodDays*DAY_MS) / PI2; 1228 1229 double lastDeltaT = deltaT; // Liu 1230 long startTime = time; // Liu 1231 1232 setTime(time + (long)deltaT); 1233 1234 // Now iterate until we get the error below epsilon. Throughout 1235 // this loop we use normPI to get values in the range -Pi to Pi, 1236 // since we're using them as correction factors rather than absolute angles. 1237 do { 1238 // Evaluate the function at the time we've estimated 1239 double angle = func.eval(); 1240 1241 // Find the # of milliseconds per radian at this point on the curve 1242 double factor = Math.abs(deltaT / normPI(angle-lastAngle)); 1243 1244 // Correct the time estimate based on how far off the angle is 1245 deltaT = normPI(desired - angle) * factor; 1246 1247 // HACK: 1248 // 1249 // If abs(deltaT) begins to diverge we need to quit this loop. 1250 // This only appears to happen when attempting to locate, for 1251 // example, a new moon on the day of the new moon. E.g.: 1252 // 1253 // This result is correct: 1254 // newMoon(7508(Mon Jul 23 00:00:00 CST 1990,false))= 1255 // Sun Jul 22 10:57:41 CST 1990 1256 // 1257 // But attempting to make the same call a day earlier causes deltaT 1258 // to diverge: 1259 // CalendarAstronomer.timeOfAngle() diverging: 1.348508727575625E9 -> 1260 // 1.3649828540224032E9 1261 // newMoon(7507(Sun Jul 22 00:00:00 CST 1990,false))= 1262 // Sun Jul 08 13:56:15 CST 1990 1263 // 1264 // As a temporary solution, we catch this specific condition and 1265 // adjust our start time by one eighth period days (either forward 1266 // or backward) and try again. 1267 // Liu 11/9/00 1268 if (Math.abs(deltaT) > Math.abs(lastDeltaT)) { 1269 long delta = (long) (periodDays * DAY_MS / 8); 1270 setTime(startTime + (next ? delta : -delta)); 1271 return timeOfAngle(func, desired, periodDays, epsilon, next); 1272 } 1273 1274 lastDeltaT = deltaT; 1275 lastAngle = angle; 1276 1277 setTime(time + (long)deltaT); 1278 } 1279 while (Math.abs(deltaT) > epsilon); 1280 1281 return time; 1282 } 1283 1284 private interface CoordFunc { 1285 public Equatorial eval(); 1286 } 1287 1288 private long riseOrSet(CoordFunc func, boolean rise, 1289 double diameter, double refraction, 1290 long epsilon) 1291 { 1292 Equatorial pos = null; 1293 double tanL = Math.tan(fLatitude); 1294 long deltaT = Long.MAX_VALUE; 1295 int count = 0; 1296 1297 // 1298 // Calculate the object's position at the current time, then use that 1299 // position to calculate the time of rising or setting. The position 1300 // will be different at that time, so iterate until the error is allowable. 1301 // 1302 do { 1303 // See "Practical Astronomy With Your Calculator, section 33. 1304 pos = func.eval(); 1305 double angle = Math.acos(-tanL * Math.tan(pos.declination)); 1306 double lst = ((rise ? PI2-angle : angle) + pos.ascension ) * 24 / PI2; 1307 1308 // Convert from LST to Universal Time. 1309 long newTime = lstToUT( lst ); 1310 1311 deltaT = newTime - time; 1312 setTime(newTime); 1313 } 1314 while (++ count < 5 && Math.abs(deltaT) > epsilon); 1315 1316 // Calculate the correction due to refraction and the object's angular diameter 1317 double cosD = Math.cos(pos.declination); 1318 double psi = Math.acos(Math.sin(fLatitude) / cosD); 1319 double x = diameter / 2 + refraction; 1320 double y = Math.asin(Math.sin(x) / Math.sin(psi)); 1321 long delta = (long)((240 * y * RAD_DEG / cosD)*SECOND_MS); 1322 1323 return time + (rise ? -delta : delta); 1324 } 1325 1326 //------------------------------------------------------------------------- 1327 // Other utility methods 1328 //------------------------------------------------------------------------- 1329 1330 /*** 1331 * Given 'value', add or subtract 'range' until 0 <= 'value' < range. 1332 * The modulus operator. 1333 */ 1334 private static final double normalize(double value, double range) { 1335 return value - range * Math.floor(value / range); 1336 } 1337 1338 /** 1339 * Normalize an angle so that it's in the range 0 - 2pi. 1340 * For positive angles this is just (angle % 2pi), but the Java 1341 * mod operator doesn't work that way for negative numbers.... 1342 */ 1343 private static final double norm2PI(double angle) { 1344 return normalize(angle, PI2); 1345 } 1346 1347 /** 1348 * Normalize an angle into the range -PI - PI 1349 */ 1350 private static final double normPI(double angle) { 1351 return normalize(angle + PI, PI2) - PI; 1352 } 1353 1354 /** 1355 * Find the "true anomaly" (longitude) of an object from 1356 * its mean anomaly and the eccentricity of its orbit. This uses 1357 * an iterative solution to Kepler's equation. 1358 * 1359 * @param meanAnomaly The object's longitude calculated as if it were in 1360 * a regular, circular orbit, measured in radians 1361 * from the point of perigee. 1362 * 1363 * @param eccentricity The eccentricity of the orbit 1364 * 1365 * @return The true anomaly (longitude) measured in radians 1366 */ 1367 private double trueAnomaly(double meanAnomaly, double eccentricity) 1368 { 1369 // First, solve Kepler's equation iteratively 1370 // Duffett-Smith, p.90 1371 double delta; 1372 double E = meanAnomaly; 1373 do { 1374 delta = E - eccentricity * Math.sin(E) - meanAnomaly; 1375 E = E - delta / (1 - eccentricity * Math.cos(E)); 1376 } 1377 while (Math.abs(delta) > 1e-5); // epsilon = 1e-5 rad 1378 1379 return 2.0 * Math.atan( Math.tan(E/2) * Math.sqrt( (1+eccentricity) 1380 /(1-eccentricity) ) ); 1381 } 1382 1383 /** 1384 * Return the obliquity of the ecliptic (the angle between the ecliptic 1385 * and the earth's equator) at the current time. This varies due to 1386 * the precession of the earth's axis. 1387 * 1388 * @return the obliquity of the ecliptic relative to the equator, 1389 * measured in radians. 1390 */ 1391 private double eclipticObliquity() { 1392 if (eclipObliquity == INVALID) { 1393 final double epoch = 2451545.0; // 2000 AD, January 1.5 1394 1395 double T = (getJulianDay() - epoch) / 36525; 1396 1397 eclipObliquity = 23.439292 1398 - 46.815/3600 * T 1399 - 0.0006/3600 * T*T 1400 + 0.00181/3600 * T*T*T; 1401 1402 eclipObliquity *= DEG_RAD; 1403 } 1404 return eclipObliquity; 1405 } 1406 1407 1408 //------------------------------------------------------------------------- 1409 // Private data 1410 //------------------------------------------------------------------------- 1411 1412 /** 1413 * Current time in milliseconds since 1/1/1970 AD 1414 * @see java.util.Date#getTime 1415 */ 1416 private long time; 1417 1418 /* These aren't used yet, but they'll be needed for sunset calculations 1419 * and equatorial to horizon coordinate conversions 1420 */ 1421 private double fLongitude = 0.0; 1422 private double fLatitude = 0.0; 1423 private long fGmtOffset = 0; 1424 1425 // 1426 // The following fields are used to cache calculated results for improved 1427 // performance. These values all depend on the current time setting 1428 // of this object, so the clearCache method is provided. 1429 // 1430 static final private double INVALID = Double.MIN_VALUE; 1431 1432 private transient double julianDay = INVALID; 1433 private transient double julianCentury = INVALID; 1434 private transient double sunLongitude = INVALID; 1435 private transient double meanAnomalySun = INVALID; 1436 private transient double moonLongitude = INVALID; 1437 private transient double moonEclipLong = INVALID; 1438 //private transient double meanAnomalyMoon = INVALID; 1439 private transient double eclipObliquity = INVALID; 1440 private transient double siderealT0 = INVALID; 1441 private transient double siderealTime = INVALID; 1442 1443 private transient Equatorial moonPosition = null; 1444 1445 private void clearCache() { 1446 julianDay = INVALID; 1447 julianCentury = INVALID; 1448 sunLongitude = INVALID; 1449 meanAnomalySun = INVALID; 1450 moonLongitude = INVALID; 1451 moonEclipLong = INVALID; 1452 //meanAnomalyMoon = INVALID; 1453 eclipObliquity = INVALID; 1454 siderealTime = INVALID; 1455 siderealT0 = INVALID; 1456 moonPosition = null; 1457 } 1458 1459 //private static void out(String s) { 1460 // System.out.println(s); 1461 //} 1462 1463 //private static String deg(double rad) { 1464 // return Double.toString(rad * RAD_DEG); 1465 //} 1466 1467 //private static String hours(long ms) { 1468 // return Double.toString((double)ms / HOUR_MS) + " hours"; 1469 //} 1470 1471 /** 1472 * @internal 1473 */ 1474 public String local(long localMillis) { 1475 return new Date(localMillis - TimeZone.getDefault().getRawOffset()).toString(); 1476 } 1477 1478 1479 /** 1480 * Represents the position of an object in the sky relative to the ecliptic, 1481 * the plane of the earth's orbit around the Sun. 1482 * This is a spherical coordinate system in which the latitude 1483 * specifies the position north or south of the plane of the ecliptic. 1484 * The longitude specifies the position along the ecliptic plane 1485 * relative to the "First Point of Aries", which is the Sun's position in the sky 1486 * at the Vernal Equinox. 1487 * <p> 1488 * Note that Ecliptic objects are immutable and cannot be modified 1489 * once they are constructed. This allows them to be passed and returned by 1490 * value without worrying about whether other code will modify them. 1491 * 1492 * @see CalendarAstronomer.Equatorial 1493 * @see CalendarAstronomer.Horizon 1494 * @internal 1495 */ 1496 public static final class Ecliptic { 1497 /** 1498 * Constructs an Ecliptic coordinate object. 1499 * <p> 1500 * @param lat The ecliptic latitude, measured in radians. 1501 * @param lon The ecliptic longitude, measured in radians. 1502 * @internal 1503 */ 1504 public Ecliptic(double lat, double lon) { 1505 latitude = lat; 1506 longitude = lon; 1507 } 1508 1509 /** 1510 * Return a string representation of this object 1511 * @internal 1512 */ 1513 @Override 1514 public String toString() { 1515 return Double.toString(longitude*RAD_DEG) + "," + (latitude*RAD_DEG); 1516 } 1517 1518 /** 1519 * The ecliptic latitude, in radians. This specifies an object's 1520 * position north or south of the plane of the ecliptic, 1521 * with positive angles representing north. 1522 * @internal 1523 */ 1524 public final double latitude; 1525 1526 /** 1527 * The ecliptic longitude, in radians. 1528 * This specifies an object's position along the ecliptic plane 1529 * relative to the "First Point of Aries", which is the Sun's position 1530 * in the sky at the Vernal Equinox, 1531 * with positive angles representing east. 1532 * <p> 1533 * A bit of trivia: the first point of Aries is currently in the 1534 * constellation Pisces, due to the precession of the earth's axis. 1535 * @internal 1536 */ 1537 public final double longitude; 1538 } 1539 1540 /** 1541 * Represents the position of an 1542 * object in the sky relative to the plane of the earth's equator. 1543 * The <i>Right Ascension</i> specifies the position east or west 1544 * along the equator, relative to the sun's position at the vernal 1545 * equinox. The <i>Declination</i> is the position north or south 1546 * of the equatorial plane. 1547 * <p> 1548 * Note that Equatorial objects are immutable and cannot be modified 1549 * once they are constructed. This allows them to be passed and returned by 1550 * value without worrying about whether other code will modify them. 1551 * 1552 * @see CalendarAstronomer.Ecliptic 1553 * @see CalendarAstronomer.Horizon 1554 * @internal 1555 */ 1556 public static final class Equatorial { 1557 /** 1558 * Constructs an Equatorial coordinate object. 1559 * <p> 1560 * @param asc The right ascension, measured in radians. 1561 * @param dec The declination, measured in radians. 1562 * @internal 1563 */ 1564 public Equatorial(double asc, double dec) { 1565 ascension = asc; 1566 declination = dec; 1567 } 1568 1569 /** 1570 * Return a string representation of this object, with the 1571 * angles measured in degrees. 1572 * @internal 1573 */ 1574 @Override 1575 public String toString() { 1576 return Double.toString(ascension*RAD_DEG) + "," + (declination*RAD_DEG); 1577 } 1578 1579 /** 1580 * Return a string representation of this object with the right ascension 1581 * measured in hours, minutes, and seconds. 1582 * @internal 1583 */ 1584 public String toHmsString() { 1585 return radToHms(ascension) + "," + radToDms(declination); 1586 } 1587 1588 /** 1589 * The right ascension, in radians. 1590 * This is the position east or west along the equator 1591 * relative to the sun's position at the vernal equinox, 1592 * with positive angles representing East. 1593 * @internal 1594 */ 1595 public final double ascension; 1596 1597 /** 1598 * The declination, in radians. 1599 * This is the position north or south of the equatorial plane, 1600 * with positive angles representing north. 1601 * @internal 1602 */ 1603 public final double declination; 1604 } 1605 1606 /** 1607 * Represents the position of an object in the sky relative to 1608 * the local horizon. 1609 * The <i>Altitude</i> represents the object's elevation above the horizon, 1610 * with objects below the horizon having a negative altitude. 1611 * The <i>Azimuth</i> is the geographic direction of the object from the 1612 * observer's position, with 0 representing north. The azimuth increases 1613 * clockwise from north. 1614 * <p> 1615 * Note that Horizon objects are immutable and cannot be modified 1616 * once they are constructed. This allows them to be passed and returned by 1617 * value without worrying about whether other code will modify them. 1618 * 1619 * @see CalendarAstronomer.Ecliptic 1620 * @see CalendarAstronomer.Equatorial 1621 * @internal 1622 */ 1623 public static final class Horizon { 1624 /** 1625 * Constructs a Horizon coordinate object. 1626 * <p> 1627 * @param alt The altitude, measured in radians above the horizon. 1628 * @param azim The azimuth, measured in radians clockwise from north. 1629 * @internal 1630 */ 1631 public Horizon(double alt, double azim) { 1632 altitude = alt; 1633 azimuth = azim; 1634 } 1635 1636 /** 1637 * Return a string representation of this object, with the 1638 * angles measured in degrees. 1639 * @internal 1640 */ 1641 @Override 1642 public String toString() { 1643 return Double.toString(altitude*RAD_DEG) + "," + (azimuth*RAD_DEG); 1644 } 1645 1646 /** 1647 * The object's altitude above the horizon, in radians. 1648 * @internal 1649 */ 1650 public final double altitude; 1651 1652 /** 1653 * The object's direction, in radians clockwise from north. 1654 * @internal 1655 */ 1656 public final double azimuth; 1657 } 1658 1659 static private String radToHms(double angle) { 1660 int hrs = (int) (angle*RAD_HOUR); 1661 int min = (int)((angle*RAD_HOUR - hrs) * 60); 1662 int sec = (int)((angle*RAD_HOUR - hrs - min/60.0) * 3600); 1663 1664 return Integer.toString(hrs) + "h" + min + "m" + sec + "s"; 1665 } 1666 1667 static private String radToDms(double angle) { 1668 int deg = (int) (angle*RAD_DEG); 1669 int min = (int)((angle*RAD_DEG - deg) * 60); 1670 int sec = (int)((angle*RAD_DEG - deg - min/60.0) * 3600); 1671 1672 return Integer.toString(deg) + "\u00b0" + min + "'" + sec + "\""; 1673 } 1674 } 1675