Blame libgweather/weather-sun.c

rpm-build ca2b01
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 4 -*- */
rpm-build ca2b01
/* weather-sun.c - Astronomy calculations for gweather
rpm-build ca2b01
 *
rpm-build ca2b01
 * This program is free software; you can redistribute it and/or
rpm-build ca2b01
 * modify it under the terms of the GNU General Public License as
rpm-build ca2b01
 * published by the Free Software Foundation; either version 2 of the
rpm-build ca2b01
 * License, or (at your option) any later version.
rpm-build ca2b01
 *
rpm-build ca2b01
 * This program is distributed in the hope that it will be useful, but
rpm-build ca2b01
 * WITHOUT ANY WARRANTY; without even the implied warranty of
rpm-build ca2b01
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
rpm-build ca2b01
 * General Public License for more details.
rpm-build ca2b01
 *
rpm-build ca2b01
 * You should have received a copy of the GNU General Public License
rpm-build ca2b01
 * along with this program; if not, see
rpm-build ca2b01
 * <http://www.gnu.org/licenses/>.
rpm-build ca2b01
 */
rpm-build ca2b01
rpm-build ca2b01
/*
rpm-build ca2b01
 * Formulas from:
rpm-build ca2b01
 * "Practical Astronomy With Your Calculator" (3e), Peter Duffett-Smith
rpm-build ca2b01
 * Cambridge University Press 1988
rpm-build ca2b01
 * Unless otherwise noted, comments referencing "steps" are related to
rpm-build ca2b01
 * the algorithm presented in section 49 of above
rpm-build ca2b01
 */
rpm-build ca2b01
rpm-build ca2b01
#ifdef HAVE_CONFIG_H
rpm-build ca2b01
#include <config.h>
rpm-build ca2b01
#endif
rpm-build ca2b01
rpm-build ca2b01
#include <math.h>
rpm-build ca2b01
#include <time.h>
rpm-build ca2b01
#include <glib.h>
rpm-build ca2b01
rpm-build ca2b01
#include "gweather-private.h"
rpm-build ca2b01
rpm-build ca2b01
#define ECCENTRICITY(d)         (0.01671123 - (d)/36525.*0.00004392)
rpm-build ca2b01
rpm-build ca2b01
/* 
rpm-build ca2b01
 * Ecliptic longitude of the sun at specified time (UT)
rpm-build ca2b01
 * The algoithm is described in section 47 of Duffett-Smith
rpm-build ca2b01
 * Return value is in radians
rpm-build ca2b01
 */
rpm-build ca2b01
gdouble
rpm-build ca2b01
sunEclipLongitude(time_t t)
rpm-build ca2b01
{
rpm-build ca2b01
    gdouble ndays, meanAnom, eccenAnom, delta, e, longitude;
rpm-build ca2b01
rpm-build ca2b01
    /*
rpm-build ca2b01
     * Start with an estimate based on a fixed daily rate
rpm-build ca2b01
     */
rpm-build ca2b01
    ndays = EPOCH_TO_J2000(t) / 86400.;
rpm-build ca2b01
    meanAnom = DEGREES_TO_RADIANS(MEAN_ECLIPTIC_LONGITUDE(ndays)
rpm-build ca2b01
				  - PERIGEE_LONGITUDE(ndays));
rpm-build ca2b01
    
rpm-build ca2b01
    /*
rpm-build ca2b01
     * Approximate solution of Kepler's equation:
rpm-build ca2b01
     * Find E which satisfies  E - e sin(E) = M (mean anomaly)
rpm-build ca2b01
     */                                         
rpm-build ca2b01
    eccenAnom = meanAnom;
rpm-build ca2b01
    e = ECCENTRICITY(ndays);
rpm-build ca2b01
rpm-build ca2b01
    while (1e-12 < fabs( delta = eccenAnom - e * sin(eccenAnom) - meanAnom))
rpm-build ca2b01
    {
rpm-build ca2b01
	eccenAnom -= delta / (1.- e * cos(eccenAnom));
rpm-build ca2b01
    }
rpm-build ca2b01
rpm-build ca2b01
    /*
rpm-build ca2b01
     * Earth's longitude on the ecliptic
rpm-build ca2b01
     */
rpm-build ca2b01
    longitude = fmod( DEGREES_TO_RADIANS (PERIGEE_LONGITUDE(ndays))
rpm-build ca2b01
		      + 2. * atan (sqrt ((1.+e)/(1.-e))
rpm-build ca2b01
				   * tan (eccenAnom / 2.)),
rpm-build ca2b01
		      2. * M_PI);
rpm-build ca2b01
    if (longitude < 0.) {
rpm-build ca2b01
	longitude += 2 * M_PI;
rpm-build ca2b01
    }
rpm-build ca2b01
    return longitude;
rpm-build ca2b01
}
rpm-build ca2b01
rpm-build ca2b01
static gdouble
rpm-build ca2b01
ecliptic_obliquity (gdouble time)
rpm-build ca2b01
{
rpm-build ca2b01
    gdouble jc = EPOCH_TO_J2000 (time) / (36525. * 86400.);
rpm-build ca2b01
    gdouble eclip_secs = (84381.448
rpm-build ca2b01
			  - (46.84024 * jc)
rpm-build ca2b01
			  - (59.e-5 * jc * jc)
rpm-build ca2b01
			  + (1.813e-3 * jc * jc * jc));
rpm-build ca2b01
    return DEGREES_TO_RADIANS(eclip_secs / 3600.);
rpm-build ca2b01
}
rpm-build ca2b01
rpm-build ca2b01
/*
rpm-build ca2b01
 * Convert ecliptic longitude and latitude (radians) to equitorial
rpm-build ca2b01
 * coordinates, expressed as right ascension (hours) and
rpm-build ca2b01
 * declination (radians)
rpm-build ca2b01
 */
rpm-build ca2b01
void
rpm-build ca2b01
ecl2equ (gdouble time,
rpm-build ca2b01
	 gdouble eclipLon, gdouble eclipLat,
rpm-build ca2b01
	 gdouble *ra, gdouble *decl)
rpm-build ca2b01
{
rpm-build ca2b01
    gdouble mEclipObliq = ecliptic_obliquity(time);
rpm-build ca2b01
rpm-build ca2b01
    if (ra) {
rpm-build ca2b01
	*ra = RADIANS_TO_HOURS (atan2 ((sin (eclipLon) * cos (mEclipObliq)
rpm-build ca2b01
					- tan (eclipLat) * sin(mEclipObliq)),
rpm-build ca2b01
				       cos (eclipLon)));
rpm-build ca2b01
	if (*ra < 0.)
rpm-build ca2b01
	    *ra += 24.;
rpm-build ca2b01
    }
rpm-build ca2b01
    if (decl) {
rpm-build ca2b01
	*decl = asin (( sin (eclipLat) * cos (mEclipObliq))
rpm-build ca2b01
		      + cos (eclipLat) * sin (mEclipObliq) * sin(eclipLon));
rpm-build ca2b01
    }
rpm-build ca2b01
}
rpm-build ca2b01
rpm-build ca2b01
/*
rpm-build ca2b01
 * Calculate rising and setting times for an object
rpm-build ca2b01
 * based on it equitorial coordinates (section 33 & 15)
rpm-build ca2b01
 * Returned "rise" and "set" values are sideral times in hours
rpm-build ca2b01
 */
rpm-build ca2b01
static void
rpm-build ca2b01
gstObsv (gdouble ra, gdouble decl,
rpm-build ca2b01
	 gdouble obsLat, gdouble obsLon,
rpm-build ca2b01
	 gdouble *rise, gdouble *set)
rpm-build ca2b01
{
rpm-build ca2b01
    double a = acos (-tan (obsLat) * tan (decl));
rpm-build ca2b01
    double b;
rpm-build ca2b01
rpm-build ca2b01
    if (isnan (a) != 0) {
rpm-build ca2b01
	*set = *rise = a;
rpm-build ca2b01
	return;
rpm-build ca2b01
    }
rpm-build ca2b01
    a = RADIANS_TO_HOURS (a);
rpm-build ca2b01
    b = 24. - a + ra;
rpm-build ca2b01
    a += ra;
rpm-build ca2b01
    a -= RADIANS_TO_HOURS (obsLon);
rpm-build ca2b01
    b -= RADIANS_TO_HOURS (obsLon);
rpm-build ca2b01
    if ((a = fmod (a, 24.)) < 0)
rpm-build ca2b01
	a += 24.;
rpm-build ca2b01
    if ((b = fmod (b, 24.)) < 0)
rpm-build ca2b01
	b += 24.;
rpm-build ca2b01
rpm-build ca2b01
    *set = a;
rpm-build ca2b01
    *rise = b;
rpm-build ca2b01
}
rpm-build ca2b01
rpm-build ca2b01
rpm-build ca2b01
static gdouble
rpm-build ca2b01
t0 (time_t date)
rpm-build ca2b01
{
rpm-build ca2b01
    gdouble t = ((gdouble)(EPOCH_TO_J2000 (date) / 86400)) / 36525.0;
rpm-build ca2b01
    gdouble t0 = fmod (6.697374558 + 2400.051366 * t + 2.5862e-5 * t * t, 24.);
rpm-build ca2b01
    if (t0 < 0.)
rpm-build ca2b01
        t0 += 24.;
rpm-build ca2b01
    return t0;
rpm-build ca2b01
}
rpm-build ca2b01
rpm-build ca2b01
rpm-build ca2b01
static gboolean
rpm-build ca2b01
calc_sun (GWeatherInfo *info, time_t t)
rpm-build ca2b01
{
rpm-build ca2b01
    GWeatherInfoPrivate *priv;
rpm-build ca2b01
    gdouble obsLat;
rpm-build ca2b01
    gdouble obsLon;
rpm-build ca2b01
    time_t gm_midn;
rpm-build ca2b01
    time_t lcl_midn;
rpm-build ca2b01
    gdouble gm_hoff, lambda;
rpm-build ca2b01
    gdouble ra1, ra2;
rpm-build ca2b01
    gdouble decl1, decl2;
rpm-build ca2b01
    gdouble decl_midn, decl_noon;
rpm-build ca2b01
    gdouble rise1, rise2;
rpm-build ca2b01
    gdouble set1, set2;
rpm-build ca2b01
    gdouble tt, t00;
rpm-build ca2b01
    gdouble x, u, dt;
rpm-build ca2b01
rpm-build ca2b01
    /* Approximate preceding local midnight at observer's longitude */
rpm-build ca2b01
    priv = info->priv;
rpm-build ca2b01
    obsLat = priv->location.latitude;
rpm-build ca2b01
    obsLon = priv->location.longitude;
rpm-build ca2b01
    gm_midn = t - (t % 86400);
rpm-build ca2b01
    gm_hoff = floor ((RADIANS_TO_DEGREES (obsLon) + 7.5) / 15.);
rpm-build ca2b01
    lcl_midn = gm_midn - 3600. * gm_hoff;
rpm-build ca2b01
    if (t - lcl_midn >= 86400)
rpm-build ca2b01
        lcl_midn += 86400;
rpm-build ca2b01
    else if (lcl_midn > t)
rpm-build ca2b01
        lcl_midn -= 86400;
rpm-build ca2b01
rpm-build ca2b01
    lambda = sunEclipLongitude (lcl_midn);
rpm-build ca2b01
rpm-build ca2b01
    /*
rpm-build ca2b01
     * Calculate equitorial coordinates of sun at previous
rpm-build ca2b01
     * and next local midnights
rpm-build ca2b01
     */
rpm-build ca2b01
    ecl2equ (lcl_midn, lambda, 0., &ra1, &decl1);
rpm-build ca2b01
    ecl2equ (lcl_midn + 86400.,
rpm-build ca2b01
	     lambda + DEGREES_TO_RADIANS(SOL_PROGRESSION), 0.,
rpm-build ca2b01
	     &ra2, &decl2);
rpm-build ca2b01
rpm-build ca2b01
    /*
rpm-build ca2b01
     * If the observer is within the Arctic or Antarctic Circles then
rpm-build ca2b01
     * the sun may be above or below the horizon for the full day.
rpm-build ca2b01
     */
rpm-build ca2b01
    decl_midn = MIN(decl1,decl2);
rpm-build ca2b01
    decl_noon = (decl1+decl2)/2.;
rpm-build ca2b01
    priv->midnightSun =
rpm-build ca2b01
	(obsLat > (M_PI/2.-decl_midn)) || (obsLat < (-M_PI/2.-decl_midn));
rpm-build ca2b01
    priv->polarNight =
rpm-build ca2b01
	(obsLat > (M_PI/2.+decl_noon)) || (obsLat < (-M_PI/2.+decl_noon));
rpm-build ca2b01
    if (priv->midnightSun || priv->polarNight) {
rpm-build ca2b01
	priv->sunriseValid = priv->sunsetValid = FALSE;
rpm-build ca2b01
	return FALSE;
rpm-build ca2b01
    }
rpm-build ca2b01
rpm-build ca2b01
    /*
rpm-build ca2b01
     * Convert to rise and set times based positions for the preceding
rpm-build ca2b01
     * and following local midnights.
rpm-build ca2b01
     */
rpm-build ca2b01
    gstObsv (ra1, decl1, obsLat, obsLon - (gm_hoff * M_PI / 12.), &rise1, &set1;;
rpm-build ca2b01
    gstObsv (ra2, decl2, obsLat, obsLon - (gm_hoff * M_PI / 12.), &rise2, &set2;;
rpm-build ca2b01
rpm-build ca2b01
    /* TODO: include calculations for regions near the poles. */
rpm-build ca2b01
    if (isnan(rise1) || isnan(rise2)) {
rpm-build ca2b01
	priv->sunriseValid = priv->sunsetValid = FALSE;
rpm-build ca2b01
        return FALSE;
rpm-build ca2b01
    }
rpm-build ca2b01
rpm-build ca2b01
    if (rise2 < rise1) {
rpm-build ca2b01
        rise2 += 24.;
rpm-build ca2b01
    }
rpm-build ca2b01
    if (set2 < set1) {
rpm-build ca2b01
        set2 += 24.;
rpm-build ca2b01
    }
rpm-build ca2b01
rpm-build ca2b01
    tt = t0(lcl_midn);
rpm-build ca2b01
    t00 = tt - (gm_hoff + RADIANS_TO_HOURS(obsLon)) * 1.002737909;
rpm-build ca2b01
rpm-build ca2b01
    if (t00 < 0.)
rpm-build ca2b01
        t00 += 24.;
rpm-build ca2b01
rpm-build ca2b01
    if (rise1 < t00) {
rpm-build ca2b01
        rise1 += 24.;
rpm-build ca2b01
        rise2 += 24.;
rpm-build ca2b01
    }
rpm-build ca2b01
    if (set1 < t00) {
rpm-build ca2b01
        set1  += 24.;
rpm-build ca2b01
        set2  += 24.;
rpm-build ca2b01
    }
rpm-build ca2b01
rpm-build ca2b01
    /*
rpm-build ca2b01
     * Interpolate between the two to get a rise and set time
rpm-build ca2b01
     * based on the sun's position at local noon (step 8)
rpm-build ca2b01
     */
rpm-build ca2b01
    rise1 = (24.07 * rise1 - t00 * (rise2 - rise1)) / (24.07 + rise1 - rise2);
rpm-build ca2b01
    set1 = (24.07 * set1 - t00 * (set2 - set1)) / (24.07 + set1 - set2);
rpm-build ca2b01
rpm-build ca2b01
    /*
rpm-build ca2b01
     * Calculate an adjustment value to account for parallax,
rpm-build ca2b01
     * refraction and the Sun's finite diameter (steps 9,10)
rpm-build ca2b01
     */
rpm-build ca2b01
    decl2 = (decl1 + decl2) / 2.;
rpm-build ca2b01
    x = DEGREES_TO_RADIANS(0.830725);
rpm-build ca2b01
    u = acos ( sin(obsLat) / cos(decl2) );
rpm-build ca2b01
    dt = RADIANS_TO_HOURS ( asin ( sin(x) / sin(u) ) / cos(decl2) );
rpm-build ca2b01
rpm-build ca2b01
    /*
rpm-build ca2b01
     * Subtract the correction value from sunrise and add to sunset,
rpm-build ca2b01
     * then (step 11) convert sideral times to UT
rpm-build ca2b01
     */
rpm-build ca2b01
    rise1 = (rise1 - dt - tt) * 0.9972695661;
rpm-build ca2b01
    if (rise1 < 0.)
rpm-build ca2b01
	rise1 += 24;
rpm-build ca2b01
    else if (rise1 >= 24.)
rpm-build ca2b01
	rise1 -= 24.;
rpm-build ca2b01
    priv->sunriseValid = ((rise1 >= 0.) && (rise1 < 24.));
rpm-build ca2b01
    priv->sunrise = (rise1 * 3600.) + lcl_midn;
rpm-build ca2b01
rpm-build ca2b01
    set1  = (set1 + dt - tt) * 0.9972695661;
rpm-build ca2b01
    if (set1 < 0.)
rpm-build ca2b01
	set1 += 24;
rpm-build ca2b01
    else if (set1 >= 24.)
rpm-build ca2b01
	set1 -= 24.;
rpm-build ca2b01
    priv->sunsetValid = ((set1 >= 0.) && (set1 < 24.));
rpm-build ca2b01
    priv->sunset = (set1 * 3600.) + lcl_midn;
rpm-build ca2b01
rpm-build ca2b01
    return (priv->sunriseValid || priv->sunsetValid);
rpm-build ca2b01
}
rpm-build ca2b01
rpm-build ca2b01
void
rpm-build ca2b01
_gweather_info_ensure_sun (GWeatherInfo *info)
rpm-build ca2b01
{
rpm-build ca2b01
    GWeatherInfoPrivate *priv;
rpm-build ca2b01
rpm-build ca2b01
    priv = info->priv;
rpm-build ca2b01
rpm-build ca2b01
    if (!priv->sunriseValid && !priv->sunsetValid)
rpm-build ca2b01
	calc_sun (info, priv->current_time);
rpm-build ca2b01
}
rpm-build ca2b01
rpm-build ca2b01
/**
rpm-build ca2b01
 * weather_info_next_sun_event:
rpm-build ca2b01
 * @info: #WeatherInfo structure
rpm-build ca2b01
 *
rpm-build ca2b01
 * Returns: the interval, in seconds, until the next "sun event":
rpm-build ca2b01
 *  - local midnight, when rise and set times are recomputed
rpm-build ca2b01
 *  - next sunrise, when icon changes to daytime version
rpm-build ca2b01
 *  - next sunset, when icon changes to nighttime version
rpm-build ca2b01
 */
rpm-build ca2b01
gint
rpm-build ca2b01
gweather_info_next_sun_event (GWeatherInfo *info)
rpm-build ca2b01
{
rpm-build ca2b01
    time_t    now = time (NULL);
rpm-build ca2b01
    struct tm ltm;
rpm-build ca2b01
    time_t    nxtEvent;
rpm-build ca2b01
    GWeatherInfoPrivate *priv;
rpm-build ca2b01
rpm-build ca2b01
    priv = info->priv;
rpm-build ca2b01
rpm-build ca2b01
    g_return_val_if_fail (info != NULL, -1);
rpm-build ca2b01
rpm-build ca2b01
    _gweather_info_ensure_sun (info);
rpm-build ca2b01
rpm-build ca2b01
    /* Determine when the next local midnight occurs */
rpm-build ca2b01
    (void) localtime_r (&now, <m;;
rpm-build ca2b01
    ltm.tm_sec = 0;
rpm-build ca2b01
    ltm.tm_min = 0;
rpm-build ca2b01
    ltm.tm_hour = 0;
rpm-build ca2b01
    ltm.tm_mday++;
rpm-build ca2b01
    nxtEvent = mktime (<m;;
rpm-build ca2b01
rpm-build ca2b01
    if (priv->sunsetValid &&
rpm-build ca2b01
	(priv->sunset > now) && (priv->sunset < nxtEvent))
rpm-build ca2b01
	nxtEvent = priv->sunset;
rpm-build ca2b01
    if (priv->sunriseValid &&
rpm-build ca2b01
	(priv->sunrise > now) && (priv->sunrise < nxtEvent))
rpm-build ca2b01
	nxtEvent = priv->sunrise;
rpm-build ca2b01
    return (gint)(nxtEvent - now);
rpm-build ca2b01
}