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__ |