##// END OF EJS Templates
Update configs
Update configs

File last commit:

r0:b84e1135c2c4
r9:2e29893b10f5
Show More
geometry.c
1114 lines | 34.2 KiB | text/x-c | CLexer
/* $Id: geometry.c 4554 2014-12-17 15:47:14Z brideout $ */
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <ctype.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <cedarIO.h>
#include <madrec.h>
#include <cedar.h>
#include <date.h>
/* include code written by National Renewable Energy Lab */
#include <solpos00.h>
/***********************************************************************
*
* sprod calculates the scalar product of two vectors a and b,
* sprod = a .dot. b.
*/
double
sprod(double *a, double *b)
{
double sp;
sp = a[0]*b[0] + a[1]*b[1] + a[2]*b[2];
return(sp);
}
/***********************************************************************
*
* vadd calculates the sum of two vectors a and b, c = a + b.
*/
int
vadd(double *a, double *b, double *c)
{
c[0] = a[0] + b[0];
c[1] = a[1] + b[1];
c[2] = a[2] + b[2];
return (0);
}
/***********************************************************************
*
* vsub calculates the difference of two vectors a and b, c = a - b.
*/
int
vsub(double *a, double *b, double *c)
{
c[0] = a[0] - b[0];
c[1] = a[1] - b[1];
c[2] = a[2] - b[2];
return(0);
}
/***********************************************************************
*
* csconv converts between cartesian coordinates x,y,z and spherical
* coordinates r,theta,phi. if imode=1, (x,y,z) -> (r,theta,phi).
* if imode=2, (r,theta,phi) -> (x,y,z). theta and phi are in
* degrees.
*/
int
csconv(double *xp, double *yp, double *zp,
double *rp, double *thetap, double *phip,
int imode)
{
double dtr=0.0174532925199, rlmin=1.e-20,
x, y, z, r, theta, phi,
rho2, ct, t, st, cp, p, sp;
if (imode == 1) {
x = *xp;
y = *yp;
z = *zp;
rho2 = x*x + y*y;
r = sqrt(rho2 + z*z);
if (z < rlmin && z > 0)
z = rlmin;
else if (z > -rlmin && z < 0)
z = -rlmin;
else
z = z;
theta = atan2(sqrt(rho2), z)/dtr;
if (x < rlmin && x > 0)
x = rlmin;
else if (x > -rlmin && x < 0)
x = -rlmin;
else
x = x;
phi = atan2(y, x)/dtr;
*rp = r;
*thetap = theta;
*phip = phi;
return(0);
}
else if (imode == 2) {
r = *rp;
theta = *thetap;
phi = *phip;
t = dtr*theta;
p = dtr*phi;
ct = cos(t);
st = sin(t);
cp = cos(p);
sp = sin(p);
x = r*st*cp;
y = r*st*sp;
z = r*ct;
*xp = x;
*yp = y;
*zp = z;
return(0);
}
else {
return(1);
}
}
/***********************************************************************
*
* vctcnv converts between the cartesian and spherical coordinate
* representations of a vector field f. (fx,fy,fz) are the
* components of the field at (x,y,z). (fr,ft,fp) are the
* components of the field at (r,theta,phi) in the directions of
* increasing r, increasing theta and increasing phi. if imode=1,
* (fx,fy,fz,x,y,z) -> (fr,ft,fp,r,theta,phi). if imode=2,
* (fr,ft,fp,r,theta,phi) -> (fx,fy,fz,x,y,z). theta and phi are
* in degrees.
*/
int
vctcnv(double *fxp, double *fyp, double *fzp,
double *xp, double *yp, double *zp,
double *frp, double *ftp, double *fpp,
double *rp, double *thetap, double *phip,
int imode)
{
double dtr=0.0174532925199, rlmin=1.e-20,
fx, fy, fz, x, y, z, fr, ft, fp, r, theta, phi,
rho2, ct, st, cp, sp, t, p;
if (imode == 1) {
fx = *fxp;
fy = *fyp;
fz = *fzp;
x = *xp;
y = *yp;
z = *zp;
rho2 = x*x + y*y;
r = sqrt(rho2 + z*z);
if (z < rlmin && z > 0)
z = rlmin;
else if (z > -rlmin && z < 0)
z = -rlmin;
else
z = z;
theta = atan2(sqrt(rho2), z);
if (x < rlmin && x > 0)
x = rlmin;
else if (x > -rlmin && x < 0)
x = -rlmin;
else
x = x;
phi = atan2(y, x);
ct = cos(theta);
st = sin(theta);
cp = cos(phi);
sp = sin(phi);
theta = theta/dtr;
phi = phi/dtr;
fr = st*cp*fx + st*sp*fy + ct*fz;
ft = ct*cp*fx + ct*sp*fy - st*fz;
fp = -sp*fx + cp*fy;
*frp= fr;
*ftp = ft;
*fpp = fp;
*rp= r;
*thetap = theta;
*phip = phi;
return(0);
}
else if (imode == 2) {
fr = *frp;
ft = *ftp;
fp = *fpp;
r = *rp;
theta = *thetap;
phi = *phip;
t = dtr*theta;
p = dtr*phi;
ct = cos(t);
st = sin(t);
cp = cos(p);
sp = sin(p);
x = r*st*cp;
y = r*st*sp;
z = r*ct;
fx = st*cp*fr + ct*cp*ft - sp*fp;
fy = st*sp*fr + ct*sp*ft + cp*fp;
fz = ct*fr - st*ft;
*fxp = fx;
*fyp = fy;
*fzp = fz;
*xp = x;
*yp = y;
*zp = z;
return(0);
}
else {
return(1);
}
}
/***********************************************************************
*
* point calculates the position of a point defined by the radar
* line-of sight vector to that point.
*
* input parameters
* sr - distance of station from center of earth (km)
* slat - geocentric latitude of station (deg)
* slon - longitude of station (deg)
* az - radar azimuth (deg)
* el - radar elevation (deg)
* range - radar range (km)
*
* output parameters
* pr - distance from center of earth of observation point (km)
* glat - observation point geocentric latitude (deg)
* glon - observation point longitude (deg)
*/
int
point(double *srp, double *slatp, double *slonp,
double *azp, double *elp, double *rangep,
double *prp, double *glatp, double *glonp)
{
double sr, slat, slon, az, el, range, pr, glat, glon,
s[3], r[3], p[3], rt, rp, rr, t, el1, az1, slat1;
sr = *srp;
slat = *slatp;
slon = *slonp;
az = *azp;
el = *elp;
range = *rangep;
/* calculate "line-of-sight" station centered cartesian coords */
el1 = 90.0 - el;
az1 = 180.0 - az;
(void) csconv(&rt, &rp, &rr, &range, &el1, &az1, 2);
/* calculate "line-of-sight" earth centered cartesian coords
and "station" earth centered cartesian coords */
slat1 = 90.0 - slat;
(void) vctcnv(&r[0], &r[1], &r[2], &s[0], &s[1], &s[2],
&rr, &rt, &rp, &sr, &slat1, &slon, 2);
/* calculate "observation-point" earth centered cartesian coords */
(void) vadd(s, r, p);
/* calculate "observation-point" earth centered spherical coords */
(void) csconv(&p[0], &p[1], &p[2], &pr, &t, &glon, 1);
glat = 90. - t;
*prp = pr;
*glatp = glat;
*glonp = glon;
return(0);
}
/***********************************************************************
*
* look calculates the azimuth, elevation and range from a radar
* of a specified point.
*
* input parameters
* sr - distance of station from center of earth (km)
* slat - geocentric latitude of station (deg)
* slon - longitude of station (deg)
* pr - distance from center of earth of observation point (km)
* glat - observation point geocentric latitude (deg)
* glon - observation point longitude (deg)
*
* output parameters
* az - radar azimuth (deg)
* el - radar elevation (deg)
* range - radar range (km)
*/
int
look(double *srp, double *slatp, double *slonp,
double *prp, double *glatp, double *glonp,
double *azp, double *elp, double *rangep)
{
double sr, slat, slon, pr, glat, glon, az, el, range,
s[3], r[3], p[3], rr, rt, rp, sr1, st2, sp1, glat1, slat1;
sr = *srp;
slat = *slatp;
slon = *slonp;
pr = *prp;
glat = *glatp;
glon = *glonp;
/*calculate "observation-point" earth centered cartesian coords */
glat1 = 90.0 - glat;
(void) csconv(&p[0], &p[1], &p[2], &pr, &glat1, &glon, 2);
/*calculate "station" earth centered cartesian coordinates*/
slat1 = 90.0 - slat;
(void) csconv(&s[0], &s[1], &s[2], &sr, &slat1, &slon, 2);
/*calculate "line-of-sight" earth centered cartesian coords*/
(void) vsub(p, s, r);
/*calculate "line-of-sight" station centered cartesian coords*/
(void) vctcnv(&r[0], &r[1], &r[2], &s[0], &s[1], &s[2],
&rr, &rt, &rp, &sr1, &st2, &sp1, 1);
/*calculate "line-of-sight" station centered spherical coords*/
(void) csconv(&rt, &rp, &rr, &range, &el, &az, 1);
el = 90. - el;
az = 180. - az;
*azp = az;
*elp = el;
*rangep = range;
return(0);
}
/***********************************************************************
*
* convrt converts between geodetic and geocentric coordinates. the
* reference geoid is that adopted by the iau in 1964. a=6378.16,
* b=6356.7746, f=1/298.25. the equations for conversion from
* geocentric to geodetic are from astron. j., vol 66, 1961, p. 15.
* i=1 geodetic to geocentric
* i=2 geocentric to geodetic
* gdlat geodetic latitude (degrees)
* gdalt altitude above geoid (km)
* gclat geocentric latitude (degrees)
* rkm geocentric radial distance (km)
*/
int
convrt(int i, double *gdlatp, double *gdaltp,
double *gclatp, double *rkmp)
{
double gdlat, gdalt, gclat, rkm,
sinlat,gdl,coslat,cl2,sb2,sinbet,cosbet,
x,y,a2,rer,ccl,gcl,scl,s4cl,s2cl,c2cl,sl2,rgeoid,a4,a6,
a8,c4cl,s8cl,s6cl,dltcl;
double a=6378.16, ab2=1.0067397, ep2=0.0067397, dtr=0.0174532925199;
/* geodetic to geocentric */
if (i == 1) {
gdlat = *gdlatp;
gdalt = *gdaltp;
gdl = dtr*gdlat;
sinlat = sin(gdl);
coslat = cos(gdl);
sl2 = sinlat*sinlat;
cl2 = ab2*coslat;
cl2 = cl2*cl2;
sinbet = sinlat/sqrt(sl2+cl2);
sb2 = sinbet*sinbet;
if (sb2 > 1.0) sb2=1.0;
cosbet = sqrt(1. - sb2);
rgeoid = a/sqrt(1. + ep2*sb2);
x = rgeoid*cosbet + gdalt*coslat;
y = rgeoid*sinbet + gdalt*sinlat;
rkm = sqrt(x*x + y*y);
gclat = atan2(y,x)/dtr;
*gclatp = gclat;
*rkmp = rkm;
return(0);
}
/* geocentic to geodetic */
else if (i == 2) {
gclat = *gclatp;
rkm = *rkmp;
rer = rkm/a;
a2 = ((-1.4127348e-8/rer + .94339131e-8)/rer + .33523288e-2)/rer;
a4 = (((-1.2545063e-10/rer + .11760996e-9)/rer +
.11238084e-4)/rer - .2814244e-5)/rer;
a6 = ((54.939685e-9/rer - 28.301730e-9)/rer + 3.5435979e-9)/rer;
a8 = (((320./rer - 252.)/rer + 64.)/rer - 5.)/rer*.98008304e-12;
gcl = dtr*gclat;
ccl = cos(gcl);
scl = sin(gcl);
s2cl = 2.*scl*ccl;
c2cl = 2.*ccl*ccl - 1.0;
s4cl = 2.*s2cl*c2cl;
c4cl = 2.*c2cl*c2cl - 1.0;
s8cl = 2.*s4cl*c4cl;
s6cl = s2cl*c4cl + c2cl*s4cl;
dltcl = s2cl*a2 + s4cl*a4 + s6cl*a6 + s8cl*a8;
gdlat = gclat + dltcl/dtr;
gdalt = rkm - a/sqrt(1.+ep2*scl*scl);
*gdlatp = gdlat;
*gdaltp = gdalt;
return(0);
}
else {
return(1);
}
}
/***********************************************************************
*
* rpcart computes the components (rfx,rfy,rfz) relative to an earth
* centered cartesian coordinate system of the radar line of sight
* vector from a radar with coordinates sr (distance from center
* of earth), slat (geocentric latitude) and slon (longitude). the
* observation point is specified by az (azimuth), el (elevation) and
* range (range). the cartesian coordinates of the observation
* point are returned in (pfx,pfy,pfz).
* input - sr,slat,slon,az,el,range
* output - rfx,rfy,rfz,pfx,pfy,pfz
*/
int
rpcart (double *srp, double *slatp, double *slonp,
double *azp, double *elp, double *rangep,
double *rfxp, double *rfyp, double *rfzp,
double *pfxp, double *pfyp, double *pfzp)
{
double dtr=0.0174532925199, rr, rtheta, rphi, a, e,
ca, sa, ce, se, rx, ry, rz, rfr, rft, rfp;
rr = *srp;
rtheta = 90. - *slatp;
rphi = *slonp;
a = dtr*(180. - *azp);
e = dtr*(90. - *elp);
ca = cos(a);
sa = sin(a);
ce = cos(e);
se = sin(e);
rfr = *rangep*ce;
rft = *rangep*se*ca;
rfp = *rangep*se*sa;
(void) vctcnv(rfxp, rfyp, rfzp, &rx, &ry, &rz, &rfr, &rft, &rfp, &rr,
&rtheta, &rphi, 2);
*pfxp = rx + *rfxp;
*pfyp = ry + *rfyp;
*pfzp = rz + *rfzp;
return(0);
}
/***********************************************************************
*
* gdv converts a vector field f at geodetic latitude gdlat and
* geocentric latitude gclat from a geocentric based representation
* to a geodetic based representation. the geocentric components
* are fr (radial outward), ft (increasing geocentric colatitude,
* e.g. southward) and fp (increasing east longitude). the
* geodetic components are fx (northward, parallel to surface of
* earth), fy (eastward, parallel to surface of earth) and fz
* (downward, perpendicular to surface of earth). fr,ft,fp thus
* correspond to spherical coordinates r,theta,phi, with their
* origin at the center of the earth. x,y,z are the coordinates
* customarily used to describe the three components of the
* geomagnetic field. fp and fy are the same.
*/
int
gdv(double *gdlatp, double *gclatp,
double *frp, double *ftp, double *fpp,
double *fxp, double *fyp, double *fzp)
{
double dtr=0.0174532925199, gdl, sinlat, coslat, t,
ct, st, sind, cosd;
gdl = dtr*(*gdlatp);
sinlat = sin(gdl);
coslat = cos(gdl);
t = dtr*(90. - *gclatp);
ct = cos(t);
st = sin(t);
sind = st*sinlat - ct*coslat;
cosd = ct*sinlat + st*coslat;
*fxp = -*ftp*cosd - *frp*sind;
*fyp = *fpp;
*fzp = *ftp*sind - *frp*cosd;
return (0);
}
/***********************************************************************
*
* los2geodetic calculates the position of a point defined by an instrument
* line-of sight vector to that point. This is a convenience routine in
* which the instrument location is specified by its CEDAR instrument code
* and which returns the geodetic coordinates of the point.
*
*
* input parameters
* kinst - instrument code in metadata
* az - radar azimuth (deg)
* el - radar elevation (deg)
* range - radar range (km)
*
* output parameters
* gdlat - observation point geodetic latitude (deg)
* glon - observation point longitude (deg)
* gdalt - altitude above geoid (km)
*/
int los2geodetic(int kinst, double az, double el, double range,
double *gdlatp, double *glonp, double *gdaltp)
{
double slat, slon, salt, sgclat, sr, pr, pgclat, plat, plon, palt;
cedarGetStationPos(kinst, &slat, &slon, &salt);
/* check for missing info (salt never missing) */
if (isnan(slat) || isnan(salt)) {
*gdlatp = missing;
*glonp = missing;
*gdaltp = missing;
return(-1);
}
/* Convert station coordinates from geodetic to geocentric */
(void) convrt(1, &slat, &salt, &sgclat, &sr);
/* Compute geocentric coordinates of specified point */
(void) point(&sr, &sgclat, &slon, &az, &el, &range,
&pr, &pgclat, &plon);
/* Convert observation point coordinates from geocentric to geodetic */
(void) convrt(2, &plat, &palt, &pgclat, &pr);
*gdlatp = plat;
*glonp = plon;
*gdaltp = palt;
return(0);
}
/***********************************************************************
*
* solarzen_az calculates the solar zenith and az angles for a given time, gdlat,
* and glon.
*
*
* input parameters
* double ut - Universal time in seconds since 1950
* double gdlat - geodetic latitude in degrees
* double glon - geodetic longitude in degrees
*
* output parameters
* szen - solar zenith angle (deg, 0=directly overhead)
* saz - solar azimuth angle (deg, N=0, E=90)
*
*
* Solar zenith angle is calculated at 0 alt, although this changes
* very little with altitute. No atmospheric correction is applied.
* This method uses solpos.c, written by National Renewable Energy
* Laboratory.
*
* This method is a modified version of stest.c found at
* http://rredc.nrel.gov/solar/codes_algs/solpos/
*/
void solarzen_az(double ut, double gdlat, double glon, double * szen, double * saz)
{
struct posdata pd, *pdat; /* declare a posdata struct and a pointer for it */
int iyr = 0, imd = 0, ihm = 0, ics = 0;
int month = 0;
int day = 0;
int hour = 0;
int min = 0;
int sec = 0;
long retval; /* to capture S_solpos return codes */
pdat = &pd; /* point to the structure for convenience */
S_init (pdat);
/* convert UT */
dinvmadptr(ut, &iyr, &imd, &ihm, &ics);
month = imd/100;
day = imd - 100*month;
hour = ihm/100;
min = ihm - 100*hour;
sec = ics/100;
pdat->longitude = glon;
pdat->latitude = gdlat;
pdat->timezone = 0.0; /* Since we always use UT. */
pdat->year = iyr;
pdat->daynum = madGetDayno(iyr, month, day);
pdat->hour = hour;
pdat->minute = min;
pdat->second = sec;
retval = S_solpos (pdat); /* S_solpos function call */
/* check whether error occured */
if (retval != 0)
{
*szen = missing;
*saz = missing;
}
else
{
*szen = pdat->zenetr;
*saz = pdat->azim;
}
}
/***********************************************************************
*
* solardist calculates the distance in km from the center of the earth
* to the center of the sun at time ut.
*
*
* input parameters
* double ut - Universal time in seconds since 1950
*
* returns double - distance in km from the center of the earth
* to the center of the sun at time ut
*
* This method is taken from "Practical Astronomy with Your
* Calculator" 2nd edition, Peter Duffett-Smith, p. 80-87.
*/
double solardist(double ut)
{
/* constants defined in Practical Astronomy */
const double daysPerYear = 365.2422;
const double eclipLongEpic = 278.83354;
const double eclipLongPer = 282.596403;
const double eccOrbit = 0.016718;
const double semiMajAxis = 1.495985E8;
/* We use epic at 1950, Practical Astronomy */
/* uses epic at 1980, so conversion needed */
const double sec1950_1980 = 946684800.0;
double numDays = 0.0;
double numDeg = 0.0;
double meanAnom = 0.0;
double eqCenter = 0.0;
double trueAnom = 0.0;
double distance = 0.0;
/* Step one and two in book (p. 83) - computes days since 1/1/1980 */
numDays = (ut-sec1950_1980)/(3600.0*24.0);
/* Step three in book (p. 83) */
numDeg = (360.0*numDays)/daysPerYear;
while (numDeg < 0.0) numDeg += 360.0;
while (numDeg > 360.0) numDeg -= 360.0;
/* Step four in book (p. 83) */
meanAnom = numDeg + eclipLongEpic - eclipLongPer;
while (meanAnom < 0.0) meanAnom += 360.0;
while (meanAnom > 360.0) meanAnom -= 360.0;
/* Step five in book (p. 83) */
eqCenter = (360.0*eccOrbit*sin(meanAnom/57.297))/3.1415;
trueAnom = meanAnom + eqCenter;
while (trueAnom < 0.0) trueAnom += 360.0;
while (trueAnom > 360.0) trueAnom -= 360.0;
/* from section 44, p. 87 */
distance = semiMajAxis*(1-pow(eccOrbit, 2.0));
distance = distance/(1+(eccOrbit*cos(trueAnom/57.297)));
return (distance);
}
/***********************************************************************
*
* shadowheight calculates the distance directly above any gdlat and glon
* for a given UT in km at which the earth's shadow terminates.
* Will be 0.0 on dayside of earth.
*
*
* input parameters
* double ut - Universal time in seconds since 1950
* double gdlat - geodetic latitude in degrees
* double glon - geodetic longitude in degrees
*
* returns double - distance directly above any gdlat and glon
* for a given UT in km at which the earth's shadow terminates.
* Will be 0.0 on dayside of earth.
*
* This method uses the results of solarzen and solardist to create a simple
* cone on a sphere model of the earth's shadow. Shadow height is defined as
* the lowest elevation at which any part of the sun can be seen. No atmospheric
* bending of light is included. The radius of the earth is calculated at the
* tan point of the sun's rays.
*
* Algorithm:
*
* Solar Zenith Angle = Z
* Solar Azimuth =Az (0 = North, 90 = East)
*
* Get latitude of tangent point of sun's rays:
*
* = gdlat + cos(Az)*(Z-90.0)
*
* Get earthRadius at that point using convrt at gdlat = 0 (sea level)
*
* ConeHalfAngle = C = atan((sunRadius - earthRadius)/soldist)
*
*
* (cos Z tan C + 1 - sin Z)
* Shadowheight = earthRadius -------------------------
* (sin Z - cos Z tan C)
*
* Daytime (Shadowheight = 0) if numerator negitive or if
* Z <= 91.0.
*/
double shadowheight(double ut, double gdlat, double glon)
{
/* constants in km */
const double sunRadius = 695500.0;
double soldist = 0.0;
double szen = 0.0;
double saz = 0.0;
double coneHAng = 0.0;
double numer = 0.0;
double denom = 0.0;
/* used to get earth radius at sun tangent point */
double dellat = 0.0; /* change in latitude from meas point to */
/* sun tan point */
double tan_gdlat = 0.0; /* latitude of sun tangent crossing */
double tan_gdalt = 0.0; /* sea level sun tangent assumed */
double gclat = 0.0; /* don't care about geocentric lat */
double rkmp = 0.0; /* radius of the earth at the sun tangent */
/* get the solar zenith */
/* if less than 91.0, return 0.0 (dayside) */
solarzen_az(ut, gdlat, glon, &szen, &saz);
if (szen <= 91.0)
return (0.0);
/* find the solar tangent lat */
dellat =cos(saz/57.297)*(szen-90.0);
tan_gdlat = gdlat + dellat;
if (tan_gdlat > 90.0)
tan_gdlat = tan_gdlat - 2*(tan_gdlat - 90.0);
if (tan_gdlat < -90.0)
tan_gdlat = tan_gdlat - 2*(tan_gdlat + 90.0);
/* find the earth's radius at that point */
convrt(1, &tan_gdlat, &tan_gdalt, &gclat, &rkmp);
/* get distance to sun at ut */
soldist = solardist(ut);
/* get cone 1/2 angle */
coneHAng = atan((sunRadius - rkmp)/soldist);
/* get numerator */
numer = cos(szen/57.297)*tan(coneHAng/57.297) + 1 - sin(szen/57.297);
if (numer < 0.0) /* sun still visible on ground */
return (0.0);
denom = sin(szen/57.297) - cos(szen/57.297)*tan(coneHAng/57.297);
return (rkmp*numer/denom);
}
/***********************************************************************
*
* sunrise_set calculates the time UT of ionospheric sunrise
* and sunset.
*
*
* input parameters
* double ut - Universal time in seconds since 1950
* double gdlat - geodetic latitude in degrees
* double glon - longitude in degrees
* double gdalt - geodetic altitude in km
* double * sunrise - pointer to double allocated by user to be
* set to sunrise time UT
* double * sunset - pointer to double allocated by user to be
* set to sunset time UT
*
* returns void
*
* If either sunrise or sunset not found, set to missing.
* All times in seconds since 1/1/1950
*
* Algorithm:
*
* Depends on shadowheight calculation, so limitations discussed
* there apply (atmospheric bending of light ignored).
*
* If glon < 0:
* solar midnight = UT - glon*24/360
* solar noon = UT + 12 - glon*24/360
*
* If sun is not set at solar midnight (defined by shadowheight > gdalt),
* or sun not up at solar noon, check if any difference between 0 and 24 UT.
* If so, find only one of sunrise and sunset as described below, and set
* the other to missing. If not, return missing for both, because that point
* is either in the sun or in the shadow all day. The user must determine
* which by comparing shadowheight and gdalt. Otherwise, seek sunrise
* between solar midnight and solar noon, slice remaining time in half each
* guess. Stop when time step less than 1 minute.
*
* If sun is up at 0 UT that day: Seek sunset between 0.0 and
* solar midnight as above.
* Else: Seek sunset between solar noon and 24.0 as above.
*
* Else if glon > 0:
* solar midnight = UT + 24 - glon*24/360
* solar noon = UT + 12 - glon*24/360
*
* If sun is not set at solar midnight (defined by shadowheight > gdalt),
* or sun not up at solar noon, check if any difference between 0 and 24 UT.
* If so, find only one of sunrise and sunset as described below, and set
* the other to missing. If not, return missing for both, because that point
* is either in the sun or in the shadow all day. The user must determine
* which by comparing shadowheight and gdalt. Otherwise, seek sunset
* between solar noon and solar midnight, slice remaining time in half each
* guess. Stop when time step less than 1 minute.
*
* If sun is up at 0 UT that day: Seek sunrise between solar
* midnight and 24.0 as above.
*
* Else: Seek sunrise between 0.0 and solar noon as above.
*
* Note: day is divided 11 times to ensure greater than one minute resolution.
*/
void sunrise_set(double ut,
double gdlat,
double glon,
double gdalt,
double * sunrise,
double * sunset)
{
double startUT = 0.0; /* time UT at UT hour 0 */
double endUT = 0.0; /* time UT at UT hour 24 */
double solmidnight = 0.0; /* solar midnight */
double solnoon = 0.0; /* solar noon */
double startTime = 0.0; /* loop start time */
double endTime = 0.0; /* loop end time */
double tempTime = 0.0; /* loop temp time */
int iyr = 0, imd = 0, ihm = 0, ics = 0;
int i = 0;
int upAt0UT = 0; /* flag to indicate sun up at 0 UT */
int upAt24UT = 0; /* flag to indicate sun up at 24 UT */
/* first find UT at beginning and end of day */
dinvmadptr(ut, &iyr, &imd, &ihm, &ics);
startUT = dmadptr(iyr, imd, 0, 0);
endUT = startUT + 24.0*3600.0;
/* make sure glon between -180 and 180 */
while (glon > 180.0) glon -= 360.0;
while (glon < -180.0) glon += 360.0;
if (glon < 0.0 ) /* west of 0 degrees lat */
{
solmidnight = startUT - glon*24.0*3600.0/360.0;
solnoon = startUT - glon*24.0*3600.0/360.0 + 12.0*3600.0;
/* check for normal sunrise/sunset */
if (shadowheight(solmidnight, gdlat, glon) > gdalt &&
shadowheight(solnoon, gdlat, glon) == 0.0)
{
/* sunrise must occur between solmidnight and solnoon */
startTime = solmidnight;
endTime = solnoon;
for (i=0; i<11; i++)
{
tempTime = (startTime + endTime)/2.0;
if (shadowheight(tempTime, gdlat, glon) < gdalt)
{
/* sun has risen at temp time */
endTime = tempTime;
}
else /* sun hasn't risen yet */
startTime = tempTime;
}
*sunrise = (startTime + endTime)/2.0;
/* check if sun is up at 0 UT */
if (shadowheight(startUT, gdlat, glon) < gdalt)
{
/* seek sunset between startUT and solmidnight */
startTime = startUT;
endTime = solmidnight;
for (i=0; i<11; i++)
{
tempTime = (startTime + endTime)/2.0;
if (shadowheight(tempTime, gdlat, glon) < gdalt)
{
/* sun still up at temp time */
startTime = tempTime;
}
else /* sun has already set */
endTime = tempTime;
}
*sunset = (startTime + endTime)/2.0;
}
else
{
/* seek sunset between solnoon and endUT */
startTime = solnoon;
endTime = endUT;
for (i=0; i<11; i++)
{
tempTime = (startTime + endTime)/2.0;
if (shadowheight(tempTime, gdlat, glon) < gdalt)
{
/* sun still up at temp time */
startTime = tempTime;
}
else /* sun has already set */
endTime = tempTime;
}
*sunset = (startTime + endTime)/2.0;
}
}
else /* not normal - see if any transition occured */
{
upAt0UT = shadowheight(startUT, gdlat, glon) < gdalt;
upAt24UT = shadowheight(endUT, gdlat, glon) < gdalt;
if (upAt0UT == upAt24UT) /* no transition */
{
*sunrise = missing;
*sunset = missing;
}
else if (upAt0UT == 0 && upAt24UT == 1)
{
/* find sunrise only */
startTime = startUT;
endTime = endUT;
for (i=0; i<11; i++)
{
tempTime = (startTime + endTime)/2.0;
if (shadowheight(tempTime, gdlat, glon) < gdalt)
{
/* sun has risen at temp time */
endTime = tempTime;
}
else /* sun hasn't risen yet */
startTime = tempTime;
}
*sunrise = (startTime + endTime)/2.0;
*sunset = missing;
}
else /* sunset only */
{
startTime = startUT;
endTime = endUT;
for (i=0; i<11; i++)
{
tempTime = (startTime + endTime)/2.0;
if (shadowheight(tempTime, gdlat, glon) < gdalt)
{
/* sun still up at temp time */
startTime = tempTime;
}
else /* sun has set */
endTime = tempTime;
}
*sunset = (startTime + endTime)/2.0;
*sunrise = missing;
}
}
}
else /* west of 0 degrees lat */
{
solmidnight = startUT - glon*24.0*3600.0/360.0 + 24.0*3600.0;
solnoon = startUT - glon*24.0*3600.0/360.0 + 12.0*3600.0;
/* check for normal sunrise/sunset */
if (shadowheight(solmidnight, gdlat, glon) > gdalt &&
shadowheight(solnoon, gdlat, glon) == 0.0)
{
/* sunset must occur between solnoon and solmidnight */
startTime = solnoon;
endTime = solmidnight;
for (i=0; i<11; i++)
{
tempTime = (startTime + endTime)/2.0;
if (shadowheight(tempTime, gdlat, glon) < gdalt)
{
/* sun has not set yet */
startTime = tempTime;
}
else /* sun already */
endTime = tempTime;
}
*sunset = (startTime + endTime)/2.0;
/* check if sun is up at 0 UT */
if (shadowheight(startUT, gdlat, glon) < gdalt)
{
/* seek sunrise between solmidnight and endUT */
startTime = solmidnight;
endTime = endUT;
for (i=0; i<11; i++)
{
tempTime = (startTime + endTime)/2.0;
if (shadowheight(tempTime, gdlat, glon) < gdalt)
{
/* sun already risen */
endTime = tempTime;
}
else /* sun not yet risen */
startTime = tempTime;
}
*sunrise = (startTime + endTime)/2.0;
}
else
{
/* seek sunrise between startUT and solnoon */
startTime = startUT;
endTime = solnoon;
for (i=0; i<11; i++)
{
tempTime = (startTime + endTime)/2.0;
if (shadowheight(tempTime, gdlat, glon) < gdalt)
{
/* sun already risen */
endTime = tempTime;
}
else /* sun not yet risen */
startTime = tempTime;
}
*sunrise = (startTime + endTime)/2.0;
}
}
else /* not normal - see if any transition occured */
{
upAt0UT = shadowheight(startUT, gdlat, glon) < gdalt;
upAt24UT = shadowheight(endUT, gdlat, glon) < gdalt;
if (upAt0UT == upAt24UT) /* no transition */
{
*sunrise = missing;
*sunset = missing;
}
else if (upAt0UT == 0 && upAt24UT == 1)
{
/* find sunrise only */
startTime = startUT;
endTime = endUT;
for (i=0; i<11; i++)
{
tempTime = (startTime + endTime)/2.0;
if (shadowheight(tempTime, gdlat, glon) < gdalt)
{
/* sun has risen at temp time */
endTime = tempTime;
}
else /* sun hasn't risen yet */
startTime = tempTime;
}
*sunrise = (startTime + endTime)/2.0;
*sunset = missing;
}
else /* sunset only */
{
startTime = startUT;
endTime = endUT;
for (i=0; i<11; i++)
{
tempTime = (startTime + endTime)/2.0;
if (shadowheight(tempTime, gdlat, glon) < gdalt)
{
/* sun still up at temp time */
startTime = tempTime;
}
else /* sun has set */
endTime = tempTime;
}
*sunset = (startTime + endTime)/2.0;
*sunrise = missing;
}
}/* end check for normal sunrise/sunset */
} /* end if glon < 0 */
}