Home | History | Annotate | Download | only in grass
      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 }