@@ -45,6 +45,7 @@ or shipping CoordinateSharp with a closed source product.
45
45
using System ;
46
46
using System . Collections . Generic ;
47
47
using System . Diagnostics ;
48
+ using System . Linq ;
48
49
using CoordinateSharp . Formatters ;
49
50
namespace CoordinateSharp
50
51
{
@@ -57,81 +58,88 @@ public static void CalculateSunTime(double lat, double lng, DateTime date, Celes
57
58
if ( el . Extensions . Solar_Cycle )
58
59
{
59
60
DateTime actualDate = new DateTime ( date . Year , date . Month , date . Day , 0 , 0 , 0 , DateTimeKind . Utc ) ;
61
+
60
62
63
+
61
64
////Sun Time Calculations
62
65
//Get solar coordinate info and feed
63
66
//Get Julian
64
67
double lw = rad * - lng ;
65
68
double phi = rad * lat ;
66
69
67
70
//Rise Set
68
- DateTime ? [ ] evDate = Get_Event_Time ( lw , phi , - .8333 , actualDate , offset , true ) ; //ADDED OFFSET TO ALL Get_Event_Time calls.
71
+ DateTime ? [ ] evDate = Get_Event_Time ( lat , lng , lw , phi , - .8333 , actualDate , offset , true ) ; //ADDED OFFSET TO ALL Get_Event_Time calls.
69
72
70
73
c . sunRise = evDate [ 0 ] ;
71
74
c . sunSet = evDate [ 1 ] ;
72
75
c . solarNoon = evDate [ 2 ] ;
73
-
74
- c . sunCondition = CelestialStatus . RiseAndSet ;
75
76
76
77
//Get Solar Coordinate
77
78
var celC = Get_Solar_Coordinates ( date , - offset ) ;
78
79
c . solarCoordinates = celC ;
79
80
//Azimuth and Altitude
80
81
CalculateSunAngle ( date . AddHours ( - offset ) , lng , lat , c , celC ) ; //SUBTRACT OFFSET TO CALC IN Z TIME AND ADJUST SUN ANGLE DURING LOCAL CALCULATIONS.
82
+
83
+ //SET SUN CONDITION
84
+ c . sunCondition = CelestialStatus . RiseAndSet ;
85
+
81
86
// neither sunrise nor sunset
82
87
if ( ( ! c . SunRise . HasValue ) && ( ! c . SunSet . HasValue ) )
83
88
{
84
- if ( c . SunAltitude < 0 )
89
+ //Check sun altitude at apex (solar noon) to ensure accurate logic.
90
+ //Previous logic determined of user time passed (c.sunAltitude), but due to Meeus limitation in 15.1, it could cause a misreport.
91
+ //https://github.com/Tronald/CoordinateSharp/issues/167
92
+
93
+ var safety = new Celestial ( ) ;
94
+ CalculateSunAngle ( c . solarNoon . Value , lng , lat , safety , celC ) ;
95
+
96
+ if ( safety . sunAltitude <= - .8333 )
85
97
{
86
98
c . sunCondition = CelestialStatus . DownAllDay ;
87
-
88
99
}
89
100
else
90
101
{
91
- c . sunCondition = CelestialStatus . UpAllDay ;
102
+ c . sunCondition = CelestialStatus . UpAllDay ;
92
103
}
93
104
}
94
105
// sunrise or sunset
95
106
else
96
107
{
97
108
if ( ! c . SunRise . HasValue )
98
109
{
99
- // No sunrise this date
100
110
c . sunCondition = CelestialStatus . NoRise ;
101
-
102
111
}
103
112
else if ( ! c . SunSet . HasValue )
104
113
{
105
- // No sunset this date
106
114
c . sunCondition = CelestialStatus . NoSet ;
107
- }
115
+ }
108
116
}
109
-
117
+
110
118
//Sat day and night time spans within 24 hours period
111
119
Set_DayNightSpan ( c ) ;
112
120
113
121
//Additional Times
114
122
c . additionalSolarTimes = new AdditionalSolarTimes ( ) ;
115
123
//Dusk and Dawn
116
124
//Civil
117
- evDate = Get_Event_Time ( lw , phi , - 6 , actualDate , offset , false ) ;
125
+ evDate = Get_Event_Time ( lat , lng , lw , phi , - 6 , actualDate , offset , false ) ;
118
126
c . AdditionalSolarTimes . civilDawn = evDate [ 0 ] ;
119
127
c . AdditionalSolarTimes . civilDusk = evDate [ 1 ] ;
120
128
121
129
122
130
//Nautical
123
- evDate = Get_Event_Time ( lw , phi , - 12 , actualDate , offset , false ) ;
131
+ evDate = Get_Event_Time ( lat , lng , lw , phi , - 12 , actualDate , offset , false ) ;
124
132
c . AdditionalSolarTimes . nauticalDawn = evDate [ 0 ] ;
125
133
c . AdditionalSolarTimes . nauticalDusk = evDate [ 1 ] ;
126
134
127
135
//Astronomical
128
- evDate = Get_Event_Time ( lw , phi , - 18 , actualDate , offset , false ) ;
136
+ evDate = Get_Event_Time ( lat , lng , lw , phi , - 18 , actualDate , offset , false ) ;
129
137
130
138
c . AdditionalSolarTimes . astronomicalDawn = evDate [ 0 ] ;
131
139
c . AdditionalSolarTimes . astronomicalDusk = evDate [ 1 ] ;
132
140
133
141
//BottomDisc
134
- evDate = Get_Event_Time ( lw , phi , - .2998 , actualDate , offset , false ) ;
142
+ evDate = Get_Event_Time ( lat , lng , lw , phi , - .2998 , actualDate , offset , false ) ;
135
143
c . AdditionalSolarTimes . sunriseBottomDisc = evDate [ 0 ] ;
136
144
c . AdditionalSolarTimes . sunsetBottomDisc = evDate [ 1 ] ;
137
145
@@ -141,17 +149,30 @@ public static void CalculateSunTime(double lat, double lng, DateTime date, Celes
141
149
if ( el . Extensions . Solstice_Equinox ) { Calculate_Solstices_Equinoxes ( date , c , offset ) ; }
142
150
if ( el . Extensions . Solar_Eclipse ) { CalculateSolarEclipse ( date , lat , lng , c ) ; }
143
151
}
152
+
153
+ private static double GetAltitude ( DateTime date , double offset , double lat , double lng )
154
+ {
155
+ var safety = new Celestial ( ) ;
156
+
157
+ var celC = Get_Solar_Coordinates ( date , offset ) ;
158
+ CalculateSunAngle ( date . AddHours ( - offset ) , lng , lat , safety , celC ) ;
159
+
160
+ return safety . sunAltitude ;
161
+ }
162
+
144
163
/// <summary>
145
164
/// Gets time of event based on specified degree below specified altitude
146
165
/// </summary>
166
+ /// <param name="lat">Observer Latitude in degrees</param>
167
+ /// <param name="lng">Observer Longitude in degrees</param>
147
168
/// <param name="lw">Observer Longitude in radians</param>
148
169
/// <param name="phi">Observer Latitude in radians</param>
149
170
/// <param name="h">Angle in Degrees</param>
150
171
/// <param name="date">Date of Event</param>
151
172
/// <param name="offset">Offset hours</param>
152
173
/// <param name="calculateNoon">Should solar noon iterate and return value</param>
153
174
/// <returns>DateTime?[]{rise, set}</returns>
154
- internal static DateTime ? [ ] Get_Event_Time ( double lw , double phi , double h , DateTime date , double offset , bool calculateNoon )
175
+ internal static DateTime ? [ ] Get_Event_Time ( double lat , double lng , double lw , double phi , double h , DateTime date , double offset , bool calculateNoon )
155
176
{
156
177
double julianOffset = offset * .04166667 ;
157
178
@@ -167,16 +188,10 @@ public static void CalculateSunTime(double lat, double lng, DateTime date, Celes
167
188
double d = JulianConversions . GetJulian ( date . AddDays ( x - 2 ) ) - j2000 + .5 ; //LESS PRECISE JULIAN NEEDED
168
189
double n = julianCycle ( d , lw ) ;
169
190
170
- //var celC = Get_Solar_Coordinates(date); //Change and Test locs!
171
-
172
- double ds = approxTransit ( 0 , lw , n ) ;
173
-
174
- //M = celC.MeanAnomaly.ToRadians();
191
+ double ds = approxTransit ( 0 , lw , n ) ;
175
192
176
193
double M = solarMeanAnomaly ( ds ) ;
177
194
178
-
179
-
180
195
double L = eclipticLongitude ( M ) ;
181
196
182
197
double dec = declination ( L , 0 ) ;
@@ -200,16 +215,144 @@ public static void CalculateSunTime(double lat, double lng, DateTime date, Celes
200
215
}
201
216
}
202
217
218
+ DateTime ? tNoon = null ;
219
+ if ( calculateNoon )
220
+ {
221
+ tNoon = Get_Event_Target_Date ( solarNoons , date ) ;
222
+ solarEventsCorrection ( lat , lng , date , offset , rises , sets , tNoon ) ;
223
+ }
224
+
203
225
//Compare and send
204
226
DateTime ? tRise = Get_Event_Target_Date ( rises , date ) ;
205
227
DateTime ? tSet = Get_Event_Target_Date ( sets , date ) ;
206
228
207
- DateTime ? tNoon = null ;
208
- if ( calculateNoon ) { tNoon = Get_Event_Target_Date ( solarNoons , date ) ; }
229
+
209
230
210
231
return new DateTime ? [ ] { tRise , tSet , tNoon } ;
211
232
}
212
233
234
+ /// <summary>
235
+ /// Corrects near horizon miss calculations that may occur on rare occasion due to Meeus 15.1 limitation.
236
+ /// May cause slight efficiency decrease during transition from rise/set to up or down all day.
237
+ /// Occurs rare so delay should be negligent in most instances.
238
+ /// </summary>
239
+ /// <param name="lat">latitude</param>
240
+ /// <param name="lng">longitude</param>
241
+ /// <param name="target">target date</param>
242
+ /// <param name="offset">timezone offset</param>
243
+ /// <param name="rises">sun rises</param>
244
+ /// <param name="sets">sun sets</param>
245
+ ///<param name="solarNoon">solar noon time</param>
246
+ private static void solarEventsCorrection ( double lat , double lng , DateTime target , double offset , DateTime ? [ ] rises , DateTime ? [ ] sets , DateTime ? solarNoon )
247
+ {
248
+ if ( ! solarNoon . HasValue ) { return ; } //can't check
249
+ if ( ! datesContainNull ( rises ) && ! datesContainNull ( sets ) ) { return ; }
250
+
251
+ DateTime ? tRise = Get_Event_Target_Date ( rises , target ) ;
252
+ DateTime ? tSet = Get_Event_Target_Date ( sets , target ) ;
253
+
254
+ int ? setIndex = null ;
255
+ int ? riseIndex = null ;
256
+
257
+ //Determine if this can be passed through a top level call in future for efficiency. This is a duplicate call, but original may not return correct value
258
+ var solarAltitude = GetAltitude ( solarNoon . Value , offset , lat , lng ) ;
259
+
260
+ if ( tRise . HasValue && tSet . HasValue )
261
+ {
262
+ setIndex = targetEventIndex ( sets , tSet . Value ) ;
263
+ riseIndex = targetEventIndex ( rises , tRise . Value ) ;
264
+
265
+ if ( setIndex == null || riseIndex == null ) { return ; }
266
+
267
+ //SEE IF NEXT DAY HAS DUPLICATE NULLS. SIGNALS ALL DAY EVENT OCCURING
268
+ if ( setIndex != 4 && riseIndex != 4 && ! sets [ setIndex . Value + 1 ] . HasValue && ! rises [ riseIndex . Value + 1 ] . HasValue )
269
+ {
270
+
271
+
272
+ //IF RISE AFTER SET AND DOWN ALL DAY ZERO OUT RISE
273
+ if ( solarAltitude <= - 0.8333 && sets [ setIndex . Value ] . Value < rises [ riseIndex . Value ] . Value )
274
+ {
275
+ rises [ riseIndex . Value ] = null ;
276
+ }
277
+ ////IF SET IS AFTER RISE AND UP ALL DAY, ZERO OUT SET
278
+ else if ( solarAltitude > - 0.8333 && sets [ setIndex . Value ] . Value > rises [ riseIndex . Value ] . Value )
279
+ {
280
+ sets [ setIndex . Value ] = null ;
281
+ }
282
+
283
+
284
+ }
285
+ //SEE IF PREVIOUS DAY HAS DUPLICATE NULLS
286
+ else if ( setIndex != 0 && riseIndex != 0 && ! sets [ setIndex . Value - 1 ] . HasValue && ! rises [ riseIndex . Value - 1 ] . HasValue )
287
+ {
288
+
289
+ ////IF RISE IS BEFORE SET AND DOWN ALL DAY ZERO OUT RISE
290
+ if ( solarAltitude <= - 0.8333 && sets [ setIndex . Value ] . Value > rises [ riseIndex . Value ] . Value )
291
+ {
292
+ rises [ riseIndex . Value ] = null ;
293
+ }
294
+ ////IF SET IS BEFORE RISE AND UP ALL DAY, ZERO OUT SET
295
+ else if ( solarAltitude > - 0.8333 && sets [ setIndex . Value ] . Value < rises [ riseIndex . Value ] . Value )
296
+ {
297
+ sets [ setIndex . Value ] = null ;
298
+ }
299
+ }
300
+ }
301
+ else if ( tRise . HasValue && ! tSet . HasValue )
302
+ {
303
+ riseIndex = targetEventIndex ( rises , tRise . Value ) ;
304
+ if ( riseIndex == null ) { return ; }
305
+
306
+ //IF NEXT DAY IS UP DOWN ALL DAY SCRATCH THIS TIME BECAUSE THE SUN HAS TO SET FIRST
307
+ if ( riseIndex != 4 && ! rises [ riseIndex . Value + 1 ] . HasValue && solarAltitude <= - 0.8333 )
308
+ {
309
+ rises [ riseIndex . Value ] = null ;
310
+ }
311
+ //IF PREVIOUS DAY IS UP ALL DAY SCRATCH AS THIS HAS TO SET FIRST
312
+ else if ( riseIndex != 0 && ! rises [ riseIndex . Value - 1 ] . HasValue && solarAltitude > - 0.8333 )
313
+ {
314
+ rises [ riseIndex . Value ] = null ;
315
+ }
316
+
317
+
318
+ }
319
+ else if ( ! tRise . HasValue && tSet . HasValue )
320
+ {
321
+ setIndex = targetEventIndex ( sets , tSet . Value ) ;
322
+ if ( setIndex == null ) { return ; }
323
+
324
+ //IF NEXT DAY IS UP ALL DAY SCRATCH THIS TIME BECAUSE THE SUN HAS TO RISE FIRST
325
+ if ( setIndex != 4 && ! sets [ setIndex . Value + 1 ] . HasValue && solarAltitude > - 0.8333 )
326
+ {
327
+ sets [ setIndex . Value ] = null ;
328
+ }
329
+ //IF PREVIOUS DAY IS DOWN ALL DAY SCRATCH AS THIS HAS TO RISE FIRST
330
+ else if ( setIndex != 0 && ! sets [ setIndex . Value - 1 ] . HasValue && solarAltitude <= - 0.8333 )
331
+ {
332
+ sets [ setIndex . Value ] = null ;
333
+ }
334
+ }
335
+
336
+ }
337
+ private static bool datesContainNull ( DateTime ? [ ] dates )
338
+ {
339
+ foreach ( var d in dates )
340
+ {
341
+ if ( ! d . HasValue ) { return true ; }
342
+ }
343
+ return false ;
344
+ }
345
+
346
+ private static int ? targetEventIndex ( DateTime ? [ ] dates , DateTime target )
347
+ {
348
+ int x = 0 ;
349
+ foreach ( var d in dates )
350
+ {
351
+ if ( d . HasValue && d . Value == target ) { return x ; }
352
+ x ++ ;
353
+ }
354
+ return null ;
355
+ }
213
356
/// <summary>
214
357
/// Iterates stored events and extracts the one that occurs on the target date.
215
358
/// </summary>
@@ -527,7 +670,7 @@ private static double approxTransit(double Ht, double lw, double n)
527
670
private static double GetTime ( double h , double lw , double phi , double dec , double n , double M , double L )
528
671
{
529
672
double approxTime = hourAngle ( h , phi , dec ) ; //Ch15 Formula 15.1
530
-
673
+
531
674
double a = approxTransit ( approxTime , lw , n ) ;
532
675
double st = solarTransitJ ( a , M , L ) ;
533
676
0 commit comments