File Coverage

blib/lib/Astro/Coord/ECI/Utils.pm
Criterion Covered Total %
statement 340 395 86.0
branch 95 162 58.6
condition 28 57 49.1
subroutine 72 76 94.7
pod 35 35 100.0
total 570 725 78.6


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Astro::Coord::ECI::Utils - Utility routines for astronomical calculations
4              
5             =head1 SYNOPSIS
6              
7             use Astro::Coord::ECI::Utils qw{ :all };
8             my $now = time ();
9             print "The current Julian day is ", julianday ($now);
10              
11             =head1 DESCRIPTION
12              
13             This module was written to provide a home for all the constants and
14             utility subroutines used by B and its descendants.
15             What ended up here was anything that was essentially a subroutine, not
16             a method.
17              
18             Because figuring out how to convert to and from Perl time bids fair to
19             become complicated, this module is also responsible for figuring out how
20             that is done, and exporting whatever is needful to export. See C<:time>
21             below for the gory details.
22              
23             This package exports nothing by default. But all the constants,
24             variables, and subroutines documented below are exportable, and the
25             following export tags may be used:
26              
27             =over
28              
29             =item :all
30              
31             This imports everything exportable into your name space.
32              
33             =item :greg_time
34              
35             This imports all time routines except the discouraged routines
36             C and C.
37              
38             =item :mainstream
39              
40             This imports everything not deprecated into your name space.
41              
42             =item :params
43              
44             This imports the parameter validation routines C<__classisa> and
45             C<__instance>.
46              
47             =item :ref
48              
49             This imports all the C<*_REF> constants.
50              
51             =item :time
52              
53             This imports the time routines into your name space. If
54             L is available, it will be loaded, and both
55             this tag and C<:all> will import C, C, C,
56             C, C, and C into your name
57             space. Otherwise, C will be loaded, and both
58             this tag and C<:all> will import C, C,
59             C, and C into your name space.
60              
61             =item :vector
62              
63             This imports the vector arithmetic routines. It includes anything whose
64             name begins with C<'vector_'>.
65              
66             =back
67              
68             Under Perl 5.6 you may find that, if you use any of the above tags, you
69             need to specify them first in your import list.
70              
71             =head2 The following constants are exportable:
72              
73             AU = number of kilometers in an astronomical unit
74             JD_OF_EPOCH = the Julian Day of Perl epoch 0
75             LIGHTYEAR = number of kilometers in a light year
76             PARSEC = number of kilometers in a parsec
77             PERL2000 = January 1 2000, 12 noon universal, in Perl time
78             PI = the circle ratio, computed as atan2 (0, -1)
79             PIOVER2 = half the circle ratio
80             SECSPERDAY = the number of seconds in a day
81             SECS_PER_SIDERIAL_DAY = seconds in a siderial day
82             SPEED_OF_LIGHT = speed of light in kilometers per second
83             TWOPI = twice the circle ratio
84              
85             ARRAY_REF = 'ARRAY'
86             CODE_REF = 'CODE'
87             HASH_REF = 'HASH'
88             SCALAR_REF = 'SCALAR'
89              
90             =head2 The following global variables are exportable:
91              
92             =head3 $DATETIMEFORMAT
93              
94             This variable represents the POSIX::strftime format used to convert
95             times to strings. The default value is '%a %b %d %Y %H:%M:%S' to be
96             consistent with the behavior of gmtime (or, to be precise, the
97             behavior of ctime as documented on my system).
98              
99             =head3 $JD_GREGORIAN
100              
101             This variable represents the Julian Day of the switch from Julian to
102             Gregorian calendars. This is used by date2jd(), jd2date(), and the
103             routines which depend on them, for deciding whether the date is to be
104             interpreted as in the Julian or Gregorian calendar. Its initial setting
105             is 2299160.5, which represents midnight October 15 1582 in the Gregorian
106             calendar, which is the date that calendar was first adopted. This is
107             slightly different than the value of 2299161 (noon of the same day) used
108             by Jean Meeus.
109              
110             If you are interested in historical calculations, you may wish to reset
111             this appropriately. If you use date2jd to calculate the new value, be
112             aware of the effect the current setting of $JD_GREGORIAN has on the
113             interpretation of the date you give.
114              
115             =head2 In addition, the following subroutines are exportable:
116              
117             =over 4
118              
119             =cut
120              
121             package Astro::Coord::ECI::Utils;
122              
123 18     18   69994 use strict;
  18         42  
  18         534  
124 18     18   104 use warnings;
  18         34  
  18         925  
125              
126             our $VERSION = '0.130';
127             our @ISA = qw{Exporter};
128              
129 18     18   103 use Carp;
  18         39  
  18         1458  
130             ## use Config;
131             ## use Data::Dumper;
132 18     18   9042 use POSIX qw{ floor modf strftime };
  18         129515  
  18         101  
133 18     18   25189 use Scalar::Util qw{ blessed };
  18         40  
  18         10079  
134              
135             my @greg_time_routines;
136              
137             BEGIN {
138              
139             # NOTE WELL
140             #
141             # The logic here should be consistent with the optional-module text
142             # emitted by inc/Astro/Coord/ECI/Recommend.pm.
143             #
144              
145             eval {
146 18         2933 require Time::y2038;
147 0         0 Time::y2038->import( qw{ gmtime localtime } );
148              
149             # sub time_gm
150             *time_gm = sub {
151 0         0 my @date = @_;
152 0         0 $date[5] = _year_adjust_y2038( $date[5] );
153 0         0 return Time::y2038::timegm( @date );
154 0         0 };
155             # greg_time_local
156             *greg_time_gm = sub {
157 0         0 my @date = @_;
158 0         0 $date[5] -= 1900;
159 0         0 return Time::y2038::timegm( @date );
160 0         0 };
161              
162             # sub time_local
163             *time_local = sub {
164 0         0 my @date = @_;
165 0         0 $date[5] = _year_adjust_y2038( $date[5] );
166 0         0 return Time::y2038::timelocal( @date );
167 0         0 };
168             # sub greg_time_local
169             *greg_time_local = sub {
170 0         0 my @date = @_;
171 0         0 $date[5] -= 1900;
172 0         0 return Time::y2038::timelocal( @date );
173 0         0 };
174              
175 0         0 @greg_time_routines = qw{
176             gmtime localtime
177             greg_time_gm greg_time_local
178             __tle_year_to_Gregorian_year
179             };
180              
181 0         0 1;
182 18 50   18   68 } or do {
183 18         7386 require Time::Local;
184              
185             # sub time_gm
186 18         36716 *time_gm = Time::Local->can( 'timegm' );
187             # sub greg_time_gm
188             *greg_time_gm = Time::Local->can( 'timegm_modern' ) || sub {
189 260     260   70742 my @date = @_;
190 260         789 $date[5] = _year_adjust_greg( $date[5] );
191 260         889 return Time::Local::timegm( @date );
192 18   50     317 };
193              
194             # sub time_local
195 18         81 *time_local = Time::Local->can( 'timelocal' );
196             # sub greg_time_local
197             *greg_time_local = Time::Local->can( 'timelocal_modern' ) || sub {
198 46     46   12689 my @date = @_;
199 46         111 $date[5] = _year_adjust_greg( $date[5] );
200 46         121 return Time::Local::timelocal( @date );
201 18   50     171 };
202              
203 18         1309 @greg_time_routines = qw{
204             greg_time_gm greg_time_local
205             __tle_year_to_Gregorian_year
206             };
207              
208             };
209             }
210              
211             # This subroutine is used to convert year numbers to Perl years in
212             # accordance with the documentation in the 5.24.0 version of
213             # Time::Local. It is intended to be called by the Time::y2038 code,
214             # which expects Perl years.
215              
216             {
217             # The following code is lifted verbatim from Time::Local 1.25.
218             # Because that code bases the window used for expanding two-digit
219             # years on the local year as of the time the module was loaded, I do
220             # too.
221              
222             my $ThisYear = ( localtime() )[5];
223             my $Breakpoint = ( $ThisYear + 50 ) % 100;
224             my $NextCentury = $ThisYear - $ThisYear % 100;
225             $NextCentury += 100 if $Breakpoint < 50;
226             my $Century = $NextCentury - 100;
227              
228             # The above code is lifted verbatim from Time::Local 1.25.
229              
230 18         8323 use constant NOT_GREG =>
231 18     18   141 '%d not interpreted as Gregorian year by Time::Local::timegm';
  18         44  
232              
233             # Adujst the year so that the Time::y2038 implementation of
234             # time_gm() and time_local() mirrors the Time::Local timegm() and
235             # timelocal() behavior. Kinda sorta.
236             sub _year_adjust_y2038 {
237 0     0   0 my ( $year ) = @_;
238              
239 0 0       0 $year < 0
240             and return $year;
241              
242 0 0       0 $year >= 1000
243             and return $year - 1900;
244              
245             # The following line of code is lifted verbatim from Time::Local
246             # 1.25.
247 0 0       0 $year += ( $year > $Breakpoint ) ? $Century : $NextCentury;
248              
249 0         0 return $year;
250             }
251             }
252              
253             # Adjust a Gregorian year so that Time::Local timegm() and timelocal()
254             # return epochs in that year.
255             sub _year_adjust_greg {
256 306     306   621 my ( $year ) = @_;
257 306 50       929 return $year >= 1000 ? $year : $year - 1900;
258             }
259              
260             our @CARP_NOT = qw{
261             Astro::Coord::ECI
262             Astro::Coord::ECI::Mixin
263             Astro::Coord::ECI::Moon
264             Astro::Coord::ECI::Star
265             Astro::Coord::ECI::Sun
266             Astro::Coord::ECI::TLE
267             Astro::Coord::ECI::TLE::Set
268             Astro::Coord::ECI::Utils
269             };
270              
271             our @EXPORT;
272             my @all_external = ( qw{
273             AU $DATETIMEFORMAT $JD_GREGORIAN JD_OF_EPOCH LIGHTYEAR PARSEC
274             PERL2000 PI PIOVER2 SECSPERDAY SECS_PER_SIDERIAL_DAY
275             SPEED_OF_LIGHT TWOPI
276             ARRAY_REF CODE_REF HASH_REF SCALAR_REF
277             acos add_magnitudes asin
278             atmospheric_extinction date2epoch date2jd
279             decode_space_track_json_time deg2rad distsq dynamical_delta
280             embodies epoch2datetime find_first_true
281             fold_case __format_epoch_time_usec
282             format_space_track_json_time intensity_to_magnitude
283             jcent2000 jd2date jd2datetime jday2000 julianday
284             keplers_equation load_module looks_like_number max min mod2pi
285             omega
286             position_angle
287             rad2deg rad2dms rad2hms tan theta0 thetag vector_cross_product
288             vector_dot_product vector_magnitude vector_unitize __classisa
289             __default_station __instance __subroutine_deprecation
290             __sprintf
291             },
292             qw{ time_gm time_local }, @greg_time_routines );
293             our @EXPORT_OK = (
294             qw{ @CARP_NOT }, # Package-private, undocumented
295             @all_external,
296             );
297              
298             my %deprecated_export = map { $_ => 1 } qw{
299             };
300              
301             our %EXPORT_TAGS = (
302             all => \@all_external,
303             greg_time => \@greg_time_routines,
304             mainstream => [ grep { ! $deprecated_export{$_} } @all_external ],
305             params => [ qw{ __classisa __instance } ],
306             ref => [ grep { m/ [[:upper:]]+ _REF \z /smx } @all_external ],
307             time => [ qw{ time_gm time_local }, @greg_time_routines ],
308             vector => [ grep { m/ \A vector_ /smx } @all_external ],
309             );
310              
311 18     18   150 use constant AU => 149597870; # 1 astronomical unit, per
  18         37  
  18         1046  
312             # Meeus, Appendix I pg 407.
313 18     18   110 use constant LIGHTYEAR => 9.4607e12; # 1 light-year, per Meeus,
  18         39  
  18         976  
314             # Appendix I pg 407.
315 18     18   113 use constant PARSEC => 30.8568e12; # 1 parsec, per Meeus,
  18         52  
  18         1236  
316             # Appendix I pg 407.
317 18     18   179 use constant PERL2000 => greg_time_gm( 0, 0, 12, 1, 0, 2000 );
  18         62  
  18         53  
318 18     18   2149 use constant PI => atan2 (0, -1);
  18         38  
  18         1170  
319 18     18   141 use constant PIOVER2 => PI / 2;
  18         49  
  18         1382  
320 18     18   240 use constant SECSPERDAY => 86400;
  18         48  
  18         973  
321 18     18   124 use constant SECS_PER_SIDERIAL_DAY => 86164.0905; # Appendix I, page 408.
  18         46  
  18         976  
322 18     18   111 use constant SPEED_OF_LIGHT => 299792.458; # KM/sec, per NIST.
  18         44  
  18         1146  
323             ### use constant SOLAR_RADIUS => 1392000 / 2; # Meeus, Appendix I, page 407.
324 18     18   171 use constant TWOPI => PI * 2;
  18         60  
  18         1062  
325              
326 18     18   115 use constant ARRAY_REF => ref [];
  18         35  
  18         1335  
327 18     18   124 use constant CODE_REF => ref sub {};
  18         51  
  18         1084  
328 18     18   109 use constant HASH_REF => ref {};
  18         42  
  18         1068  
329 18     18   114 use constant SCALAR_REF => ref \0;
  18         31  
  18         6899  
330              
331             =item $angle = acos ($value)
332              
333             This subroutine calculates the arc in radians whose cosine is the given
334             value.
335              
336             =cut
337              
338             sub acos {
339 919 50   919 1 3309 abs ($_[0]) > 1 and confess <
340             Programming error - Trying to take the arc cosine of a number greater
341             than 1.
342             eod
343 919         5561 return atan2 (sqrt (1 - $_[0] * $_[0]), $_[0])
344             }
345              
346             =item $mag = add_magnitudes( $mag1, $mag2, ... );
347              
348             This subroutine computes the total magnitude of a list of individual
349             magnitudes. The algorithm comes from Jean Meeus' "Astronomical
350             Algorithms", Second Edition, Chapter 56, Page 393.
351              
352             =cut
353              
354             sub add_magnitudes {
355 1     1 1 530 my @arg = @_;
356 1         3 my $sum = 0;
357 1         4 foreach my $mag ( @arg ) {
358 3         14 $sum += 10 ** ( -0.4 * $mag );
359             }
360 1         7 return -2.5 * log( $sum ) / log( 10 );
361             }
362              
363             =item $angle = asin ($value)
364              
365             This subroutine calculates the arc in radians whose sine is the given
366             value.
367              
368             =cut
369              
370 25613     25613 1 71058 sub asin {return atan2 ($_[0], sqrt (1 - $_[0] * $_[0]))}
371              
372             =item $magnitude = atmospheric_extinction ($elevation, $height);
373              
374             This subroutine calculates the typical atmospheric extinction in
375             magnitudes at the given elevation above the horizon in radians and the
376             given height above sea level in kilometers.
377              
378             The algorithm comes from Daniel W. E. Green's article "Magnitude
379             Corrections for Atmospheric Extinction", which was published in
380             the July 1992 issue of "International Comet Quarterly", and is
381             available online at
382             L. The text of
383             this article makes it clear that the actual value of the
384             atmospheric extinction can vary greatly from the typical
385             values given even in the absence of cloud cover.
386              
387             =cut
388              
389             # Note that the "constant" 0.120 in Aaer (aerosol scattering) is
390             # based on a compromise value A0 = 0.050 in Green's equation 3
391             # (not exhibited here), which can vary from 0.035 in the winter to
392             # 0.065 in the summer. This makes a difference of a couple tenths
393             # at 20 degrees elevation, but a couple magnitudes at the
394             # horizon. Green also remarks that the 1.5 denominator in the
395             # same equation (a.k.a. the scale height) can be up to twice
396             # that.
397              
398             sub atmospheric_extinction {
399 3     3 1 1559 my ($elevation, $height) = @_;
400 3         26 my $cosZ = cos (PIOVER2 - $elevation);
401 3         15 my $X = 1/($cosZ + 0.025 * exp (-11 * $cosZ)); # Green 1
402 3         9 my $Aray = 0.1451 * exp (-$height / 7.996); # Green 2
403 3         7 my $Aaer = 0.120 * exp (-$height / 1.5); # Green 4
404 3         9 return ($Aray + $Aaer + 0.016) * $X; # Green 5, 6
405             }
406              
407             =item $jd = date2jd ($sec, $min, $hr, $day, $mon, $yr)
408              
409             This subroutine converts the given date to the corresponding Julian day.
410             The inputs are a Perl date and time; $mon is in the range 0 -
411             11, and $yr is from 1900, with earlier years being negative. The year 1
412             BC is represented as -1900.
413              
414             If less than 6 arguments are provided, zeroes will be prepended to the
415             argument list as needed.
416              
417             The date is presumed to be in the Gregorian calendar. If the resultant
418             Julian Day is before $JD_GREGORIAN, the date is reinterpreted as being
419             from the Julian calendar.
420              
421             The only validation is that the month be between 0 and 11 inclusive, and
422             that the year be not less than -6612 (4713 BC). Fractional days are
423             accepted.
424              
425             The algorithm is from Jean Meeus' "Astronomical Algorithms", second
426             edition, chapter 7 ("Julian Day"), pages 60ff, but the month is
427             zero-based, not 1-based, and years are 1900-based.
428              
429             =cut
430              
431             our $DATETIMEFORMAT;
432             our $JD_GREGORIAN;
433             BEGIN {
434 18     18   76 $DATETIMEFORMAT = '%a %b %d %Y %H:%M:%S';
435 18         5495 $JD_GREGORIAN = 2299160.5;
436             }
437              
438             sub date2jd {
439 21     21 1 1072 my @args = @_;
440 21         134 unshift @args, 0 while @args < 6;
441 21         68 my ($sec, $min, $hr, $day, $mon, $yr) = @args;
442 21         75 $mon++; # Algorithm expects month 1-12.
443 21         62 $yr += 1900; # Algorithm expects zero-based year.
444 21 50       83 ($yr < -4712) and croak "Error - Invalid year $yr";
445 21 50 33     142 ($mon < 1 || $mon > 12) and croak "Error - Invalid month $mon";
446 21 100       78 if ($mon < 3) {
447 20         45 --$yr;
448 20         57 $mon += 12;
449             }
450 21         197 my $A = floor ($yr / 100);
451 21         120 my $B = 2 - $A + floor ($A / 4);
452 21   50     382 my $jd = floor (365.25 * ($yr + 4716)) +
      50        
      100        
453             floor (30.6001 * ($mon + 1)) + $day + $B - 1524.5 +
454             ((($sec || 0) / 60 + ($min || 0)) / 60 + ($hr || 0)) / 24;
455 21 100 50     104 $jd < $JD_GREGORIAN and
      50        
      50        
456             $jd = floor (365.25 * ($yr + 4716)) +
457             floor (30.6001 * ($mon + 1)) + $day - 1524.5 +
458             ((($sec || 0) / 60 + ($min || 0)) / 60 + ($hr || 0)) / 24;
459 21         43732 return $jd;
460             }
461              
462 18     18   131 use constant JD_OF_EPOCH => date2jd (gmtime (0));
  18         37  
  18         91  
463              
464             =item $epoch = date2epoch ($sec, $min, $hr, $day, $mon, $yr)
465              
466             This is a convenience routine that converts the given date to seconds
467             since the epoch, going through date2jd() to do so. The arguments are the
468             same as those of date2jd().
469              
470             If less than 6 arguments are provided, zeroes will be prepended to the
471             argument list as needed.
472              
473             The functionality is the same as B, but this
474             function lacks timegm's limited date range under Perls before 5.12.0. If
475             you have Perl 5.12.0 or better, the core L
476             C will probably do what you want. If you have an earlier
477             Perl, L C may do what you want.
478              
479             =cut
480              
481             sub date2epoch {
482 1     1 1 534 my @args = @_;
483 1         11 unshift @args, 0 while @args < 6;
484 1         7 my ($sec, $min, $hr, $day, $mon, $yr) = @args;
485 1   50     3 return (date2jd ($day, $mon, $yr) - JD_OF_EPOCH) * SECSPERDAY +
      50        
      50        
486             (($hr || 0) * 60 + ($min || 0)) * 60 + ($sec || 0);
487             }
488              
489             =item $time = decode_space_track_json_time( $string )
490              
491             This subroutine decodes a time in the format Space Track uses in their
492             JSON code. This is ISO-8601-ish, but with a possible fractional part and
493             no zone.
494              
495             =cut
496              
497             sub decode_space_track_json_time {
498 0     0 1 0 my ( $string ) = @_;
499 0 0       0 $string =~ m{ \A \s*
500             ( [0-9]+ ) [^0-9]+ ( [0-9]+ ) [^0-9]+ ( [0-9]+ ) [^0-9]+
501             ( [0-9]+ ) [^0-9]+ ( [0-9]+ ) [^0-9]+ ( [0-9]+ ) (?: ( [.] [0-9]* ) )? \s* \z }smx
502             or return;
503 0         0 my @time = ( $1, $2, $3, $4, $5, $6 );
504 0         0 my $frac = $7;
505 0         0 $time[0] = __tle_year_to_Gregorian_year( $time[0] );
506 0         0 $time[1] -= 1;
507 0         0 my $rslt = greg_time_gm( reverse @time );
508 0 0 0     0 defined $frac
509             and $frac ne '.'
510             and $rslt += $frac;
511 0         0 return $rslt;
512             }
513              
514             # my ( $self, $station, @args ) = __default_station( @_ )
515             #
516             # This exportable subroutine checks whether the second argument embodies
517             # an Astro::Coord::ECI object. If so, the arguments are returned
518             # verbatim. If not, the 'station' attribute of the invocant is inserted
519             # after the first argument and the result returned. If the 'station'
520             # attribute of the invocant has not been set, an exception is thrown.
521              
522             sub __default_station {
523 23     23   95 my ( $self, @args ) = @_;
524 23 100       78 if ( ! embodies( $args[0], 'Astro::Coord::ECI' ) ) {
525 8 50       44 my $sta = $self->get( 'station' )
526             or croak 'Station attribute not set';
527 8         39 unshift @args, $sta;
528             }
529 23         135 return ( $self, @args );
530             }
531              
532             =item $rad = deg2rad ($degr)
533              
534             This subroutine converts degrees to radians. If the argument is
535             C, C will be returned.
536              
537             =cut
538              
539 165031 100   165031 1 597071 sub deg2rad { return defined $_[0] ? $_[0] * PI / 180 : undef }
540              
541             =item $value = distsq (\@coord1, \@coord2)
542              
543             This subroutine calculates the square of the distance between the two
544             sets of Cartesian coordinates. We do not take the square root here
545             because of cases (e.g. the law of cosines) where we would just have
546             to square the result again.
547              
548             B that the subroutine does B assume three-dimensional
549             coordinates. If @coord1 and @coord2 have six entries, you will get a
550             six-dimensional distance.
551              
552             =cut
553              
554             sub distsq {
555 1     1 1 483 my ( $x, $y ) = @_;
556             ARRAY_REF eq ref $x
557             and ARRAY_REF eq ref $y
558 1 50 33     9 and @{ $x } == @{ $y }
  1   33     2  
  1         4  
559             or confess <<'EOD';
560             Programming error - Both arguments to distsq must be references to
561             arrays of the same length.
562             EOD
563              
564 1         3 my $sum = 0;
565 1         2 my $size = @$x;
566 1         60 for (my $inx = 0; $inx < $size; $inx++) {
567 2         4 my $delta = $x->[$inx] - $y->[$inx];
568 2         7 $sum += $delta * $delta;
569             }
570 1         3 return $sum
571             }
572              
573             =item $seconds = dynamical_delta ($time);
574              
575             This method returns the difference between dynamical and universal time
576             at the given universal time. That is,
577              
578             $dynamical = $time + dynamical_delta ($time)
579              
580             if $time is universal time.
581              
582             The algorithm is from Jean Meeus' "Astronomical Algorithms", 2nd
583             Edition, Chapter 10, page 78. Meeus notes that this is actually an
584             observed quantity, and the algorithm is an approximation.
585              
586             =cut
587              
588             sub dynamical_delta {
589 31458     31458 1 57023 my ($time) = @_;
590 31458         117284 my $year = (gmtime $time)[5] + 1900;
591 31458         71077 my $t = ($year - 2000) / 100;
592 31458         52116 my $correction = .37 * ($year - 2100); # Meeus' correction to (10.2)
593 31458         123496 return (25.3 * $t + 102) * $t + 102 # Meeus (10.2)
594             + $correction; # Meeus' correction.
595             }
596              
597             =item $boolean = embodies ($thingy, $class)
598              
599             This subroutine represents a safe way to call the 'represents' method on
600             $thingy. You get back true if and only if $thingy->can('represents')
601             does not throw an exception and returns true, and
602             $thingy->represents($class) returns true. Otherwise it returns false.
603             Any exception is trapped and dismissed.
604              
605             This subroutine is called 'embodies' because it was too confusing to
606             call it 'represents', both for the author and for the Perl interpreter.
607              
608             =cut
609              
610             sub embodies {
611 41866     41866 1 78274 my ($thingy, $class) = @_;
612 41866         61988 my $code = eval {$thingy->can('represents')};
  41866         138160  
613 41866 100       123041 return $code ? $code->($thingy, $class) : undef;
614             }
615              
616             =item ($sec, $min, $hr, $day, $mon, $yr, $wday, $yday, 0) = epoch2datetime ($epoch)
617              
618             This convenience subroutine converts the given time in seconds from the
619             system epoch to the corresponding date and time. It is implemented in
620             terms of jd2date (), with the year and month returned from that
621             subroutine. The day is a whole number, with the fractional part
622             converted to hours, minutes, and seconds. The $wday is the day of the
623             week, with Sunday being 0. The $yday is the day of the year, with
624             January 1 being 0. The trailing 0 is the summer time (or daylight saving
625             time) indicator which is always 0 to be consistent with gmtime.
626              
627             If called in scalar context, it returns the date formatted by
628             POSIX::strftime, using the format string in $DATETIMEFORMAT.
629              
630             The functionality is the same as the core C, but this function
631             lacks gmtime's limited date range under Perls before 5.12.0. If you have
632             Perl 5.12.0 or better, the core C will probably do what you
633             want. If you have an earlier Perl, L
634             C may do what you want.
635              
636             The input must convert to a non-negative Julian date. The exact lower
637             limit depends on the system, but is computed by -(JD_OF_EPOCH * 86400).
638             For Unix systems with an epoch of January 1 1970, this is -210866760000.
639              
640             Additional algorithms for day of week and day of year come from Jean
641             Meeus' "Astronomical Algorithms", 2nd Edition, Chapter 7 (Julian Day),
642             page 65.
643              
644             =cut
645              
646             sub epoch2datetime {
647 27     27 1 3025 my ($time) = @_;
648 27         87 my $day = floor ($time / SECSPERDAY);
649 27         52 my $sec = $time - $day * SECSPERDAY;
650 27         66 ($day, my $mon, my $yr, undef, my $leap) = jd2date (
651             my $jd = $day + JD_OF_EPOCH);
652 27         71 $day = floor ($day + .5);
653 27         55 my $min = floor ($sec / 60);
654 27         49 $sec = $sec - $min * 60;
655 27         44 my $hr = floor ($min / 60);
656 27         39 $min = $min - $hr * 60;
657 27         45 my $wday = ($jd + 1.5) % 7;
658 27         80 my $yd = floor (275 * ($mon + 1) / 9) - (2 - $leap) * floor (($mon +
659             10) / 12) + $day - 31;
660 27 50       126 wantarray and return ($sec, $min, $hr, $day, $mon, $yr, $wday, $yd,
661             0);
662 0         0 return strftime ($DATETIMEFORMAT, $sec, $min, $hr, $day, $mon, $yr,
663             $wday, $yd, 0);
664             }
665              
666             =item $time = find_first_true ($start, $end, \&test, $limit);
667              
668             This function finds the first time between $start and $end for which
669             test ($time) is true. The resolution is $limit, which defaults to 1 if
670             not specified. If the times are reversed (i.e. the start time is after
671             the end time) the time returned is the last time test ($time) is true.
672              
673             The C function is called with the Perl time as its only
674             argument. It is assumed to be false for the first part of the interval,
675             and true for the rest. If this assumption is violated, the result of
676             this subroutine should be considered meaningless.
677              
678             The calculation is done by, essentially, a binary search; the interval
679             is repeatedly split, the function is evaluated at the midpoint, and a
680             new interval selected based on whether the result is true or false.
681              
682             Actually, nothing in this function says the independent variable has to
683             be time.
684              
685             =cut
686              
687             sub find_first_true {
688 421     421 1 1663 my ($begin, $end, $test, $limit) = @_;
689 421   100     1842 $limit ||= 1;
690 421 50       1248 defined $begin
691             or confess 'Programming error - $begin undefined';
692 421 50       1041 defined $end
693             or confess 'Programming error - $end undefined';
694 421 100       1066 if ($limit >= 1) {
695 375 50       860 if ($begin <= $end) {
696 375         1179 $begin = floor ($begin);
697 375 50       1195 $end = floor ($end) == $end ? $end : floor ($end) + 1;
698             } else {
699 0         0 $end = floor ($end);
700 0 0       0 $begin = floor ($begin) == $begin ? $begin : floor ($begin) + 1;
701             }
702             }
703             my $iterator = (
704             $end > $begin ?
705 3993     3993   11529 sub {$end - $begin > $limit} :
706 0     0   0 sub {$begin - $end > $limit}
707 421 50       1879 );
708 421         960 while ($iterator->()) {
709 3572 100       11453 my $mid = $limit >= 1 ?
710             floor (($begin + $end) / 2) : ($begin + $end) / 2;
711 3572 100       8472 ($begin, $end) = ($test->($mid)) ?
712             ($begin, $mid) : ($mid, $end);
713             }
714 421         2913 return $end;
715             }
716              
717             =item $folded = fold_case( $text );
718              
719             This function folds the case of its input, kinda sorta. It maps to
720             C if that is available, otherwise it maps to C.
721              
722             =cut
723              
724             *fold_case = CORE->can( 'fc' ) || sub ($) { return lc $_[0] };
725              
726             =item $fmtd = format_space_track_json_time( time() )
727              
728             This function takes as input a Perl time, and returns that time
729             in a format consistent with the Space Track JSON data. This is
730             ISO-8601-ish, in Universal time, but without the zone indicated.
731              
732             B that Space Track does not represent fractional seconds, even in
733             the epoch. This subroutine deals with this by truncating the epoch to
734             seconds, and leaving the fractional seconds to the caller to deal with.
735              
736             =cut
737              
738             sub format_space_track_json_time {
739 1     1 1 528 my ( $time ) = @_;
740 1 50 33     13 defined $time
741             and $time =~ m/ \S /smx
742             or return;
743 1         6 my ( undef, $sec ) = modf( $time );
744 1         8 my @parts = ( gmtime $sec )[ reverse 0 .. 5 ];
745 1         3 $parts[0] += 1900;
746 1         2 $parts[1] += 1;
747 1         8 return sprintf '%04d-%02d-%02d %02d:%02d:%02d', @parts;
748             }
749              
750             =item $fmtd = __format_epoch_time_usec( time(), '%F %T' )
751              
752             This function takes as input a Perl time with a possible fractional
753             part, and returns that time as GMT in the given C format, but
754             with seconds expressed to the nearest microsecond.
755              
756             =cut
757              
758             {
759             # The test of this (which uses format '%F %T') failed under Windows,
760             # at least undef Strawberry, returning the empty string. Expanding
761             # %F fixed this, so I decided to expand all the 'equivalent to'
762             # format strings I could find.
763             my %equiv = (
764             'D' => 'm/%d/%y',
765             'F' => 'Y-%m-%d',
766             'r' => 'I:%M:%S %p',
767             'R' => 'H:%M',
768             'T' => 'H:%M:%S',
769             'V' => 'e-%b-%Y',
770             );
771              
772             sub __format_epoch_time_usec {
773 1     1   603 my ( $epoch, $date_format ) = @_;
774 1         4 my ( $microseconds, $seconds ) = modf( $epoch );
775 1         6 my @parts = gmtime $seconds;
776 1         8 my $string_us = sprintf '%.6f', $parts[0] + $microseconds;
777 1         8 $string_us =~ s/ [^.]* //smx;
778 1 50       11 $date_format =~ s{ ( %+ ) ( [DFrRTV] ) }
  2         17  
779 1 50       8 { length( $1 ) % 2 ? "$1$equiv{$2}" : "$1$2" }smxge;
  1         6  
780 1         132 $date_format =~ s{ ( %+ ) S }
781             { length( $1 ) % 2 ? "${1}S$string_us" : "$1$2" }smxge;
782             return strftime( $date_format, @parts );
783             }
784             }
785              
786             =item $epoch = greg_time_gm( $sec, $min, $hr, $day, $mon, $yr );
787              
788             This exportable subroutine is a wrapper for either
789             C (if that module is installed),
790             C (if that is available), or
791             C (if not.)
792              
793             This subroutine interprets years as Gregorian years.
794              
795             The difference between this and C is that C
796             interprets the year the way C does. For that
797             reason, this subroutine is preferred over c.
798              
799             This wrapper is needed because the routines have subtly different
800             signatures. L C interprets years
801             strictly as Perl years. L C
802             interprets them strictly as Gregorian years. L
803             C interprets them differently depending on the value of the
804             year. Years greater than or equal to 1000 are Gregorian years, but all
805             others are Perl years, except for the range 0 to 99 inclusive, which are
806             within 50 years of the current year.
807              
808             If you are doing historical calculations, see
809             L
810             in the L documentation
811             for a discussion of input and output time conversion.
812              
813             =item $epoch = greg_time_local( $sec, $min, $hr, $day, $mon, $yr );
814              
815             This exportable subroutine is a wrapper for either
816             C (if that module is installed),
817             C (if that is available), or
818             C (if not.)
819              
820             This subroutine interprets years as Gregorian years.
821              
822             The difference between this and c is that C
823             interprets the year the way C does. For that
824             reason, this subroutine is preferred over c.
825              
826             This wrapper is needed for the same reason C is
827             needed.
828              
829             If you are doing historical calculations, see
830             L
831             in the L documentation
832             for a discussion of input and output time conversion.
833              
834             =item $difference = intensity_to_magnitude ($ratio)
835              
836             This function converts a ratio of light intensities to a difference in
837             stellar magnitudes. The algorithm comes from Jean Meeus' "Astronomical
838             Algorithms", Second Edition, Chapter 56, Page 395.
839              
840             Note that, because of the way magnitudes work (a more negative number
841             represents a brighter star) you get back a positive result for an
842             intensity ratio less than 1, and a negative result for an intensity
843             ratio greater than 1.
844              
845             =cut
846              
847             { # Begin local symbol block
848 1   50 1 1 529 my $intensity_to_mag_factor; # Calculate only if needed.
849             sub intensity_to_magnitude {
850             return - ($intensity_to_mag_factor ||= 2.5 / log (10)) * log ($_[0]);
851             }
852             }
853              
854             =item ($day, $mon, $yr, $greg, $leap) = jd2date ($jd)
855              
856             This subroutine converts the given Julian day to the corresponding date.
857             The returns are year - 1900, month (0 to 11), day (which may have a
858             fractional part), a Gregorian calendar indicator which is true if the
859             date is in the Gregorian calendar and false if it is in the Julian
860             calendar, and a leap (or bissextile) year indicator which is true if the
861             year is a leap year and false otherwise. The year 1 BC is returned as
862             -1900 (i.e. as year 0), and so on. The date will probably have a
863             fractional part (e.g. 2006 1 1.5 for noon January first 2006).
864              
865             If the $jd is before $JD_GREGORIAN, the date will be in the Julian
866             calendar; otherwise it will be in the Gregorian calendar.
867              
868             The input may not be less than 0.
869              
870             The algorithm is from Jean Meeus' "Astronomical Algorithms", second
871             edition, chapter 7 ("Julian Day"), pages 63ff, but the month is
872             zero-based, not 1-based, and the year is 1900-based.
873              
874             =cut
875 36     36 1 5689  
876 36         60 sub jd2date {
877 36         73 my ($time) = @_;
878 36         67 my $mod_jd = $time + 0.5;
879 36 100       80 my $Z = floor ($mod_jd);
880 30         63 my $F = $mod_jd - $Z;
881 30         90 my $A = (my $julian = $Z < $JD_GREGORIAN) ? $Z : do {
882             my $alpha = floor (($Z - 1867216.25)/36524.25);
883 36         62 $Z + 1 + $alpha - floor ($alpha / 4);
884 36         74 };
885 36         73 my $B = $A + 1524;
886 36         75 my $C = floor (($B - 122.1) / 365.25);
887 36         86 my $D = floor (365.25 * $C);
888 36 100       71 my $E = floor (($B - $D) / 30.6001);
889 36 100       73 my $day = $B - $D - floor (30.6001 * $E) + $F;
890 36 50       231 my $mon = $E < 14 ? $E - 1 : $E - 13;
    100          
    100          
891             my $yr = $mon > 2 ? $C - 4716 : $C - 4715;
892             return ($day, $mon - 1, $yr - 1900, !$julian, ($julian ? !($yr % 4) : (
893             $yr % 400 ? $yr % 100 ? !($yr % 4) : 0 : 1)));
894             ## % 400 ? 1 : $yr % 100 ? 0 : !($yr % 4))));
895             }
896              
897             =item ($sec, $min, $hr, $day, $mon, $yr, $wday, $yday, 0) = jd2datetime ($jd)
898              
899             This convenience subroutine converts the given Julian day to the
900             corresponding date and time. All this really does is converts its
901             argument to seconds since the system epoch, and pass off to
902             epoch2datetime().
903              
904             The input may not be less than 0.
905              
906             =cut
907 21     21 1 10407  
908 21         60 sub jd2datetime {
909             my ($time) = @_;
910             return epoch2datetime(($time - JD_OF_EPOCH) * SECSPERDAY);
911             }
912              
913             =item $century = jcent2000 ($time);
914              
915             Several of the algorithms in Jean Meeus' "Astronomical Algorithms"
916             are expressed in terms of the number of Julian centuries from epoch
917             J2000.0 (e.g equations 12.1, 22.1). This subroutine encapsulates
918             that calculation.
919              
920 74290     74290 1 142516 =cut
921              
922             sub jcent2000 {return jday2000 ($_[0]) / 36525}
923              
924             =item $jd = jday2000 ($time);
925              
926             This subroutine converts a Perl date to the number of Julian days
927             (and fractions thereof) since Julian 2000.0. This quantity is used
928             in a number of the algorithms in Jean Meeus' "Astronomical
929             Algorithms".
930              
931             The computation makes use of information from Jean Meeus' "Astronomical
932             Algorithms", 2nd Edition, Chapter 7, page 62.
933              
934 104229     104229 1 249555 =cut
935              
936             sub jday2000 {return ($_[0] - PERL2000) / SECSPERDAY}
937              
938             =item $jd = julianday ($time);
939              
940             This subroutine converts a Perl date to a Julian day number.
941              
942             The computation makes use of information from Jean Meeus' "Astronomical
943             Algorithms", 2nd Edition, Chapter 7, page 62.
944              
945 2     2 1 1051 =cut
946              
947             sub julianday {return jday2000($_[0]) + 2_451_545.0}
948              
949             =item $ea = keplers_equation( $M, $e, $prec );
950              
951             This subroutine solves Kepler's equation for the given mean anomaly
952             C<$M> in radians, eccentricity C<$e> and precision C<$prec> in radians.
953             It returns the eccentric anomaly in radians, to the given precision.
954              
955             The C<$prec> argument is optional, and defaults to the radian equivalent
956             of 0.001 degrees.
957              
958             The algorithm is Meeus' equation 30.7, with John M. Steele's amendment
959             for large values for the correction, given on page 205 of Meeus' book,
960              
961             This subroutine is B used in the computation of satellite orbits,
962             since the models have their own implementation.
963              
964             =cut
965 1     1 1 502  
966 1 50       5 sub keplers_equation {
967             my ( $mean_anomaly, $eccentricity, $precision ) = @_;
968 1         3 defined $precision
969 1         2 or $precision = 1.74533e-5; # Radians, = 0.001 degrees
970             my $curr = $mean_anomaly;
971             my $prev;
972 1         2 # Meeus' equation 30.7, page 199.
  3         6  
973 3         10 {
974             $prev = $curr;
975             my $delta = ( $mean_anomaly + $eccentricity * sin( $curr
976 3         11 ) - $curr ) / ( 1 - $eccentricity * cos $curr );
977 3 100       13 # Steele's correction, page 205
978             $curr = $curr + max( -.5, min( .5, $delta ) );
979             $precision < abs( $curr - $prev )
980 1         3 and redo;
981             }
982             return $curr;
983             }
984              
985             =item $rslt = load_module ($module_name)
986              
987             This convenience method loads the named module (using 'require'),
988             throwing an exception if the load fails. If the load succeeds, it
989             returns the result of the 'require' built-in, which is required to be
990             true for a successful load. Results are cached, and subsequent attempts
991             to load the same module simply give the cached results.
992              
993             =cut
994              
995             { # Local symbol block. Oh, for 5.10 and state variables.
996             my %error;
997 243     243 1 467 my %rslt;
998 243 50       555 sub load_module {
999 243 100       816 my ($module) = @_;
1000             exists $error{$module} and croak $error{$module};
1001             exists $rslt{$module} and return $rslt{$module};
1002             # I considered Module::Load here, but it appears not to support
1003             # .pmc files. No, it's not an issue at the moment, but it may be
1004 48 50       3212 # if Perl 6 becomes a reality.
1005 48         217 $rslt{$module} = eval "require $module"
1006             or croak( $error{$module} = $@ );
1007             return $rslt{$module};
1008             }
1009             } # End local symbol block.
1010              
1011             =item $boolean = looks_like_number ($string);
1012              
1013             This subroutine returns true if the input looks like a number. It uses
1014             Scalar::Util::looks_like_number if that is available, otherwise it uses
1015             its own code, which is lifted verbatim from Scalar::Util 1.19, which in
1016             turn leans heavily on perlfaq4.
1017              
1018             =cut
1019              
1020 18     18   161 unless (eval {require Scalar::Util; Scalar::Util->import
  18         58  
  18         5762  
1021             ('looks_like_number'); 1}) {
1022             no warnings qw{once};
1023             *looks_like_number = sub {
1024             local $_ = shift;
1025              
1026             # checks from perlfaq4
1027             return 0 if !defined($_) || ref($_);
1028             return 1 if (/^[+-]?[0-9]+$/); # is a +/- integer
1029             return 1 if (/^([+-]?)(?=[0-9]|\.[0-9])[0-9]*(\.[0-9]*)?([Ee]([+-]?[0-9]+))?$/); # a C float
1030             return 1 if ($] >= 5.008 and /^(Inf(inity)?|NaN)$/i)
1031             or ($] >= 5.006001 and /^Inf$/i);
1032              
1033             return 0;
1034             };
1035             }
1036              
1037             =item $maximum = max (...);
1038              
1039             This subroutine returns the maximum of its arguments. If List::Util can
1040             be loaded and 'max' imported, that's what you get. Otherwise you get a
1041             pure Perl implementation.
1042              
1043             =cut
1044 18     18   150  
  18         58  
  18         2513  
1045             unless (eval {require List::Util; List::Util->import ('max'); 1}) {
1046             no warnings qw{once};
1047             *max = sub {
1048             my $rslt;
1049             foreach (@_) {
1050             defined $_ or next;
1051             if (!defined $rslt || $_ > $rslt) {
1052             $rslt = $_;
1053             }
1054             }
1055             $rslt;
1056             };
1057             }
1058              
1059             =item $minimum = min (...);
1060              
1061             This subroutine returns the minimum of its arguments. If List::Util can
1062             be loaded and 'min' imported, that's what you get. Otherwise you get a
1063             pure Perl implementation.
1064              
1065             =cut
1066 18     18   149  
  18         39  
  18         38736  
1067             unless (eval {require List::Util; List::Util->import ('min'); 1}) {
1068             no warnings qw{once};
1069             *min = sub {
1070             my $rslt;
1071             foreach (@_) {
1072             defined $_ or next;
1073             if (!defined $rslt || $_ < $rslt) {
1074             $rslt = $_;
1075             }
1076             }
1077             $rslt;
1078             };
1079             }
1080              
1081             =item $theta = mod2pi ($theta)
1082              
1083             This subroutine reduces the given angle in radians to the range 0 <=
1084             $theta < TWOPI.
1085              
1086 177429     177429 1 498728 =cut
1087              
1088             sub mod2pi {return $_[0] - floor ($_[0] / TWOPI) * TWOPI}
1089              
1090             =item $radians = omega ($time);
1091              
1092             This subroutine calculates the ecliptic longitude of the ascending node
1093             of the Moon's mean orbit at the given B time.
1094              
1095             The algorithm comes from Jean Meeus' "Astronomical Algorithms", 2nd
1096             Edition, Chapter 22, pages 143ff.
1097              
1098             =cut
1099 1     1 1 524  
1100 1         9 sub omega {
1101             my $T = jcent2000 (shift); # Meeus (22.1)
1102             return mod2pi (deg2rad ((($T / 450000 + .0020708) * $T -
1103             1934.136261) * $T + 125.04452));
1104             }
1105              
1106             =item $pa = position_angle( $alpha1, $delta1, $alpha2, $delta2 );
1107              
1108             This low-level subroutine calculates the position angle in right
1109             ascension of the second body with respect to the first, given the first
1110             body's right ascension and declination and the second body's right
1111             ascension and declination in that order, B.
1112              
1113             The return is the position angle B, in the range
1114             C<< -PI <= $pa < PI >>.
1115              
1116             The algorithm comes from Jean Meeus' "Astronomical Algorithms", 2nd
1117             Edition, page 116, but his algorithm is for the position angle of the
1118             first body with respect to the second (i.e. the roles of the two bodies
1119             are reversed). The order of arguments for this subroutine is consistent
1120             with The IDL Astronomy User's Library at
1121             L, function C.
1122              
1123             This is exposed because in principal you could calculate the position
1124             angle in any spherical coordinate system, you would just need to get the
1125             order of arguments right (e.g. azimuth, elevation or longitude,
1126             latitude).
1127              
1128             =cut
1129 1     1 1 1295  
1130 1         3 sub position_angle {
1131 1         6 my ( $alpha1, $delta1, $alpha2, $delta2 ) = @_;
1132             my $delta_alpha = $alpha2 - $alpha1;
1133             return atan2( sin( $delta_alpha ),
1134             cos( $delta1 ) * tan( $delta2 ) -
1135             sin( $delta1 ) * cos( $delta_alpha ) );
1136             }
1137              
1138             =item $degrees = rad2deg ($radians)
1139              
1140             This subroutine converts the given angle in radians to its equivalent
1141             in degrees. If the argument is C, C will be returned.
1142              
1143 8361 100   8361 1 46634 =cut
1144              
1145             sub rad2deg { return defined $_[0] ? $_[0] / PI * 180 : undef }
1146              
1147             =item $deg_min_sec = rad2dms( $radians, $decimals, $degree_sign )
1148              
1149             This subroutine converts the given angle in radians to its equivalent in
1150             degrees, minutes and seconds, represented as a string. Degrees will be
1151             suppressed if zero, and minutes will be suppressed if both degrees and
1152             minutes are zero. If degrees are present the default delimiter is a
1153             degree sign. The delimiters for minutes and seconds are C<'> and C<">
1154             respectively, with the C<"> appearing before the decimal point.
1155              
1156             The optional C<$decimals> argument specifies the number of decimal
1157             places in the seconds value, and defaults to C<3>.
1158              
1159             The optional C<$degree_sign> argument specifies the degree sign. The
1160             default is a Unicode degree sign, C<"\N{DEGREE SIGN}">, a.k.a.
1161             C<"\N{U+00B0}">.
1162              
1163             =cut
1164 2     2 1 932  
1165 2 100       9 sub rad2dms {
1166             my ( $rad, $dp, $ds ) = @_;
1167 1 50       11 defined $rad
1168             or return $rad;
1169 1 50       4 defined $dp
1170             or $dp = 3;
1171 1         3 defined $ds
1172             or $ds = "\N{U+00B0}";
1173 1 50       5 my $wid = $dp + 3;
1174 1         3  
1175 1         6 ( $rad, my $sgn ) = $rad < 0 ? ( -$rad, '-' ) : ( $rad, '' );
1176 1         5 my $sec = rad2deg( $rad );
1177 1         3 ( $sec, my $deg ) = modf( $sec );
1178 1         1 ( $sec, my $min ) = modf( $sec * 60 );
1179 1 50       3 $sec *= 60;
    0          
1180 1         14 my $rslt;
1181             if ( $deg ) {
1182 0         0 $rslt = sprintf qq<%d$ds%02d'%$wid.${dp}f>, $deg, $min, $sec;
1183             } elsif ( $min ) {
1184 0         0 $rslt = sprintf qq<%d'%$wid.${dp}f>, $min, $sec;
1185             } else {
1186 1 50       10 $rslt = sprintf qq<%.${dp}f>, $sec;
1187             }
1188 1         5 $rslt =~ s/ [.] /"./smx
1189             or $rslt .= q<">;
1190             return "$sgn$rslt";
1191             }
1192              
1193             =item $hr_min_sec = rad2hms( $radians, $decimals )
1194              
1195             This subroutine converts the given angle in radians to its equivalent in
1196             hours, minutes and seconds (presumably of right ascension), represented
1197             as a string. Hours will be suppressed if zero, and minutes will be
1198             suppressed if both hours and minutes are zero. The delimiters for hours,
1199             minutes, and seconds are C<'h'>, C<'m'>, and C<'s'> respectively, with
1200             the C<'s'> appearing before the decimal point.
1201              
1202             The optional C<$decimals> argument specifies the number of decimal
1203             places in the seconds value, and defaults to C<3>.
1204              
1205             =cut
1206 2     2 1 975  
1207 2 100       7 sub rad2hms {
1208             my ( $rad, $dp ) = @_;
1209 1 50       4 defined $rad
1210             or return $rad;
1211             defined $dp
1212 1         2 or $dp = 3;
1213 1 50       5  
1214 1         4 my $wid = $dp + 3;
1215 1         4 ( $rad, my $sgn ) = $rad < 0 ? ( -$rad, '-' ) : ( $rad, '' );
1216 1         4 my $sec = $rad * 12 / PI;
1217 1         3 ( $sec, my $hr ) = modf( $sec );
1218 1         2 ( $sec, my $min ) = modf( $sec * 60 );
1219 1 50       2 $sec *= 60;
    0          
1220 1         11 my $rslt;
1221             if ( $hr ) {
1222 0         0 $rslt = sprintf "%dh%02dm%$wid.${dp}f", $hr, $min, $sec;
1223             } elsif ( $min ) {
1224 0         0 $rslt = sprintf "%dm%$wid.${dp}f", $min, $sec;
1225             } else {
1226 1 50       16 $rslt = sprintf "%.${dp}f", $sec;
1227             }
1228 1         4 $rslt =~ s/ [.] /s./smx
1229             or $rslt .= 's';
1230             return "$sgn$rslt";
1231             }
1232              
1233             =item $value = tan ($angle)
1234              
1235             This subroutine computes the tangent of the given angle in radians.
1236              
1237 8068     8068 1 27222 =cut
1238              
1239             sub tan {return sin ($_[0]) / cos ($_[0])}
1240              
1241             =item $value = theta0 ($time);
1242              
1243             This subroutine returns the Greenwich hour angle of the mean equinox at
1244             0 hours universal on the day whose time is given (i.e. the argument is
1245             a standard Perl time).
1246              
1247             =cut
1248 1     1 1 521  
1249 1         9 sub theta0 {
1250 1         4 my ($time) = @_;
1251 1         4 my @t = gmtime $time;
1252             $t[5] += 1900;
1253             return thetag( greg_time_gm( 0, 0, 0, @t[3 .. 5] ) );
1254             }
1255              
1256             =item $value = thetag ($time);
1257              
1258             This subroutine returns the Greenwich hour angle of the mean equinox at
1259             the given time.
1260              
1261             The algorithm comes from Jean Meeus' "Astronomical Algorithms", 2nd
1262             Edition, equation 12.4, page 88.
1263              
1264             =cut
1265              
1266             # Meeus, pg 88, equation 12.4, converted to radians and Perl dates.
1267 29935     29935 1 52231  
1268 29935         54958 sub thetag {
1269 29935         54395 my ($time) = @_;
1270             my $T = jcent2000 ($time);
1271             return mod2pi (4.89496121273579 + 6.30038809898496 *
1272             jday2000 ($time))
1273             + (6.77070812713916e-06 - 4.5087296615715e-10 * $T) * $T * $T;
1274             }
1275              
1276             # time_gm and time_local are actually created at the top of the module.
1277              
1278             =item $epoch = time_gm( $sec, $min, $hr, $day, $mon, $yr );
1279              
1280             This exportable subroutine is a wrapper for either
1281             C (if that module is installed), or
1282             C (if not.)
1283              
1284             This subroutine interprets years the same way C
1285             does.
1286              
1287             This wrapper is needed because the routines have subtly different
1288             signatures. L C interprets years
1289             strictly as Perl years. L C
1290             interprets years differently depending on the value of the year; greater
1291             than 999 as Gregorian years, but other years are Perl years, except for
1292             the years 0 to 99 inclusive, which are interpreted as within 50 years of
1293             the current year.
1294              
1295             This subroutine is discouraged in favor of C, which
1296             always interprets years as Gregorian years.
1297              
1298             =item $epoch = time_local( $sec, $min, $hr, $day, $mon, $yr );
1299              
1300             This exportable subroutine is a wrapper for either
1301             C (if that module is installed), or
1302             C (if not.)
1303              
1304             This subroutine interprets years the same way
1305             C does.
1306              
1307             This wrapper is needed for the same reason C is
1308             needed.
1309              
1310             This subroutine is discouraged in favor of C, which
1311             always interprets years as Gregorian years.
1312              
1313             =item $a = vector_cross_product( $b, $c );
1314              
1315             This subroutine computes and returns the vector cross product of $b and
1316             $c. Vectors are represented by array references. The cross product is
1317             only defined if both arrays have 3 elements.
1318              
1319             =cut
1320 3     3 1 1428  
1321 3 50 33     6 sub vector_cross_product {
  3         12  
  3         10  
1322             my ( $b, $c ) = @_;
1323             @{ $b } == 3 and @{ $c } == 3
1324             or confess 'Programming error - vector_cross_product arguments',
1325 3         17 ' must be references to arrays of length 3';
1326             return [
1327             $b->[1] * $c->[2] - $b->[2] * $c->[1],
1328             $b->[2] * $c->[0] - $b->[0] * $c->[2],
1329             $b->[0] * $c->[1] - $b->[1] * $c->[0],
1330             ];
1331             }
1332              
1333             =item $a = vector_dot_product( $b, $c );
1334              
1335             This subroutine computes and returns the vector dot product of $b and
1336             $c. Vectors are represented by array references. The dot product is only
1337             defined if both arrays have the same number of elements.
1338              
1339             =cut
1340 50698     50698 1 80261  
1341 50698 50       60825 sub vector_dot_product {
  50698         70974  
  50698         101635  
1342             my ( $b, $c ) = @_;
1343             @{ $b } == @{ $c }
1344 50698         70894 or confess 'Programming error - vector_dot_product arguments ',
1345 50698         61840 'must be references to arrays of the same length';
  50698         69699  
1346 50698         84932 my $prod = 0;
1347 152094         240535 my $size = @{ $b } - 1;
1348             foreach my $inx ( 0 .. $size ) {
1349 50698         144716 $prod += $b->[$inx] * $c->[$inx];
1350             }
1351             return $prod;
1352             }
1353              
1354             =item $m = vector_magnitude( $x );
1355              
1356             This subroutine computes and returns the magnitude of vector $x. The
1357             vector is represented by an array reference.
1358              
1359             =cut
1360 24490     24490 1 41313  
1361 24490 50       55991 sub vector_magnitude {
1362             my ( $x ) = @_;
1363             ARRAY_REF eq ref $x
1364 24490         38834 or confess 'Programming error - vector_magnitude argument ',
1365 24490         32743 'must be a reference to an array';
  24490         39825  
1366 24490         49331 my $mag = 0;
1367 73467         128148 my $size = @{ $x } - 1;
1368             foreach my $inx ( 0 .. $size ) {
1369 24490         52122 $mag += $x->[$inx] * $x->[$inx];
1370             }
1371             return sqrt $mag;
1372             }
1373              
1374             =item $u = vector_unitize( $x );
1375              
1376             This subroutine computes and returns a unit vector pointing in the same
1377             direction as $x. The vectors are represented by array references.
1378              
1379             =cut
1380 2     2 1 1016  
1381 2 50       9 sub vector_unitize {
1382             my ( $x ) = @_;
1383             ARRAY_REF eq ref $x
1384 2         6 or confess 'Programming error - vector_unitize argument ',
1385 2         3 'must be a reference to an array';
  4         14  
  2         5  
1386             my $mag = vector_magnitude( $x );
1387             return [ map { $_ / $mag } @{ $x } ];
1388             }
1389              
1390             # __classisa( 'Foo', 'Bar' );
1391             #
1392             # Returns true if Foo->isa( 'Bar' ) is true, and false otherwise.
1393             # In particular, returns false if the first argument is a
1394             # reference.
1395 42738     42738   75614  
1396 42738 50       77330 sub __classisa {
1397 42738 50       74632 my ( $invocant, $class ) = @_;
1398 42738         199388 ref $invocant and return;
1399             defined $invocant or return;
1400             return $invocant->isa( $class );
1401             }
1402              
1403             # __instance( $foo, 'Bar' );
1404             #
1405             # Returns true if $foo is an instance of 'Bar', and false
1406             # otherwise. In particular, returns false if $foo is not a
1407             # reference, or if it is not blessed.
1408 115     115   212  
1409 115 50       260 sub __instance {
1410 115 50       405 my ( $object, $class ) = @_;
1411 115         589 ref $object or return;
1412             blessed( $object ) or return;
1413             return $object->isa( $class );
1414             }
1415              
1416             # $epoch_time = __parse_time_iso_8601
1417             #
1418             # Parse ISO 8601 date/time. I do not intend to expose this, since
1419             # it will probably go away when the satpass script is dropped. It
1420             # would actually be in that script except for the fact that it can
1421             # be more easily tested here, and because of the possibility that
1422             # it will be used in App::Satpass2.
1423             {
1424              
1425             my %special_day_offset = (
1426             yesterday => -SECSPERDAY(),
1427             today => 0,
1428             tomorrow => SECSPERDAY(),
1429             );
1430 51     51   2357  
1431             sub __parse_time_iso_8601 {
1432 51         79 my ( $string ) = @_;
1433 51 100       500  
1434             my @zone;
1435             $string =~ s/ \s* (?: ( Z ) |
1436 51         92 ( [+-] ) ( [0-9]{2} ) :? ( [0-9]{2} )? ) \z //smx
1437             and @zone = ( $1, $2, $3, $4 );
1438             my @date;
1439 51 50       273  
    0          
1440             # ISO 8601 date
1441             if ( $string =~ m{ \A
1442             ( [0-9]{4} [^0-9]? | [0-9]{2} [^0-9] ) # year: $1
1443             (?: ( [0-9]{1,2} ) [^0-9]? # month: $2
1444             (?: ( [0-9]{1,2} ) (?: \s* | [^0-9]? ) # day: $3
1445             (?: ( [0-9]{1,2} ) [^0-9]? # hour: $4
1446             (?: ( [0-9]{1,2} ) [^0-9]? # minute: $5
1447             (?: ( [0-9]{1,2} ) [^0-9]? # second: $6
1448             ( [0-9]* ) # fract: $7
1449             )?
1450             )?
1451             )?
1452             )?
1453             )?
1454 51         242 \z
1455             }smx ) {
1456             @date = ( $1, $2, $3, $4, $5, $6, $7, undef );
1457              
1458             # special-case 'yesterday', 'today', and 'tomorrow'.
1459             } elsif ( $string =~ m< \A
1460             ( yesterday | today | tomorrow ) # day: $1
1461             (?: [^0-9]* ( [0-9]{1,2} ) [^0-9]? # hour: $2
1462             (?: ( [0-9]{1,2} ) [^0-9]? # minute: $3
1463             (?: ( [0-9]{1,2} ) [^0-9]? # second: $4
1464             ( [0-9]* ) # fract: $5
1465             )?
1466             )?
1467 0 0       0 )?
1468             \z >smx ) {
1469 0         0 my @today = @zone ? gmtime : localtime;
1470             @date = ( $today[5] + 1900, $today[4] + 1, $today[3], $2, $3,
1471             $4, $5, $special_day_offset{$1} );
1472              
1473 0         0 } else {
1474              
1475             return;
1476              
1477 51   50     193 }
1478 51 100 100     163  
1479 5         21 my $offset = pop @date || 0;
1480 5   100     25 if ( @zone && !$zone[0] ) {
1481             my ( undef, $sign, $hr, $min ) = @zone;
1482             $offset -= $sign . ( ( $hr * 60 + ( $min || 0 ) ) * 60 )
1483 51         100 }
1484 357 100       759  
1485             foreach ( @date ) {
1486             defined $_ and s/ [^0-9]+ //smxg;
1487 51         117 }
1488              
1489 51 100       137 $date[0] = __tle_year_to_Gregorian_year( $date[0] );
1490 51 100       109  
1491 51         81 defined $date[1] and --$date[1];
1492             defined $date[2] or $date[2] = 1;
1493 51         95 my $frc = pop @date;
1494 306 100       543  
1495             foreach ( @date ) {
1496             defined $_ or $_ = 0;
1497 51         69 }
1498 51 100       104  
1499 28         56 my $time;
1500             if ( @zone ) {
1501 23         47 $time = greg_time_gm( reverse @date );
1502             } else {
1503             $time = greg_time_local( reverse @date );
1504 51 50 66     1969 }
1505 0         0  
1506 0         0 if ( defined $frc && $frc ne '') {
1507             my $denom = 1 . ( 0 x length $frc );
1508             $time += $frc / $denom;
1509 51         152 }
1510              
1511             return $time + $offset;
1512             }
1513              
1514             }
1515 30     30   92  
1516 30 100       99 sub __sprintf {
1517             my ( $tplt, @args ) = @_;
1518 18     18   12441 defined $tplt
  18         254  
  18         116  
1519 27         265 or return undef; ## no critic (ProhibitExplicitReturnUndef)
1520             no if $] gt '5.021002', qw{ warnings redundant };
1521             return sprintf $tplt, @args;
1522             }
1523              
1524             {
1525             my %deprecate = (
1526             );
1527 0     0   0  
1528 0 0       0 sub __subroutine_deprecation {
1529             ( my $sub = ( caller 1 )[3] ) =~ s/ .* :: //smx;
1530 0 0       0 my $info = $deprecate{$sub} or return;
1531 0         0 $info->{level}
1532 0   0     0 or return;
1533 0 0       0 my $msg = "Subroutine $sub() deprecated in favor of @{[
1534             $info->{method} || $sub ]}() method";
1535 0         0 $info->{level} >= 3
1536             and croak $msg;
1537 0 0       0 carp $msg;
1538 0         0 $info->{level} == 1
1539             and $info->{level} = 0;
1540             return;
1541             }
1542             }
1543              
1544             =item $year = __tle_year_to_Gregorian_year( $year )
1545              
1546             The TLE data contain the year in two-digit form. NORAD decided to deal
1547             with Y2K by decreeing that year numbers lower than 57 (the launch of
1548             Sputnik 1) are converted to Gregorian by adding 2000. Years numbers of
1549             57 or greater are still converted to Gregorian by adding 1900. This
1550             subroutine encapsulates this logic. Years of 100 or greater are returned
1551             unmodified.
1552              
1553             This subroutine is B to this package, and can be changed or
1554             revoked without notice.
1555              
1556             =cut
1557 97     97   231  
1558 97 100       394 sub __tle_year_to_Gregorian_year {
    100          
1559             my ( $year ) = @_;
1560             return $year < 57 ? $year + 2000 :
1561             $year < 100 ? $year + 1900 : $year;
1562             }
1563              
1564             1;
1565              
1566             __END__