1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Based on sunrisesunsetlib-java: 5 * Copyright 2008-2009 Mike Reedell / LuckyCatLabs. 6 * 7 * Original project and source can be found at: 8 * http://mikereedell.github.com/sunrisesunsetlib-java/ 9 * 10 * Licensed under the Apache License, Version 2.0 (the "License"); 11 * you may not use this file except in compliance with the License. 12 * You may obtain a copy of the License at 13 * 14 * http://www.apache.org/licenses/LICENSE-2.0 15 * 16 * Unless required by applicable law or agreed to in writing, software 17 * distributed under the License is distributed on an "AS IS" BASIS, 18 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 * See the License for the specific language governing permissions and 20 * limitations under the License. 21 */ 22 23 package com.android.wallpaper.grass; 24 25 import java.util.Calendar; 26 import java.util.TimeZone; 27 28 import android.location.Location; 29 30 class SunCalculator { 31 /** Astronomical sunrise/set is when the sun is 18 degrees below the horizon. */ 32 static final double ZENITH_ASTRONOMICAL = 108; 33 34 /** Nautical sunrise/set is when the sun is 12 degrees below the horizon. */ 35 static final double ZENITH_NAUTICAL = 102; 36 37 /** Civil sunrise/set (dawn/dusk) is when the sun is 6 degrees below the horizon. */ 38 static final double ZENITH_CIVIL = 96; 39 40 /** Official sunrise/set is when the sun is 50' below the horizon. */ 41 static final double ZENITH_OFFICIAL = 90.8333; 42 43 private Location mLocation; 44 private TimeZone mTimeZone; 45 46 SunCalculator(Location location, String timeZoneIdentifier) { 47 mLocation = location; 48 mTimeZone = TimeZone.getTimeZone(timeZoneIdentifier); 49 } 50 51 public void setLocation(Location location) { 52 mLocation = location; 53 } 54 55 /** 56 * Computes the sunrise time for the given zenith at the given date. 57 * 58 * @param solarZenith <code>Zenith</code> enum corresponding to the type 59 * of sunrise to compute. 60 * @param date <code>Calendar</code> object representing the date to 61 * compute the sunrise for. 62 * @return the sunrise time 63 */ 64 public double computeSunriseTime(double solarZenith, Calendar date) { 65 return computeSolarEventTime(solarZenith, date, true); 66 } 67 68 /** 69 * Computes the sunset time for the given zenith at the given date. 70 * 71 * @param solarZenith <code>Zenith</code> enum corresponding to the type of 72 * sunset to compute. 73 * @param date <code>Calendar</code> object representing the date to compute 74 * the sunset for. 75 * @return the sunset time 76 */ 77 public double computeSunsetTime(double solarZenith, Calendar date) { 78 return computeSolarEventTime(solarZenith, date, false); 79 } 80 81 public static int timeToHours(double time) { 82 int hour = (int) Math.floor(time); 83 int minute = (int) Math.round((time - hour) * 60); 84 if (minute == 60) { 85 hour++; 86 } 87 return hour; 88 } 89 90 public static int timeToMinutes(double time) { 91 int hour = (int) Math.floor(time); 92 int minute = (int) Math.round((time - hour) * 60); 93 if (minute == 60) { 94 minute = 0; 95 } 96 return minute; 97 } 98 99 public static float timeToDayFraction(double time) { 100 int hour = (int) Math.floor(time); 101 int minute = (int) Math.round((time - hour) * 60); 102 if (minute == 60) { 103 minute = 0; 104 hour++; 105 } 106 return (hour * 60 + minute) / 1440.0f; 107 } 108 109 public static String timeToString(double time) { 110 StringBuffer buffer = new StringBuffer(); 111 int hour = (int) Math.floor(time); 112 int minute = (int) Math.round((time - hour) * 60); 113 if (minute == 60) { 114 minute = 0; 115 hour++; 116 } 117 buffer.append(hour).append(':').append(minute < 10 ? "0" + minute : minute); 118 return buffer.toString(); 119 } 120 121 private double computeSolarEventTime(double solarZenith, Calendar date, boolean isSunrise) { 122 date.setTimeZone(mTimeZone); 123 double longitudeHour = getLongitudeHour(date, isSunrise); 124 double meanAnomaly = getMeanAnomaly(longitudeHour); 125 double sunTrueLong = getSunTrueLongitude(meanAnomaly); 126 double cosineSunLocalHour = getCosineSunLocalHour(sunTrueLong, solarZenith); 127 if ((cosineSunLocalHour < -1.0) || (cosineSunLocalHour > 1.0)) { 128 return 0; 129 } 130 131 double sunLocalHour = getSunLocalHour(cosineSunLocalHour, isSunrise); 132 double localMeanTime = getLocalMeanTime(sunTrueLong, longitudeHour, sunLocalHour); 133 return getLocalTime(localMeanTime, date); 134 } 135 136 /** 137 * Computes the base longitude hour, lngHour in the algorithm. 138 * 139 * @return the longitude of the location of the solar event divided by 15 (deg/hour), in 140 * <code>double</code> form. 141 */ 142 private double getBaseLongitudeHour() { 143 return mLocation.getLongitude() / 15.0; 144 } 145 146 /** 147 * Computes the longitude time, t in the algorithm. 148 * 149 * @return longitudinal time in <code>double</code> form. 150 */ 151 private double getLongitudeHour(Calendar date, Boolean isSunrise) { 152 int offset = 18; 153 if (isSunrise) { 154 offset = 6; 155 } 156 double dividend = offset - getBaseLongitudeHour(); 157 double addend = dividend / 24.0; 158 return getDayOfYear(date) + addend; 159 } 160 161 /** 162 * Computes the mean anomaly of the Sun, M in the algorithm. 163 * 164 * @return the suns mean anomaly, M, in <code>double</code> form. 165 */ 166 private static double getMeanAnomaly(double longitudeHour) { 167 return 0.9856 * longitudeHour - 3.289; 168 } 169 170 /** 171 * Computes the true longitude of the sun, L in the algorithm, at the given 172 * location, adjusted to fit in the range [0-360]. 173 * 174 * @param meanAnomaly the suns mean anomaly. 175 * @return the suns true longitude, in <code>double</code> form. 176 */ 177 private static double getSunTrueLongitude(double meanAnomaly) { 178 final double meanRadians = Math.toRadians(meanAnomaly); 179 double sinMeanAnomaly = Math.sin(meanRadians); 180 double sinDoubleMeanAnomaly = Math.sin((meanRadians * 2.0)); 181 182 double firstPart = meanAnomaly + sinMeanAnomaly * 1.916; 183 double secondPart = sinDoubleMeanAnomaly * 0.020 + 282.634; 184 double trueLongitude = firstPart + secondPart; 185 186 if (trueLongitude > 360) { 187 trueLongitude = trueLongitude - 360.0; 188 } 189 return trueLongitude; 190 } 191 192 /** 193 * Computes the suns right ascension, RA in the algorithm, adjusting for 194 * the quadrant of L and turning it into degree-hours. Will be in the 195 * range [0,360]. 196 * 197 * @param sunTrueLong Suns true longitude, in <code>double</code> 198 * @return suns right ascension in degree-hours, in <code>double</code> form. 199 */ 200 private static double getRightAscension(double sunTrueLong) { 201 double tanL = Math.tan(Math.toRadians(sunTrueLong)); 202 203 double innerParens = Math.toDegrees(tanL) * 0.91764; 204 double rightAscension = Math.atan(Math.toRadians(innerParens)); 205 rightAscension = Math.toDegrees(rightAscension); 206 207 if (rightAscension < 0.0) { 208 rightAscension = rightAscension + 360.0; 209 } else if (rightAscension > 360.0) { 210 rightAscension = rightAscension - 360.0; 211 } 212 213 double ninety = 90.0; 214 double longitudeQuadrant = (int) (sunTrueLong / ninety); 215 longitudeQuadrant = longitudeQuadrant * ninety; 216 217 double rightAscensionQuadrant = (int) (rightAscension / ninety); 218 rightAscensionQuadrant = rightAscensionQuadrant * ninety; 219 220 double augend = longitudeQuadrant - rightAscensionQuadrant; 221 return (rightAscension + augend) / 15.0; 222 } 223 224 private double getCosineSunLocalHour(double sunTrueLong, double zenith) { 225 double sinSunDeclination = getSinOfSunDeclination(sunTrueLong); 226 double cosineSunDeclination = getCosineOfSunDeclination(sinSunDeclination); 227 228 final double zenithInRads = Math.toRadians(zenith); 229 final double latitude = Math.toRadians(mLocation.getLatitude()); 230 231 double cosineZenith = Math.cos(zenithInRads); 232 double sinLatitude = Math.sin(latitude); 233 double cosLatitude = Math.cos(latitude); 234 235 double sinDeclinationTimesSinLat = sinSunDeclination * sinLatitude; 236 double dividend = cosineZenith - sinDeclinationTimesSinLat; 237 double divisor = cosineSunDeclination * cosLatitude; 238 239 return dividend / divisor; 240 } 241 242 private static double getSinOfSunDeclination(double sunTrueLong) { 243 double sinTrueLongitude = Math.sin(Math.toRadians(sunTrueLong)); 244 return sinTrueLongitude * 0.39782; 245 } 246 247 private static double getCosineOfSunDeclination(double sinSunDeclination) { 248 double arcSinOfSinDeclination = Math.asin(sinSunDeclination); 249 return Math.cos(arcSinOfSinDeclination); 250 } 251 252 private static double getSunLocalHour(double cosineSunLocalHour, Boolean isSunrise) { 253 double arcCosineOfCosineHourAngle = Math.acos(cosineSunLocalHour); 254 double localHour = Math.toDegrees(arcCosineOfCosineHourAngle); 255 if (isSunrise) { 256 localHour = 360.0 - localHour; 257 } 258 return localHour / 15.0; 259 } 260 261 private static double getLocalMeanTime(double sunTrueLong, double longitudeHour, 262 double sunLocalHour) { 263 264 double rightAscension = getRightAscension(sunTrueLong); 265 double innerParens = longitudeHour * 0.06571; 266 double localMeanTime = sunLocalHour + rightAscension - innerParens; 267 localMeanTime = localMeanTime - 6.622; 268 269 if (localMeanTime < 0.0) { 270 localMeanTime = localMeanTime + 24.0; 271 } else if (localMeanTime > 24.0) { 272 localMeanTime = localMeanTime - 24.0; 273 } 274 return localMeanTime; 275 } 276 277 private double getLocalTime(double localMeanTime, Calendar date) { 278 double utcTime = localMeanTime - getBaseLongitudeHour(); 279 double utcOffSet = getUTCOffSet(date); 280 double utcOffSetTime = utcTime + utcOffSet; 281 return adjustForDST(utcOffSetTime, date); 282 } 283 284 private double adjustForDST(double localMeanTime, Calendar date) { 285 double localTime = localMeanTime; 286 if (mTimeZone.inDaylightTime(date.getTime())) { 287 localTime++; 288 } 289 if (localTime > 24.0) { 290 localTime = localTime - 24.0; 291 } 292 return localTime; 293 } 294 295 /** 296 * ****** UTILITY METHODS (Should probably go somewhere else. ***************** 297 */ 298 299 private static double getDayOfYear(Calendar date) { 300 return date.get(Calendar.DAY_OF_YEAR); 301 } 302 303 private static double getUTCOffSet(Calendar date) { 304 int offSetInMillis = date.get(Calendar.ZONE_OFFSET); 305 return offSetInMillis / 3600000; 306 } 307 }