File Coverage

blib/lib/Astro/Coord/ECI/Sun.pm
Criterion Covered Total %
statement 182 227 80.1
branch 48 86 55.8
condition 10 18 55.5
subroutine 35 37 94.5
pod 11 11 100.0
total 286 379 75.4


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Astro::Coord::ECI::Sun - Compute the position of the Sun.
4              
5             =head1 SYNOPSIS
6              
7             use Astro::Coord::ECI;
8             use Astro::Coord::ECI::Sun;
9             use Astro::Coord::ECI::Utils qw{deg2rad};
10            
11             # 1600 Pennsylvania Ave, Washington DC USA
12             # latitude 38.899 N, longitude 77.038 W,
13             # altitude 16.68 meters above sea level
14             my $lat = deg2rad (38.899); # Radians
15             my $long = deg2rad (-77.038); # Radians
16             my $alt = 16.68 / 1000; # Kilometers
17             my $sun = Astro::Coord::ECI::Sun->new ();
18             my $sta = Astro::Coord::ECI->
19             universal (time ())->
20             geodetic ($lat, $long, $alt);
21             my ($time, $rise) = $sta->next_elevation ($sun);
22             print "Sun @{[$rise ? 'rise' : 'set']} is ",
23             scalar gmtime $time, " UT\n";
24              
25             Although this example computes the Sun rise or set in Washington D.C.
26             USA, the time is displayed in Universal Time. This is because I did not
27             want to complicate the example by adding machinery to convert the time
28             to the correct zone for Washington D.C. (which is UT - 5 except when
29             Summer Time is in effect, when it is UT - 4).
30              
31             =head1 DESCRIPTION
32              
33             This module implements the position of the Sun as a function of time, as
34             described in Jean Meeus' "Astronomical Algorithms," second edition. It
35             is a subclass of B, with the id, name, and diameter
36             attributes initialized appropriately, and the time_set() method
37             overridden to compute the position of the Sun at the given time.
38              
39             =head2 Methods
40              
41             The following methods should be considered public:
42              
43             =over
44              
45             =cut
46              
47             package Astro::Coord::ECI::Sun;
48              
49 16     16   987 use strict;
  16         33  
  16         649  
50 16     16   104 use warnings;
  16         30  
  16         1734  
51              
52             our $VERSION = '0.134';
53              
54 16     16   160 use base qw{Astro::Coord::ECI};
  16         29  
  16         2161  
55              
56 16     16   122 use Astro::Coord::ECI::Utils qw{ @CARP_NOT :mainstream };
  16         38  
  16         8312  
57 16     16   157 use Carp;
  16         30  
  16         1261  
58 16     16   98 use POSIX qw{ ceil };
  16         121  
  16         224  
59              
60 16     16   1515 use constant MEAN_MAGNITUDE => -26.8;
  16         32  
  16         8234  
61              
62             my %attrib = (
63             iterate_for_quarters => 1,
64             );
65              
66             my %static = (
67             id => 'Sun',
68             name => 'Sun',
69             diameter => 1392000,
70             iterate_for_quarters => undef,
71             );
72              
73             my $weaken = eval {
74             require Scalar::Util;
75             Scalar::Util->can('weaken');
76             };
77              
78             our $Singleton = $weaken;
79              
80             my %object; # By class
81              
82             =item $sun = Astro::Coord::ECI::Sun->new();
83              
84             This method instantiates an object to represent the coordinates of the
85             Sun. This is a subclass of L, with
86             the id and name attributes set to 'Sun', and the diameter attribute set
87             to 1392000 km per Jean Meeus' "Astronomical Algorithms", 2nd Edition,
88             Appendix I, page 407.
89              
90             Any arguments are passed to the set() method once the object has been
91             instantiated. Yes, you can override the "hard-wired" id, name, and so
92             forth in this way.
93              
94             If C<$Astro::Coord::ECI::Sun::Singleton> is true, you get a singleton
95             object; that is, only one object is instantiated and subsequent calls to
96             C just return that object. If higher-accuracy subclasses are ever
97             implemented, there will be one singleton for each class.
98              
99             The singleton logic only works if L exports
100             C. If it does not, the setting of
101             C<$Astro::Coord::ECI::Sun::Singleton> is silently ignored. The default
102             is true if L can be loaded and exports
103             C, and false otherwise.
104              
105             =cut
106              
107             sub new {
108 138     138 1 3468 my ($class, @args) = @_;
109 138 50       397 ref $class and $class = ref $class;
110 138 100 66     820 if ( $Singleton && $weaken && __classisa( $class, __PACKAGE__ ) ) {
      66        
111 136         225 my $self;
112 136 100       365 if ( $self = $object{$class} ) {
113 97 100       211 $self->set( @args ) if @args;
114 97         306 return $self;
115             } else {
116 39         296 $self = $object{$class} = $class->SUPER::new (%static, @args);
117 39         176 $weaken->( $object{$class} );
118 39         175 return $self;
119             }
120             } else {
121 2         12 return $class->SUPER::new (%static, @args);
122             }
123             }
124              
125             =item @almanac = $sun->almanac( $station, $start, $end );
126              
127             This method produces almanac data for the Sun for the given observing
128             station, between the given start and end times. The station is assumed
129             to be Earth-Fixed - that is, you can't do this for something in orbit.
130              
131             The C<$station> argument may be omitted if the C attribute has
132             been set. That is, this method can also be called as
133              
134             @almanac = $sun->almanac( $start, $end )
135              
136             The start time defaults to the current time setting of the $sun
137             object, and the end time defaults to a day after the start time.
138              
139             The almanac data consists of a list of list references. Each list
140             reference points to a list containing the following elements:
141              
142             [0] => time
143             [1] => event (string)
144             [2] => detail (integer)
145             [3] => description (string)
146              
147             The @almanac list is returned sorted by time.
148              
149             The following events, details, and descriptions are at least
150             potentially returned:
151              
152             horizon: 0 = Sunset, 1 = Sunrise;
153             transit: 0 = local midnight, 1 = local noon;
154             twilight: 0 = end twilight, 1 = begin twilight;
155             quarter: 0 = spring equinox, 1 = summer solstice,
156             2 = fall equinox, 3 = winter solstice.
157              
158             Twilight is calculated based on the current value of the twilight
159             attribute of the $sun object. This attribute is inherited from
160             L, and documented there.
161              
162             =cut
163              
164             sub __almanac_event_type_iterator {
165 2     2   7 my ( $self, $station ) = @_;
166              
167 2         4 my $inx = 0;
168              
169 2         9 my $horizon = $station->__get_almanac_horizon();
170              
171 2         18 my @events = (
172             [ $station, next_elevation => [ $self, $horizon, 1 ],
173             horizon => '__horizon_name' ],
174             [ $station, next_meridian => [ $self ],
175             transit => '__transit_name' ],
176             [ $station, next_elevation =>
177             [ $self, $self->get( 'twilight' ) + $horizon, 0 ],
178             twilight => '__twilight_name' ],
179             [ $self, next_quarter => [], quarter => '__quarter_name', ],
180             );
181              
182             return sub {
183             $inx < @events
184 10 100   10   42 and return @{ $events[$inx++] };
  8         63  
185 2         11 return;
186 2         16 };
187             }
188              
189 16     16   7398 use Astro::Coord::ECI::Mixin qw{ almanac };
  16         51  
  16         1450  
190              
191             =item @almanac = $sun->almanac_hash( $station, $start, $end );
192              
193             This convenience method wraps $sun->almanac(), but returns a list of
194             hash references, sort of like Astro::Coord::ECI::TLE->pass()
195             does. The hashes contain the following keys:
196              
197             {almanac} => {
198             {event} => the event type;
199             {detail} => the event detail (typically 0 or 1);
200             {description} => the event description;
201             }
202             {body} => the original object ($sun);
203             {station} => the observing station;
204             {time} => the time the quarter occurred.
205              
206             The {time}, {event}, {detail}, and {description} keys correspond to
207             elements 0 through 3 of the list returned by almanac().
208              
209             =cut
210              
211 16     16   139 use Astro::Coord::ECI::Mixin qw{ almanac_hash };
  16         36  
  16         15439  
212              
213             =item $coord2 = $coord->clone ();
214              
215             If singleton objects are enabled, this override of the superclass'
216             method simply returns the invocant. Otherwise it does a deep clone of an
217             object, producing a different but identical object.
218              
219             Prior to version 0.099_01 it always returned a clone. Yes,
220             this is a change in long-standing functionality, but a long-standing bug
221             is still a bug.
222              
223             =cut
224              
225             sub clone {
226 2     2 1 501 my ( $self ) = @_;
227 2 100 66     9 $Singleton
228             and $weaken
229             and return $self;
230 1         28 return $self->SUPER::clone();
231             }
232              
233             =item $elevation = $tle->correct_for_refraction( $elevation )
234              
235             This override of the superclass' method simply returns the elevation
236             passed to it. I have no algorithm for refraction at the surface of the
237             photosphere or anywhere else in the environs of the Sun, and explaining
238             why I make no correction at all seemed easier than explaining why I make
239             an incorrect correction.
240              
241             See the L C and
242             C documentation for whether this class'
243             C method is actually called by those methods.
244              
245             =cut
246              
247             sub correct_for_refraction {
248 0     0 1 0 my ( undef, $elevation ) = @_; # Invocant unused
249 0         0 return $elevation;
250             }
251              
252             =item $long = $sun->geometric_longitude ()
253              
254             This method returns the geometric longitude of the Sun in radians at
255             the last time set.
256              
257             =cut
258              
259             sub geometric_longitude {
260 1459     1459 1 2310 my $self = shift;
261 1459 50       3389 croak <{_sun_geometric_longitude};
262             Error - You must set the time of the Sun object before the geometric
263             longitude can be returned.
264             eod
265              
266 1459         3549 return $self->{_sun_geometric_longitude};
267             }
268              
269             =item $sun->get( ... )
270              
271             This method has been overridden to return the invocant as the C<'sun'>
272             attribute.
273              
274             =cut
275              
276             sub get {
277 67722     67722 1 171071 my ( $self, @args ) = @_;
278 67722         131868 my @rslt;
279 67722         130717 foreach my $name ( @args ) {
280             push @rslt, 'sun' eq $name ? $self :
281             $attrib{$name} ?
282 67722 0       300876 ref $self ? $self->{$name} : $static{$name} :
    50          
    50          
283             $self->SUPER::get( $name );
284             }
285 67722 100       227749 return wantarray ? @rslt : $rslt[0];
286             }
287              
288             =item ($point, $intens, $central) = $sun->magnitude ($theta, $omega);
289              
290             This method returns the magnitude of the Sun at a point $theta radians
291             from the center of its disk, given that the disk's angular radius
292             (B diameter) is $omega radians. The returned $point is the
293             magnitude at the given point (undef if $theta > $omega), $intens is the
294             ratio of the intensity at the given point to the central intensity (0
295             if $theta > $omega), and $central is the central magnitude.
296              
297             If this method is called in scalar context, it returns $point, the point
298             magnitude.
299              
300             If the $omega argument is omitted or undefined, it is calculated based
301             on the geocentric range to the Sun at the current time setting of the
302             object.
303              
304             If the $theta argument is omitted or undefined, the method returns
305             the average magnitude of the Sun, which is taken to be -26.8.
306              
307             The limb-darkening algorithm and the associated constants come from
308             L.
309              
310             For consistency's sake, an observing station can optionally be passed as
311             the first argument (i.e. before C<$theta>). This is currently ignored.
312              
313             =cut
314              
315             { # Begin local symbol block
316              
317             my $central_mag;
318             my @limb_darkening = (.3, .93, -.23);
319              
320             sub magnitude {
321 0     0 1 0 my ( $self, @args ) = @_;
322             # We want to accept a station as the second argument for
323             # consistency's sake, though we do not (at this point) use it.
324 0 0       0 embodies( $args[0], 'Astro::Coord::ECI' )
325             and shift @args;
326 0         0 my ( $theta, $omega ) = @args;
327 0 0       0 return MEAN_MAGNITUDE unless defined $theta;
328 0 0       0 unless (defined $omega) {
329 0         0 my @eci = $self->eci ();
330 0         0 $omega = $self->get ('diameter') / 2 /
331             sqrt (distsq (\@eci[0 .. 2], [0, 0, 0]));
332             }
333 0 0       0 unless (defined $central_mag) {
334 0         0 my $sum = 0;
335 0         0 my $quotient = 2;
336 0         0 foreach my $a (@limb_darkening) {
337 0         0 $sum += $a / $quotient++;
338             }
339 0         0 $central_mag = MEAN_MAGNITUDE - intensity_to_magnitude (2 * $sum);
340             }
341 0         0 my $intens = 0;
342 0         0 my $point;
343 0 0       0 if ($theta < $omega) {
344 0         0 my $costheta = cos ($theta);
345 0         0 my $cosomega = cos ($omega);
346 0         0 my $sinomega = sin ($omega);
347 0         0 my $cospsi = sqrt ($costheta * $costheta -
348             $cosomega * $cosomega) / $sinomega;
349 0         0 my $psiterm = 1;
350 0         0 foreach my $a (@limb_darkening) {
351 0         0 $intens += $a * $psiterm;
352 0         0 $psiterm *= $cospsi;
353             }
354 0         0 $point = $central_mag + intensity_to_magnitude ($intens);
355             }
356 0 0       0 return wantarray ? ($point, $intens, $central_mag) : $point;
357             }
358             } # End local symbol block.
359              
360             =item ( $time, $type ) = $sun->next_elevation_extreme_tod( $elev, $ipper );
361              
362             This variation on C returns the earliest (or latest)
363             time the Sun passes above (or below) the given elevation (in
364             radians) as seen from the location specified in its C
365             attribute.
366              
367             =cut
368              
369             sub __next_elevation_extreme_tod {
370 8     8   23 my ( $self, $sta, $angle, $upper ) = @_;
371              
372 8         25 my $time = $sta->universal();
373 8         18 my $before = $time + SECS_PER_TROPICAL_YEAR;
374              
375 8         19 my @prev_event;
376             my @prev_sgn;
377              
378             # FIXME this probably breaks inside the Arctic and Antarctic circles
379 8         37 $sta->universal( $time - SECSPERDAY );
380 8         25 for ( 0 .. 1 ) {
381 16         60 ( $time, my $rise ) = $sta->next_elevation( $self, $angle, $upper );
382 16         62 $prev_event[$rise] = $time;
383             }
384 8         32 for ( 0 .. 1 ) {
385 16         74 ( $time, my $rise ) = $sta->next_elevation( $self, $angle, $upper );
386 16         62 my $sgn = ( $prev_event[$rise] + SECSPERDAY ) <=> $time;
387 16 100       55 $sgn < 0
388             and $sgn = 0;
389 16         56 $prev_sgn[$rise] = $sgn;
390 16         51 $prev_event[$rise] = $time;
391             }
392              
393 8         54 while ( $time < $before ) {
394 1356         5909 ( $time, my $rise ) = $sta->next_elevation( $self, $angle, $upper );
395 1356         5978 my $sgn = ( $prev_event[$rise] + SECSPERDAY ) <=> $time;
396 1356 100       5436 $sgn < 0
397             and $sgn = 0;
398 1356 100       6062 if ( $sgn != $prev_sgn[$rise] ) {
399 8         26 my $event = $sgn * 2 + $rise;
400 8         21 $time = $prev_event[$rise];
401 8 50       106 return wantarray ? ( $time, $event ) : $time;
402             }
403 1348         2902 $prev_sgn[$rise] = $sgn;
404 1348         5466 $prev_event[$rise] = $time;
405             }
406              
407 0         0 return;
408             }
409              
410             =item ($time, $quarter, $desc) = $sun->next_quarter($want);
411              
412             This method calculates the time of the next equinox or solstice after
413             the current time setting of the $sun object. The return is the time,
414             which equinox or solstice it is as a number from 0 (March equinox) to 3
415             (December solstice), and a string describing the equinox or solstice. If
416             called in scalar context, you just get the time.
417              
418             If the C attribute is not set or set to a location on or north
419             of the Equator, the descriptor strings are
420              
421             0 - Spring equinox
422             1 - Summer solstice
423             2 - Fall equinox
424             3 - Winter solstice
425              
426             If the C attribute is set to a location south of the Equator,
427             the descriptor strings are
428              
429             0 - Fall equinox
430             1 - Winter solstice
431             2 - Spring equinox
432             3 - Summer solstice
433              
434             The optional $want argument says which equinox or solstice you want, as
435             a number from 0 through 3.
436              
437             As a side effect, the time of the $sun object ends up set to the
438             returned time.
439              
440             As of version 0.088_01, the algorithm given in Jean Meeus' "Astronomical
441             Algorithms", 2nd Edition, Chapter 27 ("Equinoxes and Solstices"), pages
442             278ff is used. This should be good for the range -1000 to 3000
443             Gregorian, and good to within a minute or so within the range 1951 to
444             2050 Gregorian, but the longitude of the Sun at the calculated time may
445             be as much as 0.01 degree off the exact time for the event.
446              
447             If you take the United States Naval Observatory's times (given to the
448             nearest minute) as the standard, the maximum deviation from that
449             standard in the range 1700 to 2100 is 226 seconds. I have no information
450             on this algorithm's accuracy outside that range. I.
451              
452             In version 0.088 and before, this calculation was done by successive
453             approximation based on the position of the Sun, and was good to about 15
454             minutes.
455              
456             If you want the old iterative version back, set attribute
457             C to a true value.
458              
459             =cut
460              
461 16     16   144 use constant NEXT_QUARTER_INCREMENT => 86400 * 85; # 85 days.
  16         33  
  16         6212  
462              
463             *__next_quarter_coordinate = __PACKAGE__->can(
464             'ecliptic_longitude' );
465              
466             # use Astro::Coord::ECI::Mixin qw{ next_quarter };
467              
468             sub next_quarter {
469 7     7 1 2088 my ( $self, $quarter ) = @_;
470             $self->{iterate_for_quarters}
471 7 50       29 and goto &Astro::Coord::ECI::Mixin::next_quarter;
472 7         29 my $time = $self->universal();
473 7         35 my $year = ( gmtime( $time ) )[5] + 1900;
474 7         17 my $season;
475 7 50       22 if ( defined $quarter ) {
476             # I can't think of an edge case that makes the first calculation
477             # give a quarter that is too late. Wish that made me feel better
478             # than it in fact does.
479 0         0 $season = $self->season( $year, $quarter );
480 0 0       0 $season < $time
481             and $season = $self->season( $year + 1, $quarter );
482 0         0 $self->universal( $season );
483             } else {
484 7         26 my ( undef, $lon ) = $self->ecliptic();
485             # The fudged-in 359 is because I am worried about the limited
486             # accuracy of the longitude calculation causing me to pick the
487             # wrong quarter. So I essentially back the Sun up by about a
488             # day. That may indeed put me a quarter too early, but I have to
489             # check for that anyway because of the accuracy (or lack
490             # thereof) of the calculated longitude.
491 7         29 $quarter = ceil( ( rad2deg( $lon ) + 359 ) / 90 ) % 4;
492 7         38 $season = $self->season( $year, $quarter );
493             # The above calculation gives the wrong $season between the
494             # December equinox and the end of the year. We fix it up here:
495 7 100       24 if ( $time - $season > SECSPERDAY * 180 ) {
496 1         4 $year++;
497 1         5 $season = $self->season( $year, $quarter );
498             }
499             # If we're a quarter too early, add one and repeat the
500             # calculation. We shouldn't have to do this more than once,
501             # since our maximum error even with the fudge factor is a day.
502 7 100       24 if ( $season < $time ) {
503 3         5 $quarter++;
504 3 50       13 if ( $quarter > 3 ) {
505 0         0 $quarter -= 4;
506 0         0 $year++;
507             }
508 3         981 $season = $self->season( $year, $quarter );
509             }
510             }
511 7         24 $season = ceil( $season ); # Make sure we're AFTER.
512 7         34 $self->universal( $season );
513 7 100       37 return wantarray ? ( $season, $quarter, $self->__quarter_name(
514             $quarter ) ) : $season;
515             }
516              
517             =item $hash_reference = $sun->next_quarter_hash($want);
518              
519             This convenience method wraps $sun->next_quarter(), but returns the
520             data in a hash reference, sort of like Astro::Coord::ECI::TLE->pass()
521             does. The hash contains the following keys:
522              
523             {body} => the original object ($sun);
524             {almanac} => {
525             {event} => 'quarter',
526             {detail} => the quarter number (0 through 3);
527             {description} => the quarter description;
528             }
529             {time} => the time the quarter occurred.
530              
531             The {time}, {detail}, and {description} keys correspond to elements 0
532             through 2 of the list returned by next_quarter().
533              
534             =cut
535              
536 16     16   120 use Astro::Coord::ECI::Mixin qw{ next_quarter_hash };
  16         30  
  16         16194  
537              
538             =item $period = $sun->period ()
539              
540             Although this method is attached to an object that represents the
541             Sun, what it actually returns is the sidereal period of the Earth,
542             per Appendix I (pg 408) of Jean Meeus' "Astronomical Algorithms,"
543             2nd edition.
544              
545             =cut
546              
547 1634     1634 1 6852 sub period {return 31558149.7632} # 365.256363 * 86400
548              
549             sub __horizon_name_tplt {
550 4     4   13 my ( $self ) = @_;
551 4 50       17 return $self->__object_is_self_named() ?
552             [ '%sset', '%srise' ] :
553             $self->SUPER::__horizon_name_tplt();
554             }
555              
556             sub __quarter_name {
557 2     2   7 my ( $self, $event, $tplt ) = @_;
558 2 50 33     16 $tplt ||= $self->__object_is_self_named() ?
559             [
560             'Spring equinox', 'Summer solstice',
561             'Fall equinox', 'Winter solstice',
562             ] : [
563             '%s Spring equinox', '%s Summer solstice',
564             '%s Fall equinox', '%s Winter solstice',
565             ];
566 2         218 my $station;
567             $station = $self->get( 'station' )
568             and ( $station->geodetic() )[0] < 0
569 2 50 66     14 and $event = ( $event + @{ $tplt } / 2 ) % @{ $tplt };
  0         0  
  0         0  
570 2         12 return $self->__event_name( $event, $tplt );
571             }
572              
573             sub __transit_name_tplt {
574 4     4   12 my ( $self ) = @_;
575 4 50       20 return $self->__object_is_self_named() ?
576             [ 'local midnight', 'local noon' ] :
577             [ '%s transits nadir', '%s transits meridian' ];
578             }
579              
580             sub __twilight_name {
581 4     4   16 my ( $self, $event, $tplt ) = @_;
582 4 50 33     32 $tplt ||= $self->__object_is_self_named() ?
583             [ 'end twilight', 'begin twilight' ] :
584             [ 'end %s twilight', 'begin %s twilight' ];
585 4         22 return $self->__event_name( $event, $tplt );
586             }
587              
588             =begin comment
589              
590             =item $time = $self->season( $year, $season );
591              
592             This method calculates the time of the given season of the given
593             Gregorian year. The $season is an integer from 0 to 3, with 0 being the
594             astronomical Spring equinox (first point of Aries), and so on.
595              
596             The algorithm comes from Jean Meeus' "Astronomical Algorithms", 2nd
597             Edition, Chapter 27 ("Equinoxes and Solstices), pages 278ff.
598              
599             THIS METHOD IS UNSUPPORTED. I understand the temptation to call it if
600             all you want are the seasons, but if possible I would like to be able to
601             remove it if its use in next_quarter() turns out to e a bad idea. I am
602             not unwilling to support it; if you want me to, please contact me.
603              
604             Because it is unsupported, its name may change without warning if
605             L becomes smart enough to
606             realize that the =begin/end comment markers mean that this method is not
607             documented after all.
608              
609             =end comment
610              
611             =cut
612              
613             sub season {
614 11     11 1 33 my ( $self, $year, $season ) = @_;
615 11 50       81 my ( $Y, $d ) = $year < 1000 ? (
616             $year / 1000,
617             [ # Meeus table 27 A
618             [ -0.00071, 0.00111, 0.06134, 365242.13740, 1721139.29189 ],
619             [ 0.00025, 0.00907, -0.05323, 365241.72562, 1721233.25401 ],
620             [ 0.00074, -0.00297, -0.11677, 365242.49558, 1721325.70455 ],
621             [ -0.00006, -0.00933, -0.00769, 365242.88257, 1721414.39987 ],
622             ]->[ $season ],
623             ) : (
624             ( $year - 2000 ) / 1000,
625             [ # Meeus table 27 B
626             [ -0.00057, -0.00411, 0.05169, 365242.37404, 2451623.80984 ],
627             [ -0.00030, 0.00888, 0.00325, 365241.62603, 2451716.56767 ],
628             [ 0.00078, 0.00337, -0.11575, 365242.01767, 2451810.21715 ],
629             [ 0.00032, -0.00823, -0.06223, 365242.74049, 2451900.05952 ],
630             ]->[ $season ],
631             );
632 11         61 my $JDE0 = ( ( ( $d->[0] * $Y + $d->[1] ) * $Y + $d->[2] ) * $Y +
633             $d->[3] ) * $Y + $d->[4];
634 11         25 my $T = ( $JDE0 - 2451545.0 ) / 36525;
635             $self->{debug}
636 11 50       104 and print "Debug - T = $T\n";
637 11         42 my $W = mod2pi( deg2rad( 35999.373 * $T - 2.47 ) );
638 11         37 my $delta_lambda = 1 + 0.0334 * cos( $W ) + 0.0007 * cos( 2 * $W );
639             $self->{debug}
640 11 50       35 and print "Debug - delta lambda = $delta_lambda\n";
641 11         21 my $S = 0;
642 11         153 foreach my $term ( # Meeus table 27 C
643             [ 485, 324.96, 1934.136 ],
644             [ 203, 337.23, 32964.467 ],
645             [ 199, 342.08, 20.186 ],
646             [ 182, 27.85, 445267.112 ],
647             [ 156, 73.14, 45036.886 ],
648             [ 136, 171.52, 22518.443 ],
649             [ 77, 222.54, 65928.934 ],
650             [ 74, 296.72, 3034.906 ],
651             [ 70, 243.58, 9037.513 ],
652             [ 58, 119.81, 33718.147 ],
653             [ 52, 297.17, 150.678 ],
654             [ 50, 21.02, 2281.226 ],
655             [ 45, 247.54, 29929.562 ],
656             [ 44, 325.15, 31555.956 ],
657             [ 29, 60.93, 4443.417 ],
658             [ 18, 155.12, 67555.328 ],
659             [ 17, 288.79, 4562.452 ],
660             [ 16, 198.04, 62894.029 ],
661             [ 14, 199.76, 31436.921 ],
662             [ 12, 95.39, 14577.848 ],
663             [ 12, 287.11, 31931.756 ],
664             [ 12, 320.81, 34777.259 ],
665             [ 9, 227.73, 1222.114 ],
666             [ 8, 15.45, 16859.074 ],
667             ) {
668 264         638 $S += $term->[0] * cos( mod2pi( deg2rad( $term->[1] + $term->[2]
669             * $T ) ) );
670             }
671             $self->{debug}
672 11 50       84 and print "Debug - S = $S\n";
673 11         32 my $JDE = 0.00001 * $S / $delta_lambda + $JDE0;
674             $self->{debug}
675 11 50       25 and print "Debug - JDE = $JDE\n";
676 11         24 my $time = ( $JDE - JD_OF_EPOCH ) * SECSPERDAY;
677             # Note that gmtime() in the following needs the parens because it
678             # might have come from Time::y2038, which appears to take more than
679             # one argument -- even though, as I read it, its prototype is (;$).
680             $self->{debug}
681 11 50       55 and print "Debug - dynamical date is ", scalar gmtime( $time ), "\n";
682 11         34 return $time - dynamical_delta( $time ); # Not quite right.
683             }
684              
685             =item $sun->set( ... )
686              
687             This method has been overridden to silently ignore any attempt to set
688             the C<'sun'> attribute.
689              
690             =cut
691              
692             sub set {
693 83     83 1 234 my ( $self, @args ) = @_;
694 83         257 while ( @args ) {
695 209         489 my ( $name, $val ) = splice @args, 0, 2;
696 209 50       582 if ( 'sun' eq $name ) {
    100          
697             } elsif ( $attrib{$name} ) {
698 41 50       124 if ( ref $self ) {
699 41         162 $self->{$name} = $val;
700             } else {
701 0         0 $static{$name} = $val;
702             }
703             } else {
704 168         438 $self->SUPER::set( $name, $val );
705             }
706             }
707 83         187 return $self;
708             }
709              
710             =item $sun->time_set ()
711              
712             This method sets coordinates of the object to the coordinates of the
713             Sun at the object's currently-set universal time. The velocity
714             components are arbitrarily set to 0. The 'equinox_dynamical' attribute
715             is set to the object's currently-set dynamical time.
716              
717             Although there's no reason this method can't be called directly, it
718             exists to take advantage of the hook in the B
719             object, to allow the position of the Sun to be computed when the
720             object's time is set.
721              
722             The algorithm comes from Jean Meeus' "Astronomical Algorithms", 2nd
723             Edition, Chapter 25, pages 163ff.
724              
725             =cut
726              
727             # The following constants are used in the time_set() method,
728             # because Meeus' equations are in degrees, I was too lazy
729             # to hand-convert them to radians, but I didn't want to
730             # penalize the user for the conversion every time.
731              
732 16     16   145 use constant SUN_C1_0 => deg2rad (1.914602);
  16         30  
  16         89  
733 16     16   103 use constant SUN_C1_1 => deg2rad (-0.004817);
  16         28  
  16         61  
734 16     16   94 use constant SUN_C1_2 => deg2rad (-0.000014);
  16         41  
  16         79  
735 16     16   92 use constant SUN_C2_0 => deg2rad (0.019993);
  16         167  
  16         65  
736 16     16   85 use constant SUN_C2_1 => deg2rad (0.000101);
  16         30  
  16         48  
737 16     16   79 use constant SUN_C3_0 => deg2rad (0.000289);
  16         65  
  16         73  
738 16     16   87 use constant SUN_LON_2000 => deg2rad (- 0.01397);
  16         29  
  16         80  
739              
740             sub time_set {
741 69578     69578 1 107806 my $self = shift;
742 69578         186245 my $time = $self->dynamical;
743              
744             # The following algorithm is from Meeus, chapter 25, page, 163 ff.
745              
746 69578         171689 my $T = jcent2000 ($time); # Meeus (25.1)
747 69578         206845 my $L0 = mod2pi(deg2rad((.0003032 * $T + 36000.76983) * $T # Meeus (25.2)
748             + 280.46646));
749 69578         161487 my $M = mod2pi(deg2rad(((-.0001537) * $T + 35999.05029) # Meeus (25.3)
750             * $T + 357.52911));
751 69578         135556 my $e = (-0.0000001267 * $T - 0.000042037) * $T + 0.016708634;# Meeus (25.4)
752 69578         237466 my $C = ((SUN_C1_2 * $T + SUN_C1_1) * $T + SUN_C1_0) * sin ($M)
753             + (SUN_C2_1 * $T + SUN_C2_0) * sin (2 * $M)
754             + SUN_C3_0 * sin (3 * $M);
755 69578         155077 my $O = $self->{_sun_geometric_longitude} = $L0 + $C;
756 69578         153353 my $omega = mod2pi (deg2rad (125.04 - 1934.136 * $T));
757 69578         165772 my $lambda = mod2pi ($O - deg2rad (0.00569 + 0.00478 * sin ($omega)));
758 69578         118862 my $nu = $M + $C;
759 69578         173925 my $R = (1.000_001_018 * (1 - $e * $e)) / (1 + $e * cos ($nu))
760             * AU;
761 69578 50       175807 $self->{debug} and print <
762 0         0 Debug sun - @{[ gm_strftime '%d-%b-%Y %H:%M:%S', $time ]}
763             T = $T
764 0         0 L0 = @{[rad2deg ($L0)]} degrees
765 0         0 M = @{[rad2deg ($M)]} degrees
766             e = $e
767 0         0 C = @{[rad2deg ($C)]} degrees
768 0         0 O = @{[rad2deg ($O)]} degrees
769 0         0 R = @{[$R / AU]} AU
770 0         0 omega = @{[rad2deg ($omega)]} degrees
771 0         0 lambda = @{[rad2deg ($lambda)]} degrees
772             eod
773              
774 69578         233237 $self->ecliptic (0, $lambda, $R);
775             ## $self->set (equinox_dynamical => $time);
776 69578         257567 $self->equinox_dynamical ($time);
777 69578         164277 return $self;
778             }
779              
780             # The Sun is normally positioned in inertial coordinates.
781              
782 41     41   166 sub __initial_inertial { return 1 }
783              
784             1;
785              
786             =back
787              
788             =head2 Attributes
789              
790             This class has the following public attributes. The description gives
791             the data type.
792              
793             =over
794              
795             =item iterate_for_quarters (Boolean)
796              
797             If this attribute is true, the C method uses the old
798             (pre-0.088_01) algorithm.
799              
800             If this attribute is false, the new algorithm is used.
801              
802             The default is C, i.e. false, because I believe the new algorithm
803             to be more accurate for reasonably-current times.
804              
805             This attribute is new with version 0.088_01.
806              
807             =back
808              
809             =head2 Historical Calculations
810              
811             This class was written for the purpose of calculating whether the Sun
812             was shining on a given point on the Earth (or in space) at a given time
813             in or reasonably close to the present. I can not say how accurate it is
814             at times far from the present. Those interested in such calculations may
815             want to consider using
816             L
817             instead.
818              
819             Historical calculations will need to use a 64-bit Perl, or at least one
820             with 64-bit integers, to represent times more than about 38 years from
821             the system epoch.
822              
823             In addition, you will need to be careful how you do input and output
824             conversions.
825              
826             L is a core module and an obvious choice, but
827             it only does Gregorian dates. Historical calculations prior to 1587
828             typically use the Julian calendar. For this you will need to go to
829             something like L,
830             or L which
831             does either Julian or Gregorian as needed.
832              
833             Should you decide to use L, you should be aware
834             that its C and C interpret the year argument
835             strangely: years in the range C<0 - 999> inclusive are not interpreted
836             as Gregorian years, though years outside that range are so interpreted.
837             Beginning with version 1.27 (released July 9 2018), additional
838             subroutines C and C were added.
839             These always interpret the year argument as a Gregorian year.
840              
841             =head1 ACKNOWLEDGMENTS
842              
843             The author wishes to acknowledge Jean Meeus, whose book "Astronomical
844             Algorithms" (second edition) formed the basis for this module.
845              
846             =head1 SEE ALSO
847              
848             The L
849             documentation for a discussion of how the pieces/parts of this
850             distribution go together and how to use them.
851              
852             L by Brett Hamilton, which contains a
853             function-based module to compute the current phase, distance and angular
854             diameter of the Moon, as well as the angular diameter and distance of
855             the Sun.
856              
857             L by Ron Hill and Jean Forget, which
858             contains a function-based module to compute sunrise and sunset for the
859             given day and location.
860              
861             L by Rob Fugina, which provides
862             functionality similar to B.
863              
864             =head1 SUPPORT
865              
866             Support is by the author. Please file bug reports at
867             L,
868             L, or in
869             electronic mail to the author.
870              
871             =head1 AUTHOR
872              
873             Thomas R. Wyant, III (F)
874              
875             =head1 COPYRIGHT AND LICENSE
876              
877             Copyright (C) 2005-2025 by Thomas R. Wyant, III
878              
879             This program is free software; you can redistribute it and/or modify it
880             under the same terms as Perl 5.10.0. For more details, see the full text
881             of the licenses in the directory LICENSES.
882              
883             This program is distributed in the hope that it will be useful, but
884             without any warranty; without even the implied warranty of
885             merchantability or fitness for a particular purpose.
886              
887             =cut
888              
889             # ex: set textwidth=72 :