KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > ibm > icu > impl > CalendarAstronomer


1 /*
2  *******************************************************************************
3  * Copyright (C) 1996-2006, International Business Machines Corporation and *
4  * others. All Rights Reserved. *
5  *******************************************************************************
6  */

7 package com.ibm.icu.impl;
8
9 import java.util.*;
10
11 /**
12  * <code>CalendarAstronomer</code> is a class that can perform the calculations to
13  * determine the positions of the sun and moon, the time of sunrise and
14  * sunset, and other astronomy-related data. The calculations it performs
15  * are in some cases quite complicated, and this utility class saves you
16  * the trouble of worrying about them.
17  * <p>
18  * The measurement of time is a very important part of astronomy. Because
19  * astronomical bodies are constantly in motion, observations are only valid
20  * at a given moment in time. Accordingly, each <code>CalendarAstronomer</code>
21  * object has a <code>time</code> property that determines the date
22  * and time for which its calculations are performed. You can set and
23  * retrieve this property with {@link #setDate setDate}, {@link #getDate getDate}
24  * and related methods.
25  * <p>
26  * Almost all of the calculations performed by this class, or by any
27  * astronomer, are approximations to various degrees of accuracy. The
28  * calculations in this class are mostly modelled after those described
29  * in the book
30  * <a HREF="http://www.amazon.com/exec/obidos/ISBN=0521356997" target="_top">
31  * Practical Astronomy With Your Calculator</a>, by Peter J.
32  * Duffett-Smith, Cambridge University Press, 1990. This is an excellent
33  * book, and if you want a greater understanding of how these calculations
34  * are performed it a very good, readable starting point.
35  * <p>
36  * <strong>WARNING:</strong> This class is very early in its development, and
37  * it is highly likely that its API will change to some degree in the future.
38  * At the moment, it basically does just enough to support {@link com.ibm.icu.util.IslamicCalendar}
39  * and {@link com.ibm.icu.util.ChineseCalendar}.
40  *
41  * @author Laura Werner
42  * @author Alan Liu
43  * @internal
44  */

45 public class CalendarAstronomer {
46     
47     //-------------------------------------------------------------------------
48
// Astronomical constants
49
//-------------------------------------------------------------------------
50

51     /**
52      * The number of standard hours in one sidereal day.
53      * Approximately 24.93.
54      * @internal
55      */

56     public static final double SIDEREAL_DAY = 23.93446960027;
57     
58     /**
59      * The number of sidereal hours in one mean solar day.
60      * Approximately 24.07.
61      * @internal
62      */

63     public static final double SOLAR_DAY = 24.065709816;
64     
65     /**
66      * The average number of solar days from one new moon to the next. This is the time
67      * it takes for the moon to return the same ecliptic longitude as the sun.
68      * It is longer than the sidereal month because the sun's longitude increases
69      * during the year due to the revolution of the earth around the sun.
70      * Approximately 29.53.
71      *
72      * @see #SIDEREAL_MONTH
73      * @internal
74      */

75     public static final double SYNODIC_MONTH = 29.530588853;
76     
77     /**
78      * The average number of days it takes
79      * for the moon to return to the same ecliptic longitude relative to the
80      * stellar background. This is referred to as the sidereal month.
81      * It is shorter than the synodic month due to
82      * the revolution of the earth around the sun.
83      * Approximately 27.32.
84      *
85      * @see #SYNODIC_MONTH
86      * @internal
87      */

88     public static final double SIDEREAL_MONTH = 27.32166;
89     
90     /**
91      * The average number number of days between successive vernal equinoxes.
92      * Due to the precession of the earth's
93      * axis, this is not precisely the same as the sidereal year.
94      * Approximately 365.24
95      *
96      * @see #SIDEREAL_YEAR
97      * @internal
98      */

99     public static final double TROPICAL_YEAR = 365.242191;
100     
101     /**
102      * The average number of days it takes
103      * for the sun to return to the same position against the fixed stellar
104      * background. This is the duration of one orbit of the earth about the sun
105      * as it would appear to an outside observer.
106      * Due to the precession of the earth's
107      * axis, this is not precisely the same as the tropical year.
108      * Approximately 365.25.
109      *
110      * @see #TROPICAL_YEAR
111      * @internal
112      */

113     public static final double SIDEREAL_YEAR = 365.25636;
114
115     //-------------------------------------------------------------------------
116
// Time-related constants
117
//-------------------------------------------------------------------------
118

119     /**
120      * The number of milliseconds in one second.
121      * @internal
122      */

123     public static final int SECOND_MS = 1000;
124
125     /**
126      * The number of milliseconds in one minute.
127      * @internal
128      */

129     public static final int MINUTE_MS = 60*SECOND_MS;
130
131     /**
132      * The number of milliseconds in one hour.
133      * @internal
134      */

135     public static final int HOUR_MS = 60*MINUTE_MS;
136
137     /**
138      * The number of milliseconds in one day.
139      * @internal
140      */

141     public static final long DAY_MS = 24*HOUR_MS;
142
143     /**
144      * The start of the julian day numbering scheme used by astronomers, which
145      * is 1/1/4713 BC (Julian), 12:00 GMT. This is given as the number of milliseconds
146      * since 1/1/1970 AD (Gregorian), a negative number.
147      * Note that julian day numbers and
148      * the Julian calendar are <em>not</em> the same thing. Also note that
149      * julian days start at <em>noon</em>, not midnight.
150      * @internal
151      */

152     public static final long JULIAN_EPOCH_MS = -210866760000000L;
153     
154 // static {
155
// Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
156
// cal.clear();
157
// cal.set(cal.ERA, 0);
158
// cal.set(cal.YEAR, 4713);
159
// cal.set(cal.MONTH, cal.JANUARY);
160
// cal.set(cal.DATE, 1);
161
// cal.set(cal.HOUR_OF_DAY, 12);
162
// System.out.println("1.5 Jan 4713 BC = " + cal.getTime().getTime());
163

164 // cal.clear();
165
// cal.set(cal.YEAR, 2000);
166
// cal.set(cal.MONTH, cal.JANUARY);
167
// cal.set(cal.DATE, 1);
168
// cal.add(cal.DATE, -1);
169
// System.out.println("0.0 Jan 2000 = " + cal.getTime().getTime());
170
// }
171

172     /**
173      * Milliseconds value for 0.0 January 2000 AD.
174      */

175     static final long EPOCH_2000_MS = 946598400000L;
176
177     //-------------------------------------------------------------------------
178
// Assorted private data used for conversions
179
//-------------------------------------------------------------------------
180

181     // My own copies of these so compilers are more likely to optimize them away
182
static private final double PI = 3.14159265358979323846;
183     static private final double PI2 = PI * 2.0;
184
185     static private final double RAD_HOUR = 12 / PI; // radians -> hours
186
static private final double DEG_RAD = PI / 180; // degrees -> radians
187
static private final double RAD_DEG = 180 / PI; // radians -> degrees
188

189     //-------------------------------------------------------------------------
190
// Constructors
191
//-------------------------------------------------------------------------
192

193     /**
194      * Construct a new <code>CalendarAstronomer</code> object that is initialized to
195      * the current date and time.
196      * @internal
197      */

198     public CalendarAstronomer() {
199         this(System.currentTimeMillis());
200     }
201     
202     /**
203      * Construct a new <code>CalendarAstronomer</code> object that is initialized to
204      * the specified date and time.
205      * @internal
206      */

207     public CalendarAstronomer(Date d) {
208         this(d.getTime());
209     }
210     
211     /**
212      * Construct a new <code>CalendarAstronomer</code> object that is initialized to
213      * the specified time. The time is expressed as a number of milliseconds since
214      * January 1, 1970 AD (Gregorian).
215      *
216      * @see java.util.Date#getTime()
217      * @internal
218      */

219     public CalendarAstronomer(long aTime) {
220         time = aTime;
221     }
222     
223     /**
224      * Construct a new <code>CalendarAstronomer</code> object with the given
225      * latitude and longitude. The object's time is set to the current
226      * date and time.
227      * <p>
228      * @param longitude The desired longitude, in <em>degrees</em> east of
229      * the Greenwich meridian.
230      *
231      * @param latitude The desired latitude, in <em>degrees</em>. Positive
232      * values signify North, negative South.
233      *
234      * @see java.util.Date#getTime()
235      * @internal
236      */

237     public CalendarAstronomer(double longitude, double latitude) {
238         this();
239         fLongitude = normPI(longitude * DEG_RAD);
240         fLatitude = normPI(latitude * DEG_RAD);
241         fGmtOffset = (long)(fLongitude * 24 * HOUR_MS / PI2);
242     }
243     
244     
245     //-------------------------------------------------------------------------
246
// Time and date getters and setters
247
//-------------------------------------------------------------------------
248

249     /**
250      * Set the current date and time of this <code>CalendarAstronomer</code> object. All
251      * astronomical calculations are performed based on this time setting.
252      *
253      * @param aTime the date and time, expressed as the number of milliseconds since
254      * 1/1/1970 0:00 GMT (Gregorian).
255      *
256      * @see #setDate
257      * @see #getTime
258      * @internal
259      */

260     public void setTime(long aTime) {
261         time = aTime;
262         clearCache();
263     }
264     
265     /**
266      * Set the current date and time of this <code>CalendarAstronomer</code> object. All
267      * astronomical calculations are performed based on this time setting.
268      *
269      * @param date the time and date, expressed as a <code>Date</code> object.
270      *
271      * @see #setTime
272      * @see #getDate
273      * @internal
274      */

275     public void setDate(Date date) {
276         setTime(date.getTime());
277     }
278     
279     /**
280      * Set the current date and time of this <code>CalendarAstronomer</code> object. All
281      * astronomical calculations are performed based on this time setting.
282      *
283      * @param jdn the desired time, expressed as a "julian day number",
284      * which is the number of elapsed days since
285      * 1/1/4713 BC (Julian), 12:00 GMT. Note that julian day
286      * numbers start at <em>noon</em>. To get the jdn for
287      * the corresponding midnight, subtract 0.5.
288      *
289      * @see #getJulianDay
290      * @see #JULIAN_EPOCH_MS
291      * @internal
292      */

293     public void setJulianDay(double jdn) {
294         time = (long)(jdn * DAY_MS) + JULIAN_EPOCH_MS;
295         clearCache();
296         julianDay = jdn;
297     }
298     
299     /**
300      * Get the current time of this <code>CalendarAstronomer</code> object,
301      * represented as the number of milliseconds since
302      * 1/1/1970 AD 0:00 GMT (Gregorian).
303      *
304      * @see #setTime
305      * @see #getDate
306      * @internal
307      */

308     public long getTime() {
309         return time;
310     }
311     
312     /**
313      * Get the current time of this <code>CalendarAstronomer</code> object,
314      * represented as a <code>Date</code> object.
315      *
316      * @see #setDate
317      * @see #getTime
318      * @internal
319      */

320     public Date getDate() {
321         return new Date(time);
322     }
323     
324     /**
325      * Get the current time of this <code>CalendarAstronomer</code> object,
326      * expressed as a "julian day number", which is the number of elapsed
327      * days since 1/1/4713 BC (Julian), 12:00 GMT.
328      *
329      * @see #setJulianDay
330      * @see #JULIAN_EPOCH_MS
331      * @internal
332      */

333     public double getJulianDay() {
334         if (julianDay == INVALID) {
335             julianDay = (double)(time - JULIAN_EPOCH_MS) / (double)DAY_MS;
336         }
337         return julianDay;
338     }
339     
340     /**
341      * Return this object's time expressed in julian centuries:
342      * the number of centuries after 1/1/1900 AD, 12:00 GMT
343      *
344      * @see #getJulianDay
345      * @internal
346      */

347     public double getJulianCentury() {
348         if (julianCentury == INVALID) {
349             julianCentury = (getJulianDay() - 2415020.0) / 36525;
350         }
351         return julianCentury;
352     }
353
354     /**
355      * Returns the current Greenwich sidereal time, measured in hours
356      * @internal
357      */

358     public double getGreenwichSidereal() {
359         if (siderealTime == INVALID) {
360             // See page 86 of "Practial Astronomy with your Calculator",
361
// by Peter Duffet-Smith, for details on the algorithm.
362

363             double UT = normalize((double)time/HOUR_MS, 24);
364         
365             siderealTime = normalize(getSiderealOffset() + UT*1.002737909, 24);
366         }
367         return siderealTime;
368     }
369     
370     private double getSiderealOffset() {
371         if (siderealT0 == INVALID) {
372             double JD = Math.floor(getJulianDay() - 0.5) + 0.5;
373             double S = JD - 2451545.0;
374             double T = S / 36525.0;
375             siderealT0 = normalize(6.697374558 + 2400.051336*T + 0.000025862*T*T, 24);
376         }
377         return siderealT0;
378     }
379     
380     /**
381      * Returns the current local sidereal time, measured in hours
382      * @internal
383      */

384     public double getLocalSidereal() {
385         return normalize(getGreenwichSidereal() + (double)fGmtOffset/HOUR_MS, 24);
386     }
387     
388     /**
389      * Converts local sidereal time to Universal Time.
390      *
391      * @param lst The Local Sidereal Time, in hours since sidereal midnight
392      * on this object's current date.
393      *
394      * @return The corresponding Universal Time, in milliseconds since
395      * 1 Jan 1970, GMT.
396      */

397     private long lstToUT(double lst) {
398         // Convert to local mean time
399
double lt = normalize((lst - getSiderealOffset()) * 0.9972695663, 24);
400         
401         // Then find local midnight on this day
402
long base = DAY_MS * ((time + fGmtOffset)/DAY_MS) - fGmtOffset;
403         
404         //out(" lt =" + lt + " hours");
405
//out(" base=" + new Date(base));
406

407         return base + (long)(lt * HOUR_MS);
408     }
409     
410     
411     //-------------------------------------------------------------------------
412
// Coordinate transformations, all based on the current time of this object
413
//-------------------------------------------------------------------------
414

415     /**
416      * Convert from ecliptic to equatorial coordinates.
417      *
418      * @param ecliptic A point in the sky in ecliptic coordinates.
419      * @return The corresponding point in equatorial coordinates.
420      * @internal
421      */

422     public final Equatorial eclipticToEquatorial(Ecliptic ecliptic)
423     {
424         return eclipticToEquatorial(ecliptic.longitude, ecliptic.latitude);
425     }
426
427     /**
428      * Convert from ecliptic to equatorial coordinates.
429      *
430      * @param eclipLong The ecliptic longitude
431      * @param eclipLat The ecliptic latitude
432      *
433      * @return The corresponding point in equatorial coordinates.
434      * @internal
435      */

436     public final Equatorial eclipticToEquatorial(double eclipLong, double eclipLat)
437     {
438         // See page 42 of "Practial Astronomy with your Calculator",
439
// by Peter Duffet-Smith, for details on the algorithm.
440

441         double obliq = eclipticObliquity();
442         double sinE = Math.sin(obliq);
443         double cosE = Math.cos(obliq);
444         
445         double sinL = Math.sin(eclipLong);
446         double cosL = Math.cos(eclipLong);
447         
448         double sinB = Math.sin(eclipLat);
449         double cosB = Math.cos(eclipLat);
450         double tanB = Math.tan(eclipLat);
451         
452         return new Equatorial(Math.atan2(sinL*cosE - tanB*sinE, cosL),
453                                Math.asin(sinB*cosE + cosB*sinE*sinL) );
454     }
455
456     /**
457      * Convert from ecliptic longitude to equatorial coordinates.
458      *
459      * @param eclipLong The ecliptic longitude
460      *
461      * @return The corresponding point in equatorial coordinates.
462      * @internal
463      */

464     public final Equatorial eclipticToEquatorial(double eclipLong)
465     {
466         return eclipticToEquatorial(eclipLong, 0); // TODO: optimize
467
}
468
469     /**
470      * @internal
471      */

472     public Horizon eclipticToHorizon(double eclipLong)
473     {
474         Equatorial equatorial = eclipticToEquatorial(eclipLong);
475         
476         double H = getLocalSidereal()*PI/12 - equatorial.ascension; // Hour-angle
477

478         double sinH = Math.sin(H);
479         double cosH = Math.cos(H);
480         double sinD = Math.sin(equatorial.declination);
481         double cosD = Math.cos(equatorial.declination);
482         double sinL = Math.sin(fLatitude);
483         double cosL = Math.cos(fLatitude);
484         
485         double altitude = Math.asin(sinD*sinL + cosD*cosL*cosH);
486         double azimuth = Math.atan2(-cosD*cosL*sinH, sinD - sinL * Math.sin(altitude));
487
488         return new Horizon(azimuth, altitude);
489     }
490
491     
492     //-------------------------------------------------------------------------
493
// The Sun
494
//-------------------------------------------------------------------------
495

496     //
497
// Parameters of the Sun's orbit as of the epoch Jan 0.0 1990
498
// Angles are in radians (after multiplying by PI/180)
499
//
500
static final double JD_EPOCH = 2447891.5; // Julian day of epoch
501

502     static final double SUN_ETA_G = 279.403303 * PI/180; // Ecliptic longitude at epoch
503
static final double SUN_OMEGA_G = 282.768422 * PI/180; // Ecliptic longitude of perigee
504
static final double SUN_E = 0.016713; // Eccentricity of orbit
505
//double sunR0 = 1.495585e8; // Semi-major axis in KM
506
//double sunTheta0 = 0.533128 * PI/180; // Angular diameter at R0
507

508     // The following three methods, which compute the sun parameters
509
// given above for an arbitrary epoch (whatever time the object is
510
// set to), make only a small difference as compared to using the
511
// above constants. E.g., Sunset times might differ by ~12
512
// seconds. Furthermore, the eta-g computation is befuddled by
513
// Duffet-Smith's incorrect coefficients (p.86). I've corrected
514
// the first-order coefficient but the others may be off too - no
515
// way of knowing without consulting another source.
516

517 // /**
518
// * Return the sun's ecliptic longitude at perigee for the current time.
519
// * See Duffett-Smith, p. 86.
520
// * @return radians
521
// */
522
// private double getSunOmegaG() {
523
// double T = getJulianCentury();
524
// return (281.2208444 + (1.719175 + 0.000452778*T)*T) * DEG_RAD;
525
// }
526

527 // /**
528
// * Return the sun's ecliptic longitude for the current time.
529
// * See Duffett-Smith, p. 86.
530
// * @return radians
531
// */
532
// private double getSunEtaG() {
533
// double T = getJulianCentury();
534
// //return (279.6966778 + (36000.76892 + 0.0003025*T)*T) * DEG_RAD;
535
// //
536
// // The above line is from Duffett-Smith, and yields manifestly wrong
537
// // results. The below constant is derived empirically to match the
538
// // constant he gives for the 1990 EPOCH.
539
// //
540
// return (279.6966778 + (-0.3262541582718024 + 0.0003025*T)*T) * DEG_RAD;
541
// }
542

543 // /**
544
// * Return the sun's eccentricity of orbit for the current time.
545
// * See Duffett-Smith, p. 86.
546
// * @return double
547
// */
548
// private double getSunE() {
549
// double T = getJulianCentury();
550
// return 0.01675104 - (0.0000418 + 0.000000126*T)*T;
551
// }
552

553     /**
554      * The longitude of the sun at the time specified by this object.
555      * The longitude is measured in radians along the ecliptic
556      * from the "first point of Aries," the point at which the ecliptic
557      * crosses the earth's equatorial plane at the vernal equinox.
558      * <p>
559      * Currently, this method uses an approximation of the two-body Kepler's
560      * equation for the earth and the sun. It does not take into account the
561      * perturbations caused by the other planets, the moon, etc.
562      * @internal
563      */

564     public double getSunLongitude()
565     {
566         // See page 86 of "Practial Astronomy with your Calculator",
567
// by Peter Duffet-Smith, for details on the algorithm.
568

569         if (sunLongitude == INVALID) {
570             double[] result = getSunLongitude(getJulianDay());
571             sunLongitude = result[0];
572             meanAnomalySun = result[1];
573         }
574         return sunLongitude;
575     }
576   
577     /**
578      * TODO Make this public when the entire class is package-private.
579      */

580     /*public*/ double[] getSunLongitude(double julianDay)
581     {
582         // See page 86 of "Practial Astronomy with your Calculator",
583
// by Peter Duffet-Smith, for details on the algorithm.
584

585         double day = julianDay - JD_EPOCH; // Days since epoch
586

587         // Find the angular distance the sun in a fictitious
588
// circular orbit has travelled since the epoch.
589
double epochAngle = norm2PI(PI2/TROPICAL_YEAR*day);
590         
591         // The epoch wasn't at the sun's perigee; find the angular distance
592
// since perigee, which is called the "mean anomaly"
593
double meanAnomaly = norm2PI(epochAngle + SUN_ETA_G - SUN_OMEGA_G);
594         
595         // Now find the "true anomaly", e.g. the real solar longitude
596
// by solving Kepler's equation for an elliptical orbit
597
// NOTE: The 3rd ed. of the book lists omega_g and eta_g in different
598
// equations; omega_g is to be correct.
599
return new double[] {
600             norm2PI(trueAnomaly(meanAnomaly, SUN_E) + SUN_OMEGA_G),
601             meanAnomaly
602         };
603     }
604
605     /**
606      * The position of the sun at this object's current date and time,
607      * in equatorial coordinates.
608      * @internal
609      */

610     public Equatorial getSunPosition() {
611         return eclipticToEquatorial(getSunLongitude(), 0);
612     }
613     
614     private static class SolarLongitude {
615         double value;
616         SolarLongitude(double val) { value = val; }
617     }
618     
619     /**
620      * Constant representing the vernal equinox.
621      * For use with {@link #getSunTime(SolarLongitude, boolean) getSunTime}.
622      * Note: In this case, "vernal" refers to the northern hemisphere's seasons.
623      * @internal
624      */

625     public static final SolarLongitude VERNAL_EQUINOX = new SolarLongitude(0);
626     
627     /**
628      * Constant representing the summer solstice.
629      * For use with {@link #getSunTime(SolarLongitude, boolean) getSunTime}.
630      * Note: In this case, "summer" refers to the northern hemisphere's seasons.
631      * @internal
632      */

633     public static final SolarLongitude SUMMER_SOLSTICE = new SolarLongitude(PI/2);
634     
635     /**
636      * Constant representing the autumnal equinox.
637      * For use with {@link #getSunTime(SolarLongitude, boolean) getSunTime}.
638      * Note: In this case, "autumn" refers to the northern hemisphere's seasons.
639      * @internal
640      */

641     public static final SolarLongitude AUTUMN_EQUINOX = new SolarLongitude(PI);
642     
643     /**
644      * Constant representing the winter solstice.
645      * For use with {@link #getSunTime(SolarLongitude, boolean) getSunTime}.
646      * Note: In this case, "winter" refers to the northern hemisphere's seasons.
647      * @internal
648      */

649     public static final SolarLongitude WINTER_SOLSTICE = new SolarLongitude((PI*3)/2);
650     
651     /**
652      * Find the next time at which the sun's ecliptic longitude will have
653      * the desired value.
654      * @internal
655      */

656     public long getSunTime(double desired, boolean next)
657     {
658         return timeOfAngle( new AngleFunc() { public double eval() { return getSunLongitude(); } },
659                             desired,
660                             TROPICAL_YEAR,
661                             MINUTE_MS,
662                             next);
663     }
664     
665     /**
666      * Find the next time at which the sun's ecliptic longitude will have
667      * the desired value.
668      * @internal
669      */

670     public long getSunTime(SolarLongitude desired, boolean next) {
671         return getSunTime(desired.value, next);
672     }
673     
674     /**
675      * Returns the time (GMT) of sunrise or sunset on the local date to which
676      * this calendar is currently set.
677      *
678      * NOTE: This method only works well if this object is set to a
679      * time near local noon. Because of variations between the local
680      * official time zone and the geographic longitude, the
681      * computation can flop over into an adjacent day if this object
682      * is set to a time near local midnight.
683      *
684      * @internal
685      */

686     public long getSunRiseSet(boolean rise)
687     {
688         long t0 = time;
689
690         // Make a rough guess: 6am or 6pm local time on the current day
691
long noon = ((time + fGmtOffset)/DAY_MS)*DAY_MS - fGmtOffset + 12*HOUR_MS;
692         
693         setTime(noon + (long)((rise ? -6 : 6) * HOUR_MS));
694         
695         long t = riseOrSet(new CoordFunc() {
696                             public Equatorial eval() { return getSunPosition(); }
697                          },
698                          rise,
699                          .533 * DEG_RAD, // Angular Diameter
700
34 /60.0 * DEG_RAD, // Refraction correction
701
MINUTE_MS / 12); // Desired accuracy
702

703         setTime(t0);
704         return t;
705     }
706
707 // Commented out - currently unused. ICU 2.6, Alan
708
// //-------------------------------------------------------------------------
709
// // Alternate Sun Rise/Set
710
// // See Duffett-Smith p.93
711
// //-------------------------------------------------------------------------
712
//
713
// // This yields worse results (as compared to USNO data) than getSunRiseSet().
714
// /**
715
// * TODO Make this public when the entire class is package-private.
716
// */
717
// /*public*/ long getSunRiseSet2(boolean rise) {
718
// // 1. Calculate coordinates of the sun's center for midnight
719
// double jd = Math.floor(getJulianDay() - 0.5) + 0.5;
720
// double[] sl = getSunLongitude(jd);
721
// double lambda1 = sl[0];
722
// Equatorial pos1 = eclipticToEquatorial(lambda1, 0);
723
//
724
// // 2. Add ... to lambda to get position 24 hours later
725
// double lambda2 = lambda1 + 0.985647*DEG_RAD;
726
// Equatorial pos2 = eclipticToEquatorial(lambda2, 0);
727
//
728
// // 3. Calculate LSTs of rising and setting for these two positions
729
// double tanL = Math.tan(fLatitude);
730
// double H = Math.acos(-tanL * Math.tan(pos1.declination));
731
// double lst1r = (PI2 + pos1.ascension - H) * 24 / PI2;
732
// double lst1s = (pos1.ascension + H) * 24 / PI2;
733
// H = Math.acos(-tanL * Math.tan(pos2.declination));
734
// double lst2r = (PI2-H + pos2.ascension ) * 24 / PI2;
735
// double lst2s = (H + pos2.ascension ) * 24 / PI2;
736
// if (lst1r > 24) lst1r -= 24;
737
// if (lst1s > 24) lst1s -= 24;
738
// if (lst2r > 24) lst2r -= 24;
739
// if (lst2s > 24) lst2s -= 24;
740
//
741
// // 4. Convert LSTs to GSTs. If GST1 > GST2, add 24 to GST2.
742
// double gst1r = lstToGst(lst1r);
743
// double gst1s = lstToGst(lst1s);
744
// double gst2r = lstToGst(lst2r);
745
// double gst2s = lstToGst(lst2s);
746
// if (gst1r > gst2r) gst2r += 24;
747
// if (gst1s > gst2s) gst2s += 24;
748
//
749
// // 5. Calculate GST at 0h UT of this date
750
// double t00 = utToGst(0);
751
//
752
// // 6. Calculate GST at 0h on the observer's longitude
753
// double offset = Math.round(fLongitude*12/PI); // p.95 step 6; he _rounds_ to nearest 15 deg.
754
// double t00p = t00 - offset*1.002737909;
755
// if (t00p < 0) t00p += 24; // do NOT normalize
756
//
757
// // 7. Adjust
758
// if (gst1r < t00p) {
759
// gst1r += 24;
760
// gst2r += 24;
761
// }
762
// if (gst1s < t00p) {
763
// gst1s += 24;
764
// gst2s += 24;
765
// }
766
//
767
// // 8.
768
// double gstr = (24.07*gst1r-t00*(gst2r-gst1r))/(24.07+gst1r-gst2r);
769
// double gsts = (24.07*gst1s-t00*(gst2s-gst1s))/(24.07+gst1s-gst2s);
770
//
771
// // 9. Correct for parallax, refraction, and sun's diameter
772
// double dec = (pos1.declination + pos2.declination) / 2;
773
// double psi = Math.acos(Math.sin(fLatitude) / Math.cos(dec));
774
// double x = 0.830725 * DEG_RAD; // parallax+refraction+diameter
775
// double y = Math.asin(Math.sin(x) / Math.sin(psi)) * RAD_DEG;
776
// double delta_t = 240 * y / Math.cos(dec) / 3600; // hours
777
//
778
// // 10. Add correction to GSTs, subtract from GSTr
779
// gstr -= delta_t;
780
// gsts += delta_t;
781
//
782
// // 11. Convert GST to UT and then to local civil time
783
// double ut = gstToUt(rise ? gstr : gsts);
784
// //System.out.println((rise?"rise=":"set=") + ut + ", delta_t=" + delta_t);
785
// long midnight = DAY_MS * (time / DAY_MS); // Find UT midnight on this day
786
// return midnight + (long) (ut * 3600000);
787
// }
788

789 // Commented out - currently unused. ICU 2.6, Alan
790
// /**
791
// * Convert local sidereal time to Greenwich sidereal time.
792
// * Section 15. Duffett-Smith p.21
793
// * @param lst in hours (0..24)
794
// * @return GST in hours (0..24)
795
// */
796
// double lstToGst(double lst) {
797
// double delta = fLongitude * 24 / PI2;
798
// return normalize(lst - delta, 24);
799
// }
800

801 // Commented out - currently unused. ICU 2.6, Alan
802
// /**
803
// * Convert UT to GST on this date.
804
// * Section 12. Duffett-Smith p.17
805
// * @param ut in hours
806
// * @return GST in hours
807
// */
808
// double utToGst(double ut) {
809
// return normalize(getT0() + ut*1.002737909, 24);
810
// }
811

812 // Commented out - currently unused. ICU 2.6, Alan
813
// /**
814
// * Convert GST to UT on this date.
815
// * Section 13. Duffett-Smith p.18
816
// * @param gst in hours
817
// * @return UT in hours
818
// */
819
// double gstToUt(double gst) {
820
// return normalize(gst - getT0(), 24) * 0.9972695663;
821
// }
822

823 // Commented out - currently unused. ICU 2.6, Alan
824
// double getT0() {
825
// // Common computation for UT <=> GST
826
//
827
// // Find JD for 0h UT
828
// double jd = Math.floor(getJulianDay() - 0.5) + 0.5;
829
//
830
// double s = jd - 2451545.0;
831
// double t = s / 36525.0;
832
// double t0 = 6.697374558 + (2400.051336 + 0.000025862*t)*t;
833
// return t0;
834
// }
835

836 // Commented out - currently unused. ICU 2.6, Alan
837
// //-------------------------------------------------------------------------
838
// // Alternate Sun Rise/Set
839
// // See sci.astro FAQ
840
// // http://www.faqs.org/faqs/astronomy/faq/part3/section-5.html
841
// //-------------------------------------------------------------------------
842
//
843
// // Note: This method appears to produce inferior accuracy as
844
// // compared to getSunRiseSet().
845
//
846
// /**
847
// * TODO Make this public when the entire class is package-private.
848
// */
849
// /*public*/ long getSunRiseSet3(boolean rise) {
850
//
851
// // Compute day number for 0.0 Jan 2000 epoch
852
// double d = (double)(time - EPOCH_2000_MS) / DAY_MS;
853
//
854
// // Now compute the Local Sidereal Time, LST:
855
// //
856
// double LST = 98.9818 + 0.985647352 * d + /*UT*15 + long*/
857
// fLongitude*RAD_DEG;
858
// //
859
// // (east long. positive). Note that LST is here expressed in degrees,
860
// // where 15 degrees corresponds to one hour. Since LST really is an angle,
861
// // it's convenient to use one unit---degrees---throughout.
862
//
863
// // COMPUTING THE SUN'S POSITION
864
// // ----------------------------
865
// //
866
// // To be able to compute the Sun's rise/set times, you need to be able to
867
// // compute the Sun's position at any time. First compute the "day
868
// // number" d as outlined above, for the desired moment. Next compute:
869
// //
870
// double oblecl = 23.4393 - 3.563E-7 * d;
871
// //
872
// double w = 282.9404 + 4.70935E-5 * d;
873
// double M = 356.0470 + 0.9856002585 * d;
874
// double e = 0.016709 - 1.151E-9 * d;
875
// //
876
// // This is the obliquity of the ecliptic, plus some of the elements of
877
// // the Sun's apparent orbit (i.e., really the Earth's orbit): w =
878
// // argument of perihelion, M = mean anomaly, e = eccentricity.
879
// // Semi-major axis is here assumed to be exactly 1.0 (while not strictly
880
// // true, this is still an accurate approximation). Next compute E, the
881
// // eccentric anomaly:
882
// //
883
// double E = M + e*(180/PI) * Math.sin(M*DEG_RAD) * ( 1.0 + e*Math.cos(M*DEG_RAD) );
884
// //
885
// // where E and M are in degrees. This is it---no further iterations are
886
// // needed because we know e has a sufficiently small value. Next compute
887
// // the true anomaly, v, and the distance, r:
888
// //
889
// /* r * cos(v) = */ double A = Math.cos(E*DEG_RAD) - e;
890
// /* r * sin(v) = */ double B = Math.sqrt(1 - e*e) * Math.sin(E*DEG_RAD);
891
// //
892
// // and
893
// //
894
// // r = sqrt( A*A + B*B )
895
// double v = Math.atan2( B, A )*RAD_DEG;
896
// //
897
// // The Sun's true longitude, slon, can now be computed:
898
// //
899
// double slon = v + w;
900
// //
901
// // Since the Sun is always at the ecliptic (or at least very very close to
902
// // it), we can use simplified formulae to convert slon (the Sun's ecliptic
903
// // longitude) to sRA and sDec (the Sun's RA and Dec):
904
// //
905
// // sin(slon) * cos(oblecl)
906
// // tan(sRA) = -------------------------
907
// // cos(slon)
908
// //
909
// // sin(sDec) = sin(oblecl) * sin(slon)
910
// //
911
// // As was the case when computing az, the Azimuth, if possible use an
912
// // atan2() function to compute sRA.
913
//
914
// double sRA = Math.atan2(Math.sin(slon*DEG_RAD) * Math.cos(oblecl*DEG_RAD), Math.cos(slon*DEG_RAD))*RAD_DEG;
915
//
916
// double sin_sDec = Math.sin(oblecl*DEG_RAD) * Math.sin(slon*DEG_RAD);
917
// double sDec = Math.asin(sin_sDec)*RAD_DEG;
918
//
919
// // COMPUTING RISE AND SET TIMES
920
// // ----------------------------
921
// //
922
// // To compute when an object rises or sets, you must compute when it
923
// // passes the meridian and the HA of rise/set. Then the rise time is
924
// // the meridian time minus HA for rise/set, and the set time is the
925
// // meridian time plus the HA for rise/set.
926
// //
927
// // To find the meridian time, compute the Local Sidereal Time at 0h local
928
// // time (or 0h UT if you prefer to work in UT) as outlined above---name
929
// // that quantity LST0. The Meridian Time, MT, will now be:
930
// //
931
// // MT = RA - LST0
932
// double MT = normalize(sRA - LST, 360);
933
// //
934
// // where "RA" is the object's Right Ascension (in degrees!). If negative,
935
// // add 360 deg to MT. If the object is the Sun, leave the time as it is,
936
// // but if it's stellar, multiply MT by 365.2422/366.2422, to convert from
937
// // sidereal to solar time. Now, compute HA for rise/set, name that
938
// // quantity HA0:
939
// //
940
// // sin(h0) - sin(lat) * sin(Dec)
941
// // cos(HA0) = ---------------------------------
942
// // cos(lat) * cos(Dec)
943
// //
944
// // where h0 is the altitude selected to represent rise/set. For a purely
945
// // mathematical horizon, set h0 = 0 and simplify to:
946
// //
947
// // cos(HA0) = - tan(lat) * tan(Dec)
948
// //
949
// // If you want to account for refraction on the atmosphere, set h0 = -35/60
950
// // degrees (-35 arc minutes), and if you want to compute the rise/set times
951
// // for the Sun's upper limb, set h0 = -50/60 (-50 arc minutes).
952
// //
953
// double h0 = -50/60 * DEG_RAD;
954
//
955
// double HA0 = Math.acos(
956
// (Math.sin(h0) - Math.sin(fLatitude) * sin_sDec) /
957
// (Math.cos(fLatitude) * Math.cos(sDec*DEG_RAD)))*RAD_DEG;
958
//
959
// // When HA0 has been computed, leave it as it is for the Sun but multiply
960
// // by 365.2422/366.2422 for stellar objects, to convert from sidereal to
961
// // solar time. Finally compute:
962
// //
963
// // Rise time = MT - HA0
964
// // Set time = MT + HA0
965
// //
966
// // convert the times from degrees to hours by dividing by 15.
967
// //
968
// // If you'd like to check that your calculations are accurate or just
969
// // need a quick result, check the USNO's Sun or Moon Rise/Set Table,
970
// // <URL:http://aa.usno.navy.mil/AA/data/docs/RS_OneYear.html>.
971
//
972
// double result = MT + (rise ? -HA0 : HA0); // in degrees
973
//
974
// // Find UT midnight on this day
975
// long midnight = DAY_MS * (time / DAY_MS);
976
//
977
// return midnight + (long) (result * 3600000 / 15);
978
// }
979

980     //-------------------------------------------------------------------------
981
// The Moon
982
//-------------------------------------------------------------------------
983

984     static final double moonL0 = 318.351648 * PI/180; // Mean long. at epoch
985
static final double moonP0 = 36.340410 * PI/180; // Mean long. of perigee
986
static final double moonN0 = 318.510107 * PI/180; // Mean long. of node
987
static final double moonI = 5.145366 * PI/180; // Inclination of orbit
988
static final double moonE = 0.054900; // Eccentricity of orbit
989

990     // These aren't used right now
991
static final double moonA = 3.84401e5; // semi-major axis (km)
992
static final double moonT0 = 0.5181 * PI/180; // Angular size at distance A
993
static final double moonPi = 0.9507 * PI/180; // Parallax at distance A
994

995     /**
996      * The position of the moon at the time set on this
997      * object, in equatorial coordinates.
998      * @internal
999      */

1000    public Equatorial getMoonPosition()
1001    {
1002        //
1003
// See page 142 of "Practial Astronomy with your Calculator",
1004
// by Peter Duffet-Smith, for details on the algorithm.
1005
//
1006
if (moonPosition == null) {
1007            // Calculate the solar longitude. Has the side effect of
1008
// filling in "meanAnomalySun" as well.
1009
double sunLongitude = getSunLongitude();
1010            
1011            //
1012
// Find the # of days since the epoch of our orbital parameters.
1013
// TODO: Convert the time of day portion into ephemeris time
1014
//
1015
double day = getJulianDay() - JD_EPOCH; // Days since epoch
1016

1017            // Calculate the mean longitude and anomaly of the moon, based on
1018
// a circular orbit. Similar to the corresponding solar calculation.
1019
double meanLongitude = norm2PI(13.1763966*PI/180*day + moonL0);
1020            double meanAnomalyMoon = norm2PI(meanLongitude - 0.1114041*PI/180 * day - moonP0);
1021            
1022            //
1023
// Calculate the following corrections:
1024
// Evection: the sun's gravity affects the moon's eccentricity
1025
// Annual Eqn: variation in the effect due to earth-sun distance
1026
// A3: correction factor (for ???)
1027
//
1028
double evection = 1.2739*PI/180 * Math.sin(2 * (meanLongitude - sunLongitude)
1029                                                - meanAnomalyMoon);
1030            double annual = 0.1858*PI/180 * Math.sin(meanAnomalySun);
1031            double a3 = 0.3700*PI/180 * Math.sin(meanAnomalySun);
1032
1033            meanAnomalyMoon += evection - annual - a3;
1034            
1035            //
1036
// More correction factors:
1037
// center equation of the center correction
1038
// a4 yet another error correction (???)
1039
//
1040
// TODO: Skip the equation of the center correction and solve Kepler's eqn?
1041
//
1042
double center = 6.2886*PI/180 * Math.sin(meanAnomalyMoon);
1043            double a4 = 0.2140*PI/180 * Math.sin(2 * meanAnomalyMoon);
1044            
1045            // Now find the moon's corrected longitude
1046
moonLongitude = meanLongitude + evection + center - annual + a4;
1047
1048            //
1049
// And finally, find the variation, caused by the fact that the sun's
1050
// gravitational pull on the moon varies depending on which side of
1051
// the earth the moon is on
1052
//
1053
double variation = 0.6583*PI/180 * Math.sin(2*(moonLongitude - sunLongitude));
1054            
1055            moonLongitude += variation;
1056            
1057            //
1058
// What we've calculated so far is the moon's longitude in the plane
1059
// of its own orbit. Now map to the ecliptic to get the latitude
1060
// and longitude. First we need to find the longitude of the ascending
1061
// node, the position on the ecliptic where it is crossed by the moon's
1062
// orbit as it crosses from the southern to the northern hemisphere.
1063
//
1064
double nodeLongitude = norm2PI(moonN0 - 0.0529539*PI/180 * day);
1065
1066            nodeLongitude -= 0.16*PI/180 * Math.sin(meanAnomalySun);
1067
1068            double y = Math.sin(moonLongitude - nodeLongitude);
1069            double x = Math.cos(moonLongitude - nodeLongitude);
1070            
1071            moonEclipLong = Math.atan2(y*Math.cos(moonI), x) + nodeLongitude;
1072            double moonEclipLat = Math.asin(y * Math.sin(moonI));
1073
1074            moonPosition = eclipticToEquatorial(moonEclipLong, moonEclipLat);
1075        }
1076        return moonPosition;
1077    }
1078    
1079    /**
1080     * The "age" of the moon at the time specified in this object.
1081     * This is really the angle between the
1082     * current ecliptic longitudes of the sun and the moon,
1083     * measured in radians.
1084     *
1085     * @see #getMoonPhase
1086     * @internal
1087     */

1088    public double getMoonAge() {
1089        // See page 147 of "Practial Astronomy with your Calculator",
1090
// by Peter Duffet-Smith, for details on the algorithm.
1091
//
1092
// Force the moon's position to be calculated. We're going to use
1093
// some the intermediate results cached during that calculation.
1094
//
1095
getMoonPosition();
1096        
1097        return norm2PI(moonEclipLong - sunLongitude);
1098    }
1099    
1100    /**
1101     * Calculate the phase of the moon at the time set in this object.
1102     * The returned phase is a <code>double</code> in the range
1103     * <code>0 <= phase < 1</code>, interpreted as follows:
1104     * <ul>
1105     * <li>0.00: New moon
1106     * <li>0.25: First quarter
1107     * <li>0.50: Full moon
1108     * <li>0.75: Last quarter
1109     * </ul>
1110     *
1111     * @see #getMoonAge
1112     * @internal
1113     */

1114    public double getMoonPhase() {
1115        // See page 147 of "Practial Astronomy with your Calculator",
1116
// by Peter Duffet-Smith, for details on the algorithm.
1117
return 0.5 * (1 - Math.cos(getMoonAge()));
1118    }
1119    
1120    private static class MoonAge {
1121        double value;
1122        MoonAge(double val) { value = val; }
1123    }
1124    
1125    /**
1126     * Constant representing a new moon.
1127     * For use with {@link #getMoonTime(MoonAge, boolean) getMoonTime}
1128     * @internal
1129     */

1130    public static final MoonAge NEW_MOON = new MoonAge(0);
1131
1132    /**
1133     * Constant representing the moon's first quarter.
1134     * For use with {@link #getMoonTime(MoonAge, boolean) getMoonTime}
1135     * @internal
1136     */

1137    public static final MoonAge FIRST_QUARTER = new MoonAge(PI/2);
1138    
1139    /**
1140     * Constant representing a full moon.
1141     * For use with {@link #getMoonTime(MoonAge, boolean) getMoonTime}
1142     * @internal
1143     */

1144    public static final MoonAge FULL_MOON = new MoonAge(PI);
1145    
1146    /**
1147     * Constant representing the moon's last quarter.
1148     * For use with {@link #getMoonTime(MoonAge, boolean) getMoonTime}
1149     * @internal
1150     */

1151    public static final MoonAge LAST_QUARTER = new MoonAge((PI*3)/2);
1152    
1153    /**
1154     * Find the next or previous time at which the Moon's ecliptic
1155     * longitude will have the desired value.
1156     * <p>
1157     * @param desired The desired longitude.
1158     * @param next <tt>true</tt> if the next occurrance of the phase
1159     * is desired, <tt>false</tt> for the previous occurrance.
1160     * @internal
1161     */

1162    public long getMoonTime(double desired, boolean next)
1163    {
1164        return timeOfAngle( new AngleFunc() {
1165                            public double eval() { return getMoonAge(); } },
1166                            desired,
1167                            SYNODIC_MONTH,
1168                            MINUTE_MS,
1169                            next);
1170    }
1171    
1172    /**
1173     * Find the next or previous time at which the moon will be in the
1174     * desired phase.
1175     * <p>
1176     * @param desired The desired phase of the moon.
1177     * @param next <tt>true</tt> if the next occurrance of the phase
1178     * is desired, <tt>false</tt> for the previous occurrance.
1179     * @internal
1180     */

1181    public long getMoonTime(MoonAge desired, boolean next) {
1182        return getMoonTime(desired.value, next);
1183    }
1184    
1185    /**
1186     * Returns the time (GMT) of sunrise or sunset on the local date to which
1187     * this calendar is currently set.
1188     * @internal
1189     */

1190    public long getMoonRiseSet(boolean rise)
1191    {
1192        return riseOrSet(new CoordFunc() {
1193                            public Equatorial eval() { return getMoonPosition(); }
1194                         },
1195                         rise,
1196                         .533 * DEG_RAD, // Angular Diameter
1197
34 /60.0 * DEG_RAD, // Refraction correction
1198
MINUTE_MS); // Desired accuracy
1199
}
1200
1201    //-------------------------------------------------------------------------
1202
// Interpolation methods for finding the time at which a given event occurs
1203
//-------------------------------------------------------------------------
1204

1205    private interface AngleFunc {
1206        public double eval();
1207    }
1208    
1209    private long timeOfAngle(AngleFunc func, double desired,
1210                             double periodDays, long epsilon, boolean next)
1211    {
1212        // Find the value of the function at the current time
1213
double lastAngle = func.eval();
1214        
1215        // Find out how far we are from the desired angle
1216
double deltaAngle = norm2PI(desired - lastAngle) ;
1217        
1218        // Using the average period, estimate the next (or previous) time at
1219
// which the desired angle occurs.
1220
double deltaT = (deltaAngle + (next ? 0 : -PI2)) * (periodDays*DAY_MS) / PI2;
1221        
1222        double lastDeltaT = deltaT; // Liu
1223
long startTime = time; // Liu
1224

1225        setTime(time + (long)deltaT);
1226
1227        // Now iterate until we get the error below epsilon. Throughout
1228
// this loop we use normPI to get values in the range -Pi to Pi,
1229
// since we're using them as correction factors rather than absolute angles.
1230
do {
1231            // Evaluate the function at the time we've estimated
1232
double angle = func.eval();
1233
1234            // Find the # of milliseconds per radian at this point on the curve
1235
double factor = Math.abs(deltaT / normPI(angle-lastAngle));
1236
1237            // Correct the time estimate based on how far off the angle is
1238
deltaT = normPI(desired - angle) * factor;
1239            
1240            // HACK:
1241
//
1242
// If abs(deltaT) begins to diverge we need to quit this loop.
1243
// This only appears to happen when attempting to locate, for
1244
// example, a new moon on the day of the new moon. E.g.:
1245
//
1246
// This result is correct:
1247
// newMoon(7508(Mon Jul 23 00:00:00 CST 1990,false))=
1248
// Sun Jul 22 10:57:41 CST 1990
1249
//
1250
// But attempting to make the same call a day earlier causes deltaT
1251
// to diverge:
1252
// CalendarAstronomer.timeOfAngle() diverging: 1.348508727575625E9 ->
1253
// 1.3649828540224032E9
1254
// newMoon(7507(Sun Jul 22 00:00:00 CST 1990,false))=
1255
// Sun Jul 08 13:56:15 CST 1990
1256
//
1257
// As a temporary solution, we catch this specific condition and
1258
// adjust our start time by one eighth period days (either forward
1259
// or backward) and try again.
1260
// Liu 11/9/00
1261
if (Math.abs(deltaT) > Math.abs(lastDeltaT)) {
1262                long delta = (long) (periodDays * DAY_MS / 8);
1263                setTime(startTime + (next ? delta : -delta));
1264                return timeOfAngle(func, desired, periodDays, epsilon, next);
1265            }
1266
1267            lastDeltaT = deltaT;
1268            lastAngle = angle;
1269
1270            setTime(time + (long)deltaT);
1271        }
1272        while (Math.abs(deltaT) > epsilon);
1273        
1274        return time;
1275    }
1276    
1277    private interface CoordFunc {
1278        public Equatorial eval();
1279    }
1280    
1281    private long riseOrSet(CoordFunc func, boolean rise,
1282                           double diameter, double refraction,
1283                           long epsilon)
1284    {
1285        Equatorial pos = null;
1286        double tanL = Math.tan(fLatitude);
1287        long deltaT = Long.MAX_VALUE;
1288        int count = 0;
1289        
1290        //
1291
// Calculate the object's position at the current time, then use that
1292
// position to calculate the time of rising or setting. The position
1293
// will be different at that time, so iterate until the error is allowable.
1294
//
1295
do {
1296            // See "Practical Astronomy With Your Calculator, section 33.
1297
pos = func.eval();
1298            double angle = Math.acos(-tanL * Math.tan(pos.declination));
1299            double lst = ((rise ? PI2-angle : angle) + pos.ascension ) * 24 / PI2;
1300                         
1301            // Convert from LST to Universal Time.
1302
long newTime = lstToUT( lst );
1303            
1304            deltaT = newTime - time;
1305            setTime(newTime);
1306        }
1307        while (++ count < 5 && Math.abs(deltaT) > epsilon);
1308
1309        // Calculate the correction due to refraction and the object's angular diameter
1310
double cosD = Math.cos(pos.declination);
1311        double psi = Math.acos(Math.sin(fLatitude) / cosD);
1312        double x = diameter / 2 + refraction;
1313        double y = Math.asin(Math.sin(x) / Math.sin(psi));
1314        long delta = (long)((240 * y * RAD_DEG / cosD)*SECOND_MS);
1315        
1316        return time + (rise ? -delta : delta);
1317    }
1318    
1319    //-------------------------------------------------------------------------
1320
// Other utility methods
1321
//-------------------------------------------------------------------------
1322

1323    /***
1324     * Given 'value', add or subtract 'range' until 0 <= 'value' < range.
1325     * The modulus operator.
1326     */

1327    private static final double normalize(double value, double range) {
1328        return value - range * Math.floor(value / range);
1329    }
1330    
1331    /**
1332     * Normalize an angle so that it's in the range 0 - 2pi.
1333     * For positive angles this is just (angle % 2pi), but the Java
1334     * mod operator doesn't work that way for negative numbers....
1335     */

1336    private static final double norm2PI(double angle) {
1337        return normalize(angle, PI2);
1338    }
1339    
1340    /**
1341     * Normalize an angle into the range -PI - PI
1342     */

1343    private static final double normPI(double angle) {
1344        return normalize(angle + PI, PI2) - PI;
1345    }
1346    
1347    /**
1348     * Find the "true anomaly" (longitude) of an object from
1349     * its mean anomaly and the eccentricity of its orbit. This uses
1350     * an iterative solution to Kepler's equation.
1351     *
1352     * @param meanAnomaly The object's longitude calculated as if it were in
1353     * a regular, circular orbit, measured in radians
1354     * from the point of perigee.
1355     *
1356     * @param eccentricity The eccentricity of the orbit
1357     *
1358     * @return The true anomaly (longitude) measured in radians
1359     */

1360    private double trueAnomaly(double meanAnomaly, double eccentricity)
1361    {
1362        // First, solve Kepler's equation iteratively
1363
// Duffett-Smith, p.90
1364
double delta;
1365        double E = meanAnomaly;
1366        do {
1367            delta = E - eccentricity * Math.sin(E) - meanAnomaly;
1368            E = E - delta / (1 - eccentricity * Math.cos(E));
1369        }
1370        while (Math.abs(delta) > 1e-5); // epsilon = 1e-5 rad
1371

1372        return 2.0 * Math.atan( Math.tan(E/2) * Math.sqrt( (1+eccentricity)
1373                                                          /(1-eccentricity) ) );
1374    }
1375    
1376    /**
1377     * Return the obliquity of the ecliptic (the angle between the ecliptic
1378     * and the earth's equator) at the current time. This varies due to
1379     * the precession of the earth's axis.
1380     *
1381     * @return the obliquity of the ecliptic relative to the equator,
1382     * measured in radians.
1383     */

1384    private double eclipticObliquity() {
1385        if (eclipObliquity == INVALID) {
1386            final double epoch = 2451545.0; // 2000 AD, January 1.5
1387

1388            double T = (getJulianDay() - epoch) / 36525;
1389            
1390            eclipObliquity = 23.439292
1391                           - 46.815/3600 * T
1392                           - 0.0006/3600 * T*T
1393                           + 0.00181/3600 * T*T*T;
1394                           
1395            eclipObliquity *= DEG_RAD;
1396        }
1397        return eclipObliquity;
1398    }
1399    
1400     
1401    //-------------------------------------------------------------------------
1402
// Private data
1403
//-------------------------------------------------------------------------
1404

1405    /**
1406     * Current time in milliseconds since 1/1/1970 AD
1407     * @see java.util.Date#getTime
1408     */

1409    private long time;
1410    
1411    /* These aren't used yet, but they'll be needed for sunset calculations
1412     * and equatorial to horizon coordinate conversions
1413     */

1414    private double fLongitude = 0.0;
1415    private double fLatitude = 0.0;
1416    private long fGmtOffset = 0;
1417    
1418    //
1419
// The following fields are used to cache calculated results for improved
1420
// performance. These values all depend on the current time setting
1421
// of this object, so the clearCache method is provided.
1422
//
1423
static final private double INVALID = Double.MIN_VALUE;
1424    
1425    private transient double julianDay = INVALID;
1426    private transient double julianCentury = INVALID;
1427    private transient double sunLongitude = INVALID;
1428    private transient double meanAnomalySun = INVALID;
1429    private transient double moonLongitude = INVALID;
1430    private transient double moonEclipLong = INVALID;
1431    private transient double meanAnomalyMoon = INVALID;
1432    private transient double eclipObliquity = INVALID;
1433    private transient double siderealT0 = INVALID;
1434    private transient double siderealTime = INVALID;
1435    
1436    private transient Equatorial moonPosition = null;
1437
1438    private void clearCache() {
1439        julianDay = INVALID;
1440        julianCentury = INVALID;
1441        sunLongitude = INVALID;
1442        meanAnomalySun = INVALID;
1443        moonLongitude = INVALID;
1444        moonEclipLong = INVALID;
1445        meanAnomalyMoon = INVALID;
1446        eclipObliquity = INVALID;
1447        siderealTime = INVALID;
1448        siderealT0 = INVALID;
1449        moonPosition = null;
1450    }
1451    
1452    //private static void out(String s) {
1453
// System.out.println(s);
1454
//}
1455

1456    //private static String deg(double rad) {
1457
// return Double.toString(rad * RAD_DEG);
1458
//}
1459

1460    //private static String hours(long ms) {
1461
// return Double.toString((double)ms / HOUR_MS) + " hours";
1462
//}
1463

1464    /**
1465     * @internal
1466     */

1467    public String JavaDoc local(long localMillis) {
1468        return new Date(localMillis - TimeZone.getDefault().getRawOffset()).toString();
1469    }
1470    
1471    
1472    /**
1473     * Represents the position of an object in the sky relative to the ecliptic,
1474     * the plane of the earth's orbit around the Sun.
1475     * This is a spherical coordinate system in which the latitude
1476     * specifies the position north or south of the plane of the ecliptic.
1477     * The longitude specifies the position along the ecliptic plane
1478     * relative to the "First Point of Aries", which is the Sun's position in the sky
1479     * at the Vernal Equinox.
1480     * <p>
1481     * Note that Ecliptic objects are immutable and cannot be modified
1482     * once they are constructed. This allows them to be passed and returned by
1483     * value without worrying about whether other code will modify them.
1484     *
1485     * @see CalendarAstronomer.Equatorial
1486     * @see CalendarAstronomer.Horizon
1487     * @internal
1488     */

1489    public static final class Ecliptic {
1490        /**
1491         * Constructs an Ecliptic coordinate object.
1492         * <p>
1493         * @param lat The ecliptic latitude, measured in radians.
1494         * @param lon The ecliptic longitude, measured in radians.
1495         * @internal
1496         */

1497        public Ecliptic(double lat, double lon) {
1498            latitude = lat;
1499            longitude = lon;
1500        }
1501
1502        /**
1503         * Return a string representation of this object
1504         * @internal
1505         */

1506        public String JavaDoc toString() {
1507            return Double.toString(longitude*RAD_DEG) + "," + (latitude*RAD_DEG);
1508        }
1509        
1510        /**
1511         * The ecliptic latitude, in radians. This specifies an object's
1512         * position north or south of the plane of the ecliptic,
1513         * with positive angles representing north.
1514         * @internal
1515         */

1516        public final double latitude;
1517        
1518        /**
1519         * The ecliptic longitude, in radians.
1520         * This specifies an object's position along the ecliptic plane
1521         * relative to the "First Point of Aries", which is the Sun's position
1522         * in the sky at the Vernal Equinox,
1523         * with positive angles representing east.
1524         * <p>
1525         * A bit of trivia: the first point of Aries is currently in the
1526         * constellation Pisces, due to the precession of the earth's axis.
1527         * @internal
1528         */

1529        public final double longitude;
1530    }
1531
1532    /**
1533     * Represents the position of an
1534     * object in the sky relative to the plane of the earth's equator.
1535     * The <i>Right Ascension</i> specifies the position east or west
1536     * along the equator, relative to the sun's position at the vernal
1537     * equinox. The <i>Declination</i> is the position north or south
1538     * of the equatorial plane.
1539     * <p>
1540     * Note that Equatorial objects are immutable and cannot be modified
1541     * once they are constructed. This allows them to be passed and returned by
1542     * value without worrying about whether other code will modify them.
1543     *
1544     * @see CalendarAstronomer.Ecliptic
1545     * @see CalendarAstronomer.Horizon
1546     * @internal
1547     */

1548    public static final class Equatorial {
1549        /**
1550         * Constructs an Equatorial coordinate object.
1551         * <p>
1552         * @param asc The right ascension, measured in radians.
1553         * @param dec The declination, measured in radians.
1554         * @internal
1555         */

1556        public Equatorial(double asc, double dec) {
1557            ascension = asc;
1558            declination = dec;
1559        }
1560
1561        /**
1562         * Return a string representation of this object, with the
1563         * angles measured in degrees.
1564         * @internal
1565         */

1566        public String JavaDoc toString() {
1567            return Double.toString(ascension*RAD_DEG) + "," + (declination*RAD_DEG);
1568        }
1569        
1570        /**
1571         * Return a string representation of this object with the right ascension
1572         * measured in hours, minutes, and seconds.
1573         * @internal
1574         */

1575        public String JavaDoc toHmsString() {
1576            return radToHms(ascension) + "," + radToDms(declination);
1577        }
1578        
1579        /**
1580         * The right ascension, in radians.
1581         * This is the position east or west along the equator
1582         * relative to the sun's position at the vernal equinox,
1583         * with positive angles representing East.
1584         * @internal
1585         */

1586        public final double ascension;
1587        
1588        /**
1589         * The declination, in radians.
1590         * This is the position north or south of the equatorial plane,
1591         * with positive angles representing north.
1592         * @internal
1593         */

1594        public final double declination;
1595    }
1596
1597    /**
1598     * Represents the position of an object in the sky relative to
1599     * the local horizon.
1600     * The <i>Altitude</i> represents the object's elevation above the horizon,
1601     * with objects below the horizon having a negative altitude.
1602     * The <i>Azimuth</i> is the geographic direction of the object from the
1603     * observer's position, with 0 representing north. The azimuth increases
1604     * clockwise from north.
1605     * <p>
1606     * Note that Horizon objects are immutable and cannot be modified
1607     * once they are constructed. This allows them to be passed and returned by
1608     * value without worrying about whether other code will modify them.
1609     *
1610     * @see CalendarAstronomer.Ecliptic
1611     * @see CalendarAstronomer.Equatorial
1612     * @internal
1613     */

1614    public static final class Horizon {
1615        /**
1616         * Constructs a Horizon coordinate object.
1617         * <p>
1618         * @param alt The altitude, measured in radians above the horizon.
1619         * @param azim The azimuth, measured in radians clockwise from north.
1620         * @internal
1621         */

1622        public Horizon(double alt, double azim) {
1623            altitude = alt;
1624            azimuth = azim;
1625        }
1626
1627        /**
1628         * Return a string representation of this object, with the
1629         * angles measured in degrees.
1630         * @internal
1631         */

1632        public String JavaDoc toString() {
1633            return Double.toString(altitude*RAD_DEG) + "," + (azimuth*RAD_DEG);
1634        }
1635        
1636        /**
1637         * The object's altitude above the horizon, in radians.
1638         * @internal
1639         */

1640        public final double altitude;
1641        
1642        /**
1643         * The object's direction, in radians clockwise from north.
1644         * @internal
1645         */

1646        public final double azimuth;
1647    }
1648
1649    static private String JavaDoc radToHms(double angle) {
1650        int hrs = (int) (angle*RAD_HOUR);
1651        int min = (int)((angle*RAD_HOUR - hrs) * 60);
1652        int sec = (int)((angle*RAD_HOUR - hrs - min/60.0) * 3600);
1653        
1654        return Integer.toString(hrs) + "h" + min + "m" + sec + "s";
1655    }
1656    
1657    static private String JavaDoc radToDms(double angle) {
1658        int deg = (int) (angle*RAD_DEG);
1659        int min = (int)((angle*RAD_DEG - deg) * 60);
1660        int sec = (int)((angle*RAD_DEG - deg - min/60.0) * 3600);
1661        
1662        return Integer.toString(deg) + "\u00b0" + min + "'" + sec + "\"";
1663    }
1664}
1665
Popular Tags