• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

NREL / SolTrace / 19315321183

12 Nov 2025 11:35PM UTC coverage: 88.752% (-1.3%) from 90.052%
19315321183

Pull #83

github

web-flow
Merge bfed87b33 into e67f9eadc
Pull Request #83: Sun position calcs

996 of 1195 new or added lines in 4 files covered. (83.35%)

5492 of 6188 relevant lines covered (88.75%)

8572678.39 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.87
/coretrace/simulation_data/solar_position_calculators/solpos.cpp
1
/*============================================================================
2
*    Contains:
3
*        S_solpos     (computes solar position and intensity
4
*                      from time and place)
5
*
6
*            INPUTS:     (via posdata struct) year, daynum, hour,
7
*                        minute, second, latitude, longitude, timezone,
8
*                        intervl
9
*            OPTIONAL:   (via posdata struct) month, day, press, temp, tilt,
10
*                        aspect, function
11
*            OUTPUTS:    EVERY variable in the struct posdata
12
*                            (defined in solpos.h)
13
*
14
*                       NOTE: Certain conditions exist during which some of
15
*                       the output variables are undefined or cannot be
16
*                       calculated.  In these cases, the variables are
17
*                       returned with flag values indicating such.  In other
18
*                       cases, the variables may return a realistic, though
19
*                       invalid, value. These variables and the flag values
20
*                       or invalid conditions are listed below:
21
*
22
*                       amass     -1.0 at zenetr angles greater than 93.0
23
*                                 degrees
24
*                       ampress   -1.0 at zenetr angles greater than 93.0
25
*                                 degrees
26
*                       azim      invalid at zenetr angle 0.0 or latitude
27
*                                 +/-90.0 or at night
28
*                       elevetr   limited to -9 degrees at night
29
*                       etr       0.0 at night
30
*                       etrn      0.0 at night
31
*                       etrtilt   0.0 when cosinc is less than 0
32
*                       prime     invalid at zenetr angles greater than 93.0
33
*                                 degrees
34
*                       sretr     +/- 2999.0 during periods of 24 hour sunup or
35
*                                 sundown
36
*                       ssetr     +/- 2999.0 during periods of 24 hour sunup or
37
*                                 sundown
38
*                       ssha      invalid at the North and South Poles
39
*                       unprime   invalid at zenetr angles greater than 93.0
40
*                                 degrees
41
*                       zenetr    limited to 99.0 degrees at night
42
*
43
*        S_init       (optional initialization for all input parameters in
44
*                      the posdata struct)
45
*           INPUTS:     struct posdata*
46
*           OUTPUTS:    struct posdata*
47
*
48
*                     (Note: initializes the required S_solpos INPUTS above
49
*                      to out-of-bounds conditions, forcing the user to
50
*                      supply the parameters; initializes the OPTIONAL
51
*                      S_solpos inputs above to nominal values.)
52
*
53
*       S_decode      (optional utility for decoding the S_solpos return code)
54
*           INPUTS:     long integer S_solpos return value, struct posdata*
55
*           OUTPUTS:    text to stderr
56
*
57
*    Usage:
58
*         In calling program, just after other 'includes', insert:
59
*
60
*              #include "solpos00.h"
61
*
62
*         Function calls:
63
*              S_init(struct posdata*)  [optional]
64
*              .
65
*              .
66
*              [set time and location parameters before S_solpos call]
67
*              .
68
*              .
69
*              int retval = S_solpos(struct posdata*)
70
*              S_decode(int retval, struct posdata*) [optional]
71
*                  (Note: you should always look at the S_solpos return
72
*                   value, which contains error codes. S_decode is one option
73
*                   for examining these codes.  It can also serve as a
74
*                   template for building your own application-specific
75
*                   decoder.)
76
*
77
*    Martin Rymes
78
*    National Renewable Energy Laboratory
79
*    25 March 1998
80
*
81
*    27 April 1999 REVISION:  Corrected leap year in S_date.
82
*    13 January 2000 REVISION:  SMW converted to structure posdata parameter
83
*                               and subdivided into functions.
84
*    01 February 2001 REVISION: SMW corrected ecobli calculation 
85
*                               (changed sign). Error is small (max 0.015 deg
86
*                               in calculation of declination angle)
87
*----------------------------------------------------------------------------*/
88
#include <math.h>
89
#include <string.h>
90
#include <stdio.h>
91
#include "solpos00.h"
92

93
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
94
*
95
* Structures defined for this module
96
*
97
*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
98
struct trigdata /* used to pass calculated values locally */
99
{
100
    float cd;       /* cosine of the declination */
101
    float ch;       /* cosine of the hour angle */
102
    float cl;       /* cosine of the latitude */
103
    float sd;       /* sine of the declination */
104
    float sl;       /* sine of the latitude */
105
};
106

107

108
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
109
*
110
* Temporary global variables used only in this file:
111
*
112
*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
113
  static int  month_days[2][13] = { { 0,   0,  31,  59,  90, 120, 151,
114
                                       181, 212, 243, 273, 304, 334 },
115
                                    { 0,   0,  31,  60,  91, 121, 152,
116
                                       182, 213, 244, 274, 305, 335 } };
117
                   /* cumulative number of days prior to beginning of month */
118

119
  static float degrad = 57.295779513; /* converts from radians to degrees */
120
  static float raddeg = 0.0174532925; /* converts from degrees to radians */
121

122
/*============================================================================
123
*    Local function prototypes
124
============================================================================*/
125
static long int validate ( struct posdata *pdat);
126
static void dom2doy( struct posdata *pdat );
127
static void doy2dom( struct posdata *pdat );
128
static void geometry ( struct posdata *pdat );
129
static void zen_no_ref ( struct posdata *pdat, struct trigdata *tdat );
130
static void ssha( struct posdata *pdat, struct trigdata *tdat );
131
static void sbcf( struct posdata *pdat, struct trigdata *tdat );
132
static void tst( struct posdata *pdat );
133
static void srss( struct posdata *pdat );
134
static void sazm( struct posdata *pdat, struct trigdata *tdat );
135
static void refrac( struct posdata *pdat );
136
static void amass( struct posdata *pdat );
137
static void prime( struct posdata *pdat );
138
static void etr( struct posdata *pdat );
139
static void tilt( struct posdata *pdat );
140
static void localtrig( struct posdata *pdat, struct trigdata *tdat );
141

142
/*============================================================================
143
*    Long integer function S_solpos, adapted from the VAX solar libraries
144
*
145
*    This function calculates the apparent solar position and the
146
*    intensity of the sun (theoretical maximum solar energy) from
147
*    time and place on Earth.
148
*
149
*    Requires (from the struct posdata parameter):
150
*        Date and time:
151
*            year
152
*            daynum   (requirement depends on the S_DOY switch)
153
*            month    (requirement depends on the S_DOY switch)
154
*            day      (requirement depends on the S_DOY switch)
155
*            hour
156
*            minute
157
*            second
158
*            interval  DEFAULT 0
159
*        Location:
160
*            latitude
161
*            longitude
162
*        Location/time adjuster:
163
*            timezone
164
*        Atmospheric pressure and temperature:
165
*            press     DEFAULT 1013.0 mb
166
*            temp      DEFAULT 10.0 degrees C
167
*        Tilt of flat surface that receives solar energy:
168
*            aspect    DEFAULT 180 (South)
169
*            tilt      DEFAULT 0 (Horizontal)
170
*        Function Switch (codes defined in solpos.h)
171
*            function  DEFAULT S_ALL
172
*
173
*    Returns (via the struct posdata parameter):
174
*        everything defined in the struct posdata in solpos.h.
175
*----------------------------------------------------------------------------*/
176
long S_solpos (struct posdata *pdat)
542✔
177
{
178
  long int retval;
179

180
  struct trigdata trigdat, *tdat;
181

182
  tdat = &trigdat;   /* point to the structure */
542✔
183

184
  /* initialize the trig structure */
185
  tdat->sd = -999.0; /* flag to force calculation of trig data */
542✔
186
  tdat->cd =    1.0;
542✔
187
  tdat->ch =    1.0; /* set the rest of these to something safe */
542✔
188
  tdat->cl =    1.0;
542✔
189
  tdat->sl =    1.0;
542✔
190

191
  if ((retval = validate ( pdat )) != 0) /* validate the inputs */
542✔
NEW
192
    return retval;
×
193

194

195
  if ( pdat->function & L_DOY )
542✔
196
    doy2dom( pdat );                /* convert input doy to month-day */
1✔
197
  else
198
    dom2doy( pdat );                /* convert input month-day to doy */
541✔
199

200
  if ( pdat->function & L_GEOM )
542✔
201
    geometry( pdat );               /* do basic geometry calculations */
542✔
202

203
  if ( pdat->function & L_ZENETR )  /* etr at non-refracted zenith angle */
542✔
204
    zen_no_ref( pdat, tdat );
542✔
205

206
  if ( pdat->function & L_SSHA )    /* Sunset hour calculation */
542✔
207
    ssha( pdat, tdat );
542✔
208

209
  if ( pdat->function & L_SBCF )    /* Shadowband correction factor */
542✔
210
    sbcf( pdat, tdat );
542✔
211

212
  if ( pdat->function & L_TST )     /* true solar time */
542✔
213
    tst( pdat );
542✔
214

215
  if ( pdat->function & L_SRSS )    /* sunrise/sunset calculations */
542✔
216
    srss( pdat );
542✔
217

218
  if ( pdat->function & L_SOLAZM )  /* solar azimuth calculations */
542✔
219
    sazm( pdat, tdat );
542✔
220

221
  if ( pdat->function & L_REFRAC )  /* atmospheric refraction calculations */
542✔
222
    refrac( pdat );
542✔
223

224
  if ( pdat->function & L_AMASS )   /* airmass calculations */
542✔
225
    amass( pdat );
542✔
226

227
  if ( pdat->function & L_PRIME )   /* kt-prime/unprime calculations */
542✔
228
    prime( pdat );
542✔
229

230
  if ( pdat->function & L_ETR )     /* ETR and ETRN (refracted) */
542✔
231
    etr( pdat );
542✔
232

233
  if ( pdat->function & L_TILT )    /* tilt calculations */
542✔
234
    tilt( pdat );
542✔
235

236
    return 0;
542✔
237
}
238

239

240
/*============================================================================
241
*    Void function S_init
242
*
243
*    This function initiates all of the input parameters in the struct
244
*    posdata passed to S_solpos().  Initialization is either to nominal
245
*    values or to out of range values, which forces the calling program to
246
*    specify parameters.
247
*
248
*    NOTE: This function is optional if you initialize ALL input parameters
249
*          in your calling code.  Note that the required parameters of date
250
*          and location are deliberately initialized out of bounds to force
251
*          the user to enter real-world values.
252
*
253
*    Requires: Pointer to a posdata structure, members of which are
254
*           initialized.
255
*
256
*    Returns: Void
257
*----------------------------------------------------------------------------*/
258
void S_init(struct posdata *pdat)
542✔
259
{
260
  pdat->day       =    -99;   /* Day of month (May 27 = 27, etc.) */
542✔
261
  pdat->daynum    =   -999;   /* Day number (day of year; Feb 1 = 32 ) */
542✔
262
  pdat->hour      =    -99;   /* Hour of day, 0 - 23 */
542✔
263
  pdat->minute    =    -99;   /* Minute of hour, 0 - 59 */
542✔
264
  pdat->month     =    -99;   /* Month number (Jan = 1, Feb = 2, etc.) */
542✔
265
  pdat->second    =    -99;   /* Second of minute, 0 - 59 */
542✔
266
  pdat->year      =    -99;   /* 4-digit year */
542✔
267
  pdat->interval  =      0;   /* instantaneous measurement interval */
542✔
268
  pdat->aspect    =  180.0;   /* Azimuth of panel surface (direction it
542✔
269
                                    faces) N=0, E=90, S=180, W=270 */
270
  pdat->latitude  =  -99.0;   /* Latitude, degrees north (south negative) */
542✔
271
  pdat->longitude = -999.0;   /* Longitude, degrees east (west negative) */
542✔
272
  pdat->press     = 1013.0;   /* Surface pressure, millibars */
542✔
273
  pdat->solcon    = 1367.0;   /* Solar constant, 1367 W/sq m */
542✔
274
  pdat->temp      =   15.0;   /* Ambient dry-bulb temperature, degrees C */
542✔
275
  pdat->tilt      =    0.0;   /* Degrees tilt from horizontal of panel */
542✔
276
  pdat->timezone  =  -99.0;   /* Time zone, east (west negative). */
542✔
277
  pdat->sbwid     =    7.6;   /* Eppley shadow band width */
542✔
278
  pdat->sbrad     =   31.7;   /* Eppley shadow band radius */
542✔
279
  pdat->sbsky     =   0.04;   /* Drummond factor for partly cloudy skies */
542✔
280
  pdat->function  =  S_ALL;   /* compute all parameters */
542✔
281
}
542✔
282

283

284
/*============================================================================
285
*    Local long int function validate
286
*
287
*    Validates the input parameters
288
*----------------------------------------------------------------------------*/
289
static long int validate ( struct posdata *pdat)
542✔
290
{
291

292
  long int retval = 0;  /* start with no errors */
542✔
293

294
  /* No absurd dates, please. */
295
  if ( pdat->function & L_GEOM )
542✔
296
  {
297
    if ( (pdat->year < 1950) || (pdat->year > 2050) ) /* limits of algoritm */
542✔
NEW
298
      retval |= (1L << S_YEAR_ERROR);
×
299
    if ( !(pdat->function & S_DOY) && ((pdat->month < 1) || (pdat->month > 12)))
542✔
NEW
300
      retval |= (1L << S_MONTH_ERROR);
×
301
    if ( !(pdat->function & S_DOY) && ((pdat->day < 1) || (pdat->day > 31)) )
542✔
NEW
302
      retval |= (1L << S_DAY_ERROR);
×
303
    if ( (pdat->function & S_DOY) && ((pdat->daynum < 1) || (pdat->daynum > 366)) )
542✔
NEW
304
      retval |= (1L << S_DOY_ERROR);
×
305

306
    /* No absurd times, please. */
307
    if ( (pdat->hour < 0) || (pdat->hour > 24) )
542✔
NEW
308
      retval |= (1L << S_HOUR_ERROR);
×
309
    if ( (pdat->minute < 0) || (pdat->minute > 59) )
542✔
NEW
310
      retval |= (1L << S_MINUTE_ERROR);
×
311
    if ( (pdat->second < 0) || (pdat->second > 59) )
542✔
NEW
312
      retval |= (1L << S_SECOND_ERROR);
×
313
    if ( (pdat->hour == 24) && (pdat->minute > 0) ) /* no more than 24 hrs */
542✔
NEW
314
      retval |= ( (1L << S_HOUR_ERROR) | (1L << S_MINUTE_ERROR) );
×
315
    if ( (pdat->hour == 24) && (pdat->second > 0) ) /* no more than 24 hrs */
542✔
NEW
316
      retval |= ( (1L << S_HOUR_ERROR) | (1L << S_SECOND_ERROR) );
×
317
    if ( fabs (pdat->timezone) > 12.0 )
542✔
NEW
318
      retval |= (1L << S_TZONE_ERROR);
×
319
    if ( (pdat->interval < 0) || (pdat->interval > 28800) )
542✔
NEW
320
      retval |= (1L << S_INTRVL_ERROR);
×
321

322
    /* No absurd locations, please. */
323
    if ( fabs (pdat->longitude) > 180.0 )
542✔
NEW
324
      retval |= (1L << S_LON_ERROR);
×
325
    if ( fabs (pdat->latitude) > 90.0 )
542✔
NEW
326
      retval |= (1L << S_LAT_ERROR);
×
327
  }
328

329
  /* No silly temperatures or pressures, please. */
330
  if ( (pdat->function & L_REFRAC) && (fabs (pdat->temp) > 100.0) )
542✔
NEW
331
    retval |= (1L << S_TEMP_ERROR);
×
332
  if ( (pdat->function & L_REFRAC) &&
542✔
333
    (pdat->press < 0.0) || (pdat->press > 2000.0) )
542✔
NEW
334
    retval |= (1L << S_PRESS_ERROR);
×
335

336
  /* No out of bounds tilts, please */
337
  if ( (pdat->function & L_TILT) && (fabs (pdat->tilt) > 180.0) )
542✔
NEW
338
    retval |= (1L << S_TILT_ERROR);
×
339
  if ( (pdat->function & L_TILT) && (fabs (pdat->aspect) > 360.0) )
542✔
NEW
340
    retval |= (1L << S_ASPECT_ERROR);
×
341

342
  /* No oddball shadowbands, please */
343
  if ( (pdat->function & L_SBCF) &&
542✔
344
       (pdat->sbwid < 1.0) || (pdat->sbwid > 100.0) )
542✔
NEW
345
    retval |= (1L << S_SBWID_ERROR);
×
346
  if ( (pdat->function & L_SBCF) &&
542✔
347
       (pdat->sbrad < 1.0) || (pdat->sbrad > 100.0) )
542✔
NEW
348
    retval |= (1L << S_SBRAD_ERROR);
×
349
  if ( (pdat->function & L_SBCF) && ( fabs (pdat->sbsky) > 1.0) )
542✔
NEW
350
    retval |= (1L << S_SBSKY_ERROR);
×
351

352
  return retval;
542✔
353
}
354

355

356
/*============================================================================
357
*    Local Void function dom2doy
358
*
359
*    Converts day-of-month to day-of-year
360
*
361
*    Requires (from struct posdata parameter):
362
*            year
363
*            month
364
*            day
365
*
366
*    Returns (via the struct posdata parameter):
367
*            year
368
*            daynum
369
*----------------------------------------------------------------------------*/
370
static void dom2doy( struct posdata *pdat )
541✔
371
{
372
  pdat->daynum = pdat->day + month_days[0][pdat->month];
541✔
373

374
  /* (adjust for leap year) */
375
  if ( ((pdat->year % 4) == 0) &&
541✔
376
         ( ((pdat->year % 100) != 0) || ((pdat->year % 400) == 0) ) &&
540✔
377
         (pdat->month > 2) )
540✔
378
      pdat->daynum += 1;
540✔
379
}
541✔
380

381

382
/*============================================================================
383
*    Local void function doy2dom
384
*
385
*    This function computes the month/day from the day number.
386
*
387
*    Requires (from struct posdata parameter):
388
*        Year and day number:
389
*            year
390
*            daynum
391
*
392
*    Returns (via the struct posdata parameter):
393
*            year
394
*            month
395
*            day
396
*----------------------------------------------------------------------------*/
397
static void doy2dom(struct posdata *pdat)
1✔
398
{
399
  int  imon;  /* Month (month_days) array counter */
400
  int  leap;  /* leap year switch */
401

402
    /* Set the leap year switch */
403
    if ( ((pdat->year % 4) == 0) &&
1✔
NEW
404
         ( ((pdat->year % 100) != 0) || ((pdat->year % 400) == 0) ) )
×
NEW
405
        leap = 1;
×
406
    else
407
        leap = 0;
1✔
408

409
    /* Find the month */
410
    imon = 12;
1✔
411
    while ( pdat->daynum <= month_days [leap][imon] )
7✔
412
        --imon;
6✔
413

414
    /* Set the month and day of month */
415
    pdat->month = imon;
1✔
416
    pdat->day   = pdat->daynum - month_days[leap][imon];
1✔
417
}
1✔
418

419

420
/*============================================================================
421
*    Local Void function geometry
422
*
423
*    Does the underlying geometry for a given time and location
424
*----------------------------------------------------------------------------*/
425
static void geometry ( struct posdata *pdat )
542✔
426
{
427
  float bottom;      /* denominator (bottom) of the fraction */
428
  float c2;          /* cosine of d2 */
429
  float cd;          /* cosine of the day angle or delination */
430
  float d2;          /* pdat->dayang times two */
431
  float delta;       /* difference between current year and 1949 */
432
  float s2;          /* sine of d2 */
433
  float sd;          /* sine of the day angle */
434
  float top;         /* numerator (top) of the fraction */
435
  int   leap;        /* leap year counter */
436

437
  /* Day angle */
438
      /*  Iqbal, M.  1983.  An Introduction to Solar Radiation.
439
            Academic Press, NY., page 3 */
440
     pdat->dayang = 360.0 * ( pdat->daynum - 1 ) / 365.0;
542✔
441

442
    /* Earth radius vector * solar constant = solar energy */
443
        /*  Spencer, J. W.  1971.  Fourier series representation of the
444
            position of the sun.  Search 2 (5), page 172 */
445
    sd     = sin (raddeg * pdat->dayang);
542✔
446
    cd     = cos (raddeg * pdat->dayang);
542✔
447
    d2     = 2.0 * pdat->dayang;
542✔
448
    c2     = cos (raddeg * d2);
542✔
449
    s2     = sin (raddeg * d2);
542✔
450

451
    pdat->erv  = 1.000110 + 0.034221 * cd + 0.001280 * sd;
542✔
452
    pdat->erv  += 0.000719 * c2 + 0.000077 * s2;
542✔
453

454
    /* Universal Coordinated (Greenwich standard) time */
455
        /*  Michalsky, J.  1988.  The Astronomical Almanac's algorithm for
456
            approximate solar position (1950-2050).  Solar Energy 40 (3),
457
            pp. 227-235. */
458
    pdat->utime =
542✔
459
        pdat->hour * 3600.0 +
542✔
460
        pdat->minute * 60.0 +
542✔
461
        pdat->second -
542✔
462
        (float)pdat->interval / 2.0;
542✔
463
    pdat->utime = pdat->utime / 3600.0 - pdat->timezone;
542✔
464

465
    /* Julian Day minus 2,400,000 days (to eliminate roundoff errors) */
466
        /*  Michalsky, J.  1988.  The Astronomical Almanac's algorithm for
467
            approximate solar position (1950-2050).  Solar Energy 40 (3),
468
            pp. 227-235. */
469

470
    /* No adjustment for century non-leap years since this function is
471
       bounded by 1950 - 2050 */
472
    delta    = pdat->year - 1949;
542✔
473
    leap     = (int) ( delta / 4.0 );
542✔
474
    pdat->julday =
542✔
475
        32916.5 + delta * 365.0 + leap + pdat->daynum + pdat->utime / 24.0;
542✔
476

477
    /* Time used in the calculation of ecliptic coordinates */
478
    /* Noon 1 JAN 2000 = 2,400,000 + 51,545 days Julian Date */
479
        /*  Michalsky, J.  1988.  The Astronomical Almanac's algorithm for
480
            approximate solar position (1950-2050).  Solar Energy 40 (3),
481
            pp. 227-235. */
482
    pdat->ectime = pdat->julday - 51545.0;
542✔
483

484
    /* Mean longitude */
485
        /*  Michalsky, J.  1988.  The Astronomical Almanac's algorithm for
486
            approximate solar position (1950-2050).  Solar Energy 40 (3),
487
            pp. 227-235. */
488
    pdat->mnlong  = 280.460 + 0.9856474 * pdat->ectime;
542✔
489

490
    /* (dump the multiples of 360, so the answer is between 0 and 360) */
491
    pdat->mnlong -= 360.0 * (int) ( pdat->mnlong / 360.0 );
542✔
492
    if ( pdat->mnlong < 0.0 )
542✔
NEW
493
        pdat->mnlong += 360.0;
×
494

495
    /* Mean anomaly */
496
        /*  Michalsky, J.  1988.  The Astronomical Almanac's algorithm for
497
            approximate solar position (1950-2050).  Solar Energy 40 (3),
498
            pp. 227-235. */
499
    pdat->mnanom  = 357.528 + 0.9856003 * pdat->ectime;
542✔
500

501
    /* (dump the multiples of 360, so the answer is between 0 and 360) */
502
    pdat->mnanom -= 360.0 * (int) ( pdat->mnanom / 360.0 );
542✔
503
    if ( pdat->mnanom < 0.0 )
542✔
NEW
504
        pdat->mnanom += 360.0;
×
505

506
    /* Ecliptic longitude */
507
        /*  Michalsky, J.  1988.  The Astronomical Almanac's algorithm for
508
            approximate solar position (1950-2050).  Solar Energy 40 (3),
509
            pp. 227-235. */
510
    pdat->eclong  = pdat->mnlong + 1.915 * sin ( pdat->mnanom * raddeg ) +
542✔
511
                    0.020 * sin ( 2.0 * pdat->mnanom * raddeg );
542✔
512

513
    /* (dump the multiples of 360, so the answer is between 0 and 360) */
514
    pdat->eclong -= 360.0 * (int) ( pdat->eclong / 360.0 );
542✔
515
    if ( pdat->eclong < 0.0 )
542✔
NEW
516
        pdat->eclong += 360.0;
×
517

518
    /* Obliquity of the ecliptic */
519
        /*  Michalsky, J.  1988.  The Astronomical Almanac's algorithm for
520
            approximate solar position (1950-2050).  Solar Energy 40 (3),
521
            pp. 227-235. */
522

523
    /* 02 Feb 2001 SMW corrected sign in the following line */
524
/*  pdat->ecobli = 23.439 + 4.0e-07 * pdat->ectime;     */
525
    pdat->ecobli = 23.439 - 4.0e-07 * pdat->ectime;
542✔
526

527
    /* Declination */
528
        /*  Michalsky, J.  1988.  The Astronomical Almanac's algorithm for
529
            approximate solar position (1950-2050).  Solar Energy 40 (3),
530
            pp. 227-235. */
531
    pdat->declin = degrad * asin ( sin (pdat->ecobli * raddeg) *
1,084✔
532
                               sin (pdat->eclong * raddeg) );
542✔
533

534
    /* Right ascension */
535
        /*  Michalsky, J.  1988.  The Astronomical Almanac's algorithm for
536
            approximate solar position (1950-2050).  Solar Energy 40 (3),
537
            pp. 227-235. */
538
    top      =  cos ( raddeg * pdat->ecobli ) * sin ( raddeg * pdat->eclong );
542✔
539
    bottom   =  cos ( raddeg * pdat->eclong );
542✔
540

541
    pdat->rascen =  degrad * atan2 ( top, bottom );
542✔
542

543
    /* (make it a positive angle) */
544
    if ( pdat->rascen < 0.0 )
542✔
545
        pdat->rascen += 360.0;
225✔
546

547
    /* Greenwich mean sidereal time */
548
        /*  Michalsky, J.  1988.  The Astronomical Almanac's algorithm for
549
            approximate solar position (1950-2050).  Solar Energy 40 (3),
550
            pp. 227-235. */
551
    pdat->gmst  = 6.697375 + 0.0657098242 * pdat->ectime + pdat->utime;
542✔
552

553
    /* (dump the multiples of 24, so the answer is between 0 and 24) */
554
    pdat->gmst -= 24.0 * (int) ( pdat->gmst / 24.0 );
542✔
555
    if ( pdat->gmst < 0.0 )
542✔
NEW
556
        pdat->gmst += 24.0;
×
557

558
    /* Local mean sidereal time */
559
        /*  Michalsky, J.  1988.  The Astronomical Almanac's algorithm for
560
            approximate solar position (1950-2050).  Solar Energy 40 (3),
561
            pp. 227-235. */
562
    pdat->lmst  = pdat->gmst * 15.0 + pdat->longitude;
542✔
563

564
    /* (dump the multiples of 360, so the answer is between 0 and 360) */
565
    pdat->lmst -= 360.0 * (int) ( pdat->lmst / 360.0 );
542✔
566
    if ( pdat->lmst < 0.)
542✔
567
        pdat->lmst += 360.0;
37✔
568

569
    /* Hour angle */
570
        /*  Michalsky, J.  1988.  The Astronomical Almanac's algorithm for
571
            approximate solar position (1950-2050).  Solar Energy 40 (3),
572
            pp. 227-235. */
573
    pdat->hrang = pdat->lmst - pdat->rascen;
542✔
574

575
    /* (force it between -180 and 180 degrees) */
576
    if ( pdat->hrang < -180.0 )
542✔
577
        pdat->hrang += 360.0;
33✔
578
    else if ( pdat->hrang > 180.0 )
509✔
579
        pdat->hrang -= 360.0;
17✔
580
}
542✔
581

582

583
/*============================================================================
584
*    Local Void function zen_no_ref
585
*
586
*    ETR solar zenith angle
587
*       Iqbal, M.  1983.  An Introduction to Solar Radiation.
588
*            Academic Press, NY., page 15
589
*----------------------------------------------------------------------------*/
590
static void zen_no_ref ( struct posdata *pdat, struct trigdata *tdat )
542✔
591
{
592
  float cz;          /* cosine of the solar zenith angle */
593

594
    localtrig( pdat, tdat );
542✔
595
    cz = tdat->sd * tdat->sl + tdat->cd * tdat->cl * tdat->ch;
542✔
596

597
    /* (watch out for the roundoff errors) */
598
    if ( fabs (cz) > 1.0 ) {
542✔
NEW
599
        if ( cz >= 0.0 )
×
NEW
600
            cz =  1.0;
×
601
        else
NEW
602
            cz = -1.0;
×
603
    }
604

605
    pdat->zenetr   = acos ( cz ) * degrad;
542✔
606

607
    /* (limit the degrees below the horizon to 9 [+90 -> 99]) */
608
    if ( pdat->zenetr > 99.0 )
542✔
NEW
609
        pdat->zenetr = 99.0;
×
610

611
    pdat->elevetr = 90.0 - pdat->zenetr;
542✔
612
}
542✔
613

614

615
/*============================================================================
616
*    Local Void function ssha
617
*
618
*    Sunset hour angle, degrees
619
*       Iqbal, M.  1983.  An Introduction to Solar Radiation.
620
*            Academic Press, NY., page 16
621
*----------------------------------------------------------------------------*/
622
static void ssha( struct posdata *pdat, struct trigdata *tdat )
542✔
623
{
624
  float cssha;       /* cosine of the sunset hour angle */
625
  float cdcl;        /* ( cd * cl ) */
626

627
    localtrig( pdat, tdat );
542✔
628
    cdcl    = tdat->cd * tdat->cl;
542✔
629

630
    if ( fabs ( cdcl ) >= 0.001 ) {
542✔
631
        cssha = -tdat->sl * tdat->sd / cdcl;
542✔
632

633
        /* This keeps the cosine from blowing on roundoff */
634
        if ( cssha < -1.0  )
542✔
NEW
635
            pdat->ssha = 180.0;
×
636
        else if ( cssha > 1.0 )
542✔
NEW
637
            pdat->ssha = 0.0;
×
638
        else
639
            pdat->ssha = degrad * acos ( cssha );
542✔
640
    }
NEW
641
    else if ( ((pdat->declin >= 0.0) && (pdat->latitude > 0.0 )) ||
×
NEW
642
              ((pdat->declin <  0.0) && (pdat->latitude < 0.0 )) )
×
NEW
643
        pdat->ssha = 180.0;
×
644
    else
NEW
645
        pdat->ssha = 0.0;
×
646
}
542✔
647

648

649
/*============================================================================
650
*    Local Void function sbcf
651
*
652
*    Shadowband correction factor
653
*       Drummond, A. J.  1956.  A contribution to absolute pyrheliometry.
654
*            Q. J. R. Meteorol. Soc. 82, pp. 481-493
655
*----------------------------------------------------------------------------*/
656
static void sbcf( struct posdata *pdat, struct trigdata *tdat )
542✔
657
{
658
  float p, t1, t2;   /* used to compute sbcf */
659

660
    localtrig( pdat, tdat );
542✔
661
    p       = 0.6366198 * pdat->sbwid / pdat->sbrad * pow (tdat->cd,3);
542✔
662
    t1      = tdat->sl * tdat->sd * pdat->ssha * raddeg;
542✔
663
    t2      = tdat->cl * tdat->cd * sin ( pdat->ssha * raddeg );
542✔
664
    pdat->sbcf = pdat->sbsky + 1.0 / ( 1.0 - p * ( t1 + t2 ) );
542✔
665

666
}
542✔
667

668

669
/*============================================================================
670
*    Local Void function tst
671
*
672
*    TST -> True Solar Time = local standard time + TSTfix, time
673
*      in minutes from midnight.
674
*        Iqbal, M.  1983.  An Introduction to Solar Radiation.
675
*            Academic Press, NY., page 13
676
*----------------------------------------------------------------------------*/
677
static void tst( struct posdata *pdat )
542✔
678
{
679
    pdat->tst    = ( 180.0 + pdat->hrang ) * 4.0;
542✔
680
    pdat->tstfix =
542✔
681
        pdat->tst -
542✔
682
        (float)pdat->hour * 60.0 -
542✔
683
        pdat->minute -
542✔
684
        (float)pdat->second / 60.0 +
542✔
685
        (float)pdat->interval / 120.0; /* add back half of the interval */
542✔
686

687
    /* bound tstfix to this day */
688
    while ( pdat->tstfix >  720.0 )
542✔
NEW
689
        pdat->tstfix -= 1440.0;
×
690
    while ( pdat->tstfix < -720.0 )
542✔
NEW
691
        pdat->tstfix += 1440.0;
×
692

693
    pdat->eqntim =
542✔
694
        pdat->tstfix + 60.0 * pdat->timezone - 4.0 * pdat->longitude;
542✔
695

696
}
542✔
697

698

699
/*============================================================================
700
*    Local Void function srss
701
*
702
*    Sunrise and sunset times (minutes from midnight)
703
*----------------------------------------------------------------------------*/
704
static void srss( struct posdata *pdat )
542✔
705
{
706
    if ( pdat->ssha <= 1.0 ) {
542✔
NEW
707
        pdat->sretr   =  2999.0;
×
NEW
708
        pdat->ssetr   = -2999.0;
×
709
    }
710
    else if ( pdat->ssha >= 179.0 ) {
542✔
NEW
711
        pdat->sretr   = -2999.0;
×
NEW
712
        pdat->ssetr   =  2999.0;
×
713
    }
714
    else {
715
        pdat->sretr   = 720.0 - 4.0 * pdat->ssha - pdat->tstfix;
542✔
716
        pdat->ssetr   = 720.0 + 4.0 * pdat->ssha - pdat->tstfix;
542✔
717
    }
718
}
542✔
719

720

721
/*============================================================================
722
*    Local Void function sazm
723
*
724
*    Solar azimuth angle
725
*       Iqbal, M.  1983.  An Introduction to Solar Radiation.
726
*            Academic Press, NY., page 15
727
*----------------------------------------------------------------------------*/
728
static void sazm( struct posdata *pdat, struct trigdata *tdat )
542✔
729
{
730
  float ca;          /* cosine of the solar azimuth angle */
731
  float ce;          /* cosine of the solar elevation */
732
  float cecl;        /* ( ce * cl ) */
733
  float se;          /* sine of the solar elevation */
734

735
    localtrig( pdat, tdat );
542✔
736
    ce         = cos ( raddeg * pdat->elevetr );
542✔
737
    se         = sin ( raddeg * pdat->elevetr );
542✔
738

739
    pdat->azim     = 180.0;
542✔
740
    cecl       = ce * tdat->cl;
542✔
741
    if ( fabs ( cecl ) >= 0.001 ) {
542✔
742
        ca     = ( se * tdat->sl - tdat->sd ) / cecl;
542✔
743
        if ( ca > 1.0 )
542✔
NEW
744
            ca = 1.0;
×
745
        else if ( ca < -1.0 )
542✔
NEW
746
            ca = -1.0;
×
747

748
        pdat->azim = 180.0 - acos ( ca ) * degrad;
542✔
749
        if ( pdat->hrang > 0 )
542✔
750
            pdat->azim  = 360.0 - pdat->azim;
302✔
751
    }
752
}
542✔
753

754

755
/*============================================================================
756
*    Local Int function refrac
757
*
758
*    Refraction correction, degrees
759
*        Zimmerman, John C.  1981.  Sun-pointing programs and their
760
*            accuracy.
761
*            SAND81-0761, Experimental Systems Operation Division 4721,
762
*            Sandia National Laboratories, Albuquerque, NM.
763
*----------------------------------------------------------------------------*/
764
static void refrac( struct posdata *pdat )
542✔
765
{
766
  float prestemp;    /* temporary pressure/temperature correction */
767
  float refcor;      /* temporary refraction correction */
768
  float tanelev;     /* tangent of the solar elevation angle */
769

770
    /* If the sun is near zenith, the algorithm bombs; refraction near 0 */
771
    if ( pdat->elevetr > 85.0 )
542✔
NEW
772
        refcor = 0.0;
×
773

774
    /* Otherwise, we have refraction */
775
    else {
776
        tanelev = tan ( raddeg * pdat->elevetr );
542✔
777
        if ( pdat->elevetr >= 5.0 )
542✔
778
            refcor  = 58.1 / tanelev -
1,611✔
779
                      0.07 / ( pow (tanelev,3) ) +
537✔
780
                      0.000086 / ( pow (tanelev,5) );
537✔
781
        else if ( pdat->elevetr >= -0.575 )
5✔
782
            refcor  = 1735.0 +
5✔
783
                      pdat->elevetr * ( -518.2 + pdat->elevetr * ( 103.4 +
5✔
784
                      pdat->elevetr * ( -12.79 + pdat->elevetr * 0.711 ) ) );
5✔
785
        else
NEW
786
            refcor  = -20.774 / tanelev;
×
787

788
        prestemp    =
542✔
789
            ( pdat->press * 283.0 ) / ( 1013.0 * ( 273.0 + pdat->temp ) );
542✔
790
        refcor     *= prestemp / 3600.0;
542✔
791
    }
792

793
    /* Refracted solar elevation angle */
794
    pdat->elevref = pdat->elevetr + refcor;
542✔
795

796
    /* (limit the degrees below the horizon to 9) */
797
    if ( pdat->elevref < -9.0 )
542✔
NEW
798
        pdat->elevref = -9.0;
×
799

800
    /* Refracted solar zenith angle */
801
    pdat->zenref  = 90.0 - pdat->elevref;
542✔
802
    pdat->coszen  = cos( raddeg * pdat->zenref );
542✔
803
}
542✔
804

805

806
/*============================================================================
807
*    Local Void function  amass
808
*
809
*    Airmass
810
*       Kasten, F. and Young, A.  1989.  Revised optical air mass
811
*            tables and approximation formula.  Applied Optics 28 (22),
812
*            pp. 4735-4738
813
*----------------------------------------------------------------------------*/
814
static void amass( struct posdata *pdat )
542✔
815
{
816
    if ( pdat->zenref > 93.0 )
542✔
817
    {
NEW
818
        pdat->amass   = -1.0;
×
NEW
819
        pdat->ampress = -1.0;
×
820
    }
821
    else
822
    {
823
        pdat->amass =
542✔
824
            1.0 / ( cos (raddeg * pdat->zenref) + 0.50572 *
542✔
825
            pow ((96.07995 - pdat->zenref),-1.6364) );
542✔
826

827
        pdat->ampress   = pdat->amass * pdat->press / 1013.0;
542✔
828
    }
829
}
542✔
830

831

832
/*============================================================================
833
*    Local Void function prime
834
*
835
*    Prime and Unprime
836
*    Prime  converts Kt to normalized Kt', etc.
837
*       Unprime deconverts Kt' to Kt, etc.
838
*            Perez, R., P. Ineichen, Seals, R., & Zelenka, A.  1990.  Making
839
*            full use of the clearness index for parameterizing hourly
840
*            insolation conditions. Solar Energy 45 (2), pp. 111-114
841
*----------------------------------------------------------------------------*/
842
static void prime( struct posdata *pdat )
542✔
843
{
844
    pdat->unprime = 1.031 * exp ( -1.4 / ( 0.9 + 9.4 / pdat->amass ) ) + 0.1;
542✔
845
    pdat->prime   = 1.0 / pdat->unprime;
542✔
846
}
542✔
847

848

849
/*============================================================================
850
*    Local Void function etr
851
*
852
*    Extraterrestrial (top-of-atmosphere) solar irradiance
853
*----------------------------------------------------------------------------*/
854
static void etr( struct posdata *pdat )
542✔
855
{
856
    if ( pdat->coszen > 0.0 ) {
542✔
857
        pdat->etrn = pdat->solcon * pdat->erv;
542✔
858
        pdat->etr  = pdat->etrn * pdat->coszen;
542✔
859
    }
860
    else {
NEW
861
        pdat->etrn = 0.0;
×
NEW
862
        pdat->etr  = 0.0;
×
863
    }
864
}
542✔
865

866

867
/*============================================================================
868
*    Local Void function localtrig
869
*
870
*    Does trig on internal variable used by several functions
871
*----------------------------------------------------------------------------*/
872
static void localtrig( struct posdata *pdat, struct trigdata *tdat )
2,168✔
873
{
874
/* define masks to prevent calculation of uninitialized variables */
875
#define SD_MASK ( L_ZENETR | L_SSHA | S_SBCF | S_SOLAZM )
876
#define SL_MASK ( L_ZENETR | L_SSHA | S_SBCF | S_SOLAZM )
877
#define CL_MASK ( L_ZENETR | L_SSHA | S_SBCF | S_SOLAZM )
878
#define CD_MASK ( L_ZENETR | L_SSHA | S_SBCF )
879
#define CH_MASK ( L_ZENETR )
880

881
    if ( tdat->sd < -900.0 )  /* sd was initialized -999 as flag */
2,168✔
882
    {
883
      tdat->sd = 1.0;  /* reflag as having completed calculations */
542✔
884
      if ( pdat->function | CD_MASK )
885
        tdat->cd = cos ( raddeg * pdat->declin );
542✔
886
      if ( pdat->function | CH_MASK )
887
        tdat->ch = cos ( raddeg * pdat->hrang );
542✔
888
      if ( pdat->function | CL_MASK )
889
        tdat->cl = cos ( raddeg * pdat->latitude );
542✔
890
      if ( pdat->function | SD_MASK )
891
        tdat->sd = sin ( raddeg * pdat->declin );
542✔
892
      if ( pdat->function | SL_MASK )
893
        tdat->sl = sin ( raddeg * pdat->latitude );
542✔
894
    }
895
}
2,168✔
896

897

898
/*============================================================================
899
*    Local Void function tilt
900
*
901
*    ETR on a tilted surface
902
*----------------------------------------------------------------------------*/
903
static void tilt( struct posdata *pdat )
542✔
904
{
905
  float ca;          /* cosine of the solar azimuth angle */
906
  float cp;          /* cosine of the panel aspect */
907
  float ct;          /* cosine of the panel tilt */
908
  float sa;          /* sine of the solar azimuth angle */
909
  float sp;          /* sine of the panel aspect */
910
  float st;          /* sine of the panel tilt */
911
  float sz;          /* sine of the refraction corrected solar zenith angle */
912

913

914
    /* Cosine of the angle between the sun and a tipped flat surface,
915
       useful for calculating solar energy on tilted surfaces */
916
    ca      = cos ( raddeg * pdat->azim );
542✔
917
    cp      = cos ( raddeg * pdat->aspect );
542✔
918
    ct      = cos ( raddeg * pdat->tilt );
542✔
919
    sa      = sin ( raddeg * pdat->azim );
542✔
920
    sp      = sin ( raddeg * pdat->aspect );
542✔
921
    st      = sin ( raddeg * pdat->tilt );
542✔
922
    sz      = sin ( raddeg * pdat->zenref );
542✔
923
    pdat->cosinc  = pdat->coszen * ct + sz * st * ( ca * cp + sa * sp );
542✔
924

925
    if ( pdat->cosinc > 0.0 )
542✔
926
        pdat->etrtilt = pdat->etrn * pdat->cosinc;
542✔
927
    else
NEW
928
        pdat->etrtilt = 0.0;
×
929

930
}
542✔
931

932

933
/*============================================================================
934
*    Void function S_decode
935
*
936
*    This function decodes the error codes from S_solpos return value
937
*
938
*    Requires the long integer return value from S_solpos
939
*
940
*    Returns descriptive text to stderr
941
*----------------------------------------------------------------------------*/
942
void S_decode(long code, struct posdata *pdat)
542✔
943
{
944
  if ( code & (1L << S_YEAR_ERROR) )
542✔
NEW
945
    fprintf(stderr, "S_decode ==> Please fix the year: %d [1950-2050]\n",
×
946
      pdat->year);
947
  if ( code & (1L << S_MONTH_ERROR) )
542✔
NEW
948
    fprintf(stderr, "S_decode ==> Please fix the month: %d\n",
×
949
      pdat->month);
950
  if ( code & (1L << S_DAY_ERROR) )
542✔
NEW
951
    fprintf(stderr, "S_decode ==> Please fix the day-of-month: %d\n",
×
952
      pdat->day);
953
  if ( code & (1L << S_DOY_ERROR) )
542✔
NEW
954
    fprintf(stderr, "S_decode ==> Please fix the day-of-year: %d\n",
×
955
      pdat->daynum);
956
  if ( code & (1L << S_HOUR_ERROR) )
542✔
NEW
957
    fprintf(stderr, "S_decode ==> Please fix the hour: %d\n",
×
958
      pdat->hour);
959
  if ( code & (1L << S_MINUTE_ERROR) )
542✔
NEW
960
    fprintf(stderr, "S_decode ==> Please fix the minute: %d\n",
×
961
      pdat->minute);
962
  if ( code & (1L << S_SECOND_ERROR) )
542✔
NEW
963
    fprintf(stderr, "S_decode ==> Please fix the second: %d\n",
×
964
      pdat->second);
965
  if ( code & (1L << S_TZONE_ERROR) )
542✔
NEW
966
    fprintf(stderr, "S_decode ==> Please fix the time zone: %f\n",
×
NEW
967
      pdat->timezone);
×
968
  if ( code & (1L << S_INTRVL_ERROR) )
542✔
NEW
969
    fprintf(stderr, "S_decode ==> Please fix the interval: %d\n",
×
970
      pdat->interval);
971
  if ( code & (1L << S_LAT_ERROR) )
542✔
NEW
972
    fprintf(stderr, "S_decode ==> Please fix the latitude: %f\n",
×
NEW
973
      pdat->latitude);
×
974
  if ( code & (1L << S_LON_ERROR) )
542✔
NEW
975
    fprintf(stderr, "S_decode ==> Please fix the longitude: %f\n",
×
NEW
976
      pdat->longitude);
×
977
  if ( code & (1L << S_TEMP_ERROR) )
542✔
NEW
978
    fprintf(stderr, "S_decode ==> Please fix the temperature: %f\n",
×
NEW
979
      pdat->temp);
×
980
  if ( code & (1L << S_PRESS_ERROR) )
542✔
NEW
981
    fprintf(stderr, "S_decode ==> Please fix the pressure: %f\n",
×
NEW
982
      pdat->press);
×
983
  if ( code & (1L << S_TILT_ERROR) )
542✔
NEW
984
    fprintf(stderr, "S_decode ==> Please fix the tilt: %f\n",
×
NEW
985
      pdat->tilt);
×
986
  if ( code & (1L << S_ASPECT_ERROR) )
542✔
NEW
987
    fprintf(stderr, "S_decode ==> Please fix the aspect: %f\n",
×
NEW
988
      pdat->aspect);
×
989
  if ( code & (1L << S_SBWID_ERROR) )
542✔
NEW
990
    fprintf(stderr, "S_decode ==> Please fix the shadowband width: %f\n",
×
NEW
991
      pdat->sbwid);
×
992
  if ( code & (1L << S_SBRAD_ERROR) )
542✔
NEW
993
    fprintf(stderr, "S_decode ==> Please fix the shadowband radius: %f\n",
×
NEW
994
      pdat->sbrad);
×
995
  if ( code & (1L << S_SBSKY_ERROR) )
542✔
NEW
996
    fprintf(stderr, "S_decode ==> Please fix the shadowband sky factor: %f\n",
×
NEW
997
      pdat->sbsky);
×
998
}
542✔
999

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc