001/*
002 *    GeoTools - The Open Source Java GIS Toolkit
003 *    http://geotools.org
004 * 
005 *    (C) 2001-2008, Open Source Geospatial Foundation (OSGeo)
006 *   
007 *    This library is free software; you can redistribute it and/or
008 *    modify it under the terms of the GNU Lesser General Public
009 *    License as published by the Free Software Foundation;
010 *    version 2.1 of the License.
011 *
012 *    This library is distributed in the hope that it will be useful,
013 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
014 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015 *    Lesser General Public License for more details.
016 *
017 *    NOTE: permission has been given to the JScience project (http://www.jscience.org)
018 *          to distribute this file under BSD-like license.
019 */
020package edu.wisc.ssec.mcidasv.util;
021
022// J2SE dependencies
023import java.awt.geom.Point2D;
024import java.text.DateFormat;
025import java.text.ParseException;
026import java.util.Date;
027import java.util.TimeZone;
028
029
030/**
031 * Calcule la position du soleil relativement à la position de l'observateur.
032 * Cette classe reçoit en entrés les coordonnées spatio-temporelles de
033 * l'observateur, soit:
034 *
035 * <TABLE border='0'><TR><TD valign="top">
036 * &nbsp;<BR>
037 * <UL>
038 *   <LI>La longitude (en degrées) de l'observateur;</LI>
039 *   <LI>La latitude (en degrées) de l'observateur;</LI>
040 *   <LI>La date et heure en heure universelle (GMT).</LI>
041 * </UL>
042 *
043 * La position du soleil calculée en sortie comprend les valeurs suivantes:
044 *
045 * <UL>
046 *   <LI>L'azimuth du soleil (en degrés dans le sens des aiguilles d'une montre depuis le nord);</LI>
047 *   <LI>L'élévation du soleil (en degrés par rapport a l'horizon).</LI>
048 * </UL>
049 * </TD>
050 *
051 * <TD><img src="doc-files/CelestialSphere.png"></TD>
052 * </TR></TABLE>
053 *
054 * Les algorithmes utilisés dans cette classe sont des adaptations des algorithmes
055 * en javascript écrit par le "National Oceanic and Atmospheric Administration,
056 * Surface Radiation Research Branch". L'application original est le
057 *
058 * <a href="http://www.srrb.noaa.gov/highlights/sunrise/azel.html">Solar Position Calculator</a>.
059 *
060 * <p>
061 * The approximations used in these programs are very good for years between
062 * 1800 and 2100. Results should still be sufficiently accurate for the range
063 * from -1000 to 3000. Outside of this range, results will be given, but the
064 * potential for error is higher.
065 *
066 * @since 2.1
067 *
068 *
069 * @source $URL$
070 * @version $Id$
071 * @author Remi Eve
072 * @author Martin Desruisseaux (IRD)
073 */
074public class SunRelativePosition {
075    /**
076     * Number of milliseconds in a day.
077     */
078    private static final int DAY_MILLIS = 24*60*60*1000;
079
080    /**
081     * Valeur affectée lorsque un resultat n'est pas calculable du
082     * fait de la nuit. Cette valeur concerne les valeurs de sorties
083     * {@link #elevation} et {@link #azimuth}.
084     */
085    private static final double DARK = Double.NaN;
086
087    /**
088     * {@linkplain #getElevation Elevation angle} of astronomical twilight, in degrees.
089     * Astronomical twilight is the time of morning or evening when the sun is 18° below
090     * the horizon (solar elevation angle of -18°).
091     */
092    public static final double ASTRONOMICAL_TWILIGHT = -18;
093
094    /**
095     * {@linkplain #getElevation Elevation angle} of nautical twilight, in degrees.
096     * Nautical twilight is the time of morning or evening when the sun is 12° below
097     * the horizon (solar elevation angle of -12°).
098     */
099    public static final double NAUTICAL_TWILIGHT = -12;
100
101    /**
102     * {@linkplain #getElevation Elevation angle} of civil twilight, in degrees. Civil
103     * twilight is the time of morning or evening when the sun is 6° below the horizon
104     * (solar elevation angle of -6°).
105     */
106    public static final double CIVIL_TWILIGHT = -6;
107
108    /**
109     * Sun's {@linkplain #getElevation elevation angle} at twilight, in degrees.
110     * Common values are defined for the
111     * {@linkplain #ASTRONOMICAL_TWILIGHT astronomical twilight} (-18°),
112     * {@linkplain #NAUTICAL_TWILIGHT nautical twilight} (-12°) and
113     * {@linkplain #CIVIL_TWILIGHT civil twilight} (-6°).
114     * If no twilight are defined, then this value is {@linkplain Double#NaN NaN}.
115     * The {@linkplain #getElevation elevation} and {@linkplain #getAzimuth azimuth} are
116     * set to {@linkplain Double#NaN NaN} when the sun elevation is below the twilight
117     * value (i.e. during night). The default value is {@link #CIVIL_TWILIGHT}.
118     */
119    private double twilight = CIVIL_TWILIGHT;
120
121    /**
122     * Heure à laquelle le soleil est au plus haut dans la journée en millisecondes
123     * écoulées depuis le 1er janvier 1970.
124     */
125    private long noonTime;
126    
127    /**
128     * Azimuth du soleil, en degrés dans le sens des
129     * aiguilles d'une montre depuis le nord.
130     */
131    private double azimuth;
132
133    /**
134     * Elévation du soleil, en degrés par rapport a l'horizon.
135     */
136    private double elevation;
137
138    /**
139     * Geographic coordinate where current elevation and azimuth were computed.
140     * Value are in degrees of longitude or latitude.
141     */
142    private double latitude, longitude;
143
144    /**
145     * Date and time when the current elevation and azimuth were computed.
146     * Value is in milliseconds ellapsed since midnight UTC, January 1st, 1970.
147     */
148    private long time = System.currentTimeMillis();
149
150    /**
151     * {@code true} is the elevation and azimuth are computed, or {@code false}
152     * if they need to be computed.  This flag is set to {@code false} when the date
153     * and/or the coordinate change.
154     */
155    private boolean updated;
156
157    /**
158     * Calculate the equation of center for the sun. This value is a correction
159     * to add to the geometric mean longitude in order to get the "true" longitude
160     * of the sun.
161     *
162     * @param  t number of Julian centuries since J2000.
163     * @return Equation of center in degrees.
164     */
165    private static double sunEquationOfCenter(final double t) {
166        final double m = Math.toRadians(sunGeometricMeanAnomaly(t));
167        return Math.sin(1*m) * (1.914602 - t*(0.004817 + 0.000014*t)) +
168               Math.sin(2*m) * (0.019993 - t*(0.000101             )) +
169               Math.sin(3*m) * (0.000289);
170    }
171
172    /**
173     * Calculate the Geometric Mean Longitude of the Sun.
174     * This value is close to 0° at the spring equinox,
175     * 90° at the summer solstice, 180° at the automne equinox
176     * and 270° at the winter solstice.
177     *
178     * @param  t number of Julian centuries since J2000.
179     * @return Geometric Mean Longitude of the Sun in degrees,
180     *         in the range 0° (inclusive) to 360° (exclusive).
181     */
182    private static double sunGeometricMeanLongitude(final double t) {
183        double L0 = 280.46646 + t*(36000.76983 + 0.0003032*t);
184        L0 = L0 - 360*Math.floor(L0/360);
185        return L0;
186    }
187
188    /**
189     * Calculate the true longitude of the sun. This the geometric mean
190     * longitude plus a correction factor ("equation of center" for the
191     * sun).
192     *
193     * @param  t number of Julian centuries since J2000.
194     * @return Sun's true longitude in degrees.
195     */
196    private static double sunTrueLongitude(final double t) {
197        return sunGeometricMeanLongitude(t) + sunEquationOfCenter(t);
198    }
199
200    /**
201     * Calculate the apparent longitude of the sun.
202     *
203     * @param  t number of Julian centuries since J2000.
204     * @return Sun's apparent longitude in degrees.
205     */
206    private static double sunApparentLongitude(final double t) {
207        final double omega = Math.toRadians(125.04 - 1934.136 * t);
208        return sunTrueLongitude(t) - 0.00569 - 0.00478 * Math.sin(omega);
209    }
210
211    /**
212     * Calculate the Geometric Mean Anomaly of the Sun.
213     *
214     * @param  t number of Julian centuries since J2000.
215     * @return Geometric Mean Anomaly of the Sun in degrees.
216     */
217    private static double sunGeometricMeanAnomaly(final double t) {
218        return 357.52911 + t * (35999.05029 - 0.0001537*t);
219    }
220
221    /**
222     * Calculate the true anamoly of the sun.
223     *
224     * @param  t number of Julian centuries since J2000.
225     * @return Sun's true anamoly in degrees.
226     */
227    private static double sunTrueAnomaly(final double t) {
228        return sunGeometricMeanAnomaly(t) + sunEquationOfCenter(t);
229    }
230
231    /**
232     * Calculate the eccentricity of earth's orbit. This is the ratio
233     * {@code (a-b)/a} where <var>a</var> is the semi-major axis
234     * length and <var>b</var> is the semi-minor axis length.   Value
235     * is 0 for a circular orbit.
236     *
237     * @param  t number of Julian centuries since J2000.
238     * @return The unitless eccentricity.
239     */
240    private static double eccentricityEarthOrbit(final double t) {
241        return 0.016708634 - t*(0.000042037 + 0.0000001267*t);
242    }
243
244    /**
245     * Calculate the distance to the sun in Astronomical Units (AU).
246     *
247     * @param  t number of Julian centuries since J2000.
248     * @return Sun radius vector in AUs.
249     */
250    private static double sunRadiusVector(final double t) {
251        final double v = Math.toRadians(sunTrueAnomaly(t));
252        final double e = eccentricityEarthOrbit(t);
253        return (1.000001018 * (1 - e*e)) / (1 + e*Math.cos(v));
254    }
255
256    /**
257     * Calculate the mean obliquity of the ecliptic.
258     *
259     * @param  t number of Julian centuries since J2000.
260     * @return Mean obliquity in degrees.
261     */
262    private static double meanObliquityOfEcliptic(final double t) {
263        final double seconds = 21.448 - t*(46.8150 + t*(0.00059 - t*(0.001813)));
264        return 23.0 + (26.0 + (seconds/60.0))/60.0;
265    }
266
267
268    /**
269     * Calculate the corrected obliquity of the ecliptic.
270     *
271     * @param  t number of Julian centuries since J2000.
272     * @return Corrected obliquity in degrees.
273     */
274    private static double obliquityCorrected(final double t) {
275        final double e0 = meanObliquityOfEcliptic(t);
276        final double omega = Math.toRadians(125.04 - 1934.136*t);
277        return e0 + 0.00256 * Math.cos(omega);
278    }
279
280    /**
281     * Calculate the right ascension of the sun. Similar to the angular system
282     * used to define latitude and longitude on Earth's surface, right ascension
283     * is roughly analogous to longitude, and defines an angular offset from the
284     * meridian of the vernal equinox.
285     *
286     * <P align="center"><img src="doc-files/CelestialSphere.png"></P>
287     *
288     * @param t number of Julian centuries since J2000.
289     * @return Sun's right ascension in degrees.
290     */
291    private static double sunRightAscension(final double t) {
292        final double e = Math.toRadians(obliquityCorrected(t));
293        final double b = Math.toRadians(sunApparentLongitude(t));
294        final double y = Math.sin(b) * Math.cos(e);
295        final double x = Math.cos(b);
296        final double alpha = Math.atan2(y, x);
297        return Math.toDegrees(alpha);
298    }
299
300    /**
301     * Calculate the declination of the sun. Declination is analogous to latitude
302     * on Earth's surface, and measures an angular displacement north or south
303     * from the projection of Earth's equator on the celestial sphere to the
304     * location of a celestial body.
305     *
306     * @param t number of Julian centuries since J2000.
307     * @return Sun's declination in degrees.
308     */
309    private static double sunDeclination(final double t) {
310        final double e = Math.toRadians(obliquityCorrected(t));
311        final double b = Math.toRadians(sunApparentLongitude(t));
312        final double sint = Math.sin(e) * Math.sin(b);
313        final double theta = Math.asin(sint);
314        return Math.toDegrees(theta);
315    }
316
317   /**
318    * Calculate the Universal Coordinated Time (UTC) of solar noon for the given day
319    * at the given location on earth.
320    *
321    * @param  lon       longitude of observer in degrees.                               
322    * @param  eqTime    Equation of time.
323    * @return Time in minutes from beginnning of day in UTC.
324    */
325    private static double solarNoonTime(final double lon, final double eqTime) { 
326        return 720.0 + (lon * 4.0) - eqTime;
327    }
328
329    /**
330     * Calculate the difference between true solar time and mean. The "equation
331     * of time" is a term accounting for changes in the time of solar noon for
332     * a given location over the course of a year. Earth's elliptical orbit and
333     * Kepler's law of equal areas in equal times are the culprits behind this
334     * phenomenon. See the
335     * <A HREF="http://www.analemma.com/Pages/framesPage.html">Analemma page</A>.
336     * Below is a plot of the equation of time versus the day of the year.
337     *
338     * <P align="center"><img src="doc-files/EquationOfTime.png"></P>
339     *
340     * @param  t number of Julian centuries since J2000.
341     * @return Equation of time in minutes of time.
342     */
343    private static double equationOfTime(final double t) {
344        double eps = Math.toRadians(obliquityCorrected(t));
345        double l0  = Math.toRadians(sunGeometricMeanLongitude(t));
346        double m   = Math.toRadians(sunGeometricMeanAnomaly(t));
347        double e   = eccentricityEarthOrbit(t);
348        double y   = Math.tan(eps/2);
349        y *= y;
350
351        double sin2l0 = Math.sin(2 * l0);
352        double cos2l0 = Math.cos(2 * l0);
353        double sin4l0 = Math.sin(4 * l0);
354        double sin1m  = Math.sin(m);
355        double sin2m  = Math.sin(2 * m);
356
357        double etime = y*sin2l0 - 2*e*sin1m + 4*e*y*sin1m*cos2l0
358                       - 0.5*y*y*sin4l0 - 1.25*e*e*sin2m;
359
360        return Math.toDegrees(etime)*4.0;
361    }
362
363    /**
364     * Computes the refraction correction angle.
365     * The effects of the atmosphere vary with atmospheric pressure, humidity
366     * and other variables. Therefore the calculation is approximate. Errors
367     * can be expected to increase the further away you are from the equator,
368     * because the sun rises and sets at a very shallow angle. Small variations
369     * in the atmosphere can have a larger effect.
370     *
371     * @param  zenith The sun zenith angle in degrees.
372     * @return The refraction correction in degrees.
373     */
374    private static double refractionCorrection(final double zenith) {
375        final double exoatmElevation = 90 - zenith;
376        if (exoatmElevation > 85) {
377            return 0;
378        }
379        final double refractionCorrection; // In minute of degrees
380        final double te = Math.tan(Math.toRadians(exoatmElevation));
381        if (exoatmElevation > 5.0) {
382            refractionCorrection = 58.1/te - 0.07/(te*te*te) + 0.000086/(te*te*te*te*te);
383        } else {
384            if (exoatmElevation > -0.575) {
385                refractionCorrection =  1735.0 + exoatmElevation *
386                                       (-518.2 + exoatmElevation *
387                                       ( 103.4 + exoatmElevation *
388                                       (-12.79 + exoatmElevation *
389                                         0.711)));
390            } else {
391                refractionCorrection = -20.774 / te;
392            }
393        }
394        return refractionCorrection / 3600;
395    }
396
397    /**
398     * Constructs a sun relative position calculator.
399     */
400    public SunRelativePosition() {
401    }
402
403    /**
404     * Constructs a sun relative position calculator with the specified value
405     * for the {@linkplain #setTwilight sun elevation at twilight}.
406     *
407     * @param twilight The new sun elevation at twilight, or {@link Double#NaN}
408     *                 if no twilight value should be taken in account.
409     * @throws IllegalArgumentException if the twilight value is illegal.
410     */
411    public SunRelativePosition(final double twilight) throws IllegalArgumentException {
412        setTwilight(twilight);
413    }
414
415    /**
416     * Calculates solar position for the current date, time and location.
417     * Results are reported in azimuth and elevation in degrees.
418     */
419    private void compute() {
420        double latitude  = this.latitude;
421        double longitude = this.longitude;
422
423        // NOAA convention use positive longitude west, and negative east.
424        // Inverse the sign, in order to be closer to OpenGIS convention.
425        longitude = -longitude;
426
427        // Compute: 1) Julian day (days ellapsed since January 1, 4723 BC at 12:00 GMT).
428        //          2) Time as the centuries ellapsed since January 1, 2000 at 12:00 GMT.
429        final double julianDay = Calendar.julianDay(this.time);
430        final double time      = (julianDay-2451545)/36525;
431
432        double solarDec = sunDeclination(time);
433        double eqTime   = equationOfTime(time);
434        this.noonTime   = Math.round(solarNoonTime(longitude, eqTime) * (60*1000)) +
435                          (this.time/DAY_MILLIS)*DAY_MILLIS;
436
437        // Formula below use longitude in degrees. Steps are:
438        //   1) Extract the time part of the date, in minutes.
439        //   2) Apply a correction for longitude and equation of time.
440        //   3) Clamp in a 24 hours range (24 hours == 1440 minutes).
441        double trueSolarTime = ((julianDay+0.5) - Math.floor(julianDay+0.5)) * 1440;
442        trueSolarTime += (eqTime - 4.0*longitude); // Correction in minutes.
443        trueSolarTime -= 1440*Math.floor(trueSolarTime/1440);
444
445        // Convert all angles to radians.  From this point until
446        // the end of this method, local variables are always in
447        // radians. Output variables ('azimuth' and 'elevation')
448        // will still computed in degrees.
449        longitude = Math.toRadians(longitude);
450        latitude  = Math.toRadians(latitude );
451        solarDec  = Math.toRadians(solarDec );
452
453        double csz = Math.sin(latitude) *
454                     Math.sin(solarDec) +
455                     Math.cos(latitude) *
456                     Math.cos(solarDec) *
457                     Math.cos(Math.toRadians(trueSolarTime/4 - 180));
458        if (csz > +1) csz = +1;
459        if (csz < -1) csz = -1;
460
461        final double zenith  = Math.acos(csz);
462        final double azDenom = Math.cos(latitude) * Math.sin(zenith);
463
464        //////////////////////////////////////////
465        ////    Compute azimuth in degrees    ////
466        //////////////////////////////////////////
467        if (Math.abs(azDenom) > 0.001) {
468            double azRad = ((Math.sin(latitude)*Math.cos(zenith)) - Math.sin(solarDec)) / azDenom;
469            if (azRad > +1) azRad = +1;
470            if (azRad < -1) azRad = -1;
471
472            azimuth = 180 - Math.toDegrees(Math.acos(azRad));
473            if (trueSolarTime > 720) { // 720 minutes == 12 hours
474                azimuth = -azimuth;
475            }
476        } else {
477            azimuth = (latitude>0) ? 180 : 0;
478        }
479        azimuth -= 360*Math.floor(azimuth/360);
480
481        ////////////////////////////////////////////
482        ////    Compute elevation in degrees    ////
483        ////////////////////////////////////////////
484        final double refractionCorrection = refractionCorrection(Math.toDegrees(zenith));
485        final double solarZen = Math.toDegrees(zenith) - refractionCorrection;
486
487        elevation = 90 - solarZen;
488        if (elevation < twilight) {
489            // do not report azimuth & elevation after twilight
490            azimuth   = DARK;
491            elevation = DARK;
492        }
493        updated = true;
494    }
495
496    /**
497     * Set the geographic coordinate where to compute the {@linkplain #getElevation elevation}
498     * and {@linkplain #getAzimuth azimuth}.
499     *
500     * @param longitude The longitude in degrees. Positive values are East; negative values are West.
501     * @param latitude  The latitude in degrees. Positive values are North, negative values are South.
502     */
503    public void setCoordinate(double longitude, double latitude) {
504        if (latitude > +89.8) latitude = +89.8;
505        if (latitude < -89.8) latitude = -89.8;
506        if (latitude != this.latitude || longitude != this.longitude) {
507            this.latitude  = latitude;
508            this.longitude = longitude;
509            this.updated   = false;
510        }
511    }
512
513    /**
514     * Set the geographic coordinate where to compute the {@linkplain #getElevation elevation}
515     * and {@linkplain #getAzimuth azimuth}.
516     *
517     * @param point The geographic coordinates in degrees of longitude and latitude.
518     */
519    public void setCoordinate(final Point2D point) {
520        setCoordinate(point.getX(), point.getY());
521    }
522
523    /**
524     * Returns the coordinate used for {@linkplain #getElevation elevation} and
525     * {@linkplain #getAzimuth azimuth} computation. This is the coordinate
526     * specified during the last call to a {@link #setCoordinate(double,double)
527     * setCoordinate(...)} method.
528     */
529    public Point2D getCoordinate() {
530        return new Point2D.Double(longitude, latitude);
531    }
532
533    /**
534     * Set the date and time when to compute the {@linkplain #getElevation elevation}
535     * and {@linkplain #getAzimuth azimuth}.
536     *
537     * @param date The date and time.
538     */
539    public void setDate(final Date date) {
540        final long time = date.getTime();
541        if (time != this.time) {
542            this.time = time;
543            this.updated = false;
544        }
545    }
546
547    /**
548     * Returns the date used for {@linkplain #getElevation elevation} and
549     * {@linkplain #getAzimuth azimuth} computation. This is the date specified
550     * during the last call to {@link #setDate}.
551     */
552    public Date getDate() {
553        return new Date(time);
554    }
555
556    /**
557     * Set the sun's {@linkplain #getElevation elevation angle} at twilight, in degrees.
558     * Common values are defined for the
559     * {@linkplain #ASTRONOMICAL_TWILIGHT astronomical twilight} (-18°),
560     * {@linkplain #NAUTICAL_TWILIGHT nautical twilight} (-12°) and
561     * {@linkplain #CIVIL_TWILIGHT civil twilight} (-6°).
562     * The {@linkplain #getElevation elevation} and {@linkplain #getAzimuth azimuth} are
563     * set to {@linkplain Double#NaN NaN} when the sun elevation is below the twilight
564     * value (i.e. during night). The default value is {@link #CIVIL_TWILIGHT}.
565     *
566     * @param twilight The new sun elevation at twilight, or {@link Double#NaN}
567     *                 if no twilight value should be taken in account.
568     * @throws IllegalArgumentException if the twilight value is illegal.
569     */
570    public void setTwilight(final double twilight) throws IllegalArgumentException {
571        if (twilight<-90 || twilight>-90) {
572            // TODO: provides a better (localized) message.
573            throw new IllegalArgumentException(String.valueOf(twilight));
574        }
575        this.twilight = twilight;
576        this.updated = false;
577    }
578
579    /**
580     * Returns the sun's {@linkplain #getElevation elevation angle} at twilight, in degrees.
581     * This is the value set during the last call to {@link #setTwilight}.
582     */
583    public double getTwilight() {
584        return twilight;
585    }
586
587    /**
588     * Retourne l'azimuth en degrés.
589     *
590     * @return L'azimuth en degrés.
591     */
592    public double getAzimuth() {
593        if (!updated) {
594            compute();
595        }
596        return azimuth;
597    }
598
599    /**
600     * Retourne l'élévation en degrés.
601     *
602     * @return L'élévation en degrés.
603     */
604    public double getElevation() {
605        if (!updated) {
606            compute();
607        }
608        return elevation;
609    }
610    
611    /**
612     * 
613     * @return solar zenith angle in degrees 
614     */
615    public double getSolarZenith() {
616       if (!updated) {
617          compute();
618       }
619       return 90.0 - elevation;
620    }
621
622    /**
623     * Retourne l'heure à laquelle le soleil est au plus haut. L'heure est
624     * retournée en nombre de millisecondes écoulées depuis le debut de la
625     * journée (minuit) en heure UTC.
626     */
627    public long getNoonTime() {
628        if (!updated) {
629            compute();
630        }
631        return noonTime % DAY_MILLIS;
632    }
633
634    /**
635     * Retourne la date à laquelle le soleil est au plus haut dans la journée.
636     * Cette méthode est équivalente à {@link #getNoonTime} mais inclue le jour
637     * de la date qui avait été spécifiée à la méthode {@link #compute}.
638     */
639    public Date getNoonDate() {
640        if (!updated) {
641            compute();
642        }
643        return new Date(noonTime);
644    }
645
646    /**
647     * Affiche la position du soleil à la date et coordonnées spécifiée.
648     * Cette application peut être lancée avec la syntaxe suivante:
649     *
650     * <pre>SunRelativePosition <var>[longitude]</var> <var>[latitude]</var> <var>[date]</var></pre>
651     *
652     * où <var>date</var> est un argument optionel spécifiant la date et l'heure.
653     * Si cet argument est omis, la date et heure actuelles seront utilisées.
654     */
655    public static void main(final String[] args) throws ParseException {
656        final DateFormat format=DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
657        format.setTimeZone(TimeZone.getTimeZone("UTC"));
658        double longitude = 0;
659        double latitude  = 0;
660        Date time = new Date();
661        switch (args.length) {
662            case 3: time      = format.parse      (args[2]); // fall through
663            case 2: latitude  = Double.parseDouble(args[1]); // fall through
664            case 1: longitude = Double.parseDouble(args[0]); // fall through
665        }
666        final SunRelativePosition calculator = new SunRelativePosition();
667        calculator.setDate(time);
668        calculator.setCoordinate(longitude, latitude);
669        System.out.print("Date (UTC): "); System.out.println(format.format(time));
670        System.out.print("Longitude:  "); System.out.println(longitude);
671        System.out.print("Latitude:   "); System.out.println(latitude);
672        System.out.print("Elevation:  "); System.out.println(calculator.getElevation());
673        System.out.print("Azimuth:    "); System.out.println(calculator.getAzimuth());
674        System.out.print("Noon date:  "); System.out.println(format.format(calculator.getNoonDate()));
675    }
676}