-
Notifications
You must be signed in to change notification settings - Fork 8
/
Solarlib.cpp
412 lines (406 loc) · 14.5 KB
/
Solarlib.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
/* Solarlib.cpp Luke Miller December 2012
* Released into the public domain (originally based on U.S. Govt. products)
* No warranty given or implied.
*
* A library of functions for Arduino to calculate aspects of solar position
* in the sky using a time value, latitude, longitude, and time zone.
* Output includes estimates of current sun elevation and azimuth (position in
* the sky), sunrise, solar noon, and sunset times for the current day, and
* various other statistics for the sun at the given time. Results should be
* accurate for the years 1901 to 2099, for locations between +/- 72 latitude.
* Calculations are based on spreadsheet and information found at:
* http://www.esrl.noaa.gov/gmd/grad/solcalc/calcdetails.html
*
* Initialize the solar calculator using the initSolarCalc() function, which
* lets you specify:
* tzOffset - time zone Offset from Greenwich Mean Time (UTC). Time zones
* west of GMT should be given negative values.
* For example, Pacific Standard Time is -8
*
* lat - Latitude of the location you want to use. Values north of the
* equator are positive, given in decimal degrees.
*
* lon - Longitude of the location you want to use. Values in the
* western hemisphere have negative values (0 to -180), given in
* decimal degrees.
* For example: Monterey, California has lat/lon (36.62, -121.904)
*
* After running initSolarCalc() in the setup loop, you can call any of the
* extractor functions to get the position of the sun and sunrise/sunset times.
* Each extractor function requires a Time value as input, from the Time
* library. The Time is given as seconds since 1970-1-1 00:00 (unix epoch).
* Useful functions, supplied with a time value t as the sole argument:
* getSAA(t) - Solar Azimuth (degrees clockwise from North)
* getSEC_Corr(t) - Solar Elevation, corrected for diffraction (degrees)
* getSZA(t) - Solar Zenith angle (degrees below straight up)
* getSunriseTime(t) - Sunrise Time for the current day (Time object)
* getSunsetTime(t) - Sunset Time for the current day (Time object)
* getSolarNoonTime(t) - Solar Noon for the current day (Time object)
* getSunDuration(t) - Minutes of Sunlight for the current day
* Many more found below...
*
* **WARNING**
* Only tested on 32-bit ARM Teensy 3.0. This library will probably fail
* horribly on 8-bit AVR products due to limits in precision of double/float
* values. Developed on Arduino software version 1.0.2
*
* You can check your results against the NOAA calculator:
* http://www.esrl.noaa.gov/gmd/grad/solcalc/
*
* Update 2021-04-19: Fixed typo in Approximate Atmospheric Refraction
* calculation (SE.AAR in calcSolar() function). Fix suggested by
* Raoul Smeets, Avans University of Applied Science
*/
#include "Arduino.h"
#include "math.h"
#include "Time.h"
#include "Solarlib.h"
static SolarElements SE; // cache that holds all calculated values
// initSolar function
void initSolarCalc(int tzOffset, double lat, double lon){
SE.tzOffset = tzOffset; // Set time zone offset
SE.lat = lat; // Set current site latitude
SE.lon = lon; // Set current site longitude
}
// Return time zone offset when user asks for it.
// Zones west of GMT are negative.
int gettzOffset(){
return SE.tzOffset;
}
// Return latitude when user asks for it.
double getlat(){
return SE.lat;
}
// Return longitude when user asks for it.
double getlon(){
return SE.lon;
}
double gettimeFracDay(time_t t){
calcSolar(t, SE);
return SE.timeFracDay;
}
long getunixDays(time_t t){
calcSolar(t, SE);
return SE.unixDays;
}
double getJDN(time_t t){
calcSolar(t, SE);
return SE.JDN;
}
// Extract Julian Century
double getJCN(time_t t){
calcSolar(t, SE);
return SE.JCN;
}
// Extract GMLS
double getGMLS(time_t t){
calcSolar(t, SE);
return SE.GMLS;
}
double getGMAS(time_t t){
calcSolar(t, SE);
return SE.GMAS;
}
// Extract Eccentricity of Earth Orbit
double getEEO(time_t t){
calcSolar(t, SE);
return SE.EEO;
}
// Extract Sun Equation of Center
double getSEC(time_t t){
calcSolar(t, SE);
return SE.SEC;
}
// Extract Sun True Longitude (degrees)
double getSTL(time_t t){
calcSolar(t, SE);
return SE.STL;
}
// Extract Sun True Anomaly (degrees)
double getSTA(time_t t){
calcSolar(t, SE);
return SE.STA;
}
// Extract Sun Radian Vector
double getSRV(time_t t){
calcSolar(t, SE);
return SE.SRV;
}
// Extract Sun Apparent Longitude (degrees)
double getSAL(time_t t){
calcSolar(t, SE);
return SE.SAL;
}
// Extract Mean Oblique Ecliptic (degrees)
double getMOE(time_t t){
calcSolar(t, SE);
return SE.MOE;
}
// Extract Oblique correction (degrees)
double getOC(time_t t){
calcSolar(t, SE);
return SE.MOE;
}
// Extract Sun Right Ascension (degrees)
double getSRA(time_t t){
calcSolar(t, SE);
return SE.SRA;
}
// Extract Sun Declination (degrees)
double getSDec(time_t t){
calcSolar(t, SE);
return SE.SDec;
}
// Extract var y
double getvy(time_t t){
calcSolar(t, SE);
return SE.vy;
}
// Extract Equation of Time
double getEOT(time_t t){
calcSolar(t, SE);
return SE.EOT;
}
// Extract Hour Angle Sunrise (degrees)
double getHAS(time_t t){
calcSolar(t, SE);
return SE.HAS;
}
// Extract Solar Noon (fraction of a day)
double getSolarNoonfrac(time_t t){
calcSolar(t,SE);
return SE.SolarNoonfrac;
}
// Extract Solar Noon Days (days since 1970-1-1, local time zone)
double getSolarNoonDays(time_t t){
calcSolar(t, SE);
return SE.SolarNoonDays;
}
// Extract Solar Noon Time (Time object, seconds since 1970-1-1)
time_t getSolarNoonTime(time_t t){
calcSolar(t, SE);
return SE.SolarNoonTime;
}
// Extract Sunrise (seconds since 1970-1-1, local time zone)
double getSunrise(time_t t){
calcSolar(t, SE);
return SE.Sunrise;
}
// Extract Sunrise as Time object (seconds since 1970-1-1, local time zone)
time_t getSunriseTime(time_t t){
calcSolar(t, SE);
return SE.SunriseTime;
}
// Extract Sunset (seconds since 1970-1-1, local time zone)
double getSunset(time_t t){
calcSolar(t, SE);
return SE.Sunset;
}
// Extract Sunset as Time object (seconds since 1970-1-1, local time zone)
time_t getSunsetTime(time_t t){
calcSolar(t, SE);
return SE.SunsetTime;
}
// Extract Sunlight Duration (day length, minutes)
double getSunDuration(time_t t){
calcSolar(t, SE);
return SE.SunDuration;
}
// Extract True Solar Time (minutes)
double getTST(time_t t){
calcSolar(t, SE);
return SE.TST;
}
// Extract Hour Angle (degrees)
double getHA(time_t t){
calcSolar(t, SE);
return SE.HA;
}
// Extract Solar Zenith Angle (degrees)
double getSZA(time_t t){
calcSolar(t, SE);
return SE.SZA;
}
// Solar Elevation Angle (degrees above horizontal)
double getSEA(time_t t){
calcSolar(t, SE);
return SE.SEA;
}
// Approximate Atmospheric Refraction (degrees)
double getAAR(time_t t){
calcSolar(t, SE);
return SE.AAR;
}
// Solar Elevation Corrected for Atmospheric refraction (degrees)
double getSEC_Corr(time_t t){
calcSolar(t, SE);
return SE.SEC_Corr;
}
// Extract Solar Azimuth Angle (degrees clockwise from North)
double getSAA(time_t t){
calcSolar(t, SE);
return SE.SAA;
}
// Main function to calculate solar values. Requires a time value (seconds since
// 1970-1-1) as input.
void calcSolar(time_t t, SolarElements &SE){
// Calculate the time past midnight, as a fractional day value
// e.g. if it's noon, the result should be 0.5.
SE.timeFracDay = ((((double)(second(t)/60) + minute(t))/60) +
hour(t))/24;
// unixDays is the number of whole days since the start
// of the Unix epoch. The division sign will truncate any remainder
// since this will be done as integer division.
SE.unixDays = t / 86400;
// calculate Julian Day Number
SE.JDN = julianUnixEpoch + SE.unixDays;
// Add the fractional day value to the Julian Day number. If the
// input value was in the GMT time zone, we could proceed directly
// with this value.
SE.JDN = SE.JDN + SE.timeFracDay;
// Adjust JDN to GMT time zone
SE.JDN = SE.JDN - ((double)SE.tzOffset / 24);
// Calculate Julian Century Number
SE.JCN = (SE.JDN - 2451545) / 36525;
// Geometric Mean Longitude of Sun (degrees)
SE.GMLS = (280.46646 + SE.JCN * (36000.76983 + SE.JCN * 0.0003032));
// Finish GMLS calculation by calculating modolu(GMLS,360) as
// it's done in R or Excel. C's fmod doesn't work in the same
// way. The floor() function is from the math.h library.
SE.GMLS = SE.GMLS - (360 * (floor(SE.GMLS/360)) );
// Geometric Mean Anomaly of Sun (degrees)
SE.GMAS = 357.52911 + (SE.JCN * (35999.05029 - 0.0001537 * SE.JCN));
// Eccentricity of Earth Orbit
SE.EEO = 0.016708634 - (SE.JCN * (0.000042037 + 0.0000001267 * SE.JCN));
// Sun Equation of Center
SE.SEC = sin(SE.GMAS * DEG_TO_RAD) * (1.914602 -
(SE.JCN * (0.004817 + 0.000014 * SE.JCN))) +
sin((2*SE.GMAS)* DEG_TO_RAD)*(0.019993-0.000101*SE.JCN) +
sin((3*SE.GMAS)* DEG_TO_RAD) * 0.000289;
// Sun True Longitude (degrees)
SE.STL = SE.GMLS + SE.SEC;
// Sun True Anomaly (degrees)
SE.STA = SE.GMAS + SE.SEC;
// Sun Radian Vector (Astronomical Units)
SE.SRV = (1.000001018 * (1- SE.EEO * SE.EEO))/(1 + SE.EEO *
cos(SE.STA * DEG_TO_RAD));
// Sun Apparent Longitude (degrees)
SE.SAL = SE.STL - 0.00569 - (0.00478 *
sin((125.04 - 1934.136 * SE.JCN) * DEG_TO_RAD));
// Mean Oblique Ecliptic (degrees)
SE.MOE = 23 + (26 + (21.448-SE.JCN * ( 46.815 + SE.JCN *
(0.00059 - SE.JCN * 0.001813)))/60)/60;
// Oblique correction (degrees)
SE.OC = SE.MOE + 0.00256 * cos((125.04-1934.136*SE.JCN)*DEG_TO_RAD);
// Sun Right Ascension (degrees)
SE.SRA = (atan2(cos(SE.OC * DEG_TO_RAD) * sin(SE.SAL * DEG_TO_RAD),
cos(SE.SAL * DEG_TO_RAD))) * RAD_TO_DEG;
// Sun Declination (degrees)
SE.SDec = (asin(sin(SE.OC * DEG_TO_RAD) *
sin(SE.SAL * DEG_TO_RAD))) * RAD_TO_DEG;
// var y
SE.vy = tan((SE.OC/2) * DEG_TO_RAD) * tan((SE.OC/2) * DEG_TO_RAD);
// Equation of Time (minutes)
SE.EOT = 4 * ((SE.vy * sin(2 * (SE.GMLS * DEG_TO_RAD)) -
2 * SE.EEO * sin(SE.GMAS * DEG_TO_RAD) +
4 * SE.EEO * SE.vy * sin(SE.GMAS * DEG_TO_RAD) *
cos(2*(SE.GMLS*DEG_TO_RAD)) -
0.5 * SE.vy * SE.vy * sin(4*(SE.GMLS * DEG_TO_RAD)) -
1.25 * SE.EEO * SE.EEO * sin(2*(SE.GMAS* DEG_TO_RAD))) *
RAD_TO_DEG);
// Hour Angle Sunrise (degrees)
SE.HAS = acos((cos(90.833*DEG_TO_RAD)/
(cos(SE.lat*DEG_TO_RAD) * cos(SE.SDec*DEG_TO_RAD))) -
tan(SE.lat * DEG_TO_RAD) * tan(SE.SDec * DEG_TO_RAD)) *
RAD_TO_DEG ;
// Solar Noon - result is given as fraction of a day
// Time value is in GMT time zone
SE.SolarNoonfrac = (720 - 4 * SE.lon - SE.EOT) / 1440 ;
// SolarNoon is given as a fraction of a day. Add this
// to the unixDays value, which currently holds the
// whole days since 1970-1-1 00:00
SE.SolarNoonDays = SE.unixDays + SE.SolarNoonfrac;
// SolarNoonDays is in GMT time zone, correct it to
// the input time zone
SE.SolarNoonDays = SE.SolarNoonDays + ((double)SE.tzOffset / 24);
// Then convert SolarNoonDays to seconds
SE.SolarNoonTime = SE.SolarNoonDays * 86400;
// Sunrise Time, given as fraction of a day
SE.Sunrise = SE.SolarNoonfrac - SE.HAS * 4/1440;
// Convert Sunrise to days since 1970-1-1
SE.Sunrise = SE.unixDays + SE.Sunrise;
// Correct Sunrise to local time zone from GMT
SE.Sunrise = SE.Sunrise + ((double)SE.tzOffset / 24);
// Convert Sunrise to seconds since 1970-1-1
SE.Sunrise = SE.Sunrise * 86400;
// Convert Sunrise to a time_t object (Time library)
SE.SunriseTime = (time_t)SE.Sunrise;
// Sunset Time
SE.Sunset = SE.SolarNoonfrac + SE.HAS * 4/1440;
// Convert Sunset to days since 1970-1-1
SE.Sunset = SE.unixDays + SE.Sunset;
// Correct Sunset to local time zone from GMT
SE.Sunset = SE.Sunset + ((double)SE.tzOffset / 24);
// Convert Sunset to seconds since 1970-1-1
SE.Sunset = SE.Sunset * 86400;
// Convert Sunset to a time_t object (Time library)
SE.SunsetTime = (time_t)SE.Sunset;
// Sunlight Duration (day length, minutes)
SE.SunDuration = 8 * SE.HAS;
// True Solar Time (minutes)
SE.TST = (SE.timeFracDay * 1440 +
SE.EOT + 4 * SE.lon - 60 * SE.tzOffset);
// Finish TST calculation by calculating modolu(TST,360) as
// it's done in R or Excel. C's fmod doesn't work in the same
// way. The floor() function is from the math.h library.
SE.TST = SE.TST - (1440 * (floor(SE.TST/1440)) );
// Hour Angle (degrees)
if (SE.TST/4 < 0) {
SE.HA = SE.TST/4 + 180;
} else if (SE.TST/4 >= 0) {
SE.HA = SE.TST/4 - 180;
}
// Solar Zenith Angle (degrees)
SE.SZA = (acos(sin(SE.lat * DEG_TO_RAD) *
sin(SE.SDec* DEG_TO_RAD) +
cos(SE.lat * DEG_TO_RAD) *
cos(SE.SDec * DEG_TO_RAD) *
cos(SE.HA * DEG_TO_RAD))) * RAD_TO_DEG;
// Solar Elevation Angle (degrees above horizontal)
SE.SEA = 90 - SE.SZA;
// Approximate Atmospheric Refraction (degrees)
if (SE.SEA > 85) {
SE.AAR = 0;
} else if (SE.SEA > 5) {
SE.AAR = (58.1 / tan(SE.SEA * DEG_TO_RAD)) -
0.07 / (pow(tan(SE.SEA * DEG_TO_RAD),3)) +
0.000086 / (pow(tan(SE.SEA * DEG_TO_RAD),5));
} else if (SE.SEA > -0.575) {
SE.AAR = 1735 + SE.SEA * (-581.2 + SE.SEA *
(103.4 + SE.SEA * (-12.79 + SE.SEA * 0.711)));
} else {
SE.AAR = -20.772 / tan(SE.SEA * DEG_TO_RAD);
}
SE.AAR = SE.AAR / 3600.0;
// Solar Elevation Corrected for Atmospheric
// refraction (degrees)
SE.SEC_Corr = SE.SEA + SE.AAR;
// Solar Azimuth Angle (degrees clockwise from North)
if (SE.HA > 0) {
SE.SAA = (((acos((sin(SE.lat * DEG_TO_RAD) *
cos(SE.SZA * DEG_TO_RAD) -
sin(SE.SDec * DEG_TO_RAD)) /
(cos(SE.lat * DEG_TO_RAD) *
sin(SE.SZA * DEG_TO_RAD))) ) *
RAD_TO_DEG) + 180);
SE.SAA = SE.SAA - (360 * (floor(SE.SAA/360)));
} else {
SE.SAA = (540 - (acos((((sin(SE.lat * DEG_TO_RAD) *
cos(SE.SZA * DEG_TO_RAD))) -
sin(SE.SDec * DEG_TO_RAD)) /
(cos(SE.lat * DEG_TO_RAD) *
sin(SE.SZA * DEG_TO_RAD)))) *
RAD_TO_DEG);
SE.SAA = SE.SAA - (360 * (floor(SE.SAA/360)));
}
}