1863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy/*
2863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy * Copyright (C) 2009 The Android Open Source Project
3863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy *
4863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy * Based on sunrisesunsetlib-java:
5863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy * Copyright 2008-2009 Mike Reedell / LuckyCatLabs.
6863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy *
7863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy * Original project and source can be found at:
8863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy * http://mikereedell.github.com/sunrisesunsetlib-java/
9863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy *
10863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy * Licensed under the Apache License, Version 2.0 (the "License");
11863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy * you may not use this file except in compliance with the License.
12863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy * You may obtain a copy of the License at
13863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy *
14863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy *      http://www.apache.org/licenses/LICENSE-2.0
15863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy *
16863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy * Unless required by applicable law or agreed to in writing, software
17863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy * distributed under the License is distributed on an "AS IS" BASIS,
18863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy * See the License for the specific language governing permissions and
20863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy * limitations under the License.
21863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy */
22863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
23863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guypackage com.android.wallpaper.grass;
24863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
25863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guyimport java.util.Calendar;
26863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guyimport java.util.TimeZone;
27863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
28863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guyimport android.location.Location;
29863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
30863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guyclass SunCalculator {
31863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    /** Astronomical sunrise/set is when the sun is 18 degrees below the horizon. */
32863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    static final double ZENITH_ASTRONOMICAL = 108;
33863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
34863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    /** Nautical sunrise/set is when the sun is 12 degrees below the horizon. */
35863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    static final double ZENITH_NAUTICAL = 102;
36863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
37863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    /** Civil sunrise/set (dawn/dusk) is when the sun is 6 degrees below the horizon. */
38863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    static final double ZENITH_CIVIL = 96;
39863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
40863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    /** Official sunrise/set is when the sun is 50' below the horizon. */
41863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    static final double ZENITH_OFFICIAL = 90.8333;
42863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
43863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    private Location mLocation;
44863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    private TimeZone mTimeZone;
45863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
46863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    SunCalculator(Location location, String timeZoneIdentifier) {
47863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        mLocation = location;
48863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        mTimeZone = TimeZone.getTimeZone(timeZoneIdentifier);
49863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    }
50863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
51863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    public void setLocation(Location location) {
52863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        mLocation = location;
53863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    }
54863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
55863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    /**
56863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     * Computes the sunrise time for the given zenith at the given date.
57863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     *
58863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     * @param solarZenith <code>Zenith</code> enum corresponding to the type
59863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     *        of sunrise to compute.
60863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     * @param date <code>Calendar</code> object representing the date to
61863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     *        compute the sunrise for.
62863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     * @return the sunrise time
63863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     */
64863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    public double computeSunriseTime(double solarZenith, Calendar date) {
65863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        return computeSolarEventTime(solarZenith, date, true);
66863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    }
67863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
68863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    /**
69863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     * Computes the sunset time for the given zenith at the given date.
70863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     *
71863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     * @param solarZenith <code>Zenith</code> enum corresponding to the type of
72863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     *        sunset to compute.
73863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     * @param date <code>Calendar</code> object representing the date to compute
74863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     *        the sunset for.
75863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     * @return the sunset time
76863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     */
77863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    public double computeSunsetTime(double solarZenith, Calendar date) {
78863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        return computeSolarEventTime(solarZenith, date, false);
79863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    }
80863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
81863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    public static int timeToHours(double time) {
82863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        int hour = (int) Math.floor(time);
83863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        int minute = (int) Math.round((time - hour) * 60);
84863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        if (minute == 60) {
85863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy            hour++;
86863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        }
87863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        return hour;
88863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    }
89863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
90863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    public static int timeToMinutes(double time) {
91863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        int hour = (int) Math.floor(time);
92863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        int minute = (int) Math.round((time - hour) * 60);
93863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        if (minute == 60) {
94863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy            minute = 0;
95863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        }
96863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        return minute;
97863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    }
98863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
99863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    public static float timeToDayFraction(double time) {
100863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        int hour = (int) Math.floor(time);
101863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        int minute = (int) Math.round((time - hour) * 60);
102863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        if (minute == 60) {
103863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy            minute = 0;
104863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy            hour++;
105863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        }
106863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        return (hour * 60 + minute) / 1440.0f;
107863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    }
108863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
109863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    public static String timeToString(double time) {
110863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        StringBuffer buffer = new StringBuffer();
111863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        int hour = (int) Math.floor(time);
112863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        int minute = (int) Math.round((time - hour) * 60);
113863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        if (minute == 60) {
114863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy            minute = 0;
115863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy            hour++;
116863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        }
117863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        buffer.append(hour).append(':').append(minute < 10 ? "0" + minute : minute);
118863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        return buffer.toString();
119863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    }
120863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
121863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    private double computeSolarEventTime(double solarZenith, Calendar date, boolean isSunrise) {
122863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        date.setTimeZone(mTimeZone);
123863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double longitudeHour = getLongitudeHour(date, isSunrise);
124863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double meanAnomaly = getMeanAnomaly(longitudeHour);
125863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double sunTrueLong = getSunTrueLongitude(meanAnomaly);
126863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double cosineSunLocalHour = getCosineSunLocalHour(sunTrueLong, solarZenith);
127863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        if ((cosineSunLocalHour < -1.0) || (cosineSunLocalHour > 1.0)) {
128863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy            return 0;
129863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        }
130863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
131863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double sunLocalHour = getSunLocalHour(cosineSunLocalHour, isSunrise);
132863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double localMeanTime = getLocalMeanTime(sunTrueLong, longitudeHour, sunLocalHour);
133863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        return getLocalTime(localMeanTime, date);
134863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    }
135863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
136863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    /**
137863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     * Computes the base longitude hour, lngHour in the algorithm.
138863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     *
139863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     * @return the longitude of the location of the solar event divided by 15 (deg/hour), in
140863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     *         <code>double</code> form.
141863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     */
142863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    private double getBaseLongitudeHour() {
143863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        return mLocation.getLongitude() / 15.0;
144863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    }
145863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
146863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    /**
147863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     * Computes the longitude time, t in the algorithm.
148863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     *
149863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     * @return longitudinal time in <code>double</code> form.
150863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     */
151863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    private double getLongitudeHour(Calendar date, Boolean isSunrise) {
152863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        int offset = 18;
153863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        if (isSunrise) {
154863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy            offset = 6;
155863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        }
156863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double dividend = offset - getBaseLongitudeHour();
157863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double addend = dividend / 24.0;
158863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        return getDayOfYear(date) + addend;
159863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    }
160863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
161863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    /**
162863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     * Computes the mean anomaly of the Sun, M in the algorithm.
163863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     *
164863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     * @return the suns mean anomaly, M, in <code>double</code> form.
165863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     */
166863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    private static double getMeanAnomaly(double longitudeHour) {
167863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        return 0.9856 * longitudeHour - 3.289;
168863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    }
169863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
170863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    /**
171863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     * Computes the true longitude of the sun, L in the algorithm, at the given
172863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     * location, adjusted to fit in the range [0-360].
173863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     *
174863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     * @param meanAnomaly the suns mean anomaly.
175863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     * @return the suns true longitude, in <code>double</code> form.
176863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     */
177863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    private static double getSunTrueLongitude(double meanAnomaly) {
178863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        final double meanRadians = Math.toRadians(meanAnomaly);
179863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double sinMeanAnomaly = Math.sin(meanRadians);
180863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double sinDoubleMeanAnomaly = Math.sin((meanRadians * 2.0));
181863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
182863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double firstPart = meanAnomaly + sinMeanAnomaly * 1.916;
183863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double secondPart = sinDoubleMeanAnomaly * 0.020 + 282.634;
184863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double trueLongitude = firstPart + secondPart;
185863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
186863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        if (trueLongitude > 360) {
187863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy            trueLongitude = trueLongitude - 360.0;
188863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        }
189863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        return trueLongitude;
190863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    }
191863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
192863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    /**
193863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     * Computes the suns right ascension, RA in the algorithm, adjusting for
194863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     * the quadrant of L and turning it into degree-hours. Will be in the
195863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     * range [0,360].
196863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     *
197863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     * @param sunTrueLong Suns true longitude, in <code>double</code>
198863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     * @return suns right ascension in degree-hours, in <code>double</code> form.
199863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     */
200863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    private static double getRightAscension(double sunTrueLong) {
201863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double tanL = Math.tan(Math.toRadians(sunTrueLong));
202863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
203863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double innerParens = Math.toDegrees(tanL) * 0.91764;
204863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double rightAscension = Math.atan(Math.toRadians(innerParens));
205863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        rightAscension = Math.toDegrees(rightAscension);
206863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
207863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        if (rightAscension < 0.0) {
208863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy            rightAscension = rightAscension + 360.0;
209863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        } else if (rightAscension > 360.0) {
210863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy            rightAscension = rightAscension - 360.0;
211863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        }
212863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
213863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double ninety = 90.0;
214863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double longitudeQuadrant = (int) (sunTrueLong / ninety);
215863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        longitudeQuadrant = longitudeQuadrant * ninety;
216863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
217863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double rightAscensionQuadrant = (int) (rightAscension / ninety);
218863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        rightAscensionQuadrant = rightAscensionQuadrant * ninety;
219863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
220863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double augend = longitudeQuadrant - rightAscensionQuadrant;
221863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        return (rightAscension + augend) / 15.0;
222863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    }
223863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
224863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    private double getCosineSunLocalHour(double sunTrueLong, double zenith) {
225863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double sinSunDeclination = getSinOfSunDeclination(sunTrueLong);
226863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double cosineSunDeclination = getCosineOfSunDeclination(sinSunDeclination);
227863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
228863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        final double zenithInRads = Math.toRadians(zenith);
229863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        final double latitude = Math.toRadians(mLocation.getLatitude());
230863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
231863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double cosineZenith = Math.cos(zenithInRads);
232863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double sinLatitude = Math.sin(latitude);
233863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double cosLatitude = Math.cos(latitude);
234863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
235863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double sinDeclinationTimesSinLat = sinSunDeclination * sinLatitude;
236863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double dividend = cosineZenith - sinDeclinationTimesSinLat;
237863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double divisor = cosineSunDeclination * cosLatitude;
238863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
239863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        return dividend / divisor;
240863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    }
241863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
242863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    private static double getSinOfSunDeclination(double sunTrueLong) {
243863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double sinTrueLongitude = Math.sin(Math.toRadians(sunTrueLong));
244863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        return sinTrueLongitude * 0.39782;
245863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    }
246863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
247863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    private static double getCosineOfSunDeclination(double sinSunDeclination) {
248863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double arcSinOfSinDeclination = Math.asin(sinSunDeclination);
249863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        return Math.cos(arcSinOfSinDeclination);
250863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    }
251863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
252863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    private static double getSunLocalHour(double cosineSunLocalHour, Boolean isSunrise) {
253863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double arcCosineOfCosineHourAngle = Math.acos(cosineSunLocalHour);
254863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double localHour = Math.toDegrees(arcCosineOfCosineHourAngle);
255863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        if (isSunrise) {
256863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy            localHour = 360.0 - localHour;
257863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        }
258863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        return localHour / 15.0;
259863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    }
260863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
261863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    private static double getLocalMeanTime(double sunTrueLong, double longitudeHour,
262863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy            double sunLocalHour) {
263863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
264863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double rightAscension = getRightAscension(sunTrueLong);
265863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double innerParens = longitudeHour * 0.06571;
266863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double localMeanTime = sunLocalHour + rightAscension - innerParens;
267863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        localMeanTime = localMeanTime - 6.622;
268863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
269863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        if (localMeanTime < 0.0) {
270863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy            localMeanTime = localMeanTime + 24.0;
271863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        } else if (localMeanTime > 24.0) {
272863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy            localMeanTime = localMeanTime - 24.0;
273863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        }
274863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        return localMeanTime;
275863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    }
276863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
277863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    private double getLocalTime(double localMeanTime, Calendar date) {
278863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double utcTime = localMeanTime - getBaseLongitudeHour();
279863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double utcOffSet = getUTCOffSet(date);
280863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double utcOffSetTime = utcTime + utcOffSet;
281863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        return adjustForDST(utcOffSetTime, date);
282863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    }
283863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
284863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    private double adjustForDST(double localMeanTime, Calendar date) {
285863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        double localTime = localMeanTime;
286863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        if (mTimeZone.inDaylightTime(date.getTime())) {
287863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy            localTime++;
288863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        }
289863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        if (localTime > 24.0) {
290863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy            localTime = localTime - 24.0;
291863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        }
292863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        return localTime;
293863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    }
294863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
295863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    /**
296863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     * ****** UTILITY METHODS (Should probably go somewhere else. *****************
297863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy     */
298863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
299863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    private static double getDayOfYear(Calendar date) {
300863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        return date.get(Calendar.DAY_OF_YEAR);
301863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    }
302863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy
303863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    private static double getUTCOffSet(Calendar date) {
304863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        int offSetInMillis = date.get(Calendar.ZONE_OFFSET);
305863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy        return offSetInMillis / 3600000;
306863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy    }
307863fd1d0c9c7ade2c8eb29ae3d642377c0fec673Romain Guy}