geometry.c
1114 lines
| 34.2 KiB
| text/x-c
|
CLexer
r0 | /* $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 */ | ||||
} | ||||