Sun position
Sun position algorithm
sky-site.c
1 /*==============================================================================
2  * sky-site.c - astronomical routines related to an observing site
3  *
4  * Author: David Hoadley
5  *
6  * Description: (see the "Site routines" sections of sky.h)
7  *
8  * Copyright (c) 2020, David Hoadley <vcrumble@westnet.com.au>
9  * All rights reserved.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions are met:
13  *
14  * * Redistributions of source code must retain the above copyright notice, this
15  * list of conditions and the following disclaimer.
16  * * Redistributions in binary form must reproduce the above copyright notice,
17  * this list of conditions and the following disclaimer in the documentation
18  * and/or other materials provided with the distribution.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
24  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30  * POSSIBILITY OF SUCH DAMAGE.
31  *
32  *==============================================================================
33  */
34 /*------------------------------------------------------------------------------
35  * Notes:
36  * Character set: UTF-8. (Non-ASCII characters appear in this file)
37  * Things you might want to edit: definition of macro SPA_COMPARISONS
38  *----------------------------------------------------------------------------*/
39 
40 
41 /* ANSI includes etc. */
42 #include "instead-of-math.h" /* for sincos() */
43 #include <math.h>
44 
45 /* Local and project includes */
46 #include "sky.h"
47 #include "general.h"
48 
49 /*
50  * Local #defines and typedefs
51  */
52 DEFINE_THIS_FILE; /* For use by REQUIRE() - assertions */
53 
54 /* Un-comment the following if you are running comparison tests vs the
55  NREL SPA code for Sun positions. It changes the constants describing
56  the Earth to match those used in the SPA itself (instead of using the
57  (better) constants from the Astronomical Almanac). Also it disables
58  the correction for diurnal aberration (because SPA does not calculate
59  this correction). */
60 /*--- #define SPA_COMPARISONS ---*/
61 
62 /*
63  * Prototypes for local functions (not called from other modules).
64  */
65 LOCAL void createAzElBaseM(Sky_SiteProp *site);
66 
67 /*
68  * Global variables accessible by other modules
69  */
70 
71 
72 /*
73  * Local variables (not accessed by other modules)
74  */
75 #ifndef SPA_COMPARISONS
76 /* Constants found in the 2007 Astronomical Almanac, pages K6 & K7 */
77 LOCAL const double c_kmps = 299792.458; // speed of light (km/s)
78 LOCAL const double f = 0.0033528197; // flattening factor of the geoid
79 LOCAL const double ae_km = 6378.1366; // equatorial radius of geoid (km)
80 LOCAL const double omega_radps = 7.292115e-5;// mean earth rotation rate(rad/s)
81 LOCAL const double au_km = 1.49597871464e8; // one Astronomical Unit (km)
82 #else
83 /* Constants found in the NREL SPA document */
84 LOCAL const double f = 1.0 - 0.99664719; // = 0.0033528100
85 LOCAL const double ae_km = 6378.140;
86 LOCAL const double au_km = 1.4960039e8; // implied by sol parallax=8.794"
87 /* Constants not found in NREL, so copied from above */
88 LOCAL const double c_kmps = 299792.458; // speed of light (km/s)
89 LOCAL const double omega_radps = 7.292115e-5;// mean earth rotation rate(rad/s)
90 #endif
91 /* Derived constants */
92 LOCAL const double esq = f + f - f * f; // square of eccentricity of geoid
93 
94 /*
95  *==============================================================================
96  *
97  * Implementation
98  *
99  *==============================================================================
100  *
101  * Global functions callable by other modules
102  *
103  *------------------------------------------------------------------------------
104  */
105 GLOBAL void sky_setSiteLocation(double latitude_deg,
106  double longitude_deg,
107  double height_m,
108  Sky_SiteProp *site)
109 /*! Initialise the \a site structure by calculating those site-related values
110  that do not change with time.
111  \param[in] latitude_deg Latitude of site (ϕ) (degrees)
112  \param[in] longitude_deg Longitude of site (degrees)
113  \param[in] height_m Height above ellipsoid (e.g. GPS height) (metres)
114 
115  \param[out] site All fields initialised
116 
117  This is a simplified version of sky_setSiteLoc2(), which is good enough
118  for most purposes.
119 
120  This function calculates the quantities that will be used to convert
121  geocentric coordinates to topocentric ones. This involves calculating the
122  position of the site relative to the centre of the earth, and calculating
123  constants that will be used for diurnal parallax and aberration corrections.
124 
125  This is complicated by the fact that the earth is elliptical rather than
126  perfectly spherical. See section K of The Astronomical Almanac (pages K11
127  & K12 in the 2007 edition) for details of the geometry. This function
128  effectively calculates the Geocentric latitude (ϕ′) from the Geodetic
129  latitude (ϕ), and then sin(ϕ - ϕ′) and cos(ϕ - ϕ′). But in practice, the
130  sin and cos terms can be obtained without explicitly calculating ϕ′ itself,
131  and that is done here.
132 
133  The \a site->refracPT field is initialised assuming that the site pressure
134  is 1010 hPa and the temperature is 10 °C. If other values are required,
135  follow this call by a call to sky_setSiteTempPress().
136 
137  The \a site->timezone_d field is initialised to 0, assuming that the site
138  time zone is UTC. If another value is required, follow this call by a call
139  to sky_setSiteTimeZone().
140 
141  \par When to call this function
142  At program initialisation time only
143 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
144 {
145  /* Use same values for astronomical and geodetic lat/long */
146  sky_setSiteLoc2(latitude_deg,
147  longitude_deg,
148  latitude_deg,
149  longitude_deg,
150  height_m,
151  site);
152 }
153 
154 
155 
156 GLOBAL void sky_setSiteLoc2(double astLat_deg,
157  double astLong_deg,
158  double geodLat_deg,
159  double geodLong_deg,
160  double height_m,
161  Sky_SiteProp *site)
162 /*! Alternative initialisation function to sky_setSiteLocation(). It initialises
163  * the \a site structure, but it supports a distinction between Astronomical
164  * latitude & longitude and Geodetic latitude & longitude.
165  \param[in] astLat_deg Astronomical Latitude of site (ϕA) (degrees)
166  \param[in] astLong_deg Astronomical Longitude of site (degrees)
167  \param[in] geodLat_deg Geodetic Latitude of site (GPS latitude) (ϕ)
168  (degrees)
169  \param[in] geodLong_deg Geodetic Longitude of site (GPS longitude) (degrees)
170  \param[in] height_m Height above ellipsoid (e.g. GPS height) (metres)
171 
172  \param[out] site All fields initialised
173 
174  This function calculates the same quantities as described for
175  sky_setSiteLocation().
176  The only difference is that the \a site.azElBaseM rotation matrix is based
177  on Astronomical latitude and latitude (instead of Geodetic).
178 
179  Astronomical latitude and longitude are coordinates as determined by the
180  local gravity vector and astronomical observation.
181  Geodetic latitude and longitude are coordinates as measured by some
182  geodetic system - e.g. a map reference, or GPS coordinates.
183 
184  \note Distinguishing between Astronomical lat & lon and Geodetic lat & lon
185  is only required for very precise work. It is most likely that you won't
186  actually know the Astronomical coords, and it won't matter. Use function
187  sky_setSiteLocation() instead - it sets the Astronomical coords to the same
188  values as the Geodetic or GPS coords.
189 
190  The \a site->refracPT field is initialised assuming that the site pressure
191  is 1010 hPa and the temperature is 10 °C. If other values are required,
192  follow this call by a call to sky_setSiteTempPress().
193 
194  The \a site->timezone_d field is initialised to 0, assuming that the site
195  time zone is UTC. If another value is required, follow this call by a call
196  to sky_setSiteTimeZone().
197 
198  \par When to call this function
199  At program initialisation time only, if you need to distinguish between
200  Astronomical latitude/longitude and Geodetic latitude/longitude. Otherwise
201  call sky_setSiteLocation() instead.
202 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
203 {
204  double geodLat_rad; // Geodetic Latitude (radian)
205  double geodLong_rad; // Geodetic Longitude (radian)
206  double sinPhi, cosPhi; // sin and cos of Geodetic Latitude
207  double invC; // = 1/C, (for C see Astron. Almanac p. K11)
208  double aeC_km; // = ae * C (km)
209  double rhoCosPhiP_km; // = ae * rho * cos(phi') (km)
210  double Kc;
211  V3D_Matrix mat1, mat2;
212 
213  REQUIRE_NOT_NULL(site);
214 
215  /* 1. Calculate radian values of some of the site constants */
216  site->astLat_rad = degToRad(astLat_deg);
217  site->astLong_rad = degToRad(astLong_deg);
218 
219  geodLat_rad = degToRad(geodLat_deg);
220  geodLong_rad = degToRad(geodLong_deg);
221 
222  /* 2. Create rotation matrix from astronomical latitude and longitude */
223  createAzElBaseM(site);
224 
225  /* 3. Calculate the geocentric radius of the observatory and the two
226  geocentric parallax quantities
227  ae*rho*sin(Phi-Phi') and -ae*rho*cos(Phi-Phi')
228  (The geodetic latitude is phi, the geocentric latitude is phi'.
229  The angle (phi - phi') is known as the "angle of the vertical".) */
230  sincos(geodLat_rad, &sinPhi, &cosPhi);
231  invC = sqrt(1.0 - esq * (sinPhi * sinPhi));
232  aeC_km = ae_km / invC;
233  Kc = sqrt(1.0 - esq * (2.0 - esq) * (sinPhi * sinPhi));
234  site->geocRadius_km = aeC_km * Kc + height_m / 1000.0;
235 
236  // Parallax corrections are offsets in x (north) and z (zenith) directions
237  site->rhoSin_au = aeC_km * esq * sinPhi * cosPhi / au_km; // x (north)
238  site->rhoCos_au = -(ae_km * invC + height_m / 1000.0) / au_km; // z (zenith)
239 
240  /* 4. Calculate the diurnal aberration correction */
241  rhoCosPhiP_km = (aeC_km + height_m / 1000.0) * cosPhi;
242  site->diurnalAberr = omega_radps * rhoCosPhiP_km / c_kmps; // y (east)
243 
244  /* 5. Might want to convert from Az-El frame to HA-Dec frame. Create
245  rotation matrix to do this. Matrix must first rotate around Y axis by
246  Pi/2 - Latitude. Second rotation is around Z' axis by Pi. */
247  v3d_createRotationMatrix(&mat1, Yaxis, HALFPI - site->astLat_rad);
248  v3d_createRotationMatrix(&mat2, Zaxis, PI);
249  v3d_multMxM(&site->haDecM, &mat2, &mat1);
250 
251  /* 6. Other initialisations, in case sky_setSiteTempPressure() or
252  sky_setSiteTimeZone() don't get called. */
253  site->refracPT = 1.0; // Default to 10 °C and 1010 hPa
254  site->timeZone_d = 0.0; // Default to UTC.
255 
256  site->azElM = &site->azElBaseM; // Assume no polar motion correction
257 }
258 
259 
260 
261 GLOBAL void sky_setSiteTempPress(double temperature_degC,
262  double pressure_hPa,
263  Sky_SiteProp *site)
264 /*! Set refraction coefficients based on atmospheric temperature and pressure
265  at the site.
266  \param[in] temperature_degC - average annual air temperature at the site (°C)
267  \param[in] pressure_hPa - average annual air pressure at the site
268  (hPa = mbar)
269  \param[out] site - field \a refracPT, a combined refraction
270  coefficient for pressure & temperature
271 
272  This coefficient will be used in the simple refraction algorithm that is
273  called from routine sky_siteTirsToTopo().
274 
275  \par When to call this function
276  1. At program initialisation time, after calling sky_setSiteLocation() (or
277  sky_setSiteLoc2())
278  2. If you have updated values of pressure or temperature
279 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
280 {
281  double p, t;
282 
283  REQUIRE_NOT_NULL(site);
284  REQUIRE(temperature_degC > -100.0); // what were you thinking?
285 
286  t = 283.0 / (273.0 + temperature_degC);
287  p = pressure_hPa / 1010.0;
288  site->refracPT = p * t;
289 }
290 
291 
292 
293 GLOBAL void sky_setSiteTimeZone(double timeZone_h,
294  Sky_SiteProp *site)
295 /*! Set the time zone offset for this site.
296  \param[in] timeZone_h Zonal correction (hours). Time zones east of
297  Greenwich are positive (e.g. Australian Eastern
298  Standard Time is +10.0) and those west of Greenwich
299  are negative (e.g. US Pacific Daylight Time is -7.0)
300 
301  \param[out] site field \a timeZone_d, time zone scaled to fractions of
302  a day
303 
304  \par When to call this function
305  1. At program initialisation time, after calling sky_setSiteLocation() (or
306  sky_setSiteLoc2())
307  2. If daylight saving begins or ends during the time the program is running
308 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
309 {
310  REQUIRE_NOT_NULL(site);
311 
312  site->timeZone_d = timeZone_h / 24.0;
313 }
314 
315 
316 
317 GLOBAL void sky_setupSiteSurface(double azimuth_deg,
318  double slope_deg,
319  Sky_SiteHorizon *surface)
320 /*! Set the orientation and slope of a surface (such as a solar panel) for which
321  you want to calculate the incidence angle of incoming radiation.
322  \param[in] azimuth_deg The azimuth angle of the normal to the surface
323  (degrees). This is measured clockwise from North, as
324  for all other azimuth angles.
325  \param[in] slope_deg The slope of the surface measured from the horizontal
326  (degrees). Alternatively, this is the zenith distance
327  of the normal to the surface.
328  \param[out] surface The coordinates of the normal to the surface, in both
329  polar and rectangular forms. The rectangular form will
330  be passed later to sky_siteIncidence_rad().
331 
332  \par When to call this function
333  At program initialisation time only, if you have a surface of interest.
334 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
335 {
336  REQUIRE_NOT_NULL(surface);
337 
338  surface->azimuth_rad = degToRad(azimuth_deg);
339  surface->elevation_rad = degToRad(90.0 - slope_deg);
340  v3d_polarToRect(&surface->rectV,
341  surface->azimuth_rad,
342  surface->elevation_rad);
343 }
344 
345 
346 
348  Sky_SiteProp *site)
349 /*! Modify our azEl rotation matrix to incorporate a polar motion rotation
350  matrix
351  \param[in] polar Polar motion parameters, as set by function
352  sky_setPolarMotion()
353  \param[in,out] site Fields \a azElM and possibly \a azElPolM are updated
354 
355  \par When to call this function
356  Polar motion is such a tiny effect that you may well decide not to bother
357  with it. So you only need to call this routine if:
358  1. you are bothering about polar motion at all, and
359  2. changes were made to the \a polar structure by a recent call to
360  function sky_setPolarMotion(). This routine is only ever to be
361  called after that routine has run. This is expected to be very
362  infrequently - once per day is more than enough.
363 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
364 {
365  REQUIRE_NOT_NULL(polar);
366  REQUIRE_NOT_NULL(site);
367 
368  if (polar->correctionInUse) {
369  v3d_multMxM(&site->azElPolM, &site->azElBaseM, &polar->corrM);
370  site->azElM = &site->azElPolM; // Use combined matrix from now on
371  } else {
372  site->azElM = &site->azElBaseM; // Use matrix without polar motion
373  }
374 }
375 
376 
377 
378 GLOBAL void sky_siteTirsToTopo(const V3D_Vector *terInterV,
379  double dist_au,
380  const Sky_SiteProp *site,
381  Sky_SiteHorizon *topo)
382 /*! Transform a coordinate vector from the Terrestrial Intermediate Reference
383  System to topocentric Az/El coordinates for the observing site whose
384  properties are described in parameter "site". Note: there is a mixture of
385  vectors in right-handed and left-handed coordinate systems here
386  \param[in] terInterV Position vector in Terrestrial Intermediate Reference
387  System. (This will have been obtained by calling either
388  routine astsite_appToTirs() or routine
389  astsite_intermedToTopo().)
390  \param[in] dist_au Geocentric Distance to object (astronomical units).
391  Note: for far distant objects outside of the solar
392  system, you can supply 0.0 for this value. It will be
393  treated as infinity.
394  \param[in] site Block of data describing the observing site, as
395  initialised by one of the functions
396  sky_setSiteLocation() or sky_setSiteLoc2().
397  \param[out] topo Topocentric position, both as a vector in horizon
398  coordinates, and as azimuth (radian) and elevation
399  (radian).
400 
401  \par When to call this function
402  When you have the position of a celestial object expressed in the
403  Terrestrial Intermediate Reference System, use this function to convert it
404  to topocentric coordinates (the coordinates as seen from a particular
405  observing location on the Earth's surface). If you are running a control
406  loop to enable continuous tracking of this object, you will need to call
407  this function every time around your control loop.
408  \par
409  If you wish to calculate the position of the object as seen from multiple
410  sites simultaneously, you need to call this function once for each of those
411  sites, passing the relevant \a site data block to each call.
412 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
413 {
414  double dEl_rad; // Change in elevation from refraction (radian)
415  double w;
416  double tanZd; // Tangent of zenith distance
417 
418  REQUIRE_NOT_NULL(terInterV);
419  REQUIRE_NOT_NULL(site);
420  REQUIRE_NOT_NULL(topo);
421 
422  /* Rotate from the equatorial system to the horizon coordinate system. The
423  resultant position vector, referred to North, East and Zenith, is in a
424  left-handed system, like the Azimuth & Elevation angle system to which it
425  will eventually be converted. */
426  v3d_multMxV(&topo->rectV, site->azElM, terInterV);
427  /* Note, at this point topoV is not actually topocentric yet - it is still
428  a geocentric point-of-view, even though the coordinate system is now
429  specific to our particular site. */
430 
431  /* Transform from Geocentric to Topocentric coordinates - i.e. correct for
432  Diurnal Aberration and Geocentric Parallax. This may be done by
433  calculating a combined correction as a vector addition.
434  (The rhoSin and rhoCos terms here are in a geodetic coordinate system
435  [i.e. referred to the geodetic vertical] whereas the vector topoV is
436  referred to the astronomical vertical. However the error introduced by
437  simply adding the correction vector [as though the two vectors were in
438  the same coordinate system] is minuscule, since the "deflection of the
439  vertical" is so small - almost certainly < 20 arcseconds.) */
440 #ifndef SPA_COMPARISONS
441  topo->rectV.a[1] += site->diurnalAberr;
442 #else
443 #warning "Correction for diurnal aberration is not being applied"
444 #endif
445  if (dist_au > 0.0) {
446  topo->rectV.a[0] += site->rhoSin_au / dist_au;
447  topo->rectV.a[2] += site->rhoCos_au / dist_au;
448  }
449  // else
450  // We treat 0.0 (or -ve values) as meaning "infinitely far away".
451  // Objects that far away have no parallax, so we need do nothing here
452 
453  v3d_rectToPolar(&topo->azimuth_rad, &topo->elevation_rad, &topo->rectV);
454 
455 #if 0
456  /* Correct for atmospheric refraction. Use the simpler NREL SPA calculation
457  instead of the more detailed atmospheric model used by Stromlo.
458  Unfortunately, this formula is expressed in degrees, so we have to
459  convert back and forth. */
460  {
461  double e0_deg; // Elevation (not corrected for refraction, degrees)
462 
463  e0_deg = radToDeg(topo->elevation_rad);
464  if (e0_deg > -2.0) {
465  dEl_rad = degToRad(1.02 / (60.0 * tan(degToRad(e0_deg + 10.3
466  / (e0_deg + 5.11)))))
467  * site->refracPT;
468  } else {
469  dEl_rad = 0.0;
470  }
471  }
472 #elif 0
473  /* Correct for atmospheric refraction using a radian version of the above */
474  if (topo->elevation_rad > (-2.0 * DEG2RAD) {
475  dEl_rad = 0.000296706 / tan(topo->elevation_rad + 0.00313756
476  / (topo->elevation_rad + 0.0891863))
477  * site->refracPT;
478  } else {
479  dEl_rad = 0.0;
480  }
481 #else
482  /* Correct for atmospheric refraction using two simpler formulae */
483  w = sqrt(topo->rectV.a[0] * topo->rectV.a[0]
484  + topo->rectV.a[1] * topo->rectV.a[1]);
485  if (topo->rectV.a[2] >= 0.268 * w) {
486  /* Elevation is greater than 15° */
487  tanZd = w / topo->rectV.a[2];
488  dEl_rad = (2.8253e-4 * tanZd - 3.9948e-7 * tanZd * tanZd * tanZd)
489  * site->refracPT;
490 
491  } else if (topo->elevation_rad > (-2.0 * DEG2RAD)) {
492  /* Low elevation, use this approx formula instead */
493  dEl_rad = (8.3323e-3 + 3.1786e-2 * topo->elevation_rad
494  + 2.0746e-2 * topo->elevation_rad * topo->elevation_rad)
495  / (1 + 20.995 * topo->elevation_rad
496  + 160.31 * topo->elevation_rad * topo->elevation_rad)
497  * site->refracPT;
498 
499  } else {
500  dEl_rad = 0.0;
501  }
502 #endif
503  topo->elevation_rad += dEl_rad;
504 
505  /* Convert back to rectangular coords */
506  v3d_polarToRect(&topo->rectV, topo->azimuth_rad, topo->elevation_rad);
507 }
508 
509 
510 
512  const Sky_SiteProp *site,
513  double *hourAngle_rad,
514  double *declination_rad)
515 /*! Take a topocentric position vector in Azimuth/Elevation frame and use it to
516  calculate the observed Hour Angle and Declination.
517  \param[in] topoV Topocentric position vector in Azimuth/Elevation
518  frame (as returned by the sky_siteTirsToTopo()
519  function)
520  \param[in] site Field \a haDecM - rotation matrix to HA/Dec
521  coordinates
522  \param[out] hourAngle_rad Hour angle (radian - note: not in hours)
523  \param[out] declination_rad Declination (radian)
524 
525  The hour angle and declination returned by this function will be those of
526  the refracted position of the object.
527 
528  \par When to call this function
529  After each call to sky_siteTirsToTopo(), but only if you want hour angle and
530  declination.
531 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
532 {
533  V3D_Vector haDecV; // topocentric posn vector in Hour Angle/Decl. frame
534  // like topoV, this vector is a left-handed set
535 
536  REQUIRE_NOT_NULL(topoV);
537  REQUIRE_NOT_NULL(site);
538  REQUIRE_NOT_NULL(hourAngle_rad);
539  REQUIRE_NOT_NULL(declination_rad);
540 
541  v3d_multMxV(&haDecV, &site->haDecM, topoV);
542  v3d_rectToPolar(hourAngle_rad, declination_rad, &haDecV);
543 }
544 
545 
546 
548  const V3D_Vector *surfaceV)
549 /*! Calculate the incidence angle of rays from the celestial object described
550  by \a topoV (such as the sun) falling onto a surface described by
551  \a surfaceV (such as a solar panel).
552  \returns Incidence angle of the radiation (radian)
553  \param[in] topoV Unit vector pointing to the celestial object, as
554  returned by the sky_siteTirsToTopo() routine (in the
555  \a topo->rectV field). Must be of unity magnitude,
556  otherwise an assertion failure occurs.
557  \param[in] surfaceV Unit vector of normal to the surface, as returned by
558  routine sky_setupSiteSurface() (in the \a surface->rectV
559  field). Must be of unity magnitude, otherwise an
560  assertion failure occurs.
561 
562  \par When to call this function
563  After each call to sky_siteTirsToTopo(), but only if you have a surface that
564  you want the incidence angle for.
565 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
566 {
567  // This function assumes that both input vectors are unit vectors.
568  REQUIRE(fabs(v3d_magVSq(topoV) - 1.0) < SFA);
569  REQUIRE(fabs(v3d_magVSq(surfaceV) - 1.0) < SFA);
570 
571  // Dot product of two unit vectors gives cosine of angle between them
572  return acos(v3d_dotProductV(topoV, surfaceV));
573 }
574 
575 /*
576  *------------------------------------------------------------------------------
577  *
578  * Local functions (not called from other modules).
579  *
580  *------------------------------------------------------------------------------
581  */
582 LOCAL void createAzElBaseM(Sky_SiteProp *site)
583 /* Calculates the transformation matrix to convert a coordinate vector
584  expressed in a terrestrial equatorial coordinate frame to one expressed in
585  the horizon coordinate frame.
586 
587  The terrestrial equatorial frame has the geocentre as its origin, the
588  x axis points to 0° Lat and 0° Long (called the terrestrial intermediate
589  origin, or TIO)
590  y axis points to 0° Lat and 90° East Longitude
591  z axis points to the (north) celestial intermediate pole (CIP)
592  If converted to polar coordinates, the angles are the negative of Greenwich
593  Hour angle (GHA) and Declination.
594 
595  The horizon frame typically is centered on the observing site, and the vector
596  is a Left-handed set, unlike the vector above. The
597  x axis points to the North horizon,
598  y axis points to the East horizon,
599  z axis points to the zenith
600  If converted to polar coordinates, the angles are Azimuth and Elevation.
601 
602  Note: this routine creates a matrix for rotation only; the resulting vector
603  is in horizon coordinates, but it is still a geocentric position. Correcting
604  for displacement to the earth's surface (diurnal parallax and aberration)
605  needs to be carried out separately. (But that is a relatively simple
606  operation in horizon coordinates.)
607 
608  To convert from one frame to the other, we must
609  1. Rotate around Z axis by astronomical longitude
610  2. Rotate around new Y' axis by -ve of astronomical latitude (phiA)
611  3. swap the resulting Z'' and X'' axes to obtain the X' and Z' axes
612  described above.
613  Thus azElBaseM = J * R2(-PhiA) * R3(longitude)
614  where J = 0 0 1
615  0 1 0
616  1 0 0
617  Inputs
618  site->astLong_rad - astronomical longitude of site (radian)
619  site->astLat_rad - astronomical latitude of site (radian)
620  Output
621  site->azElBaseM - 3x3 rotation matrix, -GHA/Dec to Az/El
622 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
623 {
624  double sinLon, cosLon;
625  double sinLat, cosLat;
626 
627  sincos(site->astLong_rad, &sinLon, &cosLon);
628  sincos(site->astLat_rad, &sinLat, &cosLat);
629  site->azElBaseM.a[0][0] = -sinLat * cosLon;
630  site->azElBaseM.a[0][1] = -sinLat * sinLon;
631  site->azElBaseM.a[0][2] = cosLat;
632 
633  site->azElBaseM.a[1][0] = -sinLon;
634  site->azElBaseM.a[1][1] = cosLon;
635  site->azElBaseM.a[1][2] = 0.0;
636 
637  site->azElBaseM.a[2][0] = cosLat * cosLon;
638  site->azElBaseM.a[2][1] = cosLat * sinLon;
639  site->azElBaseM.a[2][2] = sinLat;
640 }
641 
642 /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
643 /*! \page page-about About this code, and how to use it
644  *
645  * This code has been written with the aim of providing astronomical routines
646  * for tracking the Sun, Moon, planets and stars using a small processor. It
647  * started as a project to implement accurate tracking of the Sun for solar
648  * heliostat applications. What was required was code that would execute more
649  * quickly than the traditional Solar Position Algorithm (see reference), but
650  * without the compromises in accuracy that many simpler algorithms suffer
651  * from.
652  *
653  * Some of the principles of that implementation, in particular the use of
654  * rectangular coordinates for converting from geocentric apparent coordinates
655  * to site-specific topocentric coordinates, and the use of interpolation
656  * between apparent position vectors, are just as applicable to the tracking of
657  * any other celestial object. So gradually code to support tracking the Moon,
658  * planets and stars has been added.
659  *
660  * There are a couple of use cases for this code. One could be a field of
661  * heliostats which need accurate solar tracking but are each controlled by a
662  * small cheap processor.
663  * Another could be a home project to automate a home telescope.
664  *
665  * This code is intended to handle all the intricacies of accurate celestial
666  * calculations, leaving you free to concentrate on the code to actually drive
667  * your device to the desired position.
668  * The code is designed to provide accurate conversion of astronomical
669  * coordinate systems, such as is used on quite large telescopes. Tracking
670  * accuracy of better than one arcsecond can be expected, (although the
671  * positions of the Moon and other planets are not calculated to that level of
672  * accuracy).
673  * - \subpage page-design-choices
674  * - \subpage page-how-to-use
675  * - \subpage page-licensing
676  *
677  * \par Reference:
678  * Reda, Ibrahim and Andreas, Afshin.
679  * _Solar Position Algorithm for Solar Radiation Applications._
680  * National Renewable Energy Laboratory, publication no.
681  * NREL/TP-560-34302, June 2003, revised January 2008
682  */
683 
684 /*! \page page-conventions Conventions used in this code
685  * - \subpage page-c-style
686  * - \subpage page-var-suffixes
687  * - \subpage page-sec-arcsec
688  * - \subpage page-sign-conventions
689  * - \subpage page-interval-notation
690  * */
691 
692 /*! \page page-time About time
693  * - \subpage page-timescales
694  * - \subpage page-time-variables
695  * */
696 
697 /*! \page page-misc Miscellaneous
698  * - \subpage page-vernal-equinox
699  * - \subpage page-why-struct-array
700  * */
701 
702 /*! \page page-design-choices Design choices
703  * To make the code suitable for a small processor, the following choices have
704  * been made.
705  * - The code can be compiled with a C89/C90 compliant compiler. (Some
706  * embedded systems still use compilers of this vintage.) But if a C99
707  * or later compiler is being used, the code will use a few inline
708  * functions in place of function-like macros.
709  * - Does not use any heap storage (dynamic memory). That is, there are no
710  * \c malloc() calls.
711  * - Does not use any variable-length arrays. Thus stack usage is
712  * predictable.
713  * - \c printf() calls are basically limited to debugging code.
714  * - Very limited use of \c snprintf(). Does not use the \%f specifier in
715  * any \c snprintf().
716  * - Uses an interpolation process to dramatically reduce the calling of
717  * trigonometric functions during tracking, with a very small loss of
718  * accuracy (see \ref page-interpolation).
719  */
720 
721 /*! \page page-how-to-use How to use this code
722  * The code has been provided as a set of separate files for you to include in
723  * your project. It has not been configured as a monolithic library, because
724  * there are likely to be parts that you don't want to include. But
725  * importantly, it is assumed that you will want to make some choices, and you
726  * will need to do that by making some simple edits to the source code of
727  * various modules. Here are some guidelines.
728  *
729  * ###Required files
730  * - For all applications:
731  * + The basic sky routines - sky.h & sky-time.c & sky-site.c
732  * + Low level support routines and definitions -
733  * general.h, astron.h, instead-of-math.h, vectors3d.h & vectors3d.c.
734  * - For solar position calculations: the "all applications" set above,
735  * plus sky0.h & sky0.c, and sun.h & sun.c
736  * - For Sun tracking: the above, plus skyfast.h & skyfast.c
737  * - For Moon position calculations: the "all applications" set above, plus
738  * sky0.h & sky0.c, and moon.h & moon.c
739  * - For Moon tracking: the above, plus skyfast.h & skyfast.c
740  * - For planet position calculations: the "all applications" set above,
741  * plus sky1.h & sky1.c, and planet.h & planet.c
742  * - For tracking a planet: the above, plus skyfast.h & skyfast.c
743  * - For a star: the "all applications" set above, plus
744  * sky1.h & sky1.c, sky2.h & sky2.c, star.h & star.c, and
745  * skyio.h & skyio.c
746  * - For tracking a star: the above, plus skyfast.h & skyfast.c
747  *
748  * You may also want to use skyio.h and skyio.c to format the output angles for
749  * display for the Sun, Moon or planets.
750  *
751  * ###Editing the code
752  * You are likely to want to make some basic edits to the code, as listed
753  * below. But before you do, make sure your editor can handle files in UTF-8
754  * format. MacOS and Linux systems certainly will do this without you needing
755  * to think about it. Only Windows systems still using old 8-bit code pages
756  * like the Windows-1252 page are likely to have any issues. (You should move
757  * away from them to UTF-8 in any case. UTF-8 can handle any character; those
758  * old code pages don't.)
759  * - \subpage page-general-h
760  * - \subpage page-sky-h
761  * - \subpage page-skyfast-c
762  */
763 
764 /*! \page page-general-h Edits you may want to make to general.h
765  *
766  * ####Integer types with a C89/C90 compiler.
767  * If you are compiling your code using a C89/C90 compiler, you will need to
768  * check that the following lines are correct for your target processor. (If
769  * you have a C99 or later compiler, you can skip this section.)
770  \verbatim
771  typedef signed short int int16_t;
772  typedef unsigned short int uint16_t;
773  typedef int int32_t;
774  typedef unsigned int uint32_t;
775  \endverbatim
776  * If, for example, your processor defines an int as being 16 bits wide, you
777  * will get some strange warning messages. One possibility I have seen is
778  * "general.h:105:1: warning: division by zero [-Wdiv-by-zero]".
779  * Another possibility is
780  * "general.h:105:1: error: expression is not an integer constant expression".
781  * The important thing to do is to look at the line number where the error
782  * was detected, where you will see a line like
783  \verbatim
784  static_assert(sizeof(int16_t) == 2, "typedef of int16_t is wrong");
785  \endverbatim
786  * To fix the problem, don't change the line containing the static_assert(),
787  * go back and change the line(s) containing the typedef(s), so that these four
788  * types (\c int16_t, \c uint16_t, \c int32_t & \c uint32_t) are correctly
789  * defined for your processor.
790  *
791  * ####Assertion checking
792  * Many routines in this collection use assertions to check their parameters.
793  * In particular, parameters which are passed by address are checked to make
794  * sure that a NULL pointer has not been passed to them. You can control this
795  * checking by defining three macros within this file. They are:
796  * - ASSERTION_LEVEL. Use this to set how much checking is done. While you
797  * are still writing and testing your own code which uses this code, you
798  * are strongly recommended to keep this defined at a non-zero value.
799  * - ENABLE_NULL_CHECKING. So long as ASSERTION_LEVEL is not set to zero,
800  * this macro enables the checking of parameters which are passed by
801  * reference, to make sure they are not NULL. (There is the occasional
802  * parameter which is allowed to be NULL - such a parameter is of course
803  * not subject to this check.) Once again, while you
804  * are still writing and testing your own code which uses this code, you
805  * are strongly recommended to keep this macro defined.
806  * - USE_STANDARD_ASSERT. The code is designed to be run on an embedded
807  * processor, where the traditional assert() behaviour -- write a message
808  * and exit the program -- does not make any sense. On an embedded system
809  * you need to write your own assertion handling routine onAssert__(),
810  * which handles the information in a way that helps you debug the
811  * problem. But if you are running this code on a processor which has an
812  * operating system (say, like a Raspberry Pi runs Linux), you don't need
813  * to go to this extra trouble. You can use the traditional assert()
814  * behaviour quite happily. Define this macro to enable this behaviour.
815  */
816 
817 
818 /*! \page page-licensing Open Source Licensing
819  *
820  * All code here except for the routines planet_getHeliocentric() and
821  * sky2_nutationIAU2000B() are covered by the MIT license, as follows:
822  *
823  * \copyright
824  * \parblock
825  * Copyright (c) 2020, David Hoadley <vcrumble@westnet.com.au>
826  * All rights reserved.
827  *
828  * Redistribution and use in source and binary forms, with or without
829  * modification, are permitted provided that the following conditions are met:
830  *
831  * * Redistributions of source code must retain the above copyright notice, this
832  * list of conditions and the following disclaimer.
833  * * Redistributions in binary form must reproduce the above copyright notice,
834  * this list of conditions and the following disclaimer in the documentation
835  * and/or other materials provided with the distribution.
836  *
837  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
838  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
839  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
840  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
841  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
842  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
843  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
844  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
845  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
846  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
847  * POSSIBILITY OF SUCH DAMAGE.
848  * \endparblock
849  *
850 ** =====================
851  *
852  * The routines planet_getHeliocentric() and sky2_nutationIAU2000B() are
853  * covered by the SOFA license, as follows.
854  * See the description of each of those functions, which contains the necessary
855  * text to comply with section 3 of the license below:
856  *
857  * \copyright
858  * \verbatim
859 **
860 ** Copyright (C) 2017
861 ** Standards Of Fundamental Astronomy Board
862 ** of the International Astronomical Union.
863 **
864 ** =====================
865 ** SOFA Software License
866 ** =====================
867 **
868 ** NOTICE TO USER:
869 **
870 ** BY USING THIS SOFTWARE YOU ACCEPT THE FOLLOWING SIX TERMS AND
871 ** CONDITIONS WHICH APPLY TO ITS USE.
872 **
873 ** 1. The Software is owned by the IAU SOFA Board ("SOFA").
874 **
875 ** 2. Permission is granted to anyone to use the SOFA software for any
876 ** purpose, including commercial applications, free of charge and
877 ** without payment of royalties, subject to the conditions and
878 ** restrictions listed below.
879 **
880 ** 3. You (the user) may copy and distribute SOFA source code to others,
881 ** and use and adapt its code and algorithms in your own software,
882 ** on a world-wide, royalty-free basis. That portion of your
883 ** distribution that does not consist of intact and unchanged copies
884 ** of SOFA source code files is a "derived work" that must comply
885 ** with the following requirements:
886 **
887 ** a) Your work shall be marked or carry a statement that it
888 ** (i) uses routines and computations derived by you from
889 ** software provided by SOFA under license to you; and
890 ** (ii) does not itself constitute software provided by and/or
891 ** endorsed by SOFA.
892 **
893 ** b) The source code of your derived work must contain descriptions
894 ** of how the derived work is based upon, contains and/or differs
895 ** from the original SOFA software.
896 **
897 ** c) The names of all routines in your derived work shall not
898 ** include the prefix "iau" or "sofa" or trivial modifications
899 ** thereof such as changes of case.
900 **
901 ** d) The origin of the SOFA components of your derived work must
902 ** not be misrepresented; you must not claim that you wrote the
903 ** original software, nor file a patent application for SOFA
904 ** software or algorithms embedded in the SOFA software.
905 **
906 ** e) These requirements must be reproduced intact in any source
907 ** distribution and shall apply to anyone to whom you have
908 ** granted a further right to modify the source code of your
909 ** derived work.
910 **
911 ** Note that, as originally distributed, the SOFA software is
912 ** intended to be a definitive implementation of the IAU standards,
913 ** and consequently third-party modifications are discouraged. All
914 ** variations, no matter how minor, must be explicitly marked as
915 ** such, as explained above.
916 **
917 ** 4. You shall not cause the SOFA software to be brought into
918 ** disrepute, either by misuse, or use for inappropriate tasks, or
919 ** by inappropriate modification.
920 **
921 ** 5. The SOFA software is provided "as is" and SOFA makes no warranty
922 ** as to its use or performance. SOFA does not and cannot warrant
923 ** the performance or results which the user may obtain by using the
924 ** SOFA software. SOFA makes no warranties, express or implied, as
925 ** to non-infringement of third party rights, merchantability, or
926 ** fitness for any particular purpose. In no event will SOFA be
927 ** liable to the user for any consequential, incidental, or special
928 ** damages, including any lost profits or lost savings, even if a
929 ** SOFA representative has been advised of such damages, or for any
930 ** claim by any third party.
931 **
932 ** 6. The provision of any version of the SOFA software under the terms
933 ** and conditions specified herein does not imply that future
934 ** versions will also be made available under the same terms and
935 ** conditions.
936 *
937 ** In any published work or commercial product which uses the SOFA
938 ** software directly, acknowledgement (see www.iausofa.org) is
939 ** appreciated.
940 **
941 ** Correspondence concerning SOFA software should be addressed as
942 ** follows:
943 **
944 ** By email: sofa@ukho.gov.uk
945 ** By post: IAU SOFA Center
946 ** HM Nautical Almanac Office
947 ** UK Hydrographic Office
948 ** Admiralty Way, Taunton
949 ** Somerset, TA1 2DN
950 ** United Kingdom
951 **
952  * \endverbatim
953  */
954 
955 /*! \page page-c-style C coding style
956  *
957  * Code here largely conforms to [this style guide](../c-style-guide.pdf)
958  */
959 
960 /*! \page page-var-suffixes Suffixes on variable and parameter names
961  *
962  * In accordance with the naming convention spelt out in the
963  * [style guide](../c-style-guide.pdf),
964  * many variables and constants have a suffix indicating their
965  * units. Mostly these are SI units, for example
966  * - _d (days), _h (hours) _s (seconds) _ns (nanoseconds)
967  * - _m (metres), _km (kilometres), _kmps (kilometres/second)
968  * - _rad (radian), _deg (degrees), _radps (radian/second)
969  * - _degC (degrees Celsius)
970  *
971  * But we also have units for astronomical quantities as recommended by the
972  * International Astronomical Union (IAU) style manual
973  * (https://www.iau.org/publications/proceedings_rules/units/), for example
974  * - _as (arcseconds) _mas (milliarcseconds), (see \ref page-sec-arcsec)
975  * - _au (Astronomical Unit (of distance))
976  * - _a (Julian year)
977  *
978  * This last unit implies that we could use "ka" for millennia, and we do. But
979  * we also have quantities measured in Julian centuries. The IAU guidelines
980  * specifically rule out using "ha" for this (quite right: "ha" means
981  * hectares), and they oppose "cy", but they make no recommendation of an
982  * alternative. But the _Astronomical Almanac_ does use "cy" for a Julian
983  * century, so I will use suffix _cy.
984  * - _ka (Julian millennium), _cy (Julian century)
985  *
986  * Another convention that applies here: Objects whose name ends in a capital V
987  * are 3-dimensional vectors (often unit position vectors, or direction
988  * cosines) but not exclusively so. Objects whose name ends in a capital M are
989  * matrices, typically 3x3 rotation matrices. Occasionally, this is combined
990  * with a suffix. So a name like \c earthV_au means a vector whose components
991  * are scaled in Astronomical Units.
992  */
993 
994 /*! \page page-sec-arcsec Seconds versus arcseconds
995  * The fact that time in hours is subdivided into minutes and seconds, and
996  * angles in degrees are also subdivided into minutes and seconds, normally
997  * does not cause confusion. But astronomers measure angles on the celestial
998  * sphere using both units of time (for Right Ascension and Hour Angles) and
999  * units of degrees (for many other angles). So if we talk about an angle of
1000  * n minutes, or n seconds, do we mean a fraction of an hour, or a degree?
1001  *
1002  * For this reason, astronomers refer to the fractions of a degree as
1003  * arcminutes, and reserve the term "minutes" for fractions of an hour. And
1004  * fractions of an arcminute are called arcseconds rather than seconds. This
1005  * program follows that convention, particularly in the use of suffixes on
1006  * variable names (see page \ref page-var-suffixes). Variables in units of
1007  * seconds of time have the suffix _s, those in units of arcseconds have the
1008  * suffix _as.
1009  *
1010  * In general, within this program most angles are stored in units of radians,
1011  * and only converted to degrees-arcminutes-arcseconds, or
1012  * hours-minutes-seconds when being written out in text form.
1013  * The symbol ′ is used for arcminutes only, never for minutes of time.
1014  * The symbol ″ is used for arcseconds only, never for seconds of time.
1015  */
1016 
1017 /*! \page page-interval-notation Interval notation
1018  * Frequently throughout the documentation and comments, valid ranges of
1019  * function parameters are expressed in interval notation. This is a pair of
1020  * numbers representing the two ends of the range, separated by a comma. The
1021  * the pair of numbers is surrounded by brackets. Square brackets
1022  * indicate an inclusive end-of-range, and round brackets (parentheses)
1023  * indicate an exclusive end-of range.
1024  *
1025  * Examples:
1026  * - [0,10] means all values from 0 to 10 inclusive
1027  * - [0.0, 360.0) means all values from 0.0 up to but not including 360.0
1028  * itself.
1029  * - (-π, +π] means all values from -π, but not including -π itself, up to and
1030  * including +π.
1031  */
1032 
1033 /*! \page page-sign-conventions Sign and direction conventions
1034  * The following conventions apply throughout this software.
1035  * - Terrestrial coordinates (latitude and longitude).
1036  * - Polar form\n
1037  * Longitudes east of Greenwich are positive, those west are
1038  * negative.
1039  * Latitudes north of the equator are positive, those south are
1040  * negative. When the Earth is viewed from above the north pole
1041  * looking towards the centre of the earth, longitude increases in an
1042  * anticlockwise direction.
1043  * - Rectangular form\n
1044  * The position is indicated by a vector, whose components are as
1045  * follows:
1046  * - the x axis points from the centre of the earth towards the
1047  * equator at 0° longitude,
1048  * - the y axis points to 90° longitude, and
1049  * - the z axis points to the north pole.
1050  * .
1051  * This is a right-handed set.
1052  *
1053  * - Equatorial coordinates (Right Ascension and declination).
1054  * - Polar form\n
1055  * The equator is the projection of the Earth's equator out into
1056  * space, and the north celestial pole is the projection of the north
1057  * pole out into space. When viewed from north celestial pole looking
1058  * down towards the origin, Right Ascension increases in an
1059  * anticlockwise direction (i.e. eastwards, like longitude), and
1060  * north declinations are positive, south are negative.
1061  * - Rectangular form\n
1062  * - x points to the March Equinox (0° RA),
1063  * (\ref page-vernal-equinox "unfortunately" known as the
1064  * vernal equinox).
1065  * - y points to 90° RA, and
1066  * - z points to the north celestial pole.
1067  * .
1068  * (This is also a right-handed set.)
1069  *
1070  * - Ecliptic coordinates. (Ecliptic latitude and longitude).\n
1071  * These are exactly analogous to terrestrial coordinates, in that
1072  * longitude increases anticlockwise, and the rectangular coordinate
1073  * vector is a right-handed set.
1074  *
1075  * - Horizon coordinates (azimuth and elevation (or altitude)).
1076  * - Polar form\n
1077  * Azimuth increases in a clockwise direction from True North (just
1078  * like a compass bearing).
1079  * - Rectangular form\n
1080  * - x points to the True North horizon,
1081  * - y points the East horizon, and
1082  * - z points to the zenith.
1083  * .
1084  * This is a left-handed set. (Compared to a right-handed set, the x
1085  * and y axes are swapped.)
1086  *
1087  * - (Hour angle and declination).
1088  * - Polar form\n
1089  * Hour angles are measured positive westward. This means it
1090  * increases clockwise, unlike Right Ascension.
1091  * - Rectangular form\n
1092  * - x points to the intersection of the local meridian and the
1093  * celestial equator,
1094  * - y points to HA = 90°, and
1095  * - z points to the north celestial pole.
1096  * .
1097  * Like Horizon coordinates, this is a left-handed set.
1098  */
1099 
1100 /*! \page page-vernal-equinox Vernal equinox
1101  * The equinox that occurs in March is normally referred to as the vernal
1102  * equinox. This is unfortunate, because of the meaning of the word vernal:
1103  * \par
1104  * \b ver′nal \em adj. of or occurring in spring.\n
1105  * -- The Collins Concise Dictionary, 4th edn. 1999
1106  *
1107  * Obviously it never occurred to the person who coined this term for the
1108  * equinox that the Southern Hemisphere exists. Because \em everybody in the
1109  * Southern Hemisphere knows that the spring equinox occurs in September, not
1110  * March.
1111  */
1112 
Sky_SiteProp::azElPolM
V3D_Matrix azElPolM
rotation matrix from TIRS to Az/El coords
Definition: sky.h:325
v3d_magVSq
double v3d_magVSq(const V3D_Vector *srcV)
Return the square of the magnitude of the specified vector.
Definition: vectors3d.c:258
v3d_multMxM
V3D_Matrix * v3d_multMxM(V3D_Matrix *destM, const V3D_Matrix *srcM1, const V3D_Matrix *srcM2)
Multiply 3x3 matrix by 3x3 matrix to give a new 3x3 matrix, as per [destM] = [srcM1]*[srcM2].
Definition: vectors3d.c:517
general.h
general.h - definitions of general use to (standard) C programs
v3d_polarToRect
V3D_Vector * v3d_polarToRect(V3D_Vector *destV, double alpha_rad, double delta_rad)
Converts polar (curvilinear) coordinates to equivalent rectangular (Cartesian) coordinates.
Definition: vectors3d.c:280
REQUIRE
#define REQUIRE(test_)
Check preconditions.
Definition: general.h:273
sky_siteTirsToTopo
void sky_siteTirsToTopo(const V3D_Vector *terInterV, double dist_au, const Sky_SiteProp *site, Sky_SiteHorizon *topo)
Transform a coordinate vector from the Terrestrial Intermediate Reference System to topocentric Az/El...
Definition: sky-site.c:378
Sky_SiteProp::astLat_rad
double astLat_rad
Astronomical latitude of site (ϕA) (radian)
Definition: sky.h:316
LOCAL
#define LOCAL
C has some very silly default behaviours.
Definition: general.h:133
sky_setSiteLocation
void sky_setSiteLocation(double latitude_deg, double longitude_deg, double height_m, Sky_SiteProp *site)
Initialise the site structure by calculating those site-related values that do not change with time.
Definition: sky-site.c:105
PI
#define PI
,
Definition: general.h:158
v3d_rectToPolar
void v3d_rectToPolar(double *alpha_rad, double *delta_rad, const V3D_Vector *srcV)
Converts rectangular (Cartesian) coordinates to the equivalent polar (curvilinear) ones.
Definition: vectors3d.c:311
GLOBAL
#define GLOBAL
See above.
Definition: general.h:134
Sky_SiteProp::azElM
V3D_Matrix * azElM
points to either azElPolM or azElBaseM
Definition: sky.h:324
REQUIRE_NOT_NULL
#define REQUIRE_NOT_NULL(pointer_)
,
Definition: general.h:308
SFA
#define SFA
A very small number, used to avoid divide by 0 errors.
Definition: general.h:169
Sky_SiteProp::geocRadius_km
double geocRadius_km
Approx geocentric radius of site (≈ ae*ρ)(km)
Definition: sky.h:318
Sky_SiteProp::haDecM
V3D_Matrix haDecM
rotation matrix from Az/El to HA/Dec coords
Definition: sky.h:327
Sky_SiteProp::rhoSin_au
double rhoSin_au
ae*ρ*sin(ϕ - ϕ′) geocentre-to-site x (AU)
Definition: sky.h:319
Sky_SiteProp
Site properties.
Definition: sky.h:315
Sky_SiteHorizon::azimuth_rad
double azimuth_rad
azimuth (radian)
Definition: sky.h:131
DEG2RAD
#define DEG2RAD
degrees to radians
Definition: general.h:165
sky_adjustSiteForPolarMotion
void sky_adjustSiteForPolarMotion(const Sky_PolarMot *polar, Sky_SiteProp *site)
Modify our azEl rotation matrix to incorporate a polar motion rotation matrix.
Definition: sky-site.c:347
Sky_SiteProp::timeZone_d
double timeZone_d
time zone offset from UTC (fraction of a day)
Definition: sky.h:323
sky_setupSiteSurface
void sky_setupSiteSurface(double azimuth_deg, double slope_deg, Sky_SiteHorizon *surface)
Set the orientation and slope of a surface (such as a solar panel) for which you want to calculate th...
Definition: sky-site.c:317
V3D_Matrix
3x3 matrix.
Definition: vectors3d.h:51
instead-of-math.h
instead-of-math.h - header to be included instead of math.h
V3D_Vector
3x1 vector.
Definition: vectors3d.h:57
v3d_dotProductV
double v3d_dotProductV(const V3D_Vector *srcV1, const V3D_Vector *srcV2)
Return the dot product of the two vectors: res = [srcV1] · [srcV2] or res = ||srcV1|| * ||srcV2|| * c...
Definition: vectors3d.c:148
degToRad
static double degToRad(double angle_deg)
Returns angle_deg converted from degrees to radians.
Definition: general.h:180
Sky_PolarMot
This structure contains polar motion parameters and a rotation matrix.
Definition: sky.h:196
sky_setSiteTimeZone
void sky_setSiteTimeZone(double timeZone_h, Sky_SiteProp *site)
Set the time zone offset for this site.
Definition: sky-site.c:293
Sky_SiteProp::astLong_rad
double astLong_rad
Astronomical longitude of site (radian)
Definition: sky.h:317
DEFINE_THIS_FILE
#define DEFINE_THIS_FILE
Leave DEFINE_THIS_FILE undefined.
Definition: general.h:252
sky_setSiteTempPress
void sky_setSiteTempPress(double temperature_degC, double pressure_hPa, Sky_SiteProp *site)
Set refraction coefficients based on atmospheric temperature and pressure at the site.
Definition: sky-site.c:261
Sky_SiteProp::rhoCos_au
double rhoCos_au
-ae*ρ*cos(ϕ - ϕ′) geocentre-to-site z (AU)
Definition: sky.h:320
sky.h
sky.h - structures and routines for astronomical observing & tracking
v3d_createRotationMatrix
V3D_Matrix * v3d_createRotationMatrix(V3D_Matrix *destM, V3D_AxisNames axis, double angle_rad)
Creates a matrix to rotate a coordinate system about an axis.
Definition: vectors3d.c:377
Sky_SiteHorizon::elevation_rad
double elevation_rad
elevation (or altitude) (radian)
Definition: sky.h:132
sky_siteAzElToHaDec
void sky_siteAzElToHaDec(const V3D_Vector *topoV, const Sky_SiteProp *site, double *hourAngle_rad, double *declination_rad)
Take a topocentric position vector in Azimuth/Elevation frame and use it to calculate the observed Ho...
Definition: sky-site.c:511
HALFPI
#define HALFPI
,
Definition: general.h:159
Sky_SiteProp::refracPT
double refracPT
Refraction correction: pressure & temperature.
Definition: sky.h:322
sincos
static void sincos(double angle_rad, double *sinA, double *cosA)
Calculates sine and cosine of an angle.
Definition: instead-of-math.h:114
sky_siteIncidence_rad
double sky_siteIncidence_rad(const V3D_Vector *topoV, const V3D_Vector *surfaceV)
Calculate the incidence angle of rays from the celestial object described by topoV (such as the sun) ...
Definition: sky-site.c:547
sky_setSiteLoc2
void sky_setSiteLoc2(double astLat_deg, double astLong_deg, double geodLat_deg, double geodLong_deg, double height_m, Sky_SiteProp *site)
Alternative initialisation function to sky_setSiteLocation().
Definition: sky-site.c:156
v3d_multMxV
V3D_Vector * v3d_multMxV(V3D_Vector *destV, const V3D_Matrix *srcM, const V3D_Vector *srcV)
Multiply 3x3 matrix by 3x1 vector to give a new 3x1 vector, as per equation [destV] = [srcM] * [srcV]...
Definition: vectors3d.c:439
Sky_SiteProp::azElBaseM
V3D_Matrix azElBaseM
as above, but excluding polar motion correctn
Definition: sky.h:326
radToDeg
static double radToDeg(double angle_rad)
Returns angle_rad converted from radians to degrees.
Definition: general.h:182
Sky_SiteProp::diurnalAberr
double diurnalAberr
Diurnal aberration: caused by earth rotation.
Definition: sky.h:321
Sky_SiteHorizon
Coordinates of a celestial object in the horizon frame, in both rectangular and polar forms.
Definition: sky.h:129
Sky_SiteHorizon::rectV
V3D_Vector rectV
unit vector in horizon coordinates
Definition: sky.h:130