line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
=head1 NAME |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
Astro::Coord::ECI - Manipulate geocentric coordinates |
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
=head1 SYNOPSIS |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
use Astro::Coord::ECI; |
8
|
|
|
|
|
|
|
use Astro::Coord::ECI::Sun; |
9
|
|
|
|
|
|
|
use Astro::Coord::ECI::TLE; |
10
|
|
|
|
|
|
|
use Astro::Coord::ECI::Utils qw{rad2deg}; |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
# 1600 Pennsylvania Avenue, in radians, radians, and KM |
13
|
|
|
|
|
|
|
my ($lat, $lon, $elev) = (0.678911227503559, |
14
|
|
|
|
|
|
|
-1.34456123391096, 0.01668); |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
# Record the time |
17
|
|
|
|
|
|
|
my $time = time (); |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
# Set up observer's location |
20
|
|
|
|
|
|
|
my $loc = Astro::Coord::ECI->geodetic ($lat, $lon, $elev); |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
# Uncomment to turn off atmospheric refraction if desired. |
23
|
|
|
|
|
|
|
# $loc->set( refraction => 0 ); |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
# Instantiate the Sun. |
26
|
|
|
|
|
|
|
my $sun = Astro::Coord::ECI::Sun->universal ($time); |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
# Figure out if the Sun is up at the observer's location. |
29
|
|
|
|
|
|
|
my ($azimuth, $elevation, $range) = $loc->azel ($sun); |
30
|
|
|
|
|
|
|
print "The Sun is ", rad2deg ($elevation), |
31
|
|
|
|
|
|
|
" degrees above the horizon.\n"; |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
See the L documentation |
34
|
|
|
|
|
|
|
for an example involving satellite pass prediction. |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
=head1 DESCRIPTION |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
This module was written to provide a base class for a system to predict |
39
|
|
|
|
|
|
|
satellite visibility. Its main task is to convert the |
40
|
|
|
|
|
|
|
Earth-Centered Inertial (ECI) coordinates generated by the NORAD models |
41
|
|
|
|
|
|
|
into other coordinate systems (e.g. latitude, longitude, and altitude |
42
|
|
|
|
|
|
|
above mean sea level), but a other functions have accreted to it. |
43
|
|
|
|
|
|
|
In addition, a few support routines have been exposed for testing, or |
44
|
|
|
|
|
|
|
whatever. |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
All distances are in kilometers, and all angles are in radians |
47
|
|
|
|
|
|
|
(including right ascension, which is usually measured in hours). |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
Times are normal Perl times, whether used as universal or dynamical |
50
|
|
|
|
|
|
|
time. Universal time is what is usually meant, unless otherwise stated. |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
Known subclasses include B to predict the |
53
|
|
|
|
|
|
|
position of the Moon, B to predict the |
54
|
|
|
|
|
|
|
position of a star, or anything else that can be considered fixed on |
55
|
|
|
|
|
|
|
the celestial sphere, B to predict the position |
56
|
|
|
|
|
|
|
of the Sun, B to predict the position of a |
57
|
|
|
|
|
|
|
satellite given the NORAD orbital parameters, and |
58
|
|
|
|
|
|
|
B (a subclass of |
59
|
|
|
|
|
|
|
Astro::Coord::ECI::TLE, and as of 0.099_01 moved to its own package) to |
60
|
|
|
|
|
|
|
predict Iridium flares. |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
B that in version 0.022_01 the velocity code got a substantial |
63
|
|
|
|
|
|
|
rework, which is still in progress. I am attempting give useful |
64
|
|
|
|
|
|
|
velocities where they are available, and no velocities at all where they |
65
|
|
|
|
|
|
|
are not. Unfortunately I have yet to locate any worked problems, so the |
66
|
|
|
|
|
|
|
velocity code is, in the most literal sense, untested. |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
In general, locations specified in Earth-fixed coordinates are |
69
|
|
|
|
|
|
|
considered to share the rotational velocity of the Earth, and locations |
70
|
|
|
|
|
|
|
specified in inertial coordinates are considered to have undefined |
71
|
|
|
|
|
|
|
velocities unless a velocity was specified explicitly or produced by the |
72
|
|
|
|
|
|
|
object's model. This involved a change in the behavior of the eci() |
73
|
|
|
|
|
|
|
method when velocity was not specified but location was. See the next |
74
|
|
|
|
|
|
|
paragraph for my excuse. |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
B This class and its subclasses should probably be |
77
|
|
|
|
|
|
|
considered alpha code, meaning that the public interface may not be |
78
|
|
|
|
|
|
|
completely stable. I will try not to change it, but given sufficient |
79
|
|
|
|
|
|
|
reason I will do so. If I do so, I will draw attention to the change |
80
|
|
|
|
|
|
|
in the documentation. |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
=head2 Methods |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
The following methods should be considered public. |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
=over 4 |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
=cut |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
package Astro::Coord::ECI; |
91
|
|
|
|
|
|
|
|
92
|
17
|
|
|
17
|
|
79741
|
use strict; |
|
17
|
|
|
|
|
64
|
|
|
17
|
|
|
|
|
482
|
|
93
|
17
|
|
|
17
|
|
84
|
use warnings; |
|
17
|
|
|
|
|
28
|
|
|
17
|
|
|
|
|
842
|
|
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
our $VERSION = '0.129_01'; |
96
|
|
|
|
|
|
|
|
97
|
17
|
|
|
17
|
|
9691
|
use Astro::Coord::ECI::Utils qw{ @CARP_NOT :mainstream }; |
|
17
|
|
|
|
|
57
|
|
|
17
|
|
|
|
|
7753
|
|
98
|
17
|
|
|
17
|
|
131
|
use Carp; |
|
17
|
|
|
|
|
30
|
|
|
17
|
|
|
|
|
940
|
|
99
|
17
|
|
|
17
|
|
7431
|
use Clone (); |
|
17
|
|
|
|
|
41153
|
|
|
17
|
|
|
|
|
534
|
|
100
|
17
|
|
|
17
|
|
120
|
use POSIX qw{floor strftime}; |
|
17
|
|
|
|
|
33
|
|
|
17
|
|
|
|
|
103
|
|
101
|
|
|
|
|
|
|
|
102
|
17
|
|
|
|
|
1085
|
use constant NO_CASCADING_STATIONS => |
103
|
17
|
|
|
17
|
|
1353
|
q{Cascading 'station' attributes are not supported}; |
|
17
|
|
|
|
|
38
|
|
104
|
17
|
|
|
17
|
|
138
|
use constant TWO_DEGREES => deg2rad( 2 ); |
|
17
|
|
|
|
|
34
|
|
|
17
|
|
|
|
|
87
|
|
105
|
17
|
|
|
17
|
|
100
|
use constant CIVIL_TWILIGHT => deg2rad( -6 ); |
|
17
|
|
|
|
|
43
|
|
|
17
|
|
|
|
|
56
|
|
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
my %mutator; # Attribute mutators. We define these just after the |
108
|
|
|
|
|
|
|
# set() method, for convenience. |
109
|
|
|
|
|
|
|
my %known_ellipsoid; # Known reference ellipsoids. We define these |
110
|
|
|
|
|
|
|
# just before the reference_ellipsoid() method for |
111
|
|
|
|
|
|
|
# convenience. |
112
|
|
|
|
|
|
|
my %static = ( # The geoid, etc. Geoid gets set later. |
113
|
|
|
|
|
|
|
# almanac_horizon => set when instantiated, so that the following |
114
|
|
|
|
|
|
|
# _almanac_horizon => gets set automatically by the mutator. |
115
|
|
|
|
|
|
|
angularvelocity => 7.292114992e-5, # Of surface of Earth, 1998. Meeus, p.83 |
116
|
|
|
|
|
|
|
debug => 0, |
117
|
|
|
|
|
|
|
diameter => 0, |
118
|
|
|
|
|
|
|
edge_of_earths_shadow => 1, |
119
|
|
|
|
|
|
|
horizon => deg2rad (20), |
120
|
|
|
|
|
|
|
refraction => 1, |
121
|
|
|
|
|
|
|
twilight => CIVIL_TWILIGHT, |
122
|
|
|
|
|
|
|
); |
123
|
|
|
|
|
|
|
my %savatr; # Attribs saved across "normal" operations. Set at end. |
124
|
|
|
|
|
|
|
my @kilatr = # Attributes to purge when setting coordinates. |
125
|
|
|
|
|
|
|
qw{_need_purge _ECI_cache inertial local_mean_time specified}; #? |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
=item $coord = Astro::Coord::ECI->new (); |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
This method instantiates a coordinate object. Any arguments are passed |
130
|
|
|
|
|
|
|
to the set() method once the object has been instantiated. |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
=cut |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
sub new { |
135
|
172
|
|
|
172
|
1
|
13657
|
my ( $class, @args ) = @_; |
136
|
172
|
|
33
|
|
|
1710
|
my $self = bless { %static }, ref $class || $class; |
137
|
172
|
|
|
|
|
696
|
$self->{inertial} = $self->__initial_inertial(); |
138
|
|
|
|
|
|
|
ref $static{sun} |
139
|
172
|
50
|
|
|
|
812
|
and $self->set( sun => $static{sun}->clone() ); |
140
|
172
|
100
|
|
|
|
741
|
@args and $self->set( @args ); |
141
|
|
|
|
|
|
|
exists $self->{almanac_horizon} |
142
|
172
|
50
|
|
|
|
686
|
or $self->set( almanac_horizon => 0 ); |
143
|
172
|
50
|
|
|
|
409
|
$self->{debug} and do { |
144
|
0
|
|
|
|
|
0
|
require Data::Dumper; |
145
|
0
|
|
|
|
|
0
|
local $Data::Dumper::Terse = 1; |
146
|
0
|
|
|
|
|
0
|
print "Debug - Instantiated ", Data::Dumper::Dumper ($self); |
147
|
|
|
|
|
|
|
}; |
148
|
172
|
|
|
|
|
736
|
return $self; |
149
|
|
|
|
|
|
|
} |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
=item $angle = $coord->angle ($coord2, $coord3); |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
This method calculates the angle between the $coord2 and $coord3 |
154
|
|
|
|
|
|
|
objects, as seen from $coord. The calculation uses the law of |
155
|
|
|
|
|
|
|
haversines, and does not take atmospheric refraction into account. The |
156
|
|
|
|
|
|
|
return is a number of radians between 0 and pi. |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
The algorithm comes from "Ask Dr. Math" on the Math Forum, |
159
|
|
|
|
|
|
|
C, which |
160
|
|
|
|
|
|
|
attributes it to the Geographic Information Systems FAQ, |
161
|
|
|
|
|
|
|
L, which in turn |
162
|
|
|
|
|
|
|
attributes it to R. W. Sinnott, "Virtues of the Haversine," Sky and |
163
|
|
|
|
|
|
|
Telescope, volume 68, number 2, 1984, page 159. |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
Unfortunately, as of early 2021 the National Council of Teachers of |
166
|
|
|
|
|
|
|
Mathematics restricted the Dr. Math content to their members, but an |
167
|
|
|
|
|
|
|
annotated and expanded version of the article on haversines is available |
168
|
|
|
|
|
|
|
at |
169
|
|
|
|
|
|
|
L. |
170
|
|
|
|
|
|
|
If you want the original article, you can feed the URL |
171
|
|
|
|
|
|
|
C to the Wayback |
172
|
|
|
|
|
|
|
Machine. |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
Prior to version 0.011_03 the law of cosines was used, but this produced |
175
|
|
|
|
|
|
|
errors on extremely small angles. The haversine law was selected based |
176
|
|
|
|
|
|
|
on Jean Meeus, "Astronomical Algorithms", 2nd edition, chapter 17 |
177
|
|
|
|
|
|
|
"Angular Separation". |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
This method ignores the C attribute. |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
=cut |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
sub angle { |
184
|
1205
|
|
|
1205
|
1
|
1800
|
my $self = shift; |
185
|
1205
|
|
|
|
|
1667
|
my $B = shift; |
186
|
1205
|
|
|
|
|
1669
|
my $C = shift; |
187
|
1205
|
50
|
33
|
|
|
3793
|
(ref $B && $B->represents (__PACKAGE__) |
|
|
|
33
|
|
|
|
|
|
|
|
33
|
|
|
|
|
188
|
|
|
|
|
|
|
&& ref $C && $C->represents (__PACKAGE__)) |
189
|
|
|
|
|
|
|
or croak <
|
190
|
0
|
|
|
|
|
0
|
Error - Both arguments must represent @{[__PACKAGE__]} objects. |
191
|
|
|
|
|
|
|
eod |
192
|
|
|
|
|
|
|
my ($ra1, $dec1) = $self->{inertial} ? |
193
|
1205
|
100
|
|
|
|
3669
|
$B->equatorial ($self) : $self->_angle_non_inertial ($B); |
194
|
|
|
|
|
|
|
my ($ra2, $dec2) = $self->{inertial} ? |
195
|
1205
|
100
|
|
|
|
2936
|
$C->equatorial ($self) : $self->_angle_non_inertial ($C); |
196
|
1205
|
|
|
|
|
2338
|
my $sindec = sin (($dec2 - $dec1)/2); |
197
|
1205
|
|
|
|
|
1758
|
my $sinra = sin (($ra2 - $ra1)/2); |
198
|
1205
|
|
|
|
|
2372
|
my $a = $sindec * $sindec + |
199
|
|
|
|
|
|
|
cos ($dec1) * cos ($dec2) * $sinra * $sinra; |
200
|
1205
|
|
|
|
|
5436
|
return 2 * atan2 (sqrt ($a), sqrt (1 - $a)); |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
} |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
sub _angle_non_inertial { |
205
|
2408
|
|
|
2408
|
|
3448
|
my $self = shift; |
206
|
2408
|
|
|
|
|
3118
|
my $other = shift; |
207
|
2408
|
|
|
|
|
4198
|
my ($x1, $y1, $z1) = $self->ecef (); |
208
|
2408
|
|
|
|
|
4676
|
my ($x2, $y2, $z2) = $other->ecef (); |
209
|
2408
|
|
|
|
|
3930
|
my $X = $x2 - $x1; |
210
|
2408
|
|
|
|
|
3517
|
my $Y = $y2 - $y1; |
211
|
2408
|
|
|
|
|
3214
|
my $Z = $z2 - $z1; |
212
|
2408
|
|
|
|
|
4379
|
my $lon = atan2 ($Y, $X); |
213
|
2408
|
|
|
|
|
6387
|
my $lat = mod2pi (atan2 ($Z, sqrt ($X * $X + $Y * $Y))); |
214
|
2408
|
|
|
|
|
5246
|
return ($lon, $lat); |
215
|
|
|
|
|
|
|
} |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
=item $which = $coord->attribute ($name); |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
This method returns the name of the class that implements the named |
220
|
|
|
|
|
|
|
attribute, or undef if the attribute name is not valid. |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
=cut |
223
|
|
|
|
|
|
|
|
224
|
0
|
0
|
|
0
|
1
|
0
|
sub attribute {return exists $mutator{$_[1]} ? __PACKAGE__ : undef} |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
=item ($azimuth, $elevation, $range) = $coord->azel( $coord2 ); |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
This method takes another coordinate object, and computes its azimuth, |
229
|
|
|
|
|
|
|
elevation, and range in reference to the object doing the computing. |
230
|
|
|
|
|
|
|
The return is azimuth in radians measured clockwise from North (always |
231
|
|
|
|
|
|
|
positive), elevation above the horizon in radians (negative if |
232
|
|
|
|
|
|
|
below), and range in kilometers. |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
As a side effect, the time of the $coord object may be set from the |
235
|
|
|
|
|
|
|
$coord2 object. |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
Atmospheric refraction is taken into account using the |
238
|
|
|
|
|
|
|
C method if the C<$coord> object's |
239
|
|
|
|
|
|
|
C attribute is true. The invocant's |
240
|
|
|
|
|
|
|
C method is the one used; that is, if |
241
|
|
|
|
|
|
|
C<$coord> and C<$coord2> have different C |
242
|
|
|
|
|
|
|
methods, the C<$coord> object's method is used. |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
B that the C attribute defaults to true. |
245
|
|
|
|
|
|
|
For cases where both invocant and argument are ground-based objects, you |
246
|
|
|
|
|
|
|
should probably set the invocant's C false |
247
|
|
|
|
|
|
|
before invoking this method. |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
This method is implemented in terms of azel_offset(). See that method's |
250
|
|
|
|
|
|
|
documentation for further details. |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
=item ( $azimuth, $elevation, $range ) = $coord->azel(); |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
This method computes the azimuth, elevation, and range if the C<$coord> |
255
|
|
|
|
|
|
|
object as seen from the position stored in the C<$coord> object's |
256
|
|
|
|
|
|
|
C attribute. An exception will be thrown if the C |
257
|
|
|
|
|
|
|
attribute is not set. |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
=cut |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
sub azel { ## no critic (RequireArgUnpacking) |
262
|
16905
|
50
|
|
16905
|
1
|
33090
|
@_ > 2 |
263
|
|
|
|
|
|
|
and croak q{Too many arguments}; |
264
|
16905
|
|
|
|
|
33277
|
@_ = _expand_args_default_station( @_ ); |
265
|
16905
|
50
|
|
|
|
31031
|
$_[2] = $_[2] ? 1 : 0; |
266
|
16905
|
|
|
|
|
38950
|
goto &azel_offset; |
267
|
|
|
|
|
|
|
} |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
=item ($azimuth, $elevation, $range) = $coord->azel_offset($coord2, $offset); |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
This method takes another coordinate object, and computes its azimuth, |
272
|
|
|
|
|
|
|
elevation, and range in reference to the object doing the computing. |
273
|
|
|
|
|
|
|
The return is azimuth in radians measured clockwise from North (always |
274
|
|
|
|
|
|
|
positive), elevation above the horizon in radians (negative if |
275
|
|
|
|
|
|
|
below), and range in kilometers. |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
If the optional C<$offset> argument is provided, the elevation is offset |
278
|
|
|
|
|
|
|
upward by the given fraction of the radius of the C<$coord2> object. |
279
|
|
|
|
|
|
|
Thus, an C<$offset> of C<1> specifies the upper limb of the object, C<0> |
280
|
|
|
|
|
|
|
specifies the center of the object, and C<-1> specifies the lower limb. |
281
|
|
|
|
|
|
|
This offset is applied before atmospheric refraction. |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
As a side effect, the time of the $coord object may be set from the |
284
|
|
|
|
|
|
|
$coord2 object. |
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
By default, atmospheric refraction is taken into account in the |
287
|
|
|
|
|
|
|
calculation of elevation, using the C method. |
288
|
|
|
|
|
|
|
This better represents the observed position in the sky when the object |
289
|
|
|
|
|
|
|
is above the horizon, but produces a gap in the data when the object |
290
|
|
|
|
|
|
|
passes below the horizon, since I have no refraction equations for rock. |
291
|
|
|
|
|
|
|
See the C documentation for details. |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
The invocant's C method is the one used; that |
294
|
|
|
|
|
|
|
is, if C<$coord> and C<$coord2> have different |
295
|
|
|
|
|
|
|
C methods, the C<$coord> object's method is |
296
|
|
|
|
|
|
|
used. |
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
If you want to ignore atmospheric refraction (and not have a gap in your |
299
|
|
|
|
|
|
|
data), set the C attribute of the $coord object |
300
|
|
|
|
|
|
|
to any value Perl considers false (e.g. 0). |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
The algorithm for position is the author's, but he confesses to having |
303
|
|
|
|
|
|
|
to refer to T. S. Kelso's "Computers and Satellites" |
304
|
|
|
|
|
|
|
column in "Satellite Times", November/December 1995, titled "Orbital |
305
|
|
|
|
|
|
|
Coordinate Systems, Part II" and available at |
306
|
|
|
|
|
|
|
F to get the signs right. |
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
If velocities are available for both bodies involved in the calculation, |
309
|
|
|
|
|
|
|
they will be returned after the position (i.e. you get a six-element |
310
|
|
|
|
|
|
|
array back instead of a three-element array). The velocity of a point |
311
|
|
|
|
|
|
|
specified in Earth-fixed coordinates (e.g. geodetic latitude, longitude, |
312
|
|
|
|
|
|
|
and altitude) is assumed to be the rotational velocity of the Earth at |
313
|
|
|
|
|
|
|
that point. The returned velocities are azimuthal velocity in radians |
314
|
|
|
|
|
|
|
per second (B radians of azimuth, which get smaller as you go |
315
|
|
|
|
|
|
|
farther away from the Equator), elevational velocity in radians per |
316
|
|
|
|
|
|
|
second, and velocity in recession in kilometers per second, in that |
317
|
|
|
|
|
|
|
order. |
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
If velocities are available for both bodies B the C<$coord2> object |
320
|
|
|
|
|
|
|
has its C attribute set, the returned array will contain |
321
|
|
|
|
|
|
|
seven elements, with the seventh being the Doppler shift. |
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
The algorithm for recessional velocity comes from John A. Magliacane's |
324
|
|
|
|
|
|
|
C program, available at |
325
|
|
|
|
|
|
|
L. The transverse velocity |
326
|
|
|
|
|
|
|
computations are the author's, but use the same basic approach: vector |
327
|
|
|
|
|
|
|
dot product of the velocity vector with a unit vector in the appropriate |
328
|
|
|
|
|
|
|
direction, the latter generated by the appropriate (I hope!) vector |
329
|
|
|
|
|
|
|
cross products. |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
Velocity conversions to C appear to me to be mostly sane. See |
332
|
|
|
|
|
|
|
L, below, for details. |
333
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
If velocities are available I you have provided a non-zero value |
335
|
|
|
|
|
|
|
for the C attribute, you will get the Doppler shift as the |
336
|
|
|
|
|
|
|
seventh element of the returned array. The I about velocity in |
337
|
|
|
|
|
|
|
recession apply to the Doppler shift as well. The frequency is from the |
338
|
|
|
|
|
|
|
C<$coord2> object. Getting the frequency from the C<$coord> object used |
339
|
|
|
|
|
|
|
to be supported as a fallback, but now results in an exception. |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
=item ( $azimuth, $elevation, $range ) = $coord->azel_offset( $offset ); |
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
This method computes the azimuth, elevation, and range if the C<$coord> |
344
|
|
|
|
|
|
|
object as seen from the location stored in the C<$coord> object's |
345
|
|
|
|
|
|
|
C attribute. The functionality is as above, except for the fact |
346
|
|
|
|
|
|
|
that in the above version the station is the invocant, whereas in this |
347
|
|
|
|
|
|
|
version the orbiting body is the invocant. |
348
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
This version also returns velocities if they are available for both |
350
|
|
|
|
|
|
|
bodies, and Doppler shift if in addition the C attribute of |
351
|
|
|
|
|
|
|
the invocant is set. |
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
=cut |
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
sub azel_offset { |
356
|
21993
|
|
|
21993
|
1
|
41855
|
my ( $self, $trn2, $offset ) = _expand_args_default_station( @_ ); |
357
|
21993
|
50
|
|
|
|
47977
|
$self->{debug} and do { |
358
|
0
|
|
|
|
|
0
|
require Data::Dumper; |
359
|
0
|
|
|
|
|
0
|
local $Data::Dumper::Terse = 1; |
360
|
0
|
|
|
|
|
0
|
print "Debug azel_offset - ", Data::Dumper::Dumper ($self, $trn2, $offset); |
361
|
|
|
|
|
|
|
}; |
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
# _local_cartesian() returns NEU coordinates. Converting these to |
364
|
|
|
|
|
|
|
# spherical gets Azimuth (clockwise from North), Elevation, and |
365
|
|
|
|
|
|
|
# Range. |
366
|
|
|
|
|
|
|
|
367
|
21993
|
|
|
|
|
47661
|
my ( $azimuth, $elevation, $range, @velocity ) = |
368
|
|
|
|
|
|
|
_convert_cartesian_to_spherical( |
369
|
|
|
|
|
|
|
$self->_local_cartesian( $trn2 ) |
370
|
|
|
|
|
|
|
); |
371
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
# If the velocity and frequency are defined, we provide the Doppler |
373
|
|
|
|
|
|
|
# shift as well. |
374
|
|
|
|
|
|
|
|
375
|
21993
|
100
|
|
|
|
48370
|
if ( defined $velocity[2] ) { |
376
|
16896
|
|
|
|
|
42379
|
my $freq = $trn2->get( 'frequency' ); |
377
|
16896
|
100
|
|
|
|
35035
|
if ( not defined $freq ) { |
378
|
16895
|
|
|
|
|
28161
|
$freq = $self->get( 'frequency' ); |
379
|
16895
|
50
|
|
|
|
34025
|
defined $freq |
380
|
|
|
|
|
|
|
and croak 'Calculation of Doppler shift based ', |
381
|
|
|
|
|
|
|
'on the frequency attribute of the observing ', |
382
|
|
|
|
|
|
|
'station is not allowed'; |
383
|
|
|
|
|
|
|
} |
384
|
16896
|
100
|
|
|
|
29043
|
if ( defined $freq ) { |
385
|
1
|
|
|
|
|
4
|
$velocity[3] = - $freq * $velocity[2] / SPEED_OF_LIGHT; |
386
|
|
|
|
|
|
|
} |
387
|
|
|
|
|
|
|
} |
388
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
# Adjust for upper limb and refraction if needed. |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
$offset |
392
|
21993
|
100
|
|
|
|
42659
|
and $elevation += atan2( |
393
|
|
|
|
|
|
|
$trn2->get( 'diameter' ) * $offset / 2, |
394
|
|
|
|
|
|
|
$range, |
395
|
|
|
|
|
|
|
); |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
$self->{refraction} and |
398
|
21993
|
50
|
|
|
|
56373
|
$elevation = $self->correct_for_refraction( $elevation ); |
399
|
|
|
|
|
|
|
|
400
|
21993
|
|
|
|
|
76259
|
return ( $azimuth, $elevation, $range, @velocity ); |
401
|
|
|
|
|
|
|
} |
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
=item $coord2 = $coord->clone (); |
404
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
This method does a deep clone of an object, producing a different |
406
|
|
|
|
|
|
|
but identical object. |
407
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
At the moment, it's really just a wrapper for Clone::clone. |
409
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
=cut |
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
sub clone { |
413
|
3
|
|
|
3
|
1
|
14
|
my ( $self ) = @_; |
414
|
3
|
|
|
|
|
85
|
return Clone::clone( $self ); |
415
|
|
|
|
|
|
|
} |
416
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
=item $elevation = $coord->correct_for_refraction ($elevation); |
418
|
|
|
|
|
|
|
|
419
|
|
|
|
|
|
|
This method corrects the given angular elevation for atmospheric |
420
|
|
|
|
|
|
|
refraction. This is done only if the elevation has a reasonable chance |
421
|
|
|
|
|
|
|
of being visible; that is, if the elevation before correction is not |
422
|
|
|
|
|
|
|
more than two degrees below either the 'horizon' attribute or zero, |
423
|
|
|
|
|
|
|
whichever is lesser. Sorry for the discontinuity thus introduced, but I |
424
|
|
|
|
|
|
|
did not wish to carry the refraction calculation too low because I am |
425
|
|
|
|
|
|
|
uncertain about the behavior of the algorithm, and I do not have a |
426
|
|
|
|
|
|
|
corresponding algorithm for refraction through magma. |
427
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
This method can also be called as a class method, in which case the |
429
|
|
|
|
|
|
|
correction is applied only if the uncorrected elevation is greater than |
430
|
|
|
|
|
|
|
minus two degrees. It is really only exposed for testing purposes (hence |
431
|
|
|
|
|
|
|
the cumbersome name). The azel() method calls it for you if the |
432
|
|
|
|
|
|
|
C attribute is true. |
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
The algorithm for atmospheric refraction comes from Thorfinn |
435
|
|
|
|
|
|
|
Saemundsson's article in "Sky and Telescope", volume 72, page 70 |
436
|
|
|
|
|
|
|
(July 1986) as reported Jean Meeus' "Astronomical Algorithms", |
437
|
|
|
|
|
|
|
2nd Edition, chapter 16, page 106, and includes the adjustment |
438
|
|
|
|
|
|
|
suggested by Meeus. |
439
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
=cut |
441
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
sub correct_for_refraction { |
443
|
21077
|
|
|
21077
|
1
|
31147
|
my $self = shift; |
444
|
21077
|
|
|
|
|
26109
|
my $elevation = shift; |
445
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
# If called as a static method, our effective horizon is zero. If |
447
|
|
|
|
|
|
|
# called as a normal method, our effective horizon is the lesser of |
448
|
|
|
|
|
|
|
# the 'horizon' setting or zero. |
449
|
|
|
|
|
|
|
|
450
|
21077
|
100
|
|
|
|
49140
|
my $horizon = ref $self ? min( 0, $self->get( 'horizon' ) ) : 0; |
451
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
# We exclude anything with an elevation <= 2 degrees below our |
453
|
|
|
|
|
|
|
# effective horizon; this is presumed to be not visible, since the |
454
|
|
|
|
|
|
|
# maximum deflection is about 35 minutes of arc. This is not |
455
|
|
|
|
|
|
|
# portable to (e.g.) Venus. |
456
|
|
|
|
|
|
|
|
457
|
21077
|
100
|
|
|
|
51022
|
if ( $elevation > $horizon - TWO_DEGREES ) { |
458
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
# Thorsteinn Saemundsson's algorithm for refraction, as reported |
460
|
|
|
|
|
|
|
# in Meeus, page 106, equation 16.4, and adjusted per the |
461
|
|
|
|
|
|
|
# suggestion in Meeus' following paragraph. Thorsteinn's |
462
|
|
|
|
|
|
|
# formula is in terms of angles in degrees and produces |
463
|
|
|
|
|
|
|
# a correction in minutes of arc. Meeus reports the original |
464
|
|
|
|
|
|
|
# publication as Sky and Telescope, volume 72 page 70, July 1986. |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
# In deference to Thorsteinn I will point out: |
467
|
|
|
|
|
|
|
# * The Icelanders do not use family names. The "Saemundsson" |
468
|
|
|
|
|
|
|
# simply means his father's name was Saemund. |
469
|
|
|
|
|
|
|
# * I have transcribed the names into 7-bit characters. |
470
|
|
|
|
|
|
|
# "Thorsteinn" actually does not begin with "Th" but with |
471
|
|
|
|
|
|
|
# the letter "Thorn." Similarly, the "ae" in "Saemund" is |
472
|
|
|
|
|
|
|
# supposed to be a ligature (i.e. squished into one letter). |
473
|
|
|
|
|
|
|
|
474
|
5961
|
|
|
|
|
12883
|
my $deg = rad2deg ($elevation); |
475
|
5961
|
|
|
|
|
15521
|
my $correction = 1.02 / tan (deg2rad ($deg + 10.3/($deg + 5.11))) + |
476
|
|
|
|
|
|
|
.0019279; |
477
|
5961
|
50
|
|
|
|
12195
|
$self->get ('debug') and print <
|
478
|
|
|
|
|
|
|
Debug correct_for_refraction |
479
|
|
|
|
|
|
|
input: $deg degrees of arc |
480
|
|
|
|
|
|
|
correction: $correction minutes of arc |
481
|
|
|
|
|
|
|
eod |
482
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
# End of Thorsteinn's algorithm. |
484
|
|
|
|
|
|
|
|
485
|
5961
|
|
|
|
|
13852
|
$correction = deg2rad ($correction / 60); |
486
|
5961
|
|
|
|
|
9147
|
$elevation += $correction; |
487
|
|
|
|
|
|
|
} |
488
|
21077
|
|
|
|
|
32944
|
return $elevation; |
489
|
|
|
|
|
|
|
} |
490
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
=item $angle = $coord->dip (); |
492
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
This method calculates the dip angle of the horizon due to the |
494
|
|
|
|
|
|
|
altitude of the body, in radians. It will be negative for a location |
495
|
|
|
|
|
|
|
above the surface of the reference ellipsoid, and positive for a |
496
|
|
|
|
|
|
|
location below the surface. |
497
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
The algorithm is simple enough to be the author's. |
499
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
=cut |
501
|
|
|
|
|
|
|
|
502
|
|
|
|
|
|
|
sub dip { |
503
|
917
|
|
|
917
|
1
|
1470
|
my $self = shift; |
504
|
917
|
|
|
|
|
1871
|
my (undef, undef, $h) = $self->geodetic (); |
505
|
917
|
|
|
|
|
1842
|
my (undef, undef, $rho) = $self->geocentric (); |
506
|
917
|
50
|
|
|
|
3312
|
return $h >= 0 ? |
507
|
|
|
|
|
|
|
- acos (($rho - $h) / $rho) : |
508
|
|
|
|
|
|
|
acos ($rho / ($rho - $h)); |
509
|
|
|
|
|
|
|
} |
510
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
=item $coord = $coord->dynamical ($time); |
512
|
|
|
|
|
|
|
|
513
|
|
|
|
|
|
|
This method sets the dynamical time represented by the object. |
514
|
|
|
|
|
|
|
|
515
|
|
|
|
|
|
|
This method can also be called as a class method, in which case it |
516
|
|
|
|
|
|
|
instantiates the desired object. |
517
|
|
|
|
|
|
|
|
518
|
|
|
|
|
|
|
The algorithm for converting this to universal time comes from Jean |
519
|
|
|
|
|
|
|
Meeus' "Astronomical Algorithms", 2nd Edition, Chapter 10, pages 78ff. |
520
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
=item $time = $coord->dynamical (); |
522
|
|
|
|
|
|
|
|
523
|
|
|
|
|
|
|
This method returns the dynamical time previously set, or the |
524
|
|
|
|
|
|
|
universal time previously set, converted to dynamical. |
525
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
The algorithm comes from Jean Meeus' "Astronomical Algorithms", 2nd |
527
|
|
|
|
|
|
|
Edition, Chapter 10, pages 78ff. |
528
|
|
|
|
|
|
|
|
529
|
|
|
|
|
|
|
=cut |
530
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
sub dynamical { |
532
|
58444
|
|
|
58444
|
1
|
96819
|
my ($self, @args) = @_; |
533
|
58444
|
100
|
|
|
|
107920
|
unless (@args) { |
534
|
58315
|
50
|
|
|
|
111393
|
ref $self or croak <
|
535
|
|
|
|
|
|
|
Error - The dynamical() method may not be called as a class method |
536
|
|
|
|
|
|
|
unless you specify arguments. |
537
|
|
|
|
|
|
|
eod |
538
|
|
|
|
|
|
|
return ($self->{dynamical} ||= $self->{universal} + |
539
|
58315
|
|
33
|
|
|
181976
|
dynamical_delta ($self->{universal} || croak <
|
|
|
|
66
|
|
|
|
|
540
|
|
|
|
|
|
|
Error - Universal time of object has not been set. |
541
|
|
|
|
|
|
|
eod |
542
|
|
|
|
|
|
|
} |
543
|
|
|
|
|
|
|
|
544
|
129
|
50
|
|
|
|
276
|
if (@args == 1) { |
545
|
129
|
|
|
|
|
263
|
my $time = shift @args; |
546
|
129
|
100
|
|
|
|
305
|
$self = $self->new () unless ref $self; |
547
|
129
|
|
|
|
|
267
|
$self->{_no_set}++; # Supress running the model if any |
548
|
129
|
|
|
|
|
336
|
$self->universal ($time - dynamical_delta ($time)); |
549
|
129
|
|
|
|
|
236
|
$self->{dynamical} = $time; |
550
|
129
|
|
|
|
|
221
|
--$self->{_no_set}; # Undo supression of model |
551
|
129
|
|
|
|
|
242
|
$self->_call_time_set (); # Run the model if any |
552
|
|
|
|
|
|
|
} else { |
553
|
0
|
|
|
|
|
0
|
croak <
|
554
|
|
|
|
|
|
|
Error - The dynamical() method must be called with either zero |
555
|
|
|
|
|
|
|
arguments (to retrieve the time) or one argument (to set the |
556
|
|
|
|
|
|
|
time). |
557
|
|
|
|
|
|
|
eod |
558
|
|
|
|
|
|
|
} |
559
|
|
|
|
|
|
|
|
560
|
129
|
|
|
|
|
397
|
return $self; |
561
|
|
|
|
|
|
|
} |
562
|
|
|
|
|
|
|
|
563
|
|
|
|
|
|
|
=item $coord = $coord->ecef($x, $y, $z, $xdot, $ydot, $zdot) |
564
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
This method sets the coordinates represented by the object in terms of |
566
|
|
|
|
|
|
|
L in kilometers, with |
567
|
|
|
|
|
|
|
the x axis being latitude 0 longitude 0, the y axis being latitude 0 |
568
|
|
|
|
|
|
|
longitude 90 degrees east, and the z axis being latitude 90 degrees |
569
|
|
|
|
|
|
|
north. The velocities in kilometers per second are optional, and will |
570
|
|
|
|
|
|
|
default to zero. ECEF velocities are considered to be relative to the |
571
|
|
|
|
|
|
|
surface of the Earth; when converting to ECI, the rotational velocity of |
572
|
|
|
|
|
|
|
the Earth will be added in. |
573
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
B that prior to version 0.022_01, the documentation said that the |
575
|
|
|
|
|
|
|
default velocity was the rotational velocity of the Earth. This was not |
576
|
|
|
|
|
|
|
correct B The functionality of the code itself in |
577
|
|
|
|
|
|
|
this respect did not change. |
578
|
|
|
|
|
|
|
|
579
|
|
|
|
|
|
|
The object itself is returned. |
580
|
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
This method can also be called as a class method, in which case it |
582
|
|
|
|
|
|
|
instantiates the desired object. |
583
|
|
|
|
|
|
|
|
584
|
|
|
|
|
|
|
=item ($x, $y, $z, $xdot, $ydot, $zdot) = $coord->ecef(); |
585
|
|
|
|
|
|
|
|
586
|
|
|
|
|
|
|
This method returns the object's L
|
587
|
|
|
|
|
|
|
coordinates>. |
588
|
|
|
|
|
|
|
|
589
|
|
|
|
|
|
|
If the original coordinate setting was in an inertial system (e.g. eci, |
590
|
|
|
|
|
|
|
equatorial, or ecliptic) B the absolute difference between the |
591
|
|
|
|
|
|
|
current value of 'equinox_dynamical' and the current dynamical() setting |
592
|
|
|
|
|
|
|
is greater than the value of $Astro::Coord::ECI::EQUINOX_TOLERANCE, the |
593
|
|
|
|
|
|
|
coordinates will be precessed to the current dynamical time before |
594
|
|
|
|
|
|
|
conversion. Yes, this should be done any time the equinox is not the |
595
|
|
|
|
|
|
|
current time, but for satellite prediction precession by a year or |
596
|
|
|
|
|
|
|
so does not seem to make much difference in practice. The default value |
597
|
|
|
|
|
|
|
of $Astro::Coord:ECI::EQUINOX_TOLERANCE is 365 days. B that if |
598
|
|
|
|
|
|
|
this behavior or the default value of $EQUINOX_TOLERANCE begins to look |
599
|
|
|
|
|
|
|
like a bug, it will be changed, and noted in the documentation. |
600
|
|
|
|
|
|
|
|
601
|
|
|
|
|
|
|
Some velocity conversions involving C appear to me to be mostly |
602
|
|
|
|
|
|
|
sane. See L, below, for |
603
|
|
|
|
|
|
|
details. |
604
|
|
|
|
|
|
|
|
605
|
|
|
|
|
|
|
=cut |
606
|
|
|
|
|
|
|
|
607
|
|
|
|
|
|
|
sub ecef { |
608
|
57640
|
|
|
57640
|
1
|
93103
|
my ($self, @args) = @_; |
609
|
|
|
|
|
|
|
|
610
|
57640
|
|
|
|
|
106818
|
$self = $self->_check_coord (ecef => \@args); |
611
|
|
|
|
|
|
|
|
612
|
57640
|
100
|
|
|
|
118156
|
unless (@args) { |
613
|
55526
|
|
50
|
|
|
109993
|
my $cache = ($self->{_ECI_cache} ||= {}); |
614
|
55526
|
100
|
|
|
|
112683
|
return @{$cache->{fixed}{ecef}} if $cache->{fixed}{ecef}; |
|
25605
|
|
|
|
|
60046
|
|
615
|
29921
|
50
|
|
|
|
72691
|
return $self->_convert_eci_to_ecef () if $self->{inertial}; |
616
|
0
|
|
|
|
|
0
|
croak <
|
617
|
|
|
|
|
|
|
Error - Object has not been initialized. |
618
|
|
|
|
|
|
|
eod |
619
|
|
|
|
|
|
|
} |
620
|
|
|
|
|
|
|
|
621
|
2114
|
50
|
|
|
|
6610
|
@args == 3 and push @args, 0, 0, 0; |
622
|
|
|
|
|
|
|
|
623
|
2114
|
50
|
|
|
|
3900
|
if (@args == 6) { |
624
|
2114
|
|
|
|
|
3531
|
foreach my $key (@kilatr) {delete $self->{$key}} |
|
10570
|
|
|
|
|
21693
|
|
625
|
|
|
|
|
|
|
## $self->{_ECI_cache}{fixed}{ecef} = \@args; |
626
|
2114
|
|
|
|
|
6344
|
$self->{_ECI_cache} = {fixed => {ecef => \@args}}; |
627
|
2114
|
|
|
|
|
4097
|
$self->{specified} = 'ecef'; |
628
|
2114
|
|
|
|
|
3740
|
$self->{inertial} = 0; |
629
|
|
|
|
|
|
|
} else { |
630
|
0
|
|
|
|
|
0
|
croak <
|
631
|
|
|
|
|
|
|
Error - The ecef() method must be called with either zero arguments (to |
632
|
|
|
|
|
|
|
retrieve coordinates), three arguments (to set coordinates, |
633
|
|
|
|
|
|
|
with velocity defaulting to zero) or six arguments. |
634
|
|
|
|
|
|
|
eod |
635
|
|
|
|
|
|
|
} |
636
|
|
|
|
|
|
|
|
637
|
2114
|
|
|
|
|
3577
|
return $self; |
638
|
|
|
|
|
|
|
} |
639
|
|
|
|
|
|
|
|
640
|
|
|
|
|
|
|
=item $coord = $coord->eci ($x, $y, $z, $xdot, $ydot, $zdot, $time) |
641
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
This method sets the coordinates represented by the object in terms of |
643
|
|
|
|
|
|
|
L in kilometers, time being |
644
|
|
|
|
|
|
|
universal time, the x axis being 0 hours L 0 degrees |
645
|
|
|
|
|
|
|
L, y being 6 hours L 0 degrees |
646
|
|
|
|
|
|
|
L, and z being 90 degrees north L. The |
647
|
|
|
|
|
|
|
velocities in kilometers per second are optional. If omitted, the object |
648
|
|
|
|
|
|
|
will be considered not to have a velocity. B
|
649
|
|
|
|
|
|
|
with version 0.022_01.> Before that, the velocity defaulted to 0. |
650
|
|
|
|
|
|
|
|
651
|
|
|
|
|
|
|
The time argument is optional if the time represented by the object |
652
|
|
|
|
|
|
|
has already been set (e.g. by the universal() or dynamical() methods). |
653
|
|
|
|
|
|
|
|
654
|
|
|
|
|
|
|
The object itself is returned. |
655
|
|
|
|
|
|
|
|
656
|
|
|
|
|
|
|
This method can also be called as a class method, in which case it |
657
|
|
|
|
|
|
|
instantiates the desired object. In this case the time is not optional. |
658
|
|
|
|
|
|
|
|
659
|
|
|
|
|
|
|
The algorithm for converting from ECI to geocentric coordinates and |
660
|
|
|
|
|
|
|
back is based on the description of ECI coordinates in T. S. Kelso's |
661
|
|
|
|
|
|
|
"Computers and Satellites" column in "Satellite Times", |
662
|
|
|
|
|
|
|
September/October 1995, titled "Orbital Coordinate Systems, Part I" |
663
|
|
|
|
|
|
|
and available at F. |
664
|
|
|
|
|
|
|
|
665
|
|
|
|
|
|
|
=item ($x, $y, $z, $xdot, $ydot, $zdot) = $coord->eci($time); |
666
|
|
|
|
|
|
|
|
667
|
|
|
|
|
|
|
This method returns the L |
668
|
|
|
|
|
|
|
of the object at the given time. The time argument is actually |
669
|
|
|
|
|
|
|
optional if the time represented by the object has already been set. |
670
|
|
|
|
|
|
|
|
671
|
|
|
|
|
|
|
If you specify a time, the time represented by the object will be set |
672
|
|
|
|
|
|
|
to that time. The net effect of specifying a time is equivalent to |
673
|
|
|
|
|
|
|
|
674
|
|
|
|
|
|
|
($x, $y, $z, $xdot, $ydot, $zdot) = $coord->universal($time)->eci() |
675
|
|
|
|
|
|
|
|
676
|
|
|
|
|
|
|
If the original coordinate setting was in a non-inertial system (e.g. |
677
|
|
|
|
|
|
|
ECEF or geodetic), the equinox_dynamical attribute will be set to the |
678
|
|
|
|
|
|
|
object's dynamical time. |
679
|
|
|
|
|
|
|
|
680
|
|
|
|
|
|
|
Velocities will be returned if they were originally provided. B
|
681
|
|
|
|
|
|
|
a change introduced in version 0.022_01.> Prior to that version, |
682
|
|
|
|
|
|
|
velocities were always returned. |
683
|
|
|
|
|
|
|
|
684
|
|
|
|
|
|
|
Some velocity conversions involving C appear to me to be mostly |
685
|
|
|
|
|
|
|
sane. See L, below, for |
686
|
|
|
|
|
|
|
details. |
687
|
|
|
|
|
|
|
|
688
|
|
|
|
|
|
|
=cut |
689
|
|
|
|
|
|
|
|
690
|
|
|
|
|
|
|
sub eci { |
691
|
66903
|
|
|
66903
|
1
|
137537
|
my ($self, @args) = @_; |
692
|
|
|
|
|
|
|
|
693
|
66903
|
|
|
|
|
137423
|
$self = $self->_check_coord (eci => \@args); |
694
|
|
|
|
|
|
|
|
695
|
66903
|
100
|
|
|
|
132004
|
unless (@args) { |
696
|
32740
|
|
50
|
|
|
63793
|
my $cache = ($self->{_ECI_cache} ||= {}); |
697
|
32740
|
100
|
|
|
|
63879
|
return @{$cache->{inertial}{eci}} if $cache->{inertial}{eci}; |
|
32733
|
|
|
|
|
97298
|
|
698
|
7
|
50
|
|
|
|
42
|
return $self->_convert_ecef_to_eci () if $self->{specified}; |
699
|
0
|
|
|
|
|
0
|
croak <
|
700
|
|
|
|
|
|
|
Error - Object has not been initialized. |
701
|
|
|
|
|
|
|
eod |
702
|
|
|
|
|
|
|
|
703
|
|
|
|
|
|
|
} |
704
|
|
|
|
|
|
|
|
705
|
|
|
|
|
|
|
## @args == 3 and push @args, 0, 0, 0; |
706
|
|
|
|
|
|
|
|
707
|
34163
|
50
|
66
|
|
|
93724
|
if (@args == 3 || @args == 6) { |
708
|
34163
|
|
|
|
|
56609
|
foreach my $key (@kilatr) {delete $self->{$key}} |
|
170815
|
|
|
|
|
296091
|
|
709
|
34163
|
|
|
|
|
91677
|
$self->{_ECI_cache} = {inertial => {eci => \@args}}; |
710
|
34163
|
|
|
|
|
62740
|
$self->{specified} = 'eci'; |
711
|
34163
|
|
|
|
|
52148
|
$self->{inertial} = 1; |
712
|
|
|
|
|
|
|
} else { |
713
|
0
|
|
|
|
|
0
|
croak <
|
714
|
|
|
|
|
|
|
Error - The eci() method must be called with either zero or one |
715
|
|
|
|
|
|
|
arguments (to retrieve coordinates), three or four arguments |
716
|
|
|
|
|
|
|
(to set coordinates, with velocity defaulting to zero), or |
717
|
|
|
|
|
|
|
six or seven arguments. |
718
|
|
|
|
|
|
|
eod |
719
|
|
|
|
|
|
|
} |
720
|
34163
|
|
|
|
|
58227
|
return $self; |
721
|
|
|
|
|
|
|
} |
722
|
|
|
|
|
|
|
|
723
|
|
|
|
|
|
|
=item $coord = $coord->ecliptic_cartesian( $X, $Y, $Z, $time ); |
724
|
|
|
|
|
|
|
|
725
|
|
|
|
|
|
|
This method sets the Cartesian L coordinates represented by |
726
|
|
|
|
|
|
|
the object in terms of C (kilometers toward the vernal equinox), C |
727
|
|
|
|
|
|
|
(90 degrees along the ecliptic from C), and C (toward ecliptic |
728
|
|
|
|
|
|
|
North). The time is universal time. The object itself is returned. |
729
|
|
|
|
|
|
|
|
730
|
|
|
|
|
|
|
The time argument is optional if the time represented by the object has |
731
|
|
|
|
|
|
|
already been set (e.g. by the universal() or dynamical() methods). |
732
|
|
|
|
|
|
|
|
733
|
|
|
|
|
|
|
This method can also be called as a class method, in which case it |
734
|
|
|
|
|
|
|
instantiates the desired object. In this case the time is not optional. |
735
|
|
|
|
|
|
|
|
736
|
|
|
|
|
|
|
B you can pass in velocities (before the C<$time>) but they are |
737
|
|
|
|
|
|
|
unsupported, meaning that I can not at this point say whether they will |
738
|
|
|
|
|
|
|
be transformed sanely, much less correctly. B. |
739
|
|
|
|
|
|
|
|
740
|
|
|
|
|
|
|
=item ( $X, $Y, $Z) = $coord->ecliptic_cartesian( $time ); |
741
|
|
|
|
|
|
|
|
742
|
|
|
|
|
|
|
This method returns the Cartesian ecliptic coordinates of the object at |
743
|
|
|
|
|
|
|
the given time. The time is optional if the time represented by the |
744
|
|
|
|
|
|
|
object has already been set (e.g. by the universal() or dynamical() |
745
|
|
|
|
|
|
|
methods). |
746
|
|
|
|
|
|
|
|
747
|
|
|
|
|
|
|
B velocities are unsupported by this method. That means you may |
748
|
|
|
|
|
|
|
(or may not, depending on the coordinates originally set) get them back, |
749
|
|
|
|
|
|
|
but I have not looked into whether they are actually correct. |
750
|
|
|
|
|
|
|
|
751
|
|
|
|
|
|
|
=cut |
752
|
|
|
|
|
|
|
|
753
|
|
|
|
|
|
|
sub ecliptic_cartesian { |
754
|
14420
|
|
|
14420
|
1
|
33227
|
my ( $self, @args ) = @_; |
755
|
|
|
|
|
|
|
|
756
|
14420
|
|
|
|
|
26207
|
$self = $self->_check_coord( ecliptic_cartesian => \@args ); |
757
|
|
|
|
|
|
|
|
758
|
14420
|
100
|
|
|
|
25492
|
if ( @args ) { |
759
|
13058
|
50
|
|
|
|
25582
|
@args % 3 |
760
|
|
|
|
|
|
|
and croak <<'EOD'; |
761
|
|
|
|
|
|
|
Error - The ecliptic_cartesian() method must be called with either zero |
762
|
|
|
|
|
|
|
or one arguments (to retrieve coordinates), or three or four |
763
|
|
|
|
|
|
|
arguments (to set coordinates). There is currently no six or |
764
|
|
|
|
|
|
|
seven argument version. |
765
|
|
|
|
|
|
|
EOD |
766
|
13058
|
|
|
|
|
24568
|
my $epsilon = $self->obliquity(); |
767
|
13058
|
|
|
|
|
19936
|
my $sin_epsilon = sin $epsilon; |
768
|
13058
|
|
|
|
|
18403
|
my $cos_epsilon = cos $epsilon; |
769
|
13058
|
|
|
|
|
16770
|
my @eci; |
770
|
13058
|
|
|
|
|
29587
|
for ( my $inx = 0; $inx < @args; $inx += 3 ) { |
771
|
13058
|
|
|
|
|
21806
|
push @eci, $args[ $inx ]; |
772
|
13058
|
|
|
|
|
26767
|
push @eci, - $args[ $inx + 2 ] * $sin_epsilon + |
773
|
|
|
|
|
|
|
$args[ $inx + 1 ] * $cos_epsilon; |
774
|
13058
|
|
|
|
|
33036
|
push @eci, $args[ $inx + 2 ] * $cos_epsilon + |
775
|
|
|
|
|
|
|
$args[ $inx + 1 ] * $sin_epsilon; |
776
|
|
|
|
|
|
|
} |
777
|
13058
|
|
|
|
|
31482
|
$self->eci( @eci ); |
778
|
13058
|
|
|
|
|
23373
|
$self->{_ECI_cache}{inertial}{ecliptic_cartesian} = \@args; |
779
|
13058
|
|
|
|
|
19956
|
$self->{inertial} = 1; |
780
|
13058
|
|
|
|
|
18286
|
$self->{specified} = 'ecliptic_cartesian'; |
781
|
13058
|
|
|
|
|
25190
|
return $self; |
782
|
|
|
|
|
|
|
} else { |
783
|
|
|
|
|
|
|
$self->{_ECI_cache}{inertial}{ecliptic_cartesian} |
784
|
|
|
|
|
|
|
and return @{ |
785
|
1362
|
100
|
|
|
|
2633
|
$self->{_ECI_cache}{inertial}{ecliptic_cartesian} |
786
|
237
|
|
|
|
|
669
|
}; |
787
|
1125
|
|
|
|
|
2163
|
my @eci = $self->eci(); |
788
|
1125
|
|
|
|
|
2211
|
my $epsilon = $self->obliquity(); |
789
|
1125
|
|
|
|
|
1714
|
my $sin_epsilon = sin $epsilon; |
790
|
1125
|
|
|
|
|
1660
|
my $cos_epsilon = cos $epsilon; |
791
|
1125
|
|
|
|
|
1425
|
my @ecliptic_cartesian; |
792
|
1125
|
|
|
|
|
2430
|
for ( my $inx = 0; $inx < @eci; $inx += 3 ) { |
793
|
1125
|
|
|
|
|
2267
|
push @ecliptic_cartesian, $eci[ $inx ]; |
794
|
1125
|
|
|
|
|
2294
|
push @ecliptic_cartesian, $eci[ $inx + 2 ] * $sin_epsilon + |
795
|
|
|
|
|
|
|
$eci[ $inx + 1 ] * $cos_epsilon; |
796
|
1125
|
|
|
|
|
2882
|
push @ecliptic_cartesian, $eci[ $inx + 2 ] * $cos_epsilon - |
797
|
|
|
|
|
|
|
$eci[ $inx + 1 ] * $sin_epsilon; |
798
|
|
|
|
|
|
|
} |
799
|
|
|
|
|
|
|
return @{ |
800
|
1125
|
|
|
|
|
1570
|
$self->{_ECI_cache}{inertial}{ecliptic_cartesian} = |
801
|
|
|
|
|
|
|
\@ecliptic_cartesian |
802
|
1125
|
|
|
|
|
4441
|
}; |
803
|
|
|
|
|
|
|
} |
804
|
|
|
|
|
|
|
|
805
|
|
|
|
|
|
|
} |
806
|
|
|
|
|
|
|
|
807
|
|
|
|
|
|
|
=item $coord = $coord->ecliptic ($latitude, $longitude, $range, $time); |
808
|
|
|
|
|
|
|
|
809
|
|
|
|
|
|
|
This method sets the L coordinates represented by the object |
810
|
|
|
|
|
|
|
in terms of L and L in radians, |
811
|
|
|
|
|
|
|
and the range to the object in kilometers, time being universal time. |
812
|
|
|
|
|
|
|
The object itself is returned. |
813
|
|
|
|
|
|
|
|
814
|
|
|
|
|
|
|
The time argument is optional if the time represented by the object has |
815
|
|
|
|
|
|
|
already been set (e.g. by the universal() or dynamical() methods). |
816
|
|
|
|
|
|
|
|
817
|
|
|
|
|
|
|
The latitude should be a number between -PI/2 and PI/2 radians |
818
|
|
|
|
|
|
|
inclusive. The longitude should be a number between -2*PI and 2*PI |
819
|
|
|
|
|
|
|
radians inclusive. The increased range (one would expect -PI to PI) is |
820
|
|
|
|
|
|
|
because in some astronomical usages latitudes outside the range + or - |
821
|
|
|
|
|
|
|
180 degrees are employed. A warning will be generated if either of these |
822
|
|
|
|
|
|
|
range checks fails. |
823
|
|
|
|
|
|
|
|
824
|
|
|
|
|
|
|
This method can also be called as a class method, in which case it |
825
|
|
|
|
|
|
|
instantiates the desired object. In this case the time is not optional. |
826
|
|
|
|
|
|
|
|
827
|
|
|
|
|
|
|
The algorithm for converting from ecliptic latitude and longitude to |
828
|
|
|
|
|
|
|
right ascension and declination comes from Jean Meeus' |
829
|
|
|
|
|
|
|
"Astronomical Algorithms", 2nd Edition, Chapter 13, page 93. |
830
|
|
|
|
|
|
|
|
831
|
|
|
|
|
|
|
=item ($latitude, $longitude, $range) = $coord->ecliptic ($time); |
832
|
|
|
|
|
|
|
|
833
|
|
|
|
|
|
|
This method returns the ecliptic latitude and longitude of the |
834
|
|
|
|
|
|
|
object at the given time. The time is optional if the time represented |
835
|
|
|
|
|
|
|
by the object has already been set (e.g. by the universal() or |
836
|
|
|
|
|
|
|
dynamical() methods). |
837
|
|
|
|
|
|
|
|
838
|
|
|
|
|
|
|
B velocities are not returned by this method. |
839
|
|
|
|
|
|
|
|
840
|
|
|
|
|
|
|
=cut |
841
|
|
|
|
|
|
|
|
842
|
|
|
|
|
|
|
sub ecliptic { |
843
|
14436
|
|
|
14436
|
1
|
29368
|
my ($self, @args) = @_; |
844
|
|
|
|
|
|
|
|
845
|
14436
|
|
|
|
|
28662
|
$self = $self->_check_coord( ecliptic => \@args ); |
846
|
|
|
|
|
|
|
|
847
|
14436
|
100
|
|
|
|
26088
|
if ( @args ) { |
848
|
|
|
|
|
|
|
|
849
|
13058
|
50
|
|
|
|
24950
|
@args % 3 |
850
|
|
|
|
|
|
|
and croak 'Arguments are in threes, plus an optional time'; |
851
|
13058
|
|
|
|
|
27772
|
$self->{_ECI_cache}{inertial}{ecliptic} = \@args; |
852
|
13058
|
|
|
|
|
28150
|
$self->ecliptic_cartesian( |
853
|
|
|
|
|
|
|
_convert_spherical_x_to_cartesian( @args ) ); |
854
|
13058
|
|
|
|
|
21734
|
$self->{specified} = 'ecliptic'; |
855
|
13058
|
|
|
|
|
17262
|
$self->{inertial} = 1; |
856
|
13058
|
|
|
|
|
25960
|
return $self; |
857
|
|
|
|
|
|
|
|
858
|
|
|
|
|
|
|
} else { |
859
|
|
|
|
|
|
|
|
860
|
1378
|
|
|
|
|
1782
|
return @{ $self->{_ECI_cache}{inertial}{ecliptic} ||= [ |
861
|
1378
|
|
100
|
|
|
4354
|
_convert_cartesian_to_spherical_x( |
862
|
|
|
|
|
|
|
$self->ecliptic_cartesian() ) |
863
|
|
|
|
|
|
|
] |
864
|
|
|
|
|
|
|
}; |
865
|
|
|
|
|
|
|
|
866
|
|
|
|
|
|
|
} |
867
|
|
|
|
|
|
|
} |
868
|
|
|
|
|
|
|
|
869
|
|
|
|
|
|
|
=item $longitude = $coord->ecliptic_longitude(); |
870
|
|
|
|
|
|
|
|
871
|
|
|
|
|
|
|
This method returns the ecliptic longitude of the body at its current |
872
|
|
|
|
|
|
|
time setting, in radians. It is really just a convenience method, since |
873
|
|
|
|
|
|
|
it is equivalent to C<< ( $coord->ecliptic() )[1] >>, and in fact that |
874
|
|
|
|
|
|
|
is how it is implemented. |
875
|
|
|
|
|
|
|
|
876
|
|
|
|
|
|
|
=cut |
877
|
|
|
|
|
|
|
|
878
|
|
|
|
|
|
|
sub ecliptic_longitude { |
879
|
0
|
|
|
0
|
1
|
0
|
my ( $self ) = @_; |
880
|
0
|
|
|
|
|
0
|
return ( $self->ecliptic() )[1]; |
881
|
|
|
|
|
|
|
} |
882
|
|
|
|
|
|
|
|
883
|
|
|
|
|
|
|
=item $seconds = $self->equation_of_time( $time ); |
884
|
|
|
|
|
|
|
|
885
|
|
|
|
|
|
|
This method returns the equation of time at the given B |
886
|
|
|
|
|
|
|
time. If the time is C, the invocant's dynamical time is used. |
887
|
|
|
|
|
|
|
|
888
|
|
|
|
|
|
|
The algorithm is from W. S. Smart's "Text-Book on Spherical Astronomy", |
889
|
|
|
|
|
|
|
as reported in Jean Meeus' "Astronomical Algorithms", 2nd Edition, |
890
|
|
|
|
|
|
|
Chapter 28, page 185. |
891
|
|
|
|
|
|
|
|
892
|
|
|
|
|
|
|
=cut |
893
|
|
|
|
|
|
|
|
894
|
|
|
|
|
|
|
sub equation_of_time { |
895
|
1
|
|
|
1
|
1
|
343
|
my ( $self, $time ) = @_; |
896
|
|
|
|
|
|
|
|
897
|
1
|
50
|
|
|
|
6
|
if ( looks_like_number( $self ) ) { |
898
|
0
|
|
|
|
|
0
|
( $self, $time ) = ( __PACKAGE__, $self ); |
899
|
0
|
|
|
|
|
0
|
__subroutine_deprecation(); |
900
|
|
|
|
|
|
|
## Carp::cluck( 'Subroutine call to equation_of_time() is deprecated' ); |
901
|
|
|
|
|
|
|
} |
902
|
1
|
50
|
|
|
|
4
|
defined $time |
903
|
|
|
|
|
|
|
or $time = $self->dynamical(); |
904
|
|
|
|
|
|
|
|
905
|
1
|
|
|
|
|
3
|
my $epsilon = $self->obliquity( $time ); |
906
|
1
|
|
|
|
|
4
|
my $y = tan($epsilon / 2); |
907
|
1
|
|
|
|
|
3
|
$y *= $y; |
908
|
|
|
|
|
|
|
|
909
|
|
|
|
|
|
|
# The following algorithm is from Meeus, chapter 25, page, 163 ff. |
910
|
|
|
|
|
|
|
|
911
|
1
|
|
|
|
|
13
|
my $T = jcent2000( $time ); # Meeus (25.1) |
912
|
1
|
|
|
|
|
5
|
my $L0 = mod2pi( deg2rad( (.0003032 * $T + 36000.76983 ) * $T |
913
|
|
|
|
|
|
|
+ 280.46646 ) ); # Meeus (25.2) |
914
|
1
|
|
|
|
|
4
|
my $M = mod2pi( deg2rad( ( ( -.0001537 ) * $T + 35999.05029 ) |
915
|
|
|
|
|
|
|
* $T + 357.52911 ) ); # Meeus (25.3) |
916
|
1
|
|
|
|
|
4
|
my $e = ( -0.0000001267 * $T - 0.000042037 ) * $T |
917
|
|
|
|
|
|
|
+ 0.016708634; # Meeus (25.4) |
918
|
|
|
|
|
|
|
|
919
|
1
|
|
|
|
|
11
|
my $E = $y * sin( 2 * $L0 ) - 2 * $e * sin( $M ) + |
920
|
|
|
|
|
|
|
4 * $e * $y * sin( $M ) * cos( 2 * $L0 ) - |
921
|
|
|
|
|
|
|
$y * $y * .5 * sin( 4 * $L0 ) - |
922
|
|
|
|
|
|
|
1.25 * $e * $e * sin( 2 * $M ); # Meeus (28.3) |
923
|
|
|
|
|
|
|
|
924
|
1
|
|
|
|
|
4
|
return $E * SECSPERDAY / TWOPI; # The formula gives radians. |
925
|
|
|
|
|
|
|
} |
926
|
|
|
|
|
|
|
|
927
|
|
|
|
|
|
|
=item $coord->equatorial ($rightasc, $declin, $range, $time); |
928
|
|
|
|
|
|
|
|
929
|
|
|
|
|
|
|
This method sets the L coordinates represented by the |
930
|
|
|
|
|
|
|
object (relative to the center of the Earth) in terms of |
931
|
|
|
|
|
|
|
L and L in radians, and the range to the |
932
|
|
|
|
|
|
|
object in kilometers, time being universal time. The object itself is |
933
|
|
|
|
|
|
|
returned. |
934
|
|
|
|
|
|
|
|
935
|
|
|
|
|
|
|
If C is called in this way, the C attribute will |
936
|
|
|
|
|
|
|
be ignored, for historical reasons. |
937
|
|
|
|
|
|
|
|
938
|
|
|
|
|
|
|
The right ascension should be a number between 0 and 2*PI radians |
939
|
|
|
|
|
|
|
inclusive. The declination should be a number between -PI/2 and PI/2 |
940
|
|
|
|
|
|
|
radians inclusive. A warning will be generated if either of these range |
941
|
|
|
|
|
|
|
checks fails. |
942
|
|
|
|
|
|
|
|
943
|
|
|
|
|
|
|
The time argument is optional if the time represented by the object |
944
|
|
|
|
|
|
|
has already been set (e.g. by the universal() or dynamical() methods). |
945
|
|
|
|
|
|
|
|
946
|
|
|
|
|
|
|
This method can also be called as a class method, in which case it |
947
|
|
|
|
|
|
|
instantiates the desired object. In this case the time is not optional. |
948
|
|
|
|
|
|
|
|
949
|
|
|
|
|
|
|
You may optionally pass velocity information in addition to position |
950
|
|
|
|
|
|
|
information. If you do this, the velocity in right ascension (in radians |
951
|
|
|
|
|
|
|
per second), declination (ditto), and range (in kilometers per second in |
952
|
|
|
|
|
|
|
recession) are passed after the position arguments, and before the $time |
953
|
|
|
|
|
|
|
argument if any. |
954
|
|
|
|
|
|
|
|
955
|
|
|
|
|
|
|
=item ( $rightasc, $declin, $range ) = $coord->equatorial( $time ); |
956
|
|
|
|
|
|
|
|
957
|
|
|
|
|
|
|
This method returns the L coordinates of the object at the |
958
|
|
|
|
|
|
|
relative to the center of the Earth. The C attribute is |
959
|
|
|
|
|
|
|
ignored. The time argument is optional if the time represented by the |
960
|
|
|
|
|
|
|
object has already been set (e.g. by the universal() or dynamical() |
961
|
|
|
|
|
|
|
methods). |
962
|
|
|
|
|
|
|
|
963
|
|
|
|
|
|
|
If velocities are available from the object (i.e. if it is an instance |
964
|
|
|
|
|
|
|
of Astro::Coord::ECI::TLE) the return will contain velocity in right |
965
|
|
|
|
|
|
|
ascension, declination, and range, the first two being in radians per |
966
|
|
|
|
|
|
|
second and the third in kilometers per second in recession. |
967
|
|
|
|
|
|
|
|
968
|
|
|
|
|
|
|
B these velocities are believed by me to be sane B they are |
969
|
|
|
|
|
|
|
derived from ECI coordinates. This method does not support setting |
970
|
|
|
|
|
|
|
velocities. See L, below, |
971
|
|
|
|
|
|
|
for details. |
972
|
|
|
|
|
|
|
|
973
|
|
|
|
|
|
|
=item ($rightasc, $declin, $range) = $coord->equatorial( $coord2 ); |
974
|
|
|
|
|
|
|
|
975
|
|
|
|
|
|
|
This method is retained (for the moment) for historical reasons, but |
976
|
|
|
|
|
|
|
C is preferred. |
977
|
|
|
|
|
|
|
|
978
|
|
|
|
|
|
|
This method returns the apparent equatorial coordinates of the object |
979
|
|
|
|
|
|
|
represented by $coord2, as seen from the location represented by |
980
|
|
|
|
|
|
|
$coord. |
981
|
|
|
|
|
|
|
|
982
|
|
|
|
|
|
|
As a side effect, the time of the $coord object may be set from the |
983
|
|
|
|
|
|
|
$coord2 object. |
984
|
|
|
|
|
|
|
|
985
|
|
|
|
|
|
|
If the C attribute of the $coord object is |
986
|
|
|
|
|
|
|
true, the coordinates will be corrected for atmospheric refraction using |
987
|
|
|
|
|
|
|
the correct_for_refraction() method. |
988
|
|
|
|
|
|
|
|
989
|
|
|
|
|
|
|
If velocities are available from both objects (i.e. if both objects are |
990
|
|
|
|
|
|
|
Astro::Coord::ECI::TLE objects) the return will contain velocity in |
991
|
|
|
|
|
|
|
right ascension, declination, and range, the first two being in radians |
992
|
|
|
|
|
|
|
per second and the third in kilometers per second in recession. |
993
|
|
|
|
|
|
|
|
994
|
|
|
|
|
|
|
B these velocities are believed by me B to be correct. |
995
|
|
|
|
|
|
|
|
996
|
|
|
|
|
|
|
=cut |
997
|
|
|
|
|
|
|
|
998
|
|
|
|
|
|
|
sub equatorial { |
999
|
3388
|
|
|
3388
|
1
|
6391
|
my ( $self, @args ) = @_; |
1000
|
|
|
|
|
|
|
|
1001
|
3388
|
|
|
|
|
4216
|
my $body; |
1002
|
|
|
|
|
|
|
@args |
1003
|
3388
|
100
|
100
|
|
|
8813
|
and embodies( $args[0], __PACKAGE__ ) |
1004
|
|
|
|
|
|
|
and $body = shift @args; |
1005
|
|
|
|
|
|
|
|
1006
|
3388
|
|
|
|
|
7213
|
$self = $self->_check_coord( equatorial => \@args ); |
1007
|
3388
|
|
|
|
|
4681
|
my $time; |
1008
|
3388
|
100
|
|
|
|
7679
|
$body or $time = $self->universal; |
1009
|
|
|
|
|
|
|
|
1010
|
3388
|
100
|
|
|
|
6161
|
if ( @args ) { |
|
|
100
|
|
|
|
|
|
1011
|
2253
|
50
|
|
|
|
4342
|
@args % 3 |
1012
|
|
|
|
|
|
|
and croak 'Arguments must be in threes, with an optional time'; |
1013
|
2253
|
50
|
|
|
|
4075
|
$body |
1014
|
|
|
|
|
|
|
and croak 'You may not set the equatorial coordinates ', |
1015
|
|
|
|
|
|
|
'relative to an observer'; |
1016
|
|
|
|
|
|
|
|
1017
|
|
|
|
|
|
|
## my ($ra, $dec, $range, @eqvel) = @args; |
1018
|
2253
|
|
|
|
|
4029
|
$args[0] = _check_right_ascension( 'right ascension' => $args[0] ); |
1019
|
2253
|
|
|
|
|
4146
|
$args[1] = _check_latitude( declination => $args[1] ); |
1020
|
2253
|
|
|
|
|
4077
|
foreach my $key (@kilatr) {delete $self->{$key}} |
|
11265
|
|
|
|
|
19302
|
|
1021
|
2253
|
|
|
|
|
5463
|
$self->{_ECI_cache}{inertial}{equatorial} = \@args; |
1022
|
2253
|
|
|
|
|
4638
|
$self->eci( |
1023
|
|
|
|
|
|
|
_convert_spherical_to_cartesian( @args ) ); |
1024
|
2253
|
|
|
|
|
3666
|
$self->{specified} = 'equatorial'; |
1025
|
2253
|
|
|
|
|
2971
|
$self->{inertial} = 1; |
1026
|
2253
|
|
|
|
|
5000
|
return $self; |
1027
|
|
|
|
|
|
|
|
1028
|
|
|
|
|
|
|
} elsif ( $body ) { |
1029
|
|
|
|
|
|
|
|
1030
|
3
|
|
|
|
|
12
|
return $self->_equatorial_reduced( $body ); |
1031
|
|
|
|
|
|
|
|
1032
|
|
|
|
|
|
|
} else { |
1033
|
|
|
|
|
|
|
|
1034
|
1132
|
|
|
|
|
1449
|
return @{ $self->{_ECI_cache}{inertial}{equatorial} ||= [ |
1035
|
1132
|
|
50
|
|
|
3305
|
_convert_cartesian_to_spherical( $self->eci() ) |
1036
|
|
|
|
|
|
|
] }; |
1037
|
|
|
|
|
|
|
|
1038
|
|
|
|
|
|
|
} |
1039
|
|
|
|
|
|
|
} |
1040
|
|
|
|
|
|
|
|
1041
|
|
|
|
|
|
|
=item ($ra, $decl, $rng) = $coord->equatorial_apparent( $sta ); |
1042
|
|
|
|
|
|
|
|
1043
|
|
|
|
|
|
|
This method returns the apparent equatorial coordinates of the C<$coord> |
1044
|
|
|
|
|
|
|
object as seen from the object specified by the C<$sta> argument, or by |
1045
|
|
|
|
|
|
|
the object in the C attribute of the C<$coord> object if no |
1046
|
|
|
|
|
|
|
argument is specified. |
1047
|
|
|
|
|
|
|
|
1048
|
|
|
|
|
|
|
This method will return velocities if available, but I have no idea |
1049
|
|
|
|
|
|
|
whether they are correct. |
1050
|
|
|
|
|
|
|
|
1051
|
|
|
|
|
|
|
=cut |
1052
|
|
|
|
|
|
|
|
1053
|
|
|
|
|
|
|
sub equatorial_apparent { |
1054
|
1
|
|
|
1
|
1
|
6
|
my ( $self, $station ) = @_; |
1055
|
1
|
50
|
33
|
|
|
5
|
( $station ||= $self->get( 'station' ) ) |
1056
|
|
|
|
|
|
|
or croak 'Station attribute is required'; |
1057
|
1
|
|
|
|
|
4
|
return $station->_equatorial_reduced( $self ); |
1058
|
|
|
|
|
|
|
} |
1059
|
|
|
|
|
|
|
|
1060
|
|
|
|
|
|
|
=item my ($rasc, $decl, $range, $v_rasc, $v_decl, $v_r) = $coord->equatorial_unreduced($body); |
1061
|
|
|
|
|
|
|
|
1062
|
|
|
|
|
|
|
This method computes the unreduced equatorial position of the second ECI |
1063
|
|
|
|
|
|
|
object as seen from the first. If the second argument is undefined, |
1064
|
|
|
|
|
|
|
computes the equatorial position of the first object as seen from the |
1065
|
|
|
|
|
|
|
center of the Earth. Unlike the equatorial() method itself, this method |
1066
|
|
|
|
|
|
|
is an accessor only. This method would probably not be exposed except |
1067
|
|
|
|
|
|
|
for the anticipation of the usefulness of $range and $v_r in satellite |
1068
|
|
|
|
|
|
|
conjunction computations, and the desirability of not taking the time to |
1069
|
|
|
|
|
|
|
make the two atan2() calls that are unneeded in this application. |
1070
|
|
|
|
|
|
|
|
1071
|
|
|
|
|
|
|
The 'unreduced' in the name of the method is intended to refer to the |
1072
|
|
|
|
|
|
|
fact that the $rasc and $decl are not the right ascension and |
1073
|
|
|
|
|
|
|
declination themselves, but the arguments to atan2() needed to compute |
1074
|
|
|
|
|
|
|
them, and $v_rasc and $v_decl are in km/sec, rather than being divided |
1075
|
|
|
|
|
|
|
by the range to get radians per second. |
1076
|
|
|
|
|
|
|
|
1077
|
|
|
|
|
|
|
This method ignores the C<'station'> attribute. |
1078
|
|
|
|
|
|
|
|
1079
|
|
|
|
|
|
|
The returned data are: |
1080
|
|
|
|
|
|
|
|
1081
|
|
|
|
|
|
|
$rasc is an array reference, which can be converted to the right |
1082
|
|
|
|
|
|
|
ascension in radians by mod2pi(atan2($rasc->[0], $rasc->[1])). |
1083
|
|
|
|
|
|
|
|
1084
|
|
|
|
|
|
|
$decl is an array reference, which can be converted to the declination |
1085
|
|
|
|
|
|
|
in radians by atan2($decl->[0], $decl->[1]). |
1086
|
|
|
|
|
|
|
|
1087
|
|
|
|
|
|
|
$range is the range in kilometers. |
1088
|
|
|
|
|
|
|
|
1089
|
|
|
|
|
|
|
$v_rasc is the velocity in the right ascensional direction in kilometers |
1090
|
|
|
|
|
|
|
per second. It can be converted to radians per second by dividing by |
1091
|
|
|
|
|
|
|
$range. |
1092
|
|
|
|
|
|
|
|
1093
|
|
|
|
|
|
|
$v_decl is the velocity in the declinational direction in kilometers per |
1094
|
|
|
|
|
|
|
second. It can be converted to radians per second by dividing by $range. |
1095
|
|
|
|
|
|
|
|
1096
|
|
|
|
|
|
|
$v_r is the velocity in recession in kilometers per second. Negative |
1097
|
|
|
|
|
|
|
values indicate that the objects are approaching. |
1098
|
|
|
|
|
|
|
|
1099
|
|
|
|
|
|
|
The velocities are only returned if they are available from the input |
1100
|
|
|
|
|
|
|
objects. |
1101
|
|
|
|
|
|
|
|
1102
|
|
|
|
|
|
|
B these velocities are believed by me B to be correct. |
1103
|
|
|
|
|
|
|
|
1104
|
|
|
|
|
|
|
=cut |
1105
|
|
|
|
|
|
|
|
1106
|
|
|
|
|
|
|
sub equatorial_unreduced { |
1107
|
|
|
|
|
|
|
|
1108
|
|
|
|
|
|
|
# Unpack the two objects. |
1109
|
|
|
|
|
|
|
|
1110
|
4
|
|
|
4
|
1
|
10
|
my ($self, $body) = @_; |
1111
|
|
|
|
|
|
|
|
1112
|
|
|
|
|
|
|
# Compute the relative positions if there are in fact two objects; |
1113
|
|
|
|
|
|
|
# otherwise just get the absolute position. |
1114
|
|
|
|
|
|
|
|
1115
|
4
|
|
|
|
|
6
|
my @pos; |
1116
|
4
|
50
|
|
|
|
10
|
if ($body) { |
1117
|
4
|
|
|
|
|
9
|
@pos = $body->eci(); |
1118
|
4
|
|
|
|
|
11
|
my @base = $self->eci($body->universal()); |
1119
|
4
|
50
|
|
|
|
16
|
my $limit = @pos < @base ? @pos : @base; |
1120
|
4
|
|
|
|
|
14
|
foreach my $inx (0 .. $limit - 1) { |
1121
|
21
|
|
|
|
|
382
|
$pos[$inx] -= $base[$inx]; |
1122
|
|
|
|
|
|
|
} |
1123
|
4
|
|
|
|
|
14
|
splice @pos, $limit; |
1124
|
|
|
|
|
|
|
} else { |
1125
|
|
|
|
|
|
|
$self->{_ECI_cache}{inertial}{equatorial_unreduced} and return |
1126
|
0
|
0
|
|
|
|
0
|
@{$self->{_ECI_cache}{inertial}{equatorial_unreduced}}; |
|
0
|
|
|
|
|
0
|
|
1127
|
0
|
|
|
|
|
0
|
@pos = $self->eci(); |
1128
|
|
|
|
|
|
|
} |
1129
|
|
|
|
|
|
|
|
1130
|
|
|
|
|
|
|
# Rotate the coordinate system so that the second body lies in the |
1131
|
|
|
|
|
|
|
# X-Z plane. The matrix is |
1132
|
|
|
|
|
|
|
# +- -+ |
1133
|
|
|
|
|
|
|
# | cos rasc -sin rasc 0 | |
1134
|
|
|
|
|
|
|
# | sin rasc cos rasc 0 | |
1135
|
|
|
|
|
|
|
# | 0 0 1 | |
1136
|
|
|
|
|
|
|
# +- -+ |
1137
|
|
|
|
|
|
|
# where rasc is -atan2(y,x). This means sin rasc = -y/h and |
1138
|
|
|
|
|
|
|
# cos rasc = x/h where h = sqrt(x*x + y*y). You postmultiply |
1139
|
|
|
|
|
|
|
# the matrix by the vector to get the new vector. |
1140
|
|
|
|
|
|
|
|
1141
|
4
|
|
|
|
|
13
|
my $hypot_rasc = sqrt($pos[0] * $pos[0] + $pos[1] * $pos[1]); |
1142
|
|
|
|
|
|
|
|
1143
|
|
|
|
|
|
|
# Now we need to rotate the coordinates in the new X-Z plane until |
1144
|
|
|
|
|
|
|
# the second body lies along the X axis. The matrix is |
1145
|
|
|
|
|
|
|
# +- -+ |
1146
|
|
|
|
|
|
|
# | cos decl 0 -sin decl | |
1147
|
|
|
|
|
|
|
# | 0 1 0 | |
1148
|
|
|
|
|
|
|
# | sin decl 0 cos decl | |
1149
|
|
|
|
|
|
|
# +- -+ |
1150
|
|
|
|
|
|
|
# where decl is -atan2(z,x') (in the new coordinate system), or |
1151
|
|
|
|
|
|
|
# -atan2(z,h) in the old coordinate system. This means that sin decl |
1152
|
|
|
|
|
|
|
# = z/h' and cos decl = x/h' where h' = sqrt(h*h + z*z). Again you |
1153
|
|
|
|
|
|
|
# postmultiply the matrix by the vector to get the result. |
1154
|
|
|
|
|
|
|
|
1155
|
4
|
|
|
|
|
10
|
my $hypot_decl = sqrt($hypot_rasc * $hypot_rasc + $pos[2] * $pos[2]); |
1156
|
|
|
|
|
|
|
|
1157
|
|
|
|
|
|
|
# But at this point we have the equatorial coordinates themselves in |
1158
|
|
|
|
|
|
|
# terms of the original coordinates and the various intermediate |
1159
|
|
|
|
|
|
|
# quantities needed to compute the matrix. So we only need the |
1160
|
|
|
|
|
|
|
# matrix if we need to deal with the velocities. For this, the |
1161
|
|
|
|
|
|
|
# velocity rotation matrix is |
1162
|
|
|
|
|
|
|
# +- -+ +- -+ |
1163
|
|
|
|
|
|
|
# | cos decl 0 -sin decl | | cos rasc -sin rasc 0 | |
1164
|
|
|
|
|
|
|
# | 0 1 0 | x | sin rasc cos rasc 0 | = |
1165
|
|
|
|
|
|
|
# | sin decl 0 cos decl | | 0 0 1 | |
1166
|
|
|
|
|
|
|
# +- -+ +- -+ |
1167
|
|
|
|
|
|
|
# |
1168
|
|
|
|
|
|
|
# +- -+ |
1169
|
|
|
|
|
|
|
# | cos decl cos rasc -cos decl sin rasc -sin decl | |
1170
|
|
|
|
|
|
|
# | sin rasc cos rasc 0 | |
1171
|
|
|
|
|
|
|
# | sin decl cos rasc -sin decl sin rasc cos decl | |
1172
|
|
|
|
|
|
|
# +- + |
1173
|
|
|
|
|
|
|
|
1174
|
4
|
|
|
|
|
23
|
my @rslt = ([$pos[1], $pos[0]], [$pos[2], $hypot_rasc], $hypot_decl); |
1175
|
4
|
100
|
|
|
|
16
|
if (@pos >= 6) { |
1176
|
3
|
|
|
|
|
14
|
my $cos_rasc = $pos[0]/$hypot_rasc; |
1177
|
3
|
|
|
|
|
10
|
my $sin_rasc = -$pos[1]/$hypot_rasc; |
1178
|
3
|
|
|
|
|
6
|
my $cos_decl = $hypot_rasc/$hypot_decl; |
1179
|
3
|
|
|
|
|
7
|
my $sin_decl = -$pos[2]/$hypot_decl; |
1180
|
3
|
|
|
|
|
15
|
push @rslt, |
1181
|
|
|
|
|
|
|
$sin_rasc * $pos[3] + $cos_rasc * $pos[4], |
1182
|
|
|
|
|
|
|
$sin_decl * $cos_rasc * $pos[3] |
1183
|
|
|
|
|
|
|
- $sin_decl * $sin_rasc * $pos[4] + $cos_decl * $pos[5], |
1184
|
|
|
|
|
|
|
# Computationally the following is the top row of the |
1185
|
|
|
|
|
|
|
# matrix, but it is swapped to the bottom in the output for |
1186
|
|
|
|
|
|
|
# consistency of returned data sequence. |
1187
|
|
|
|
|
|
|
$cos_decl * $cos_rasc * $pos[3] |
1188
|
|
|
|
|
|
|
- $cos_decl * $sin_rasc * $pos[4] - $sin_decl * $pos[5]; |
1189
|
|
|
|
|
|
|
} |
1190
|
|
|
|
|
|
|
$body |
1191
|
4
|
50
|
|
|
|
19
|
or $self->{_ECI_cache}{inertial}{equatorial_unreduced} = \@rslt; |
1192
|
4
|
|
|
|
|
25
|
return @rslt; |
1193
|
|
|
|
|
|
|
|
1194
|
|
|
|
|
|
|
} |
1195
|
|
|
|
|
|
|
|
1196
|
|
|
|
|
|
|
sub _equatorial_reduced { |
1197
|
4
|
|
|
4
|
|
15
|
my ( $self, $body ) = @_; |
1198
|
4
|
|
|
|
|
13
|
my @rslt = $self->equatorial_unreduced( $body ); |
1199
|
4
|
|
|
|
|
23
|
$rslt[0] = mod2pi( atan2( $rslt[0][0], $rslt[0][1] ) ); |
1200
|
4
|
|
|
|
|
13
|
$rslt[1] = atan2( $rslt[1][0], $rslt[1][1] ); |
1201
|
4
|
100
|
|
|
|
14
|
if (@rslt >= 6) { |
1202
|
3
|
|
|
|
|
6
|
$rslt[3] /= $rslt[2]; |
1203
|
3
|
|
|
|
|
6
|
$rslt[4] /= $rslt[2]; |
1204
|
|
|
|
|
|
|
} |
1205
|
4
|
|
|
|
|
18
|
return @rslt; |
1206
|
|
|
|
|
|
|
} |
1207
|
|
|
|
|
|
|
|
1208
|
|
|
|
|
|
|
=item $coord = $coord->equinox_dynamical ($value); |
1209
|
|
|
|
|
|
|
|
1210
|
|
|
|
|
|
|
This method sets the value of the equinox_dynamical attribute, and |
1211
|
|
|
|
|
|
|
returns the modified object. If called without an argument, it returns |
1212
|
|
|
|
|
|
|
the current value of the equinox_dynamical attribute. |
1213
|
|
|
|
|
|
|
|
1214
|
|
|
|
|
|
|
Yes, it is equivalent to $coord->set (equinox_dynamical => $value) and |
1215
|
|
|
|
|
|
|
$coord->get ('equinox_dynamical'). But there seems to be a significant |
1216
|
|
|
|
|
|
|
performance penalty in the $self->SUPER::set () needed to get this |
1217
|
|
|
|
|
|
|
attribute set from a subclass. It is possible that more methods like |
1218
|
|
|
|
|
|
|
this will be added, but I do not plan to eliminate the set() interface. |
1219
|
|
|
|
|
|
|
|
1220
|
|
|
|
|
|
|
=cut |
1221
|
|
|
|
|
|
|
|
1222
|
|
|
|
|
|
|
sub equinox_dynamical { |
1223
|
31897
|
50
|
|
31897
|
1
|
59902
|
if (@_ > 1) { |
1224
|
31897
|
|
|
|
|
51219
|
$_[0]{equinox_dynamical} = $_[1]; |
1225
|
31897
|
|
|
|
|
53676
|
return $_[0]; |
1226
|
|
|
|
|
|
|
} else { |
1227
|
0
|
|
|
|
|
0
|
return $_[0]{equinox_dynamical}; |
1228
|
|
|
|
|
|
|
} |
1229
|
|
|
|
|
|
|
} |
1230
|
|
|
|
|
|
|
|
1231
|
|
|
|
|
|
|
=item $coord = $coord->geocentric($psiprime, $lambda, $rho); |
1232
|
|
|
|
|
|
|
|
1233
|
|
|
|
|
|
|
This method sets the L coordinates represented by the |
1234
|
|
|
|
|
|
|
object in terms of L psiprime and L |
1235
|
|
|
|
|
|
|
lambda in radians, and distance from the center of the Earth rho in |
1236
|
|
|
|
|
|
|
kilometers. |
1237
|
|
|
|
|
|
|
|
1238
|
|
|
|
|
|
|
The latitude should be a number between -PI/2 and PI/2 radians |
1239
|
|
|
|
|
|
|
inclusive. The longitude should be a number between -2*PI and 2*PI |
1240
|
|
|
|
|
|
|
radians inclusive. The increased range (one would expect -PI to PI) is |
1241
|
|
|
|
|
|
|
because in some astronomical usages latitudes outside the range + or - |
1242
|
|
|
|
|
|
|
180 degrees are employed. A warning will be generated if either of these |
1243
|
|
|
|
|
|
|
range checks fails. |
1244
|
|
|
|
|
|
|
|
1245
|
|
|
|
|
|
|
This method can also be called as a class method, in which case it |
1246
|
|
|
|
|
|
|
instantiates the desired object. |
1247
|
|
|
|
|
|
|
|
1248
|
|
|
|
|
|
|
B because map |
1249
|
|
|
|
|
|
|
latitude is L, measured in terms of the tangent of |
1250
|
|
|
|
|
|
|
the reference ellipsoid, whereas geocentric coordinates are, |
1251
|
|
|
|
|
|
|
essentially, spherical coordinates. |
1252
|
|
|
|
|
|
|
|
1253
|
|
|
|
|
|
|
The algorithm for conversion between geocentric and ECEF is the |
1254
|
|
|
|
|
|
|
author's. |
1255
|
|
|
|
|
|
|
|
1256
|
|
|
|
|
|
|
=item ($psiprime, $lambda, $rho) = $coord->geocentric(); |
1257
|
|
|
|
|
|
|
|
1258
|
|
|
|
|
|
|
This method returns the L, L, and |
1259
|
|
|
|
|
|
|
distance to the center of the Earth. |
1260
|
|
|
|
|
|
|
|
1261
|
|
|
|
|
|
|
=cut |
1262
|
|
|
|
|
|
|
|
1263
|
|
|
|
|
|
|
sub geocentric { |
1264
|
16664
|
|
|
16664
|
1
|
30132
|
my ($self, @args) = @_; |
1265
|
|
|
|
|
|
|
|
1266
|
16664
|
|
|
|
|
32030
|
$self = $self->_check_coord (geocentric => \@args); |
1267
|
|
|
|
|
|
|
|
1268
|
16664
|
100
|
|
|
|
33776
|
unless (@args) { |
1269
|
|
|
|
|
|
|
|
1270
|
14557
|
100
|
|
|
|
31502
|
if ( ! $self->{_ECI_cache}{fixed}{geocentric} ) { |
1271
|
|
|
|
|
|
|
|
1272
|
|
|
|
|
|
|
## my ($x, $y, $z, $xdot, $ydot, $zdot) = $self->ecef; |
1273
|
6700
|
|
|
|
|
12522
|
my ($x, $y, $z) = $self->ecef; |
1274
|
6700
|
|
|
|
|
13222
|
my $rsq = $x * $x + $y * $y; |
1275
|
6700
|
|
|
|
|
11776
|
my $rho = sqrt ($z * $z + $rsq); |
1276
|
6700
|
|
|
|
|
14195
|
my $lambda = atan2 ($y, $x); |
1277
|
6700
|
|
|
|
|
11236
|
my $psiprime = atan2 ($z, sqrt ($rsq)); |
1278
|
6700
|
50
|
|
|
|
18336
|
$self->get ('debug') and print <
|
1279
|
|
|
|
|
|
|
Debug geocentric () - ecef -> geocentric |
1280
|
|
|
|
|
|
|
inputs: |
1281
|
|
|
|
|
|
|
x = $x |
1282
|
|
|
|
|
|
|
y = $y |
1283
|
|
|
|
|
|
|
z = $z |
1284
|
|
|
|
|
|
|
outputs: |
1285
|
|
|
|
|
|
|
psiprime = $psiprime |
1286
|
|
|
|
|
|
|
lambda = $lambda |
1287
|
|
|
|
|
|
|
rho = $rho |
1288
|
|
|
|
|
|
|
eod |
1289
|
|
|
|
|
|
|
$self->{_ECI_cache}{fixed}{geocentric} = |
1290
|
6700
|
|
|
|
|
18349
|
[ $psiprime, $lambda, $rho ]; |
1291
|
|
|
|
|
|
|
} |
1292
|
|
|
|
|
|
|
|
1293
|
14557
|
|
|
|
|
19738
|
return @{ $self->{_ECI_cache}{fixed}{geocentric} }; |
|
14557
|
|
|
|
|
39521
|
|
1294
|
|
|
|
|
|
|
} |
1295
|
|
|
|
|
|
|
|
1296
|
2107
|
50
|
|
|
|
4080
|
if (@args == 3) { |
1297
|
2107
|
|
|
|
|
3962
|
my ($psiprime, $lambda, $rho) = @args; |
1298
|
2107
|
|
|
|
|
3818
|
$psiprime = _check_latitude(latitude => $psiprime); |
1299
|
2107
|
|
|
|
|
3857
|
$lambda = _check_longitude(longitude => $lambda); |
1300
|
2107
|
|
|
|
|
3806
|
my $z = $rho * sin ($psiprime); |
1301
|
2107
|
|
|
|
|
3415
|
my $r = $rho * cos ($psiprime); |
1302
|
2107
|
|
|
|
|
3396
|
my $x = $r * cos ($lambda); |
1303
|
2107
|
|
|
|
|
3466
|
my $y = $r * sin ($lambda); |
1304
|
2107
|
50
|
|
|
|
4594
|
$self->get ('debug') and print <
|
1305
|
|
|
|
|
|
|
Debug geocentric () - geocentric -> ecef |
1306
|
|
|
|
|
|
|
inputs: |
1307
|
|
|
|
|
|
|
psiprime = $psiprime |
1308
|
|
|
|
|
|
|
lambda = $lambda |
1309
|
|
|
|
|
|
|
rho = $rho |
1310
|
|
|
|
|
|
|
outputs: |
1311
|
|
|
|
|
|
|
x = $x |
1312
|
|
|
|
|
|
|
y = $y |
1313
|
|
|
|
|
|
|
z = $z |
1314
|
|
|
|
|
|
|
eod |
1315
|
2107
|
|
|
|
|
6424
|
$self->ecef ($x, $y, $z); |
1316
|
2107
|
|
|
|
|
3906
|
$self->{_ECI_cache}{fixed}{geocentric} = \@args; |
1317
|
2107
|
|
|
|
|
3436
|
$self->{specified} = 'geocentric'; |
1318
|
2107
|
|
|
|
|
3736
|
$self->{inertial} = 0; |
1319
|
|
|
|
|
|
|
} else { |
1320
|
0
|
|
|
|
|
0
|
croak <
|
1321
|
|
|
|
|
|
|
Error - Method geocentric() must be called with either zero arguments |
1322
|
|
|
|
|
|
|
(to retrieve coordinates) or three arguments (to set |
1323
|
|
|
|
|
|
|
coordinates). There is currently no six argument version. |
1324
|
|
|
|
|
|
|
eod |
1325
|
|
|
|
|
|
|
} |
1326
|
2107
|
|
|
|
|
3232
|
return $self; |
1327
|
|
|
|
|
|
|
} |
1328
|
|
|
|
|
|
|
|
1329
|
|
|
|
|
|
|
=item $coord = $coord->geodetic($psi, $lambda, $h, $ellipsoid); |
1330
|
|
|
|
|
|
|
|
1331
|
|
|
|
|
|
|
This method sets the L coordinates represented by the object |
1332
|
|
|
|
|
|
|
in terms of its L psi and L lambda in |
1333
|
|
|
|
|
|
|
radians, and its height h above mean sea level in kilometers. |
1334
|
|
|
|
|
|
|
|
1335
|
|
|
|
|
|
|
The latitude should be a number between -PI/2 and PI/2 radians |
1336
|
|
|
|
|
|
|
inclusive. The longitude should be a number between -2*PI and 2*PI |
1337
|
|
|
|
|
|
|
radians inclusive. The increased range (one would expect -PI to PI) is |
1338
|
|
|
|
|
|
|
because in some astronomical usages latitudes outside the range + or - |
1339
|
|
|
|
|
|
|
180 degrees are employed. A warning will be generated if either of these |
1340
|
|
|
|
|
|
|
range checks fails. |
1341
|
|
|
|
|
|
|
|
1342
|
|
|
|
|
|
|
The ellipsoid argument is the name of a L known |
1343
|
|
|
|
|
|
|
to the class, and is optional. If passed, it will set the ellipsoid |
1344
|
|
|
|
|
|
|
to be used for calculations with this object. If not passed, the |
1345
|
|
|
|
|
|
|
default ellipsoid is used. |
1346
|
|
|
|
|
|
|
|
1347
|
|
|
|
|
|
|
This method can also be called as a class method, in which case it |
1348
|
|
|
|
|
|
|
instantiates the desired object. |
1349
|
|
|
|
|
|
|
|
1350
|
|
|
|
|
|
|
The conversion from geodetic to geocentric comes from Jean Meeus' |
1351
|
|
|
|
|
|
|
"Astronomical Algorithms", 2nd Edition, Chapter 11, page 82. |
1352
|
|
|
|
|
|
|
|
1353
|
|
|
|
|
|
|
B |
1354
|
|
|
|
|
|
|
|
1355
|
|
|
|
|
|
|
=item ($psi, $lambda, $h) = $coord->geodetic($ellipsoid); |
1356
|
|
|
|
|
|
|
|
1357
|
|
|
|
|
|
|
This method returns the geodetic latitude, longitude, and height |
1358
|
|
|
|
|
|
|
above mean sea level. |
1359
|
|
|
|
|
|
|
|
1360
|
|
|
|
|
|
|
The ellipsoid argument is the name of a L known |
1361
|
|
|
|
|
|
|
to the class, and is optional. If not specified, the most-recently-set |
1362
|
|
|
|
|
|
|
ellipsoid will be used. |
1363
|
|
|
|
|
|
|
|
1364
|
|
|
|
|
|
|
The conversion from geocentric to geodetic comes from Kazimierz |
1365
|
|
|
|
|
|
|
Borkowski's "Accurate Algorithms to Transform Geocentric to Geodetic |
1366
|
|
|
|
|
|
|
Coordinates", at F. |
1367
|
|
|
|
|
|
|
This is best viewed with Internet Explorer because of its use of Microsoft's |
1368
|
|
|
|
|
|
|
Symbol font. |
1369
|
|
|
|
|
|
|
|
1370
|
|
|
|
|
|
|
=cut |
1371
|
|
|
|
|
|
|
|
1372
|
|
|
|
|
|
|
sub geodetic { |
1373
|
27116
|
|
|
27116
|
1
|
1018078
|
my ($self, @args) = @_; |
1374
|
|
|
|
|
|
|
|
1375
|
|
|
|
|
|
|
# Detect and acquire the optional ellipsoid name argument. We do |
1376
|
|
|
|
|
|
|
# this before the check, since the check expects the extra |
1377
|
|
|
|
|
|
|
# argument to be a time. |
1378
|
|
|
|
|
|
|
|
1379
|
27116
|
50
|
33
|
|
|
95394
|
my $elps = (@args == 1 || @args == 4) ? pop @args : undef; |
1380
|
|
|
|
|
|
|
|
1381
|
27116
|
50
|
|
|
|
68840
|
$self = $self->_check_coord (geodetic => \@args, $elps ? $elps : ()); |
1382
|
|
|
|
|
|
|
|
1383
|
|
|
|
|
|
|
# The following is just a sleazy way to get a consistent |
1384
|
|
|
|
|
|
|
# error message if the ellipsoid name is unknown. |
1385
|
|
|
|
|
|
|
|
1386
|
27116
|
50
|
|
|
|
53965
|
$elps && $self->reference_ellipsoid ($elps); |
1387
|
|
|
|
|
|
|
|
1388
|
|
|
|
|
|
|
# If we're fetching the geodetic coordinates |
1389
|
|
|
|
|
|
|
|
1390
|
27116
|
100
|
|
|
|
51379
|
unless (@args) { |
1391
|
|
|
|
|
|
|
|
1392
|
|
|
|
|
|
|
# Return cached coordinates if they exist and we did not |
1393
|
|
|
|
|
|
|
# override the default ellipsoid. |
1394
|
|
|
|
|
|
|
|
1395
|
23171
|
|
|
|
|
68042
|
return @{$self->{_ECI_cache}{fixed}{geodetic}} |
1396
|
25011
|
100
|
66
|
|
|
87485
|
if $self->{_ECI_cache}{fixed}{geodetic} && !$elps; |
1397
|
1840
|
50
|
|
|
|
3491
|
$self->{debug} and do { |
1398
|
0
|
|
|
|
|
0
|
require Data::Dumper; |
1399
|
0
|
|
|
|
|
0
|
local $Data::Dumper::Terse = 1; |
1400
|
0
|
|
|
|
|
0
|
print "Debug geodetic - explicit ellipsoid ", |
1401
|
|
|
|
|
|
|
Data::Dumper::Dumper( $elps ); |
1402
|
|
|
|
|
|
|
}; |
1403
|
|
|
|
|
|
|
|
1404
|
|
|
|
|
|
|
# Get a reference to the ellipsoid data to use. |
1405
|
|
|
|
|
|
|
|
1406
|
1840
|
50
|
|
|
|
3229
|
$elps = $elps ? $known_ellipsoid{$elps} : $self; |
1407
|
1840
|
50
|
|
|
|
3261
|
$self->{debug} and do { |
1408
|
0
|
|
|
|
|
0
|
require Data::Dumper; |
1409
|
0
|
|
|
|
|
0
|
local $Data::Dumper::Terse = 1; |
1410
|
0
|
|
|
|
|
0
|
print "Debug geodetic - ellipsoid ", Data::Dumper::Dumper( $elps ); |
1411
|
|
|
|
|
|
|
}; |
1412
|
|
|
|
|
|
|
|
1413
|
|
|
|
|
|
|
# Calculate geodetic coordinates. |
1414
|
|
|
|
|
|
|
|
1415
|
1840
|
|
|
|
|
3374
|
my ($phiprime, $lambda, $rho) = $self->geocentric; |
1416
|
1840
|
|
|
|
|
3573
|
my $r = $rho * cos ($phiprime); |
1417
|
1840
|
|
|
|
|
3219
|
my $b = $elps->{semimajor} * (1- $elps->{flattening}); |
1418
|
1840
|
|
|
|
|
2545
|
my $a = $elps->{semimajor}; |
1419
|
1840
|
|
|
|
|
3053
|
my $z = $rho * sin ($phiprime); |
1420
|
|
|
|
|
|
|
# The $b is _not_ a magic variable, since we are not inside |
1421
|
|
|
|
|
|
|
# any of the specialized blocks that make $b magic. Perl::Critic |
1422
|
|
|
|
|
|
|
# is simply confused. |
1423
|
1840
|
100
|
|
|
|
3696
|
$b = - $b ## no critic (RequireLocalizedPunctuationVars) |
1424
|
|
|
|
|
|
|
if $z < 0; # Per Borkowski, for southern hemisphere. |
1425
|
|
|
|
|
|
|
|
1426
|
|
|
|
|
|
|
# The following algorithm is due to Kazimierz Borkowski's |
1427
|
|
|
|
|
|
|
# paper "Accurate Algorithms to Transform Geocentric to Geodetic |
1428
|
|
|
|
|
|
|
# Coordinates", from |
1429
|
|
|
|
|
|
|
# http://www.astro.uni.torun.pl/~kb/Papers/geod/Geod-BG.htm |
1430
|
|
|
|
|
|
|
|
1431
|
1840
|
|
|
|
|
2490
|
my $bz = $b * $z; |
1432
|
1840
|
|
|
|
|
2856
|
my $asq_bsq = $a * $a - $b * $b; |
1433
|
1840
|
|
|
|
|
2614
|
my $ar = $a * $r; |
1434
|
1840
|
|
|
|
|
2829
|
my $E = ($bz - $asq_bsq) / $ar; # Borkowski (10) |
1435
|
1840
|
|
|
|
|
2676
|
my $F = ($bz + $asq_bsq) / $ar; # Borkowski (11) |
1436
|
1840
|
|
|
|
|
2805
|
my $Q = ($E * $E - $F * $F) * 2; # Borkowski (17) |
1437
|
1840
|
|
|
|
|
2871
|
my $P = ($E * $F + 1) * 4 / 3; # Borkowski (16) |
1438
|
1840
|
|
|
|
|
3191
|
my $D = $P * $P * $P + $Q * $Q; # Borkowski (15) |
1439
|
|
|
|
|
|
|
my $v = $D >= 0 ? do { |
1440
|
1840
|
|
|
|
|
3182
|
my $d = sqrt $D; |
1441
|
1840
|
|
|
|
|
2597
|
my $onethird = 1 / 3; |
1442
|
1840
|
|
|
|
|
5458
|
my $vp = ($d - $Q) ** $onethird - # Borkowski (14a) |
1443
|
|
|
|
|
|
|
($d + $Q) ** $onethird; |
1444
|
1840
|
50
|
|
|
|
5294
|
$vp * $vp >= abs ($P) ? $vp : |
1445
|
|
|
|
|
|
|
- ($vp * $vp * $vp + 2 * $Q) / # Borkowski (20) |
1446
|
|
|
|
|
|
|
(3 * $P); |
1447
|
1840
|
50
|
|
|
|
3307
|
} : do { |
1448
|
0
|
|
|
|
|
0
|
my $p = - $P; |
1449
|
0
|
|
|
|
|
0
|
sqrt (cos (acos ($Q / # Borkowski (14b) |
1450
|
|
|
|
|
|
|
sqrt ($p * $p * $p)) / 3) * $p) * 2; |
1451
|
|
|
|
|
|
|
}; |
1452
|
1840
|
|
|
|
|
3372
|
my $G = (sqrt ($E * $E + $v) # Borkowski (13) |
1453
|
|
|
|
|
|
|
+ $E) / 2; |
1454
|
1840
|
|
|
|
|
3553
|
my $t = sqrt ($G * $G + ($F - $v * $G) # Borkowski (12) |
1455
|
|
|
|
|
|
|
/ (2 * $G - $E)) - $G; |
1456
|
|
|
|
|
|
|
|
1457
|
|
|
|
|
|
|
# Borkowski (18) |
1458
|
|
|
|
|
|
|
# equivalent to atan (arg1) |
1459
|
1840
|
|
|
|
|
2496
|
my $phi = do { |
1460
|
1840
|
|
|
|
|
2830
|
my $Y = $a * ( 1 - $t * $t ); |
1461
|
1840
|
|
|
|
|
2604
|
my $X = 2 * $b * $t; |
1462
|
1840
|
100
|
|
|
|
3462
|
( $Y, $X ) = ( -$Y, -$X ) if $X < 0; |
1463
|
1840
|
|
|
|
|
3364
|
atan2 $Y, $X; |
1464
|
|
|
|
|
|
|
}; |
1465
|
|
|
|
|
|
|
|
1466
|
1840
|
|
|
|
|
3582
|
my $h = ($r - $a * $t) * cos ($phi) + # Borkowski (19) |
1467
|
|
|
|
|
|
|
($z - $b) * sin ($phi); |
1468
|
|
|
|
|
|
|
|
1469
|
|
|
|
|
|
|
# End of Borkowski's algorthm. |
1470
|
|
|
|
|
|
|
|
1471
|
|
|
|
|
|
|
# Cache the results of the calculation if they were done using |
1472
|
|
|
|
|
|
|
# the default ellipsoid. |
1473
|
|
|
|
|
|
|
|
1474
|
1840
|
50
|
|
|
|
3650
|
$self->{_ECI_cache}{fixed}{geodetic} = [$phi, $lambda, $h] unless $elps; |
1475
|
|
|
|
|
|
|
|
1476
|
|
|
|
|
|
|
# Return the results in any event. |
1477
|
|
|
|
|
|
|
|
1478
|
1840
|
50
|
|
|
|
4605
|
$self->get ('debug') and print <
|
1479
|
|
|
|
|
|
|
Debug geodetic: geocentric to geodetic |
1480
|
|
|
|
|
|
|
inputs: |
1481
|
|
|
|
|
|
|
phiprime = $phiprime |
1482
|
|
|
|
|
|
|
lambda = $lambda |
1483
|
|
|
|
|
|
|
rho = $rho |
1484
|
|
|
|
|
|
|
intermediates: |
1485
|
|
|
|
|
|
|
z = $z |
1486
|
|
|
|
|
|
|
r = $r |
1487
|
|
|
|
|
|
|
E = $E |
1488
|
|
|
|
|
|
|
F = $F |
1489
|
|
|
|
|
|
|
P = $P |
1490
|
|
|
|
|
|
|
Q = $Q |
1491
|
|
|
|
|
|
|
D = $D |
1492
|
|
|
|
|
|
|
v = $v |
1493
|
|
|
|
|
|
|
G = $G |
1494
|
|
|
|
|
|
|
t = $t |
1495
|
|
|
|
|
|
|
outputs: |
1496
|
|
|
|
|
|
|
phi = atan2 (a * (1 - t * t), 2 * b * t) |
1497
|
0
|
|
|
|
|
0
|
= atan2 (@{[$a * (1 - $t * $t)]}, @{[2 * $b * $t]}) |
|
0
|
|
|
|
|
0
|
|
1498
|
|
|
|
|
|
|
= $phi (radians) |
1499
|
|
|
|
|
|
|
h = (r - a * t) * cos (phi) + (z - b) * sin (phi) |
1500
|
0
|
|
|
|
|
0
|
= @{[$r - $a * $t]} * cos (phi) + @{[$z - $b]} * sin (phi) |
|
0
|
|
|
|
|
0
|
|
1501
|
|
|
|
|
|
|
= $h (kilometers) |
1502
|
|
|
|
|
|
|
eod |
1503
|
1840
|
|
|
|
|
5266
|
return ($phi, $lambda, $h); |
1504
|
|
|
|
|
|
|
} |
1505
|
|
|
|
|
|
|
|
1506
|
|
|
|
|
|
|
# If we're setting the geodetic coordinates. |
1507
|
|
|
|
|
|
|
|
1508
|
2105
|
50
|
|
|
|
3922
|
if (@args == 3) { |
1509
|
|
|
|
|
|
|
|
1510
|
|
|
|
|
|
|
# Set the ellipsoid for the object if one was specified. |
1511
|
|
|
|
|
|
|
|
1512
|
2105
|
50
|
|
|
|
3953
|
$self->set (ellipsoid => $elps) if $elps; |
1513
|
|
|
|
|
|
|
|
1514
|
|
|
|
|
|
|
# Calculate the geocentric data. |
1515
|
|
|
|
|
|
|
|
1516
|
2105
|
|
|
|
|
4319
|
my ($phi, $lambda, $h) = @args; |
1517
|
2105
|
|
|
|
|
4019
|
$phi = _check_latitude(latitude => $phi); |
1518
|
2105
|
|
|
|
|
4325
|
$lambda = _check_longitude(longitude => $lambda); |
1519
|
2105
|
|
|
|
|
3893
|
my $bovera = 1 - $self->{flattening}; |
1520
|
|
|
|
|
|
|
|
1521
|
|
|
|
|
|
|
# The following algorithm appears on page 82 of the second |
1522
|
|
|
|
|
|
|
# edition of Jean Meeus' "Astronomical Algorithms." |
1523
|
|
|
|
|
|
|
|
1524
|
2105
|
|
|
|
|
4972
|
my $u = atan2 ($bovera * tan ($phi), 1); |
1525
|
|
|
|
|
|
|
my $rhosinlatprime = $bovera * sin ($u) + |
1526
|
2105
|
|
|
|
|
5393
|
$h / $self->{semimajor} * sin ($phi); |
1527
|
|
|
|
|
|
|
my $rhocoslatprime = cos ($u) + |
1528
|
2105
|
|
|
|
|
4123
|
$h / $self->{semimajor} * cos ($phi); |
1529
|
2105
|
|
|
|
|
3374
|
my $phiprime = atan2 ($rhosinlatprime, $rhocoslatprime); |
1530
|
2105
|
50
|
|
|
|
4867
|
my $rho = $self->{semimajor} * ($rhocoslatprime ? |
1531
|
|
|
|
|
|
|
$rhocoslatprime / cos ($phiprime) : |
1532
|
|
|
|
|
|
|
$rhosinlatprime / sin ($phiprime)); |
1533
|
|
|
|
|
|
|
|
1534
|
|
|
|
|
|
|
# End of Meeus' algorithm. |
1535
|
|
|
|
|
|
|
|
1536
|
|
|
|
|
|
|
# Set the geocentric data as the coordinates. |
1537
|
|
|
|
|
|
|
|
1538
|
2105
|
|
|
|
|
5744
|
$self->geocentric ($phiprime, $lambda, $rho); |
1539
|
|
|
|
|
|
|
|
1540
|
|
|
|
|
|
|
|
1541
|
|
|
|
|
|
|
# Cache the geodetic coordinates. |
1542
|
|
|
|
|
|
|
|
1543
|
2105
|
|
|
|
|
5127
|
$self->{_ECI_cache}{fixed}{geodetic} = [$phi, $lambda, $h]; |
1544
|
2105
|
|
|
|
|
3327
|
$self->{specified} = 'geodetic'; |
1545
|
2105
|
|
|
|
|
3394
|
$self->{inertial} = 0; |
1546
|
|
|
|
|
|
|
|
1547
|
|
|
|
|
|
|
# Else if the number of coordinates is bogus, croak. |
1548
|
|
|
|
|
|
|
|
1549
|
|
|
|
|
|
|
} else { |
1550
|
0
|
|
|
|
|
0
|
croak <
|
1551
|
|
|
|
|
|
|
Error - Method geodetic() must be called with either zero arguments |
1552
|
|
|
|
|
|
|
(to retrieve coordinates) or three arguments (to set |
1553
|
|
|
|
|
|
|
coordinates). There is currently no six argument version. |
1554
|
|
|
|
|
|
|
eod |
1555
|
|
|
|
|
|
|
} |
1556
|
|
|
|
|
|
|
|
1557
|
|
|
|
|
|
|
# Return the object, wherever it came from. |
1558
|
|
|
|
|
|
|
|
1559
|
2105
|
|
|
|
|
6197
|
return $self; |
1560
|
|
|
|
|
|
|
|
1561
|
|
|
|
|
|
|
} |
1562
|
|
|
|
|
|
|
|
1563
|
|
|
|
|
|
|
=item $value = $coord->get ($attrib); |
1564
|
|
|
|
|
|
|
|
1565
|
|
|
|
|
|
|
This method returns the named attributes of the object. If called in |
1566
|
|
|
|
|
|
|
list context, you can give more than one attribute name, and it will |
1567
|
|
|
|
|
|
|
return all their values. |
1568
|
|
|
|
|
|
|
|
1569
|
|
|
|
|
|
|
If called as a class method, it returns the current default values. |
1570
|
|
|
|
|
|
|
|
1571
|
|
|
|
|
|
|
See L for a list of the attributes you can get. |
1572
|
|
|
|
|
|
|
|
1573
|
|
|
|
|
|
|
=cut |
1574
|
|
|
|
|
|
|
|
1575
|
|
|
|
|
|
|
{ # Begin local symbol block. |
1576
|
|
|
|
|
|
|
|
1577
|
|
|
|
|
|
|
my %accessor = ( |
1578
|
|
|
|
|
|
|
sun => sub { |
1579
|
|
|
|
|
|
|
## my ( $self, $name ) = @_; |
1580
|
|
|
|
|
|
|
my ( $self ) = @_; # Name unused |
1581
|
|
|
|
|
|
|
$self->{sun} |
1582
|
|
|
|
|
|
|
and return $self->{sun}; |
1583
|
|
|
|
|
|
|
my $sun_class = $self->SUN_CLASS(); |
1584
|
|
|
|
|
|
|
( my $path = "$sun_class.pm" ) =~ s< :: >>smxg; |
1585
|
|
|
|
|
|
|
require $path; |
1586
|
|
|
|
|
|
|
return( $self->{sun} ||= $sun_class->new() ); |
1587
|
|
|
|
|
|
|
}, |
1588
|
|
|
|
|
|
|
); |
1589
|
|
|
|
|
|
|
|
1590
|
|
|
|
|
|
|
sub get { |
1591
|
99518
|
|
|
99518
|
1
|
183931
|
my ($self, @args) = @_; |
1592
|
99518
|
100
|
|
|
|
180758
|
ref $self or $self = \%static; |
1593
|
99518
|
|
|
|
|
121028
|
my @rslt; |
1594
|
99518
|
|
|
|
|
152143
|
foreach my $name (@args) { |
1595
|
99518
|
50
|
|
|
|
183454
|
exists $mutator{$name} |
1596
|
|
|
|
|
|
|
or croak " Error - Attribute '$name' does not exist"; |
1597
|
99518
|
100
|
|
|
|
161891
|
if ($accessor{$name}) { |
1598
|
1263
|
|
|
|
|
2425
|
push @rslt, $accessor{$name}->($self, $name); |
1599
|
|
|
|
|
|
|
} else { |
1600
|
98255
|
|
|
|
|
183919
|
push @rslt, $self->{$name}; |
1601
|
|
|
|
|
|
|
} |
1602
|
|
|
|
|
|
|
} |
1603
|
99518
|
100
|
|
|
|
275897
|
return wantarray ? @rslt : $rslt[0]; |
1604
|
|
|
|
|
|
|
} |
1605
|
|
|
|
|
|
|
|
1606
|
|
|
|
|
|
|
} # End local symbol block |
1607
|
|
|
|
|
|
|
|
1608
|
|
|
|
|
|
|
=item @coords = $coord->enu(); |
1609
|
|
|
|
|
|
|
|
1610
|
|
|
|
|
|
|
This method reports the coordinates of C<$coord> in the East-North-Up |
1611
|
|
|
|
|
|
|
coordinate system, as seen from the position stored in the C |
1612
|
|
|
|
|
|
|
attribute of C<$coord>. |
1613
|
|
|
|
|
|
|
|
1614
|
|
|
|
|
|
|
Velocity conversions to C appear to me to be mostly sane. See |
1615
|
|
|
|
|
|
|
L, below, for details. |
1616
|
|
|
|
|
|
|
|
1617
|
|
|
|
|
|
|
=cut |
1618
|
|
|
|
|
|
|
|
1619
|
|
|
|
|
|
|
sub enu { # East, North, Up |
1620
|
3
|
|
|
3
|
1
|
7
|
my ( $self ) = @_; |
1621
|
3
|
|
|
|
|
11
|
my @vector = $self->neu(); |
1622
|
3
|
|
|
|
|
8
|
@vector[ 0, 1 ] = @vector[ 1, 0 ]; # Swap North and East |
1623
|
3
|
50
|
|
|
|
9
|
@vector > 3 # If we have velocity, |
1624
|
|
|
|
|
|
|
and @vector[ 3, 4 ] = @vector[ 4, 3 ]; # Ditto |
1625
|
3
|
|
|
|
|
11
|
return @vector; |
1626
|
|
|
|
|
|
|
} |
1627
|
|
|
|
|
|
|
|
1628
|
|
|
|
|
|
|
=item @coords = $coord->neu(); |
1629
|
|
|
|
|
|
|
|
1630
|
|
|
|
|
|
|
This method reports the coordinates of C<$coord2> in the North-East-Up |
1631
|
|
|
|
|
|
|
coordinate system, as seen from the position stored in the C |
1632
|
|
|
|
|
|
|
attribute of C<$coord>. This is a B coordinate system. |
1633
|
|
|
|
|
|
|
|
1634
|
|
|
|
|
|
|
Velocity conversions to C appear to me to be mostly sane. See |
1635
|
|
|
|
|
|
|
L, below, for details. |
1636
|
|
|
|
|
|
|
|
1637
|
|
|
|
|
|
|
=cut |
1638
|
|
|
|
|
|
|
|
1639
|
|
|
|
|
|
|
sub neu { # North, East, Up |
1640
|
6
|
|
|
6
|
1
|
10
|
my ( $self ) = @_; |
1641
|
6
|
50
|
|
|
|
10
|
my $station = $self->get( 'station' ) |
1642
|
|
|
|
|
|
|
or croak 'Station attribute required'; |
1643
|
6
|
|
|
|
|
15
|
return $station->_local_cartesian( $self ); |
1644
|
|
|
|
|
|
|
} |
1645
|
|
|
|
|
|
|
|
1646
|
|
|
|
|
|
|
=item @coords = $coord->heliocentric_ecliptic_cartesian() |
1647
|
|
|
|
|
|
|
|
1648
|
|
|
|
|
|
|
This method returns the Heliocentric ecliptic Cartesian position of the |
1649
|
|
|
|
|
|
|
object. You can optionally pass time as an argument; this is equivalent |
1650
|
|
|
|
|
|
|
to |
1651
|
|
|
|
|
|
|
|
1652
|
|
|
|
|
|
|
@coords = $coord->universal( $time ) |
1653
|
|
|
|
|
|
|
->heliocentric_ecliptic_cartesian(); |
1654
|
|
|
|
|
|
|
|
1655
|
|
|
|
|
|
|
At this time this object is not able to derive Heliocentric ecliptic |
1656
|
|
|
|
|
|
|
Cartesian coordinates from other coordinates (say, ECI). |
1657
|
|
|
|
|
|
|
|
1658
|
|
|
|
|
|
|
=item $coord->heliocentric_ecliptic_cartesian( $X, $Y, $Z ) |
1659
|
|
|
|
|
|
|
|
1660
|
|
|
|
|
|
|
This method sets the object's position in Heliocentric ecliptic |
1661
|
|
|
|
|
|
|
Cartesian coordinates. Velocities may not be set at the moment, though |
1662
|
|
|
|
|
|
|
this is planned. You may also pass an optional time as the last |
1663
|
|
|
|
|
|
|
argument. |
1664
|
|
|
|
|
|
|
|
1665
|
|
|
|
|
|
|
The invocant is returned. |
1666
|
|
|
|
|
|
|
|
1667
|
|
|
|
|
|
|
=cut |
1668
|
|
|
|
|
|
|
|
1669
|
|
|
|
|
|
|
sub heliocentric_ecliptic_cartesian { |
1670
|
0
|
|
|
0
|
1
|
0
|
my ( $self, @args ) = @_; |
1671
|
|
|
|
|
|
|
|
1672
|
0
|
|
|
|
|
0
|
$self = $self->_check_coord( heliocentric_ecliptic_cartesian => \@args ); |
1673
|
|
|
|
|
|
|
|
1674
|
0
|
0
|
|
|
|
0
|
if ( @args == 3 ) { |
|
|
0
|
|
|
|
|
|
1675
|
|
|
|
|
|
|
$self->{_ECI_cache} = { |
1676
|
0
|
|
|
|
|
0
|
inertial => { |
1677
|
|
|
|
|
|
|
heliocentric_ecliptic_cartesian => \@args, |
1678
|
|
|
|
|
|
|
}, |
1679
|
|
|
|
|
|
|
}; |
1680
|
0
|
0
|
|
|
|
0
|
my $sun = $self->get( 'sun' ) |
1681
|
|
|
|
|
|
|
or croak q{Attribute 'sun' not set}; |
1682
|
0
|
|
|
|
|
0
|
my ( $X_sun, $Y_sun, $Z_sun ) = $sun->universal( |
1683
|
|
|
|
|
|
|
$self->universal() )->ecliptic_cartesian(); |
1684
|
0
|
|
|
|
|
0
|
my ( $X, $Y, $Z ) = ( $args[0] + $X_sun, $args[1] + $Y_sun, |
1685
|
|
|
|
|
|
|
$args[2] + $Z_sun ); |
1686
|
0
|
|
|
|
|
0
|
$self->ecliptic_cartesian( $X, $Y, $Z ); |
1687
|
0
|
|
|
|
|
0
|
$self->{specified} = 'heliocentric_ecliptic_cartesian'; |
1688
|
0
|
|
|
|
|
0
|
$self->{inertial} = 1; |
1689
|
|
|
|
|
|
|
} elsif ( @args ) { |
1690
|
0
|
|
|
|
|
0
|
croak 'heliocentric_ecliptic_cartesian() wants 0, 1, 3 or 4 arguments'; |
1691
|
|
|
|
|
|
|
} else { |
1692
|
|
|
|
|
|
|
$self->{_ECI_cache}{inertial}{heliocentric_ecliptic_cartesian} |
1693
|
|
|
|
|
|
|
and return @{ |
1694
|
0
|
0
|
|
|
|
0
|
$self->{_ECI_cache}{inertial}{heliocentric_ecliptic_cartesian} |
1695
|
0
|
|
|
|
|
0
|
}; |
1696
|
0
|
0
|
|
|
|
0
|
my $sun = $self->get( 'sun' ) |
1697
|
|
|
|
|
|
|
or croak q{Attribute 'sun' not set}; |
1698
|
0
|
|
|
|
|
0
|
my ( $X_self, $Y_self, $Z_self ) = $self->ecliptic_cartesian(); |
1699
|
0
|
|
|
|
|
0
|
my ( $X_sun, $Y_sun, $Z_sun ) = $sun->universal( |
1700
|
|
|
|
|
|
|
$self->universal() )->ecliptic_cartesian(); |
1701
|
|
|
|
|
|
|
|
1702
|
0
|
|
|
|
|
0
|
my @hec = ( $X_self - $X_sun, $Y_self - $Y_sun, $Z_self - $Z_sun ); |
1703
|
|
|
|
|
|
|
|
1704
|
0
|
|
|
|
|
0
|
$self->set( equinox_dynamical => $self->dynamical() ); |
1705
|
0
|
|
|
|
|
0
|
$self->{_ECI_cache}{inertial}{heliocentric_ecliptic_cartesian} = \@hec; |
1706
|
0
|
|
|
|
|
0
|
return @hec; |
1707
|
|
|
|
|
|
|
} |
1708
|
0
|
|
|
|
|
0
|
return $self; |
1709
|
|
|
|
|
|
|
} |
1710
|
|
|
|
|
|
|
|
1711
|
|
|
|
|
|
|
=item @coords = $coord->heliocentric_ecliptic(); |
1712
|
|
|
|
|
|
|
|
1713
|
|
|
|
|
|
|
This method returns the Heliocentric ecliptic coordinates of the object. |
1714
|
|
|
|
|
|
|
|
1715
|
|
|
|
|
|
|
=cut |
1716
|
|
|
|
|
|
|
|
1717
|
|
|
|
|
|
|
sub heliocentric_ecliptic { |
1718
|
0
|
|
|
0
|
1
|
0
|
my ( $self, @args ) = @_; |
1719
|
|
|
|
|
|
|
|
1720
|
0
|
|
|
|
|
0
|
$self = $self->_check_coord( heliocentric_ecliptic => \@args ); |
1721
|
|
|
|
|
|
|
|
1722
|
0
|
0
|
|
|
|
0
|
if ( @args ) { |
1723
|
0
|
0
|
|
|
|
0
|
@args % 3 |
1724
|
|
|
|
|
|
|
and croak 'Arguments must be in threes, plus an optional time'; |
1725
|
0
|
|
|
|
|
0
|
$self->{_ECI_cache}{inertial}{heliocentric_ecliptic} = \@args; |
1726
|
0
|
|
|
|
|
0
|
$self->heliocentric_ecliptic_cartesian( |
1727
|
|
|
|
|
|
|
_convert_spherical_x_to_cartesian( @args ) ); |
1728
|
0
|
|
|
|
|
0
|
$self->{specified} = 'heliocentric_ecliptic'; |
1729
|
0
|
|
|
|
|
0
|
return $self; |
1730
|
|
|
|
|
|
|
} else { |
1731
|
|
|
|
|
|
|
return @{ |
1732
|
0
|
|
|
|
|
0
|
$self->{_ECI_cache}{inertial}{heliocentric_ecliptic} ||= [ |
1733
|
0
|
|
0
|
|
|
0
|
_convert_cartesian_to_spherical_x( |
1734
|
|
|
|
|
|
|
$self->heliocentric_ecliptic_cartesian() ) ] } |
1735
|
|
|
|
|
|
|
} |
1736
|
0
|
|
|
|
|
0
|
return $self; |
1737
|
|
|
|
|
|
|
} |
1738
|
|
|
|
|
|
|
|
1739
|
|
|
|
|
|
|
# ( $temp, $X, $Y, $Z, $Xprime, $Yprime, $Zprime ) = |
1740
|
|
|
|
|
|
|
# $self->_local_cartesian( $trn2 ); |
1741
|
|
|
|
|
|
|
# This method computes local Cartesian coordinates of $trn2 as seen from |
1742
|
|
|
|
|
|
|
# $self. The first return is intermediate results useful for the azimuth |
1743
|
|
|
|
|
|
|
# and elevation. The subsequent results are X, Y, and Z coordinates (and |
1744
|
|
|
|
|
|
|
# velocities if available), in the North, East, Up coordinate system. |
1745
|
|
|
|
|
|
|
# This is a left-handed coordinate system, but so is the azel() system, |
1746
|
|
|
|
|
|
|
# which it serves. |
1747
|
|
|
|
|
|
|
|
1748
|
|
|
|
|
|
|
sub _local_cartesian { |
1749
|
21999
|
|
|
21999
|
|
34611
|
my ( $self, $trn2 ) = @_; |
1750
|
21999
|
50
|
|
|
|
38114
|
$self->{debug} and do { |
1751
|
0
|
|
|
|
|
0
|
require Data::Dumper; |
1752
|
0
|
|
|
|
|
0
|
local $Data::Dumper::Terse = 1; |
1753
|
0
|
|
|
|
|
0
|
print "Debug local_cartesian - ", Data::Dumper::Dumper( $self, $trn2 ); |
1754
|
|
|
|
|
|
|
}; |
1755
|
|
|
|
|
|
|
|
1756
|
21999
|
|
|
|
|
37401
|
my $time = $trn2->universal(); |
1757
|
21999
|
|
|
|
|
50674
|
$self->universal( $time ); |
1758
|
|
|
|
|
|
|
|
1759
|
|
|
|
|
|
|
# Pick up the ECEF of the body and the station |
1760
|
|
|
|
|
|
|
|
1761
|
21999
|
|
|
|
|
41448
|
my @tgt = $trn2->ecef(); |
1762
|
21999
|
|
|
|
|
42032
|
my @obs = $self->ecef(); |
1763
|
|
|
|
|
|
|
|
1764
|
|
|
|
|
|
|
# Translate the ECEF coordinates to the station. |
1765
|
|
|
|
|
|
|
|
1766
|
21999
|
|
|
|
|
71532
|
my $limit = int( min( scalar @tgt, scalar @obs ) / 3 ) * 3 - 1; |
1767
|
21999
|
|
|
|
|
44801
|
foreach my $inx ( 0 .. $limit ) { |
1768
|
116703
|
|
|
|
|
165064
|
$tgt[$inx] -= $obs[$inx]; |
1769
|
|
|
|
|
|
|
} |
1770
|
21999
|
|
|
|
|
37756
|
splice @tgt, $limit + 1; |
1771
|
|
|
|
|
|
|
|
1772
|
|
|
|
|
|
|
# Pick up the latitude and longitude. |
1773
|
|
|
|
|
|
|
|
1774
|
21999
|
|
|
|
|
45510
|
my ( $lat, $lon ) = $self->geodetic(); |
1775
|
21999
|
|
|
|
|
38184
|
my $sinlat = sin $lat; |
1776
|
21999
|
|
|
|
|
31810
|
my $coslat = cos $lat; |
1777
|
21999
|
|
|
|
|
28397
|
my $sinlon = sin $lon; |
1778
|
21999
|
|
|
|
|
27058
|
my $coslon = cos $lon; |
1779
|
|
|
|
|
|
|
|
1780
|
|
|
|
|
|
|
# Rotate X->Y to longitude of station, |
1781
|
|
|
|
|
|
|
# then Z->X to latitude of station |
1782
|
|
|
|
|
|
|
|
1783
|
|
|
|
|
|
|
# NOTE to Flat Earthers: For the next two statements to produce a |
1784
|
|
|
|
|
|
|
# position in a local Cartesian system with the X-Y plane coincident |
1785
|
|
|
|
|
|
|
# with the horizon, the Earth must be the oblate spheroid specified |
1786
|
|
|
|
|
|
|
# by the currently-set reference elllipsoid. |
1787
|
|
|
|
|
|
|
|
1788
|
21999
|
|
|
|
|
44904
|
@tgt[ 0, 1 ] = ( |
1789
|
|
|
|
|
|
|
$tgt[0] * $coslon + $tgt[1] * $sinlon, |
1790
|
|
|
|
|
|
|
- $tgt[0] * $sinlon + $tgt[1] * $coslon, |
1791
|
|
|
|
|
|
|
); |
1792
|
21999
|
|
|
|
|
37782
|
@tgt[ 0, 2 ] = ( |
1793
|
|
|
|
|
|
|
$tgt[0] * $sinlat - $tgt[2] * $coslat, |
1794
|
|
|
|
|
|
|
$tgt[0] * $coslat + $tgt[2] * $sinlat, |
1795
|
|
|
|
|
|
|
); |
1796
|
|
|
|
|
|
|
|
1797
|
21999
|
|
|
|
|
32273
|
$tgt[0] = - $tgt[0]; # Convert Southing to Northing |
1798
|
|
|
|
|
|
|
|
1799
|
21999
|
100
|
|
|
|
43474
|
if ( @tgt > 5 ) { |
1800
|
16902
|
|
|
|
|
32976
|
@tgt[ 3, 4 ] = ( |
1801
|
|
|
|
|
|
|
$tgt[3] * $coslon + $tgt[4] * $sinlon, |
1802
|
|
|
|
|
|
|
- $tgt[3] * $sinlon + $tgt[4] * $coslon, |
1803
|
|
|
|
|
|
|
); |
1804
|
16902
|
|
|
|
|
29678
|
@tgt[ 3, 5 ] = ( |
1805
|
|
|
|
|
|
|
$tgt[3] * $sinlat - $tgt[5] * $coslat, |
1806
|
|
|
|
|
|
|
$tgt[3] * $coslat + $tgt[5] * $sinlat, |
1807
|
|
|
|
|
|
|
); |
1808
|
|
|
|
|
|
|
|
1809
|
16902
|
|
|
|
|
21827
|
$tgt[3] = - $tgt[3]; # Convert South velocity to North |
1810
|
|
|
|
|
|
|
} |
1811
|
|
|
|
|
|
|
|
1812
|
21999
|
|
|
|
|
73534
|
return @tgt; |
1813
|
|
|
|
|
|
|
} |
1814
|
|
|
|
|
|
|
|
1815
|
|
|
|
|
|
|
=item $coord = $coord->local_mean_time ($time); |
1816
|
|
|
|
|
|
|
|
1817
|
|
|
|
|
|
|
This method sets the local mean time of the object. B
|
1818
|
|
|
|
|
|
|
local standard time,> but the universal time plus the longitude |
1819
|
|
|
|
|
|
|
of the object expressed in seconds. Another definition is mean |
1820
|
|
|
|
|
|
|
solar time plus 12 hours (since the solar day begins at noon). |
1821
|
|
|
|
|
|
|
You will get an exception of some sort if the position of the |
1822
|
|
|
|
|
|
|
object has not been set, or if the object represents inertial |
1823
|
|
|
|
|
|
|
coordinates, or on any subclass whose position depends on the time. |
1824
|
|
|
|
|
|
|
|
1825
|
|
|
|
|
|
|
=item $time = $coord->local_mean_time () |
1826
|
|
|
|
|
|
|
|
1827
|
|
|
|
|
|
|
This method returns the local mean time of the object. It will raise |
1828
|
|
|
|
|
|
|
an exception if the time has not been set. |
1829
|
|
|
|
|
|
|
|
1830
|
|
|
|
|
|
|
Note that this returns the actual local time, not the GMT equivalent. |
1831
|
|
|
|
|
|
|
This means that in formatting for output, you call |
1832
|
|
|
|
|
|
|
|
1833
|
|
|
|
|
|
|
strftime $format, gmtime $coord->local_mean_time (); |
1834
|
|
|
|
|
|
|
|
1835
|
|
|
|
|
|
|
=cut |
1836
|
|
|
|
|
|
|
|
1837
|
|
|
|
|
|
|
sub local_mean_time { |
1838
|
2
|
|
|
2
|
1
|
5
|
my ($self, @args) = @_; |
1839
|
|
|
|
|
|
|
|
1840
|
2
|
50
|
|
|
|
7
|
ref $self or croak <
|
1841
|
|
|
|
|
|
|
Error - The local_mean_time() method may not be called as a class method. |
1842
|
|
|
|
|
|
|
eod |
1843
|
|
|
|
|
|
|
|
1844
|
2
|
100
|
|
|
|
5
|
unless (@args) { |
1845
|
1
|
50
|
|
|
|
15
|
$self->{universal} || croak <
|
1846
|
|
|
|
|
|
|
Error - Object's time has not been set. |
1847
|
|
|
|
|
|
|
eod |
1848
|
|
|
|
|
|
|
$self->{local_mean_time} = $self->universal () + |
1849
|
|
|
|
|
|
|
_local_mean_delta ($self) |
1850
|
1
|
50
|
|
|
|
7
|
unless defined $self->{local_mean_time}; |
1851
|
1
|
|
|
|
|
9
|
return $self->{local_mean_time}; |
1852
|
|
|
|
|
|
|
} |
1853
|
|
|
|
|
|
|
|
1854
|
1
|
50
|
|
|
|
5
|
if (@args == 1) { |
1855
|
1
|
|
|
|
|
3
|
my $time = shift @args; |
1856
|
1
|
50
|
|
|
|
4
|
$self->{specified} or croak <
|
1857
|
|
|
|
|
|
|
Error - Object's coordinates have not been set. |
1858
|
|
|
|
|
|
|
eod |
1859
|
1
|
50
|
|
|
|
3
|
$self->{inertial} and croak <
|
1860
|
|
|
|
|
|
|
Error - You can not set the local time of an object that represents |
1861
|
|
|
|
|
|
|
a position in an inertial coordinate system, because this |
1862
|
|
|
|
|
|
|
causes the earth-fixed position to change, invalidating the |
1863
|
|
|
|
|
|
|
local time. |
1864
|
|
|
|
|
|
|
$self->can ('time_set') and croak <
|
1865
|
0
|
|
|
|
|
0
|
Error - You can not set the local time on an @{[ref $self]} |
1866
|
|
|
|
|
|
|
object, because when you do the time_set() method will just |
1867
|
|
|
|
|
|
|
move the object, invalidating the local time. |
1868
|
|
|
|
|
|
|
eod |
1869
|
1
|
|
|
|
|
4
|
$self->universal($time - _local_mean_delta($self)); |
1870
|
1
|
|
|
|
|
3
|
$self->{local_mean_time} = $time; |
1871
|
|
|
|
|
|
|
} else { |
1872
|
0
|
|
|
|
|
0
|
croak <
|
1873
|
|
|
|
|
|
|
Error - The local_mean_time() method must be called with either zero |
1874
|
|
|
|
|
|
|
arguments (to retrieve the time) or one argument (to set |
1875
|
|
|
|
|
|
|
the time). |
1876
|
|
|
|
|
|
|
eod |
1877
|
|
|
|
|
|
|
} |
1878
|
|
|
|
|
|
|
|
1879
|
1
|
|
|
|
|
4
|
return $self; |
1880
|
|
|
|
|
|
|
} |
1881
|
|
|
|
|
|
|
|
1882
|
|
|
|
|
|
|
=item $time = $coord->local_time (); |
1883
|
|
|
|
|
|
|
|
1884
|
|
|
|
|
|
|
This method returns the local time (defined as solar time plus 12 hours) |
1885
|
|
|
|
|
|
|
of the given object. There is no way to set the local time. |
1886
|
|
|
|
|
|
|
|
1887
|
|
|
|
|
|
|
This is really just a convenience routine - the calculation is simply |
1888
|
|
|
|
|
|
|
the local mean time plus the equation of time at the given time. |
1889
|
|
|
|
|
|
|
|
1890
|
|
|
|
|
|
|
Note that this returns the actual local time, not the GMT equivalent. |
1891
|
|
|
|
|
|
|
This means that in formatting for output, you call |
1892
|
|
|
|
|
|
|
|
1893
|
|
|
|
|
|
|
strftime $format, gmtime $coord->local_time (); |
1894
|
|
|
|
|
|
|
|
1895
|
|
|
|
|
|
|
=cut |
1896
|
|
|
|
|
|
|
|
1897
|
|
|
|
|
|
|
sub local_time { |
1898
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
1899
|
0
|
|
|
|
|
0
|
return $self->local_mean_time() + $self->equation_of_time(); |
1900
|
|
|
|
|
|
|
} |
1901
|
|
|
|
|
|
|
|
1902
|
|
|
|
|
|
|
=item ( $maidenhead_loc, $height ) = $coord->maidenhead( $precision ); |
1903
|
|
|
|
|
|
|
|
1904
|
|
|
|
|
|
|
This method returns the location of the object in the Maidenhead Locator |
1905
|
|
|
|
|
|
|
System. Height above the reference ellipsoid is not part of the system, |
1906
|
|
|
|
|
|
|
but is returned anyway, in kilometers. The $precision is optional, and |
1907
|
|
|
|
|
|
|
is an integer greater than 0. |
1908
|
|
|
|
|
|
|
|
1909
|
|
|
|
|
|
|
The default precision is 4, but this can be modified by setting |
1910
|
|
|
|
|
|
|
C<$Astro::Coord::ECI::DEFAULT_MAIDENHEAD_PRECISION> to the desired |
1911
|
|
|
|
|
|
|
value. For example, if you want the default precision to be 3 (which it |
1912
|
|
|
|
|
|
|
probably should have been in the first place), you can use |
1913
|
|
|
|
|
|
|
|
1914
|
|
|
|
|
|
|
no warnings qw{ once }; |
1915
|
|
|
|
|
|
|
local $Astro::Coord::ECI::DEFAULT_MAIDENHEAD_PRECISION = 3; |
1916
|
|
|
|
|
|
|
|
1917
|
|
|
|
|
|
|
Note that precisions greater than 4 are not defined by the standard. |
1918
|
|
|
|
|
|
|
This method extends the system by alternating letters (base 24) with |
1919
|
|
|
|
|
|
|
digits (base 10), but this is unsupported since the results will change, |
1920
|
|
|
|
|
|
|
possibly without notice, if the standard is extended in a manner |
1921
|
|
|
|
|
|
|
incompatible with this implementation. |
1922
|
|
|
|
|
|
|
|
1923
|
|
|
|
|
|
|
Conversion of latitudes and longitudes to Maidenhead Grid is subject to |
1924
|
|
|
|
|
|
|
truncation error, perhaps more so since latitude and longitude are |
1925
|
|
|
|
|
|
|
specified in radians. An attempt has been made to minimize this by using |
1926
|
|
|
|
|
|
|
Perl's stringification of numbers to ensure that something that looks |
1927
|
|
|
|
|
|
|
like C<42> is not handled as C<41.999999999385>. This probably amounts |
1928
|
|
|
|
|
|
|
to shifting some grid squares very slightly to the north-west, but in |
1929
|
|
|
|
|
|
|
practice it seems to give better results for points on the boundaries of |
1930
|
|
|
|
|
|
|
the grid squares. |
1931
|
|
|
|
|
|
|
|
1932
|
|
|
|
|
|
|
=item $coord->maidenhead( $maidenhead_loc, $height ); |
1933
|
|
|
|
|
|
|
|
1934
|
|
|
|
|
|
|
This method sets the geodetic location in the Maidenhead Locator System. |
1935
|
|
|
|
|
|
|
Height above the reference ellipsoid is not part of the system, but is |
1936
|
|
|
|
|
|
|
accepted anyway, in kilometers, defaulting to 0. |
1937
|
|
|
|
|
|
|
|
1938
|
|
|
|
|
|
|
The actual location set is the center of the specified grid square. |
1939
|
|
|
|
|
|
|
|
1940
|
|
|
|
|
|
|
Locations longer than 8 characters are not defined by the standard. This |
1941
|
|
|
|
|
|
|
method extends precision by assuming alternate letters (base 24) and |
1942
|
|
|
|
|
|
|
digits (base 10), but this will change, possibly without notice, if the |
1943
|
|
|
|
|
|
|
standard is extended in a manner incompatible with this implementation. |
1944
|
|
|
|
|
|
|
|
1945
|
|
|
|
|
|
|
The object itself is returned, to allow call chaining. |
1946
|
|
|
|
|
|
|
|
1947
|
|
|
|
|
|
|
=cut |
1948
|
|
|
|
|
|
|
{ |
1949
|
|
|
|
|
|
|
|
1950
|
|
|
|
|
|
|
our $DEFAULT_MAIDENHEAD_PRECISION = 4; |
1951
|
|
|
|
|
|
|
|
1952
|
|
|
|
|
|
|
my $alpha = 'abcdefghijklmnopqrstuvwxyz'; |
1953
|
|
|
|
|
|
|
|
1954
|
|
|
|
|
|
|
sub maidenhead { |
1955
|
2085
|
|
|
2085
|
1
|
579284
|
my ( $self, @args ) = @_; |
1956
|
2085
|
100
|
66
|
|
|
16596
|
if ( @args > 1 || defined $args[0] && $args[0] =~ m/ [^0-9] /smx ) { |
|
|
|
66
|
|
|
|
|
1957
|
1042
|
|
|
|
|
2384
|
my ( $loc, $alt ) = @args; |
1958
|
1042
|
50
|
|
|
|
2337
|
defined $alt or $alt = 0; |
1959
|
|
|
|
|
|
|
|
1960
|
1042
|
|
|
|
|
1830
|
my $precision = length $loc; |
1961
|
1042
|
50
|
|
|
|
2575
|
$precision % 2 |
1962
|
|
|
|
|
|
|
and croak "Invalid Maidenhead locator; length must be even"; |
1963
|
1042
|
|
|
|
|
2099
|
$precision /= 2; |
1964
|
|
|
|
|
|
|
|
1965
|
1042
|
|
|
|
|
1472
|
my $lat = 0.5; |
1966
|
1042
|
|
|
|
|
1621
|
my $lon = 0.5; |
1967
|
1042
|
|
|
|
|
6988
|
my @chars = split qr{}smx, $loc; |
1968
|
1042
|
|
|
|
|
2899
|
foreach my $base ( reverse _maidenhead_bases( $precision ) ) { |
1969
|
3125
|
|
|
|
|
5147
|
foreach ( $lat, $lon ) { |
1970
|
6250
|
|
|
|
|
9519
|
my $chr = pop @chars; |
1971
|
6250
|
100
|
|
|
|
10308
|
if ( $base > 10 ) { |
1972
|
4166
|
50
|
|
|
|
9756
|
( my $inx = index $alpha, lc $chr ) < 0 |
1973
|
|
|
|
|
|
|
and croak 'Invalid Maidenhead locator; ', |
1974
|
|
|
|
|
|
|
"'$chr' is not a letter"; |
1975
|
4166
|
100
|
|
|
|
7941
|
my $limit = @chars > 1 ? $base - 1 : $base; |
1976
|
4166
|
50
|
|
|
|
7741
|
$inx > $limit |
1977
|
|
|
|
|
|
|
and croak 'Invalid Maidenhead locator; ', |
1978
|
|
|
|
|
|
|
"'$chr' is greater than '", |
1979
|
|
|
|
|
|
|
substr( $alpha, $limit, 1 ), "'"; |
1980
|
4166
|
|
|
|
|
6889
|
$_ += $inx; |
1981
|
4166
|
|
|
|
|
7672
|
$_ /= $base; |
1982
|
|
|
|
|
|
|
} else { |
1983
|
2084
|
50
|
|
|
|
4647
|
$chr =~ m/ [^0-9] /smx |
1984
|
|
|
|
|
|
|
and croak 'Invalid Maidenhead locator; ', |
1985
|
|
|
|
|
|
|
"'$chr' is not a digit"; |
1986
|
2084
|
|
|
|
|
3606
|
$_ += $chr; |
1987
|
2084
|
|
|
|
|
3626
|
$_ /= $base; |
1988
|
|
|
|
|
|
|
} |
1989
|
|
|
|
|
|
|
} |
1990
|
|
|
|
|
|
|
} |
1991
|
|
|
|
|
|
|
|
1992
|
|
|
|
|
|
|
$self->geodetic( |
1993
|
1042
|
|
|
|
|
3622
|
deg2rad( $lat * 180 - 90 ), |
1994
|
|
|
|
|
|
|
deg2rad( $lon * 360 - 180 ), |
1995
|
|
|
|
|
|
|
$alt |
1996
|
|
|
|
|
|
|
); |
1997
|
1042
|
|
|
|
|
5772
|
return $self; |
1998
|
|
|
|
|
|
|
|
1999
|
|
|
|
|
|
|
} else { |
2000
|
1043
|
|
|
|
|
2507
|
my ( $precision ) = @args; |
2001
|
1043
|
50
|
33
|
|
|
3665
|
defined $precision |
2002
|
|
|
|
|
|
|
and $precision > 0 |
2003
|
|
|
|
|
|
|
or $precision = $DEFAULT_MAIDENHEAD_PRECISION; |
2004
|
|
|
|
|
|
|
|
2005
|
1043
|
|
|
|
|
2132
|
my @bases = reverse _maidenhead_bases( $precision ); |
2006
|
|
|
|
|
|
|
|
2007
|
|
|
|
|
|
|
# In order to minimize truncation errors we multiply |
2008
|
|
|
|
|
|
|
# everything out, and then work right-to-left using the mod |
2009
|
|
|
|
|
|
|
# operator. We stringify to take advantage of Perl's smarts |
2010
|
|
|
|
|
|
|
# about when something is 42 versus 41.999999999583. |
2011
|
|
|
|
|
|
|
|
2012
|
1043
|
|
|
|
|
1668
|
my $multiplier = 1; |
2013
|
1043
|
|
|
|
|
1847
|
foreach ( @bases ) { |
2014
|
3126
|
|
|
|
|
4668
|
$multiplier *= $_; |
2015
|
|
|
|
|
|
|
} |
2016
|
|
|
|
|
|
|
|
2017
|
1043
|
|
|
|
|
1959
|
my ( $lat, $lon, $alt ) = $self->geodetic(); |
2018
|
1043
|
|
|
|
|
2431
|
$lat = ( $lat + PIOVER2 ) * $multiplier / PI; |
2019
|
1043
|
|
|
|
|
1462
|
$lon = ( $lon + PI ) * $multiplier / TWOPI; |
2020
|
1043
|
|
|
|
|
10058
|
$lat = floor( "$lat" ); |
2021
|
1043
|
|
|
|
|
5864
|
$lon = floor( "$lon" ); |
2022
|
|
|
|
|
|
|
|
2023
|
1043
|
|
|
|
|
1972
|
my @rslt; |
2024
|
1043
|
|
|
|
|
1858
|
foreach my $base ( @bases ) { |
2025
|
3126
|
|
|
|
|
5037
|
foreach ( $lat, $lon ) { |
2026
|
6252
|
|
|
|
|
8978
|
my $inx = $_ % $base; |
2027
|
6252
|
100
|
|
|
|
13127
|
push @rslt, $base > 10 ? |
2028
|
|
|
|
|
|
|
substr( $alpha, $inx, 1 ) : $inx; |
2029
|
6252
|
|
|
|
|
14094
|
$_ = floor( $_ / $base ); |
2030
|
|
|
|
|
|
|
} |
2031
|
|
|
|
|
|
|
} |
2032
|
|
|
|
|
|
|
|
2033
|
|
|
|
|
|
|
@rslt |
2034
|
1043
|
50
|
|
|
|
2864
|
and $rslt[-1] = uc $rslt[-1]; |
2035
|
1043
|
50
|
|
|
|
2254
|
@rslt > 1 |
2036
|
|
|
|
|
|
|
and $rslt[-2] = uc $rslt[-2]; |
2037
|
|
|
|
|
|
|
|
2038
|
|
|
|
|
|
|
return ( |
2039
|
1043
|
|
|
|
|
6801
|
join( '', reverse @rslt ), |
2040
|
|
|
|
|
|
|
$alt, |
2041
|
|
|
|
|
|
|
); |
2042
|
|
|
|
|
|
|
} |
2043
|
|
|
|
|
|
|
} |
2044
|
|
|
|
|
|
|
|
2045
|
|
|
|
|
|
|
sub _maidenhead_bases { |
2046
|
2085
|
|
|
2085
|
|
3930
|
my ( $precision ) = @_; |
2047
|
2085
|
|
|
|
|
3108
|
my @bases; |
2048
|
2085
|
|
|
|
|
4891
|
foreach my $inx ( 1 .. $precision ) { |
2049
|
6251
|
100
|
|
|
|
13439
|
push @bases, $inx % 2 ? 24 : 10; |
2050
|
|
|
|
|
|
|
} |
2051
|
2085
|
|
|
|
|
3306
|
$bases[0] = 18; |
2052
|
2085
|
|
|
|
|
4835
|
return @bases; |
2053
|
|
|
|
|
|
|
} |
2054
|
|
|
|
|
|
|
} |
2055
|
|
|
|
|
|
|
|
2056
|
|
|
|
|
|
|
=item $value = $coord->mean_angular_velocity(); |
2057
|
|
|
|
|
|
|
|
2058
|
|
|
|
|
|
|
This method returns the mean angular velocity of the body in radians |
2059
|
|
|
|
|
|
|
per second. If the $coord object has a period() method, this method |
2060
|
|
|
|
|
|
|
just returns two pi divided by the period. Otherwise it returns the |
2061
|
|
|
|
|
|
|
contents of the angularvelocity attribute. |
2062
|
|
|
|
|
|
|
|
2063
|
|
|
|
|
|
|
=cut |
2064
|
|
|
|
|
|
|
|
2065
|
|
|
|
|
|
|
sub mean_angular_velocity { |
2066
|
528
|
|
|
528
|
1
|
871
|
my $self = shift; |
2067
|
|
|
|
|
|
|
return $self->can ('period') ? |
2068
|
|
|
|
|
|
|
TWOPI / $self->period : |
2069
|
528
|
100
|
|
|
|
2541
|
$self->{angularvelocity}; |
2070
|
|
|
|
|
|
|
} |
2071
|
|
|
|
|
|
|
|
2072
|
|
|
|
|
|
|
=item $time = $coord->next_azimuth( $body, $azimuth ); |
2073
|
|
|
|
|
|
|
|
2074
|
|
|
|
|
|
|
This method returns the next time the given C<$body> passes the given |
2075
|
|
|
|
|
|
|
C<$azimuth> as seen from the given C<$coord>, calculated to the nearest |
2076
|
|
|
|
|
|
|
second. The start time is the current time setting of the C<$body> |
2077
|
|
|
|
|
|
|
object. |
2078
|
|
|
|
|
|
|
|
2079
|
|
|
|
|
|
|
=item $time = $coord->next_azimuth( $azimuth ); |
2080
|
|
|
|
|
|
|
|
2081
|
|
|
|
|
|
|
This method returns the next time the C<$coord> object passes the given |
2082
|
|
|
|
|
|
|
C<$azimuth> as seen from the location in the C<$coord> object's |
2083
|
|
|
|
|
|
|
C attribute, calculated to the nearest second. The start time |
2084
|
|
|
|
|
|
|
is the current time setting of the C<$coord> object. |
2085
|
|
|
|
|
|
|
|
2086
|
|
|
|
|
|
|
=cut |
2087
|
|
|
|
|
|
|
|
2088
|
|
|
|
|
|
|
sub next_azimuth { |
2089
|
0
|
|
|
0
|
1
|
0
|
my ( $self, $body, $azimuth ) = _expand_args_default_station( @_ ); |
2090
|
0
|
0
|
|
|
|
0
|
ref $self or croak <<'EOD'; |
2091
|
|
|
|
|
|
|
Error - The next_azimuth() method may not be called as a class method. |
2092
|
|
|
|
|
|
|
EOD |
2093
|
|
|
|
|
|
|
|
2094
|
0
|
0
|
|
|
|
0
|
$body->represents(__PACKAGE__) or croak <<"EOD"; |
2095
|
|
|
|
|
|
|
Error - The argument to next_azimuth() must be a subclass of |
2096
|
0
|
|
|
|
|
0
|
@{[__PACKAGE__]}. |
2097
|
|
|
|
|
|
|
EOD |
2098
|
|
|
|
|
|
|
|
2099
|
0
|
|
|
|
|
0
|
my $want = shift; |
2100
|
0
|
0
|
|
|
|
0
|
defined $want and $want = $want ? 1 : 0; |
|
|
0
|
|
|
|
|
|
2101
|
|
|
|
|
|
|
|
2102
|
0
|
|
|
|
|
0
|
my $denom = $body->mean_angular_velocity - |
2103
|
|
|
|
|
|
|
$self->mean_angular_velocity; |
2104
|
|
|
|
|
|
|
## my $retro = $denom >= 0 ? 0 : 1; |
2105
|
0
|
0
|
|
|
|
0
|
($denom = abs ($denom)) < 1e-11 and croak <
|
2106
|
|
|
|
|
|
|
Error - The next_azimuth() method will not work for geosynchronous |
2107
|
|
|
|
|
|
|
bodies. |
2108
|
|
|
|
|
|
|
eod |
2109
|
|
|
|
|
|
|
|
2110
|
0
|
|
|
|
|
0
|
my $apparent = TWOPI / $denom; |
2111
|
0
|
|
|
|
|
0
|
my $begin = $self->universal; |
2112
|
0
|
|
|
|
|
0
|
my $delta = floor( $apparent / 8 ); |
2113
|
0
|
|
|
|
|
0
|
my $end = $begin + $delta; |
2114
|
|
|
|
|
|
|
|
2115
|
0
|
|
|
|
|
0
|
my $begin_angle = mod2pi( |
2116
|
|
|
|
|
|
|
( $self->azel( $body->universal( $begin ) ) )[0] - $azimuth ); |
2117
|
0
|
|
|
|
|
0
|
my $end_angle = mod2pi( |
2118
|
|
|
|
|
|
|
( $self->azel( $body->universal( $end ) ) )[0] - $azimuth ); |
2119
|
0
|
|
0
|
|
|
0
|
while ( $begin_angle < PI || $end_angle >= PI ) { |
2120
|
0
|
|
|
|
|
0
|
$begin_angle = $end_angle; |
2121
|
0
|
|
|
|
|
0
|
$begin = $end; |
2122
|
0
|
|
|
|
|
0
|
$end = $end + $delta; |
2123
|
0
|
|
|
|
|
0
|
$end_angle = mod2pi( |
2124
|
|
|
|
|
|
|
( $self->azel( $body->universal( $end ) ) )[0] - $azimuth ); |
2125
|
|
|
|
|
|
|
} |
2126
|
|
|
|
|
|
|
|
2127
|
0
|
|
|
|
|
0
|
while ($end - $begin > 1) { |
2128
|
0
|
|
|
|
|
0
|
my $mid = floor (($begin + $end) / 2); |
2129
|
0
|
|
|
|
|
0
|
my $mid_angle = mod2pi( |
2130
|
|
|
|
|
|
|
( $self->azel( $body->universal( $mid ) ) )[0] - $azimuth ); |
2131
|
0
|
0
|
|
|
|
0
|
( $begin, $end ) = ( $mid_angle >= PI ) ? |
2132
|
|
|
|
|
|
|
( $mid, $end ) : ( $begin, $mid ); |
2133
|
|
|
|
|
|
|
} |
2134
|
|
|
|
|
|
|
|
2135
|
0
|
|
|
|
|
0
|
$body->universal ($end); |
2136
|
0
|
|
|
|
|
0
|
$self->universal ($end); |
2137
|
0
|
|
|
|
|
0
|
return $end; |
2138
|
|
|
|
|
|
|
} |
2139
|
|
|
|
|
|
|
|
2140
|
|
|
|
|
|
|
=item ($time, $rise) = $coord->next_elevation ($body, $elev, $upper) |
2141
|
|
|
|
|
|
|
|
2142
|
|
|
|
|
|
|
This method calculates the next time the given body passes above or |
2143
|
|
|
|
|
|
|
below the given elevation (in radians) as seen from C<$coord>. The |
2144
|
|
|
|
|
|
|
C<$elev> argument may be omitted (or passed as undef), and will default |
2145
|
|
|
|
|
|
|
to 0. If the C<$upper> argument is true, the calculation will be based |
2146
|
|
|
|
|
|
|
on the upper limb of the body (as determined from its C |
2147
|
|
|
|
|
|
|
attribute); if false, the calculation will be based on the center of the |
2148
|
|
|
|
|
|
|
body. The C<$upper> argument defaults to true if the C<$elev> argument |
2149
|
|
|
|
|
|
|
is zero or positive, and false if the C<$elev> argument is negative. |
2150
|
|
|
|
|
|
|
|
2151
|
|
|
|
|
|
|
The algorithm is successive approximation, and assumes that the |
2152
|
|
|
|
|
|
|
body will be at its highest at meridian passage. It also assumes |
2153
|
|
|
|
|
|
|
that if the body hasn't passed the given elevation in 183 days it |
2154
|
|
|
|
|
|
|
never will. In this case it returns undef in scalar context, or |
2155
|
|
|
|
|
|
|
an empty list in list context. |
2156
|
|
|
|
|
|
|
|
2157
|
|
|
|
|
|
|
=item ($time, $rise) = $coord->next_elevation( $elev, $upper ); |
2158
|
|
|
|
|
|
|
|
2159
|
|
|
|
|
|
|
This method calculates the next time the C<$coord> object passes above |
2160
|
|
|
|
|
|
|
or below the given elevation (in radians) as seen from the position |
2161
|
|
|
|
|
|
|
found in the C<$coord> object's C attribute. The C<$elev> |
2162
|
|
|
|
|
|
|
argument may be omitted (or passed as undef), and will default to 0. If |
2163
|
|
|
|
|
|
|
the C<$upper> argument is true, the calculation will be based on the |
2164
|
|
|
|
|
|
|
upper limb of the body (as determined from its C |
2165
|
|
|
|
|
|
|
attribute); if false, the calculation will be based on the center of the |
2166
|
|
|
|
|
|
|
body. The C<$upper> argument defaults to true if the C<$elev> argument |
2167
|
|
|
|
|
|
|
is zero or positive, and false if the C<$elev> argument is negative. |
2168
|
|
|
|
|
|
|
|
2169
|
|
|
|
|
|
|
The algorithm is successive approximation, and assumes that the |
2170
|
|
|
|
|
|
|
body will be at its highest at meridian passage. It also assumes |
2171
|
|
|
|
|
|
|
that if the body hasn't passed the given elevation in 183 days it |
2172
|
|
|
|
|
|
|
never will. In this case it returns undef in scalar context, or |
2173
|
|
|
|
|
|
|
an empty list in list context. |
2174
|
|
|
|
|
|
|
|
2175
|
|
|
|
|
|
|
=cut |
2176
|
|
|
|
|
|
|
|
2177
|
17
|
|
|
17
|
|
166
|
use constant NEVER_PASS_ELEV => 183 * SECSPERDAY; |
|
17
|
|
|
|
|
49
|
|
|
17
|
|
|
|
|
16929
|
|
2178
|
|
|
|
|
|
|
|
2179
|
|
|
|
|
|
|
sub next_elevation { |
2180
|
241
|
|
|
241
|
1
|
3498
|
my ( $self, $body, $angle, $upper ) = _expand_args_default_station( @_ ); |
2181
|
241
|
50
|
|
|
|
699
|
ref $self or croak <<'EOD'; |
2182
|
|
|
|
|
|
|
Error - The next_elevation() method may not be called as a class method. |
2183
|
|
|
|
|
|
|
EOD |
2184
|
|
|
|
|
|
|
|
2185
|
241
|
50
|
|
|
|
696
|
$body->represents(__PACKAGE__) or croak <<"EOD"; |
2186
|
|
|
|
|
|
|
Error - The first argument to next_elevation() must be a subclass of |
2187
|
0
|
|
|
|
|
0
|
@{[__PACKAGE__]}. |
2188
|
|
|
|
|
|
|
EOD |
2189
|
|
|
|
|
|
|
|
2190
|
241
|
|
100
|
|
|
697
|
$angle ||= 0; |
2191
|
241
|
100
|
|
|
|
677
|
defined $upper or $upper = $angle >= 0; |
2192
|
241
|
100
|
|
|
|
689
|
$upper = $upper ? 1 : 0; |
2193
|
|
|
|
|
|
|
|
2194
|
241
|
|
|
|
|
539
|
my $begin = $self->universal; |
2195
|
241
|
|
|
|
|
572
|
my $original = $begin; |
2196
|
241
|
|
|
|
|
717
|
my ( undef, $elev ) = $self->azel_offset( |
2197
|
|
|
|
|
|
|
$body->universal( $begin ), $upper ); |
2198
|
241
|
|
100
|
|
|
875
|
my $rise = ( $elev < $angle ) || 0; |
2199
|
|
|
|
|
|
|
|
2200
|
241
|
|
|
|
|
847
|
my ($end, $above) = $self->next_meridian ($body, $rise); |
2201
|
|
|
|
|
|
|
|
2202
|
241
|
|
|
|
|
780
|
my $give_up = $body->NEVER_PASS_ELEV (); |
2203
|
|
|
|
|
|
|
|
2204
|
241
|
|
|
|
|
386
|
while ( 1 ) { |
2205
|
241
|
|
|
|
|
686
|
my ( undef, $elev ) = $self->azel_offset( $body, $upper ); |
2206
|
241
|
50
|
100
|
|
|
1296
|
( ( $elev < $angle ) || 0 ) == $rise |
2207
|
|
|
|
|
|
|
or last; |
2208
|
0
|
0
|
|
|
|
0
|
return if $end - $original > $give_up; |
2209
|
0
|
|
|
|
|
0
|
$begin = $end; |
2210
|
0
|
|
|
|
|
0
|
($end, $above) = $self->next_meridian ($body, $rise); |
2211
|
|
|
|
|
|
|
} |
2212
|
|
|
|
|
|
|
|
2213
|
241
|
|
|
|
|
849
|
while ($end - $begin > 1) { |
2214
|
3689
|
|
|
|
|
8962
|
my $mid = floor (($begin + $end) / 2); |
2215
|
3689
|
|
|
|
|
7942
|
my ( undef, $elev ) = $self->universal( $mid )-> |
2216
|
|
|
|
|
|
|
azel_offset( $body->universal( $mid ), $upper ); |
2217
|
3689
|
100
|
100
|
|
|
16981
|
( $begin, $end ) = |
2218
|
|
|
|
|
|
|
($elev < $angle || 0) == $rise ? ($mid, $end) : ($begin, $mid); |
2219
|
|
|
|
|
|
|
} |
2220
|
|
|
|
|
|
|
|
2221
|
241
|
|
|
|
|
856
|
$self->universal ($end); # Ensure consistent time. |
2222
|
241
|
|
|
|
|
742
|
$body->universal ($end); |
2223
|
241
|
100
|
|
|
|
1616
|
return wantarray ? ($end, $rise) : $end; |
2224
|
|
|
|
|
|
|
} |
2225
|
|
|
|
|
|
|
|
2226
|
|
|
|
|
|
|
=item ( $time, $above ) = $coord->next_meridian( $body, $want ) |
2227
|
|
|
|
|
|
|
|
2228
|
|
|
|
|
|
|
This method calculates the next meridian passage of the given C<$body> |
2229
|
|
|
|
|
|
|
over (or under) the location specified by the C<$coord> object. The |
2230
|
|
|
|
|
|
|
C<$body> object must be a subclass of Astro::Coord::ECI. |
2231
|
|
|
|
|
|
|
|
2232
|
|
|
|
|
|
|
The optional C<$want> argument should be specified as true (e.g. 1) if |
2233
|
|
|
|
|
|
|
you want the next passage above the observer, or as false (e.g. 0) if |
2234
|
|
|
|
|
|
|
you want the next passage below the observer. If this argument is |
2235
|
|
|
|
|
|
|
omitted or undefined, you get whichever passage is next. |
2236
|
|
|
|
|
|
|
|
2237
|
|
|
|
|
|
|
The start time of the search is the current time setting of the |
2238
|
|
|
|
|
|
|
C<$coord> object. |
2239
|
|
|
|
|
|
|
|
2240
|
|
|
|
|
|
|
The returns are the time of the meridian passage, and an indicator which |
2241
|
|
|
|
|
|
|
is true if the passage is above the observer (i.e. local noon if the |
2242
|
|
|
|
|
|
|
C<$body> represents the Sun), or false if below (i.e. local midnight if |
2243
|
|
|
|
|
|
|
the C<$body> represents the Sun). If called in scalar context, you get |
2244
|
|
|
|
|
|
|
the time only. |
2245
|
|
|
|
|
|
|
|
2246
|
|
|
|
|
|
|
The current time of both C<$coord> and C<$body> objects are left at the |
2247
|
|
|
|
|
|
|
returned time. |
2248
|
|
|
|
|
|
|
|
2249
|
|
|
|
|
|
|
The algorithm is by successive approximation. It will croak if the |
2250
|
|
|
|
|
|
|
period of the C<$body> is close to synchronous, and will probably not |
2251
|
|
|
|
|
|
|
work well for bodies in highly eccentric orbits. The calculation is to |
2252
|
|
|
|
|
|
|
the nearest second, and the time returned is the first even second after |
2253
|
|
|
|
|
|
|
the body crosses the meridian. |
2254
|
|
|
|
|
|
|
|
2255
|
|
|
|
|
|
|
=item ( $time, $above ) = $coord->next_meridian( $want ) |
2256
|
|
|
|
|
|
|
|
2257
|
|
|
|
|
|
|
This method calculates the next meridian passage of the C<$coord> object |
2258
|
|
|
|
|
|
|
over (or under) the location specified by the C<$coord> object's |
2259
|
|
|
|
|
|
|
C attribute. |
2260
|
|
|
|
|
|
|
|
2261
|
|
|
|
|
|
|
The optional C<$want> argument should be specified as true (e.g. 1) if |
2262
|
|
|
|
|
|
|
you want the next passage above the observer, or as false (e.g. 0) if |
2263
|
|
|
|
|
|
|
you want the next passage below the observer. If this argument is |
2264
|
|
|
|
|
|
|
omitted or undefined, you get whichever passage is next. |
2265
|
|
|
|
|
|
|
|
2266
|
|
|
|
|
|
|
The start time of the search is the current time setting of the |
2267
|
|
|
|
|
|
|
C<$coord> object. |
2268
|
|
|
|
|
|
|
|
2269
|
|
|
|
|
|
|
The returns are the time of the meridian passage, and an indicator which |
2270
|
|
|
|
|
|
|
is true if the passage is above the observer (i.e. local noon if the |
2271
|
|
|
|
|
|
|
C<$coord> object represents the Sun), or false if below (i.e. local |
2272
|
|
|
|
|
|
|
midnight if the C<$coord> object represents the Sun). If called in |
2273
|
|
|
|
|
|
|
scalar context, you get the time only. |
2274
|
|
|
|
|
|
|
|
2275
|
|
|
|
|
|
|
The current time of both C<$coord> and its C are left at the |
2276
|
|
|
|
|
|
|
returned time. |
2277
|
|
|
|
|
|
|
|
2278
|
|
|
|
|
|
|
The algorithm is by successive approximation. It will croak if the |
2279
|
|
|
|
|
|
|
period of the C<$body> is close to synchronous, and will probably not |
2280
|
|
|
|
|
|
|
work well for bodies in highly eccentric orbits. The calculation is to |
2281
|
|
|
|
|
|
|
the nearest second, and the time returned is the first even second after |
2282
|
|
|
|
|
|
|
the body crosses the meridian. |
2283
|
|
|
|
|
|
|
|
2284
|
|
|
|
|
|
|
=cut |
2285
|
|
|
|
|
|
|
|
2286
|
|
|
|
|
|
|
sub next_meridian { |
2287
|
264
|
|
|
264
|
1
|
3942
|
my ( $self, $body, $want ) = _expand_args_default_station( @_ ); |
2288
|
264
|
50
|
|
|
|
868
|
ref $self or croak <<'EOD'; |
2289
|
|
|
|
|
|
|
Error - The next_meridian() method may not be called as a class method. |
2290
|
|
|
|
|
|
|
EOD |
2291
|
|
|
|
|
|
|
|
2292
|
264
|
50
|
|
|
|
862
|
$body->represents(__PACKAGE__) or croak <<"EOD"; |
2293
|
|
|
|
|
|
|
Error - The argument to next_meridian() must be a subclass of |
2294
|
0
|
|
|
|
|
0
|
@{[__PACKAGE__]}. |
2295
|
|
|
|
|
|
|
EOD |
2296
|
|
|
|
|
|
|
|
2297
|
264
|
100
|
|
|
|
1023
|
defined $want and $want = $want ? 1 : 0; |
|
|
100
|
|
|
|
|
|
2298
|
|
|
|
|
|
|
|
2299
|
264
|
|
|
|
|
1089
|
my $denom = $body->mean_angular_velocity - |
2300
|
|
|
|
|
|
|
$self->mean_angular_velocity; |
2301
|
264
|
50
|
|
|
|
817
|
my $retro = $denom >= 0 ? 0 : 1; |
2302
|
264
|
50
|
|
|
|
848
|
($denom = abs ($denom)) < 1e-11 and croak <<'EOD'; |
2303
|
|
|
|
|
|
|
Error - The next_meridian() method will not work for geosynchronous |
2304
|
|
|
|
|
|
|
bodies. |
2305
|
|
|
|
|
|
|
EOD |
2306
|
|
|
|
|
|
|
|
2307
|
264
|
|
|
|
|
553
|
my $apparent = TWOPI / $denom; |
2308
|
264
|
|
|
|
|
589
|
my $begin = $self->universal; |
2309
|
264
|
|
|
|
|
1054
|
my $delta = floor ($apparent / 16); |
2310
|
264
|
|
|
|
|
597
|
my $end = $begin + $delta; |
2311
|
|
|
|
|
|
|
|
2312
|
264
|
100
|
|
|
|
692
|
my ($above, $opposite) = |
2313
|
|
|
|
|
|
|
mod2pi (($body->universal($begin)->geocentric)[1] |
2314
|
|
|
|
|
|
|
- ($self->universal($begin)->geocentric)[1]) >= PI ? |
2315
|
|
|
|
|
|
|
(1 - $retro, PI) : ($retro, 0); |
2316
|
|
|
|
|
|
|
|
2317
|
264
|
|
|
|
|
1014
|
($begin, $end) = ($end, $end + $delta) |
2318
|
|
|
|
|
|
|
while mod2pi (($body->universal($end)->geocentric)[1] - |
2319
|
|
|
|
|
|
|
($self->universal($end)->geocentric)[1] + $opposite) < PI; |
2320
|
|
|
|
|
|
|
|
2321
|
264
|
100
|
100
|
|
|
1361
|
if (defined $want && $want != $above) { |
2322
|
108
|
|
|
|
|
222
|
$above = $want; |
2323
|
108
|
100
|
|
|
|
327
|
$opposite = $opposite ? 0 : PI; |
2324
|
108
|
|
|
|
|
314
|
($begin, $end) = ($end, $end + $delta) |
2325
|
|
|
|
|
|
|
while mod2pi (($body->universal($end)->geocentric)[1] - |
2326
|
|
|
|
|
|
|
($self->universal($end)->geocentric)[1] + $opposite) < PI; |
2327
|
|
|
|
|
|
|
} |
2328
|
|
|
|
|
|
|
|
2329
|
264
|
|
|
|
|
895
|
while ($end - $begin > 1) { |
2330
|
3302
|
|
|
|
|
7802
|
my $mid = floor (($begin + $end) / 2); |
2331
|
3302
|
|
|
|
|
6585
|
my $long = ($body->universal($mid)->geocentric)[1]; |
2332
|
3302
|
|
|
|
|
7601
|
my $merid = ($self->universal($mid)->geocentric)[1]; |
2333
|
3302
|
100
|
|
|
|
9816
|
($begin, $end) = |
2334
|
|
|
|
|
|
|
mod2pi ($long - $merid + $opposite) < PI ? |
2335
|
|
|
|
|
|
|
($mid, $end) : ($begin, $mid); |
2336
|
|
|
|
|
|
|
} |
2337
|
|
|
|
|
|
|
|
2338
|
264
|
|
|
|
|
905
|
$body->universal ($end); |
2339
|
264
|
|
|
|
|
920
|
$self->universal ($end); |
2340
|
264
|
100
|
|
|
|
1197
|
return wantarray ? ($end, $above) : $end; |
2341
|
|
|
|
|
|
|
} |
2342
|
|
|
|
|
|
|
|
2343
|
|
|
|
|
|
|
=item ( $delta_psi, $delta_epsilon ) = $self->nutation( $time ) |
2344
|
|
|
|
|
|
|
|
2345
|
|
|
|
|
|
|
This method calculates the nutation in longitude (delta psi) and |
2346
|
|
|
|
|
|
|
obliquity (delta epsilon) for the given B time. If the time |
2347
|
|
|
|
|
|
|
is unspecified or specified as C, the current B time |
2348
|
|
|
|
|
|
|
of the object is used. |
2349
|
|
|
|
|
|
|
|
2350
|
|
|
|
|
|
|
The algorithm comes from Jean Meeus' "Astronomical Algorithms", 2nd |
2351
|
|
|
|
|
|
|
Edition, Chapter 22, pages 143ff. Meeus states that it is good to |
2352
|
|
|
|
|
|
|
0.5 seconds of arc. |
2353
|
|
|
|
|
|
|
|
2354
|
|
|
|
|
|
|
=cut |
2355
|
|
|
|
|
|
|
|
2356
|
|
|
|
|
|
|
sub nutation { |
2357
|
15985
|
|
|
15985
|
1
|
27150
|
my ( $self, $time ) = @_; |
2358
|
15985
|
100
|
|
|
|
28724
|
defined $time |
2359
|
|
|
|
|
|
|
or $time = $self->dynamical(); |
2360
|
|
|
|
|
|
|
|
2361
|
15985
|
|
|
|
|
28919
|
my $T = jcent2000( $time ); # Meeus (22.1) |
2362
|
|
|
|
|
|
|
|
2363
|
15985
|
|
|
|
|
40464
|
my $omega = mod2pi( deg2rad( ( ( $T / 450000 + .0020708 ) * $T - |
2364
|
|
|
|
|
|
|
1934.136261 ) * $T + 125.04452 ) ); |
2365
|
|
|
|
|
|
|
|
2366
|
15985
|
|
|
|
|
34934
|
my $L = mod2pi( deg2rad( 36000.7698 * $T + 280.4665 ) ); |
2367
|
15985
|
|
|
|
|
34706
|
my $Lprime = mod2pi( deg2rad( 481267.8813 * $T + 218.3165 ) ); |
2368
|
|
|
|
|
|
|
|
2369
|
15985
|
|
|
|
|
51141
|
my $delta_psi = deg2rad( ( -17.20 * sin( $omega ) |
2370
|
|
|
|
|
|
|
- 1.32 * sin( 2 * $L ) |
2371
|
|
|
|
|
|
|
- 0.23 * sin( 2 * $Lprime ) |
2372
|
|
|
|
|
|
|
+ 0.21 * sin( 2 * $omega ) ) / 3600 ); |
2373
|
15985
|
|
|
|
|
48769
|
my $delta_epsilon = deg2rad( ( 9.20 * cos( $omega ) |
2374
|
|
|
|
|
|
|
+ 0.57 * cos( 2 * $L ) |
2375
|
|
|
|
|
|
|
+ 0.10 * cos( 2 * $Lprime ) |
2376
|
|
|
|
|
|
|
- 0.09 * cos( 2 * $omega ) ) / 3600 ); |
2377
|
|
|
|
|
|
|
|
2378
|
15985
|
|
|
|
|
33292
|
return ( $delta_psi, $delta_epsilon ); |
2379
|
|
|
|
|
|
|
} |
2380
|
|
|
|
|
|
|
|
2381
|
|
|
|
|
|
|
=item $epsilon = $self->obliquity( $time ) |
2382
|
|
|
|
|
|
|
|
2383
|
|
|
|
|
|
|
This method calculates the obliquity of the ecliptic in radians at the |
2384
|
|
|
|
|
|
|
given B time. If the time is unspecified or specified as |
2385
|
|
|
|
|
|
|
C, the current B time of the object is used. |
2386
|
|
|
|
|
|
|
|
2387
|
|
|
|
|
|
|
The algorithm comes from Jean Meeus' "Astronomical Algorithms", 2nd |
2388
|
|
|
|
|
|
|
Edition, Chapter 22, pages 143ff. The conversion from universal to |
2389
|
|
|
|
|
|
|
dynamical time comes from chapter 10, equation 10.2 on page 78. |
2390
|
|
|
|
|
|
|
|
2391
|
|
|
|
|
|
|
=cut |
2392
|
|
|
|
|
|
|
|
2393
|
17
|
|
|
17
|
|
347
|
use constant E0BASE => (21.446 / 60 + 26) / 60 + 23; |
|
17
|
|
|
|
|
38
|
|
|
17
|
|
|
|
|
17069
|
|
2394
|
|
|
|
|
|
|
|
2395
|
|
|
|
|
|
|
sub obliquity { |
2396
|
14185
|
|
|
14185
|
1
|
24884
|
my ( $self, $time ) = @_; |
2397
|
|
|
|
|
|
|
|
2398
|
14185
|
50
|
|
|
|
38802
|
if ( looks_like_number( $self ) ) { |
2399
|
0
|
|
|
|
|
0
|
( $self, $time ) = ( __PACKAGE__, $self ); |
2400
|
0
|
|
|
|
|
0
|
__subroutine_deprecation(); |
2401
|
|
|
|
|
|
|
## Carp::cluck( 'Subroutine call to obliquity() is deprecated' ); |
2402
|
|
|
|
|
|
|
} |
2403
|
14185
|
100
|
|
|
|
32543
|
defined $time |
2404
|
|
|
|
|
|
|
or $time = $self->dynamical(); |
2405
|
|
|
|
|
|
|
|
2406
|
14185
|
|
|
|
|
34547
|
my $T = jcent2000 ($time); # Meeus (22.1) |
2407
|
|
|
|
|
|
|
|
2408
|
14185
|
|
|
|
|
30763
|
my ( undef, $delta_epsilon ) = $self->nutation( $time ); |
2409
|
|
|
|
|
|
|
|
2410
|
14185
|
|
|
|
|
33862
|
my $epsilon0 = deg2rad( ( ( 0.001813 * $T - 0.00059 ) * $T - 46.8150 ) |
2411
|
|
|
|
|
|
|
* $T / 3600 + E0BASE); |
2412
|
14185
|
|
|
|
|
25210
|
return $epsilon0 + $delta_epsilon; |
2413
|
|
|
|
|
|
|
} |
2414
|
|
|
|
|
|
|
|
2415
|
|
|
|
|
|
|
=item $coord = $coord->precess ($time); |
2416
|
|
|
|
|
|
|
|
2417
|
|
|
|
|
|
|
This method is a convenience wrapper for precess_dynamical(). The |
2418
|
|
|
|
|
|
|
functionality is the same except that B
|
2419
|
|
|
|
|
|
|
universal time.> |
2420
|
|
|
|
|
|
|
|
2421
|
|
|
|
|
|
|
=cut |
2422
|
|
|
|
|
|
|
|
2423
|
|
|
|
|
|
|
sub precess { |
2424
|
2
|
50
|
|
2
|
1
|
26
|
$_[1] |
2425
|
|
|
|
|
|
|
and $_[1] += dynamical_delta( $_[1] ); |
2426
|
2
|
|
|
|
|
8
|
goto &precess_dynamical; |
2427
|
|
|
|
|
|
|
} |
2428
|
|
|
|
|
|
|
|
2429
|
|
|
|
|
|
|
=item $coord = $coord->precess_dynamical ($time); |
2430
|
|
|
|
|
|
|
|
2431
|
|
|
|
|
|
|
This method precesses the coordinates of the object to the given |
2432
|
|
|
|
|
|
|
equinox, B The starting equinox is the |
2433
|
|
|
|
|
|
|
value of the 'equinox_dynamical' attribute, or the current time setting |
2434
|
|
|
|
|
|
|
if the 'equinox_dynamical' attribute is any false value (i.e. undef, 0, |
2435
|
|
|
|
|
|
|
or ''). A warning will be issued if the value of 'equinox_dynamical' is |
2436
|
|
|
|
|
|
|
undef (which is the default setting) and the object represents inertial |
2437
|
|
|
|
|
|
|
coordinates. As of version 0.013_02, B
|
2438
|
|
|
|
|
|
|
unaffected by this operation.> |
2439
|
|
|
|
|
|
|
|
2440
|
|
|
|
|
|
|
As a side effect, the value of the 'equinox_dynamical' attribute will be |
2441
|
|
|
|
|
|
|
set to the dynamical time corresponding to the argument. |
2442
|
|
|
|
|
|
|
|
2443
|
|
|
|
|
|
|
As of version 0.061_01, this does nothing to non-inertial |
2444
|
|
|
|
|
|
|
objects -- that is, those whose position was set in Earth-fixed |
2445
|
|
|
|
|
|
|
coordinates. |
2446
|
|
|
|
|
|
|
|
2447
|
|
|
|
|
|
|
If the object's 'station' attribute is set, the station is also |
2448
|
|
|
|
|
|
|
precessed. |
2449
|
|
|
|
|
|
|
|
2450
|
|
|
|
|
|
|
The object itself is returned. |
2451
|
|
|
|
|
|
|
|
2452
|
|
|
|
|
|
|
The algorithm comes from Jean Meeus, "Astronomical Algorithms", 2nd |
2453
|
|
|
|
|
|
|
Edition, Chapter 21, pages 134ff (a.k.a. "the rigorous method"). |
2454
|
|
|
|
|
|
|
|
2455
|
|
|
|
|
|
|
=cut |
2456
|
|
|
|
|
|
|
|
2457
|
|
|
|
|
|
|
sub precess_dynamical { |
2458
|
1127
|
|
|
1127
|
1
|
1926
|
my ( $self, $end ) = @_; |
2459
|
|
|
|
|
|
|
|
2460
|
1127
|
50
|
|
|
|
2085
|
$end |
2461
|
|
|
|
|
|
|
or croak "No equinox time specified"; |
2462
|
|
|
|
|
|
|
|
2463
|
|
|
|
|
|
|
# Non-inertial coordinate systems are not referred to the equinox, |
2464
|
|
|
|
|
|
|
# and so do not get precessed. |
2465
|
1127
|
50
|
|
|
|
2308
|
$self->get( 'inertial' ) |
2466
|
|
|
|
|
|
|
or return $self; |
2467
|
|
|
|
|
|
|
|
2468
|
1127
|
50
|
|
|
|
1908
|
defined ( my $start = $self->get( 'equinox_dynamical' ) ) |
2469
|
|
|
|
|
|
|
or carp "Warning - Precess called with equinox_dynamical ", |
2470
|
|
|
|
|
|
|
"attribute undefined"; |
2471
|
1127
|
|
33
|
|
|
2023
|
$start ||= $self->dynamical (); |
2472
|
|
|
|
|
|
|
|
2473
|
1127
|
|
|
|
|
1489
|
my $sta; |
2474
|
1127
|
100
|
100
|
|
|
1773
|
if ( $sta = $self->get( 'station' ) and $sta->get( 'inertial' ) |
2475
|
|
|
|
|
|
|
) { |
2476
|
1
|
50
|
|
|
|
4
|
$sta->get( 'station' ) |
2477
|
|
|
|
|
|
|
and croak NO_CASCADING_STATIONS; |
2478
|
1
|
|
|
|
|
4
|
$sta->universal( $self->universal() ); |
2479
|
1
|
|
|
|
|
19
|
$sta->precess_dynamical( $end ); |
2480
|
|
|
|
|
|
|
} |
2481
|
|
|
|
|
|
|
|
2482
|
1127
|
100
|
|
|
|
2452
|
$start == $end |
2483
|
|
|
|
|
|
|
and return $self; |
2484
|
|
|
|
|
|
|
|
2485
|
1125
|
|
|
|
|
2349
|
my ($alpha0, $delta0, $rho0) = $self->equatorial (); |
2486
|
|
|
|
|
|
|
|
2487
|
1125
|
|
|
|
|
2645
|
my $T = jcent2000($start); |
2488
|
1125
|
|
|
|
|
1866
|
my $t = ($end - $start) / (36525 * SECSPERDAY); |
2489
|
|
|
|
|
|
|
|
2490
|
|
|
|
|
|
|
# The following four assignments correspond to Meeus' (21.2). |
2491
|
1125
|
|
|
|
|
1788
|
my $zterm = (- 0.000139 * $T + 1.39656) * $T + 2306.2181; |
2492
|
1125
|
|
|
|
|
2783
|
my $zeta = deg2rad((((0.017998 * $t + (- 0.000344 * $T + 0.30188)) * |
2493
|
|
|
|
|
|
|
$t + $zterm) * $t) / 3600); |
2494
|
1125
|
|
|
|
|
2656
|
my $z = deg2rad((((0.018203 * $t + (0.000066 * $T + 1.09468)) * $t + |
2495
|
|
|
|
|
|
|
$zterm) * $t) / 3600); |
2496
|
1125
|
|
|
|
|
3222
|
my $theta = deg2rad(((( - 0.041833 * $t - (0.000217 * $T + 0.42665)) |
2497
|
|
|
|
|
|
|
* $t + (- 0.000217 * $T - 0.85330) * $T + 2004.3109) * $t) / |
2498
|
|
|
|
|
|
|
3600); |
2499
|
|
|
|
|
|
|
|
2500
|
|
|
|
|
|
|
# The following assignments correspond to Meeus' (21.4). |
2501
|
1125
|
|
|
|
|
2049
|
my $sindelta0 = sin ($delta0); |
2502
|
1125
|
|
|
|
|
1674
|
my $cosdelta0 = cos ($delta0); |
2503
|
1125
|
|
|
|
|
1455
|
my $sintheta = sin ($theta); |
2504
|
1125
|
|
|
|
|
1496
|
my $costheta = cos ($theta); |
2505
|
1125
|
|
|
|
|
2140
|
my $cosdelta0cosalpha0 = cos ($alpha0 + $zeta) * $cosdelta0; |
2506
|
1125
|
|
|
|
|
1715
|
my $A = $cosdelta0 * sin ($alpha0 + $zeta); |
2507
|
1125
|
|
|
|
|
1524
|
my $B = $costheta * $cosdelta0cosalpha0 - $sintheta * $sindelta0; |
2508
|
1125
|
|
|
|
|
1660
|
my $C = $sintheta * $cosdelta0cosalpha0 + $costheta * $sindelta0; |
2509
|
|
|
|
|
|
|
|
2510
|
1125
|
|
|
|
|
2518
|
my $alpha = mod2pi(atan2 ($A , $B) + $z); |
2511
|
1125
|
|
|
|
|
2375
|
my $delta = asin($C); |
2512
|
|
|
|
|
|
|
|
2513
|
1125
|
|
|
|
|
2752
|
$self->equatorial ($alpha, $delta, $rho0); |
2514
|
1125
|
|
|
|
|
2935
|
$self->set (equinox_dynamical => $end); |
2515
|
|
|
|
|
|
|
|
2516
|
1125
|
|
|
|
|
2623
|
return $self; |
2517
|
|
|
|
|
|
|
} |
2518
|
|
|
|
|
|
|
|
2519
|
|
|
|
|
|
|
=item Astro::Coord::ECI->reference_ellipsoid($semi, $flat, $name); |
2520
|
|
|
|
|
|
|
|
2521
|
|
|
|
|
|
|
This class method can be used to define or redefine reference |
2522
|
|
|
|
|
|
|
ellipsoids. |
2523
|
|
|
|
|
|
|
|
2524
|
|
|
|
|
|
|
Nothing bad will happen if you call this as an object method, but |
2525
|
|
|
|
|
|
|
it still just creates a reference ellipsoid definition -- the object |
2526
|
|
|
|
|
|
|
is unaffected. |
2527
|
|
|
|
|
|
|
|
2528
|
|
|
|
|
|
|
It is not an error to redefine an existing ellipsoid. |
2529
|
|
|
|
|
|
|
|
2530
|
|
|
|
|
|
|
=item ($semi, $flat, $name) = Astro::Coord::ECI->reference_ellipsoid($name) |
2531
|
|
|
|
|
|
|
|
2532
|
|
|
|
|
|
|
This class method returns the definition of the named reference |
2533
|
|
|
|
|
|
|
ellipsoid. It croaks if there is no such ellipsoid. |
2534
|
|
|
|
|
|
|
|
2535
|
|
|
|
|
|
|
You can also call this as an object method, but the functionality is |
2536
|
|
|
|
|
|
|
the same. |
2537
|
|
|
|
|
|
|
|
2538
|
|
|
|
|
|
|
The following reference ellipsoids are known to the class initially: |
2539
|
|
|
|
|
|
|
|
2540
|
|
|
|
|
|
|
CLARKE-1866 - 1866. |
2541
|
|
|
|
|
|
|
semimajor => 6378.2064 km, flattening => 1/294.98. |
2542
|
|
|
|
|
|
|
Source: http://www.colorado.edu/geography/gcraft/notes/datum/elist.html |
2543
|
|
|
|
|
|
|
|
2544
|
|
|
|
|
|
|
GRS67 - Geodetic Reference System 1967. |
2545
|
|
|
|
|
|
|
semimajor => 6378.160 km, flattening => 1/298.247. |
2546
|
|
|
|
|
|
|
Source: http://www.colorado.edu/geography/gcraft/notes/datum/elist.html |
2547
|
|
|
|
|
|
|
|
2548
|
|
|
|
|
|
|
GRS80 - Geodetic Reference System 1980. |
2549
|
|
|
|
|
|
|
semimajor => 6378.137 km, flattening => 1/298.25722210088 |
2550
|
|
|
|
|
|
|
(flattening per U.S. Department of Commerce 1989). |
2551
|
|
|
|
|
|
|
Source: http://biology.usgs.gov/fgdc.metadata/version2/spref/horiz/geodet/faq.htm |
2552
|
|
|
|
|
|
|
|
2553
|
|
|
|
|
|
|
IAU68 - International Astronomical Union, 1968. |
2554
|
|
|
|
|
|
|
semimajor => 6378.160 km, flattening => 1/298.25. |
2555
|
|
|
|
|
|
|
Source: http://maic.jmu.edu/sic/standards/ellipsoid.htm |
2556
|
|
|
|
|
|
|
|
2557
|
|
|
|
|
|
|
IAU76 - International Astronomical Union, 1976. |
2558
|
|
|
|
|
|
|
semimajor => 6378.14 km, flattening => 1 / 298.257. |
2559
|
|
|
|
|
|
|
Source: Jean Meeus, "Astronomical Algorithms", 2nd Edition |
2560
|
|
|
|
|
|
|
|
2561
|
|
|
|
|
|
|
NAD83 - North American Datum, 1983. |
2562
|
|
|
|
|
|
|
semimajor => 6378.137 km, flattening => 1/298.257. |
2563
|
|
|
|
|
|
|
Source: http://biology.usgs.gov/fgdc.metadata/version2/spref/horiz/geodet/faq.htm |
2564
|
|
|
|
|
|
|
(NAD83 uses the GRS80 ellipsoid) |
2565
|
|
|
|
|
|
|
|
2566
|
|
|
|
|
|
|
sphere - Just in case you were wondering how much difference it |
2567
|
|
|
|
|
|
|
makes (a max of 11 minutes 32.73 seconds of arc, per Jean |
2568
|
|
|
|
|
|
|
Meeus). |
2569
|
|
|
|
|
|
|
semimajor => 6378.137 km (from GRS80), flattening => 0. |
2570
|
|
|
|
|
|
|
|
2571
|
|
|
|
|
|
|
WGS72 - World Geodetic System 1972. |
2572
|
|
|
|
|
|
|
semimajor => 6378.135 km, flattening=> 1/298.26. |
2573
|
|
|
|
|
|
|
Source: http://biology.usgs.gov/fgdc.metadata/version2/spref/horiz/geodet/faq.htm |
2574
|
|
|
|
|
|
|
|
2575
|
|
|
|
|
|
|
WGS84 - World Geodetic System 1984. |
2576
|
|
|
|
|
|
|
semimajor => 6378.137 km, flattening => 1/1/298.257223563. |
2577
|
|
|
|
|
|
|
Source: http://www.colorado.edu/geography/gcraft/notes/datum/elist.html |
2578
|
|
|
|
|
|
|
|
2579
|
|
|
|
|
|
|
Reference ellipsoid names are case-sensitive. |
2580
|
|
|
|
|
|
|
|
2581
|
|
|
|
|
|
|
The default model is WGS84. |
2582
|
|
|
|
|
|
|
|
2583
|
|
|
|
|
|
|
=cut |
2584
|
|
|
|
|
|
|
|
2585
|
|
|
|
|
|
|
# Wish I had: |
2586
|
|
|
|
|
|
|
# Maling, D.H., 1989, Measurements from Maps: Principles and methods of cartometry, Pergamon Press, Oxford, England. |
2587
|
|
|
|
|
|
|
# Maling, D.H., 1993, Coordinate Systems and Map Projections, Pergamon Press, Oxford, England. |
2588
|
|
|
|
|
|
|
|
2589
|
|
|
|
|
|
|
# http://www.gsi.go.jp/PCGIAP/95wg/wg3/geodinf.htm has a partial list of who uses |
2590
|
|
|
|
|
|
|
# what in the Asia/Pacific. |
2591
|
|
|
|
|
|
|
|
2592
|
|
|
|
|
|
|
%known_ellipsoid = ( # Reference Ellipsoids |
2593
|
|
|
|
|
|
|
'CLARKE-1866' => { # http://www.colorado.edu/geography/gcraft/notes/datum/elist.html |
2594
|
|
|
|
|
|
|
semimajor => 6378.2064, |
2595
|
|
|
|
|
|
|
flattening => 1/294.9786982, |
2596
|
|
|
|
|
|
|
}, |
2597
|
|
|
|
|
|
|
GRS67 => { # http://www.colorado.edu/geography/gcraft/notes/datum/elist.html |
2598
|
|
|
|
|
|
|
semimajor => 6378.160, |
2599
|
|
|
|
|
|
|
flattening => 1/298.247167427, |
2600
|
|
|
|
|
|
|
}, |
2601
|
|
|
|
|
|
|
GRS80 => { # http://biology.usgs.gov/fgdc.metadata/version2/spref/horiz/geodet/faq.htm |
2602
|
|
|
|
|
|
|
semimajor => 6378.137, # km |
2603
|
|
|
|
|
|
|
flattening => 1/298.25722210088, # U.S. Dept of Commerce 1989 (else 1/298.26) |
2604
|
|
|
|
|
|
|
}, |
2605
|
|
|
|
|
|
|
IAU68 => { # http://maic.jmu.edu/sic/standards/ellipsoid.htm |
2606
|
|
|
|
|
|
|
semimajor => 6378.160, |
2607
|
|
|
|
|
|
|
flattening => 1/298.25, |
2608
|
|
|
|
|
|
|
}, |
2609
|
|
|
|
|
|
|
IAU76 => { # Meeus, p. 82. |
2610
|
|
|
|
|
|
|
semimajor => 6378.14, |
2611
|
|
|
|
|
|
|
flattening => 1/298.257, |
2612
|
|
|
|
|
|
|
}, |
2613
|
|
|
|
|
|
|
NAD83 => { # http://biology.usgs.gov/fgdc.metadata/version2/spref/horiz/geodet/faq.htm |
2614
|
|
|
|
|
|
|
semimajor => 6378.137, # km |
2615
|
|
|
|
|
|
|
flattening => 1/298.257, |
2616
|
|
|
|
|
|
|
}, |
2617
|
|
|
|
|
|
|
sphere => { # Defined by me for grins, with semimajor from GRS80. |
2618
|
|
|
|
|
|
|
semimajor => 6378.137, # km, from GRS80 |
2619
|
|
|
|
|
|
|
flattening => 0, # It's a sphere! |
2620
|
|
|
|
|
|
|
}, |
2621
|
|
|
|
|
|
|
WGS72 => { # http://biology.usgs.gov/fgdc.metadata/version2/spref/horiz/geodet/faq.htm |
2622
|
|
|
|
|
|
|
semimajor => 6378.135, # km |
2623
|
|
|
|
|
|
|
flattening => 1/298.26, |
2624
|
|
|
|
|
|
|
}, |
2625
|
|
|
|
|
|
|
WGS84 => { # http://www.colorado.edu/geography/gcraft/notes/datum/elist.html |
2626
|
|
|
|
|
|
|
semimajor => 6378.137, |
2627
|
|
|
|
|
|
|
flattening => 1/298.257223563, |
2628
|
|
|
|
|
|
|
}, |
2629
|
|
|
|
|
|
|
); |
2630
|
|
|
|
|
|
|
foreach my $name (keys %known_ellipsoid) { |
2631
|
|
|
|
|
|
|
$known_ellipsoid{$name}{name} = $name; |
2632
|
|
|
|
|
|
|
} |
2633
|
|
|
|
|
|
|
|
2634
|
|
|
|
|
|
|
sub reference_ellipsoid { |
2635
|
0
|
|
|
0
|
1
|
0
|
my ( undef, @args ) = @_; # Invocant unused |
2636
|
0
|
0
|
|
|
|
0
|
my $name = pop @args or croak <
|
2637
|
|
|
|
|
|
|
Error - You must specify the name of a reference ellipsoid. |
2638
|
|
|
|
|
|
|
eod |
2639
|
0
|
0
|
|
|
|
0
|
if (@args == 0) { |
|
|
0
|
|
|
|
|
|
2640
|
0
|
0
|
|
|
|
0
|
$known_ellipsoid{$name} or croak <
|
2641
|
|
|
|
|
|
|
Error - Reference ellipsoid $name is unknown to this software. Known |
2642
|
|
|
|
|
|
|
ellipsoids are: |
2643
|
0
|
|
|
|
|
0
|
@{[join ', ', sort keys %known_ellipsoid]}. |
2644
|
|
|
|
|
|
|
eod |
2645
|
|
|
|
|
|
|
} elsif (@args == 2) { |
2646
|
0
|
|
|
|
|
0
|
$known_ellipsoid{$name} = { |
2647
|
|
|
|
|
|
|
semimajor => $args[0], |
2648
|
|
|
|
|
|
|
flattening => $args[1], |
2649
|
|
|
|
|
|
|
name => $name, |
2650
|
|
|
|
|
|
|
}; |
2651
|
|
|
|
|
|
|
} else { |
2652
|
0
|
|
|
|
|
0
|
croak <
|
2653
|
|
|
|
|
|
|
Error - You must call the reference_ellipsoid class method with either one |
2654
|
|
|
|
|
|
|
argument (to fetch the definition of a known ellipsoid) or three |
2655
|
|
|
|
|
|
|
arguments (to define a new ellipsoid or redefine an old one). |
2656
|
|
|
|
|
|
|
eod |
2657
|
|
|
|
|
|
|
} |
2658
|
0
|
|
|
|
|
0
|
return @{$known_ellipsoid{$name}}{qw{semimajor flattening name}} |
|
0
|
|
|
|
|
0
|
|
2659
|
|
|
|
|
|
|
} |
2660
|
|
|
|
|
|
|
|
2661
|
|
|
|
|
|
|
=item $coord->represents ($class); |
2662
|
|
|
|
|
|
|
|
2663
|
|
|
|
|
|
|
This method returns true if the $coord object represents the given class. |
2664
|
|
|
|
|
|
|
It is pretty much like isa (), but if called on a container class (i.e. |
2665
|
|
|
|
|
|
|
Astro::Coord::ECI::TLE::Set), it returns true based on the class of |
2666
|
|
|
|
|
|
|
the members of the set, and dies if the set has no members. |
2667
|
|
|
|
|
|
|
|
2668
|
|
|
|
|
|
|
The $class argument is optional. If not specified (or undef), it is |
2669
|
|
|
|
|
|
|
pretty much like ref $coord || $coord (i.e. it returns the class |
2670
|
|
|
|
|
|
|
name), but with the delegation behavior described in the previous |
2671
|
|
|
|
|
|
|
paragraph if the $coord object is a container. |
2672
|
|
|
|
|
|
|
|
2673
|
|
|
|
|
|
|
There. This took many more words to explain than it did to implement. |
2674
|
|
|
|
|
|
|
|
2675
|
|
|
|
|
|
|
=cut |
2676
|
|
|
|
|
|
|
|
2677
|
|
|
|
|
|
|
sub represents { |
2678
|
85018
|
100
|
66
|
85018
|
1
|
236450
|
return defined ($_[1]) ? |
|
|
100
|
|
|
|
|
|
2679
|
|
|
|
|
|
|
## $_[0]->represents()->isa($_[1]) : |
2680
|
|
|
|
|
|
|
__classisa($_[0]->represents(), $_[1]) ? 1 : 0 : |
2681
|
|
|
|
|
|
|
(ref $_[0] || $_[0]); |
2682
|
|
|
|
|
|
|
} |
2683
|
|
|
|
|
|
|
|
2684
|
|
|
|
|
|
|
=item $coord->set (name => value ...); |
2685
|
|
|
|
|
|
|
|
2686
|
|
|
|
|
|
|
This method sets various attributes of the object. If called as a class |
2687
|
|
|
|
|
|
|
method, it changes the defaults. |
2688
|
|
|
|
|
|
|
|
2689
|
|
|
|
|
|
|
For reasons that seemed good at the time, this method returns the |
2690
|
|
|
|
|
|
|
object it was called on (i.e. $coord in the above example). |
2691
|
|
|
|
|
|
|
|
2692
|
|
|
|
|
|
|
See L for a list of the attributes you can set. |
2693
|
|
|
|
|
|
|
|
2694
|
|
|
|
|
|
|
=cut |
2695
|
|
|
|
|
|
|
|
2696
|
17
|
|
|
17
|
|
298
|
use constant SET_ACTION_NONE => 0; # Do nothing. |
|
17
|
|
|
|
|
67
|
|
|
17
|
|
|
|
|
1159
|
|
2697
|
17
|
|
|
17
|
|
143
|
use constant SET_ACTION_RESET => 1; # Reset the coordinates based on the initial setting. |
|
17
|
|
|
|
|
43
|
|
|
17
|
|
|
|
|
14133
|
|
2698
|
|
|
|
|
|
|
|
2699
|
|
|
|
|
|
|
sub set { |
2700
|
1618
|
|
|
1618
|
1
|
3824
|
my ($self, @args) = @_; |
2701
|
1618
|
|
|
|
|
2245
|
my $original = $self; |
2702
|
1618
|
100
|
|
|
|
3212
|
ref $self |
2703
|
|
|
|
|
|
|
or $self = \%static; |
2704
|
1618
|
50
|
|
|
|
3258
|
@args % 2 |
2705
|
|
|
|
|
|
|
and croak 'Error - The set() method requires an even ', |
2706
|
|
|
|
|
|
|
'number of arguments.'; |
2707
|
1618
|
|
|
|
|
2284
|
my $action = 0; |
2708
|
1618
|
|
|
|
|
3014
|
while (@args) { |
2709
|
1647
|
|
|
|
|
2526
|
my $name = shift @args; |
2710
|
1647
|
50
|
|
|
|
3637
|
exists $mutator{$name} |
2711
|
|
|
|
|
|
|
or croak "Error - Attribute '$name' does not exist."; |
2712
|
1647
|
50
|
|
|
|
4018
|
CODE_REF eq ref $mutator{$name} |
2713
|
|
|
|
|
|
|
or croak "Error - Attribute '$name' is read-only"; |
2714
|
1647
|
|
|
|
|
4020
|
$action |= $mutator{$name}->($self, $name, shift @args); |
2715
|
|
|
|
|
|
|
} |
2716
|
|
|
|
|
|
|
|
2717
|
|
|
|
|
|
|
$self->{_need_purge} = 1 |
2718
|
1617
|
100
|
66
|
|
|
7837
|
if ref $self && $self->{specified} && $action & SET_ACTION_RESET; |
|
|
|
100
|
|
|
|
|
2719
|
|
|
|
|
|
|
|
2720
|
1617
|
|
|
|
|
2823
|
return $original; |
2721
|
|
|
|
|
|
|
} |
2722
|
|
|
|
|
|
|
|
2723
|
|
|
|
|
|
|
# The following are the mutators for the attributes. All are |
2724
|
|
|
|
|
|
|
# passed three arguments: a reference to the hash to be set, |
2725
|
|
|
|
|
|
|
# the hash key to be set, and the value. They must return the |
2726
|
|
|
|
|
|
|
# bitwise 'or' of the desired action masks, defined above. |
2727
|
|
|
|
|
|
|
|
2728
|
|
|
|
|
|
|
%mutator = ( |
2729
|
|
|
|
|
|
|
almanac_horizon => \&_set_almanac_horizon, |
2730
|
|
|
|
|
|
|
angularvelocity => \&_set_value, |
2731
|
|
|
|
|
|
|
debug => \&_set_value, |
2732
|
|
|
|
|
|
|
diameter => \&_set_value, |
2733
|
|
|
|
|
|
|
edge_of_earths_shadow => \&_set_value, |
2734
|
|
|
|
|
|
|
ellipsoid => \&_set_reference_ellipsoid, |
2735
|
|
|
|
|
|
|
equinox_dynamical => \&_set_value, # CAVEAT: _convert_eci_to_ecef |
2736
|
|
|
|
|
|
|
# accesses this directly for |
2737
|
|
|
|
|
|
|
# speed. |
2738
|
|
|
|
|
|
|
flattening => \&_set_custom_ellipsoid, |
2739
|
|
|
|
|
|
|
frequency => \&_set_value, |
2740
|
|
|
|
|
|
|
horizon => \&_set_elevation, |
2741
|
|
|
|
|
|
|
id => \&_set_id, |
2742
|
|
|
|
|
|
|
inertial => undef, |
2743
|
|
|
|
|
|
|
name => \&_set_value, |
2744
|
|
|
|
|
|
|
refraction => \&_set_value, |
2745
|
|
|
|
|
|
|
specified => undef, |
2746
|
|
|
|
|
|
|
semimajor => \&_set_custom_ellipsoid, |
2747
|
|
|
|
|
|
|
station => \&_set_station, |
2748
|
|
|
|
|
|
|
sun => \&_set_sun, |
2749
|
|
|
|
|
|
|
twilight => \&_set_elevation, |
2750
|
|
|
|
|
|
|
); |
2751
|
|
|
|
|
|
|
|
2752
|
|
|
|
|
|
|
{ |
2753
|
|
|
|
|
|
|
# TODO this will all be nicer if I had state variables. |
2754
|
|
|
|
|
|
|
|
2755
|
|
|
|
|
|
|
my %special = ( |
2756
|
|
|
|
|
|
|
## horizon => sub { return $_[0]->get( 'horizon' ); }, |
2757
|
|
|
|
|
|
|
height => sub { return $_[0]->dip(); }, |
2758
|
|
|
|
|
|
|
); |
2759
|
|
|
|
|
|
|
|
2760
|
|
|
|
|
|
|
sub _set_almanac_horizon { |
2761
|
172
|
|
|
172
|
|
481
|
my ( $hash, $attr, $value ) = @_; |
2762
|
172
|
50
|
|
|
|
405
|
defined $value |
2763
|
|
|
|
|
|
|
or $value = 0; # Default |
2764
|
172
|
50
|
33
|
|
|
1384
|
if ( $special{$value} ) { |
|
|
50
|
33
|
|
|
|
|
2765
|
0
|
|
|
|
|
0
|
$hash->{"_$attr"} = $special{$value}; |
2766
|
|
|
|
|
|
|
} elsif ( looks_like_number( $value ) |
2767
|
|
|
|
|
|
|
&& $value >= - PIOVER2 # Not -PIOVER2 to avoid warning under 5.10.1. |
2768
|
|
|
|
|
|
|
&& $value <= PIOVER2 |
2769
|
|
|
|
|
|
|
) { |
2770
|
172
|
|
|
5
|
|
1041
|
$hash->{"_$attr"} = sub { return $_[0]->get( $attr ) }; |
|
5
|
|
|
|
|
14
|
|
2771
|
|
|
|
|
|
|
} else { |
2772
|
0
|
|
|
|
|
0
|
croak "'$value' is an invalid value for '$attr'"; |
2773
|
|
|
|
|
|
|
} |
2774
|
172
|
|
|
|
|
589
|
$hash->{$attr} = $value; |
2775
|
172
|
|
|
|
|
523
|
return SET_ACTION_NONE; |
2776
|
|
|
|
|
|
|
} |
2777
|
|
|
|
|
|
|
} |
2778
|
|
|
|
|
|
|
|
2779
|
|
|
|
|
|
|
# Get the actual value of the almanac horizon, from whatever |
2780
|
|
|
|
|
|
|
# source. |
2781
|
|
|
|
|
|
|
|
2782
|
|
|
|
|
|
|
sub __get_almanac_horizon { |
2783
|
5
|
|
|
5
|
|
20
|
goto $_[0]{_almanac_horizon}; |
2784
|
|
|
|
|
|
|
} |
2785
|
|
|
|
|
|
|
|
2786
|
|
|
|
|
|
|
# If you set semimajor or flattening, the ellipsoid name becomes |
2787
|
|
|
|
|
|
|
# undefined. Also clear any cached geodetic coordinates. |
2788
|
|
|
|
|
|
|
|
2789
|
|
|
|
|
|
|
sub _set_custom_ellipsoid { |
2790
|
0
|
|
|
0
|
|
0
|
$_[0]->{ellipsoid} = undef; |
2791
|
0
|
|
|
|
|
0
|
$_[0]->{$_[1]} = $_[2]; |
2792
|
0
|
|
|
|
|
0
|
return SET_ACTION_RESET; |
2793
|
|
|
|
|
|
|
} |
2794
|
|
|
|
|
|
|
|
2795
|
|
|
|
|
|
|
{ |
2796
|
|
|
|
|
|
|
my %dflt = ( |
2797
|
|
|
|
|
|
|
twilight => CIVIL_TWILIGHT, |
2798
|
|
|
|
|
|
|
); |
2799
|
|
|
|
|
|
|
|
2800
|
|
|
|
|
|
|
# Any attribute specified as angle above or below the horizon. |
2801
|
|
|
|
|
|
|
sub _set_elevation { |
2802
|
1
|
|
|
1
|
|
4
|
my ( $self, $name, $value ) = @_; |
2803
|
|
|
|
|
|
|
defined $value |
2804
|
1
|
50
|
0
|
|
|
4
|
or $value = ( $dflt{$name} || 0 ); # Default |
2805
|
1
|
50
|
33
|
|
|
336
|
looks_like_number( $value ) |
|
|
|
33
|
|
|
|
|
2806
|
|
|
|
|
|
|
and $value >= - PIOVER2 # Not -PIOVER2 to avoid warning under 5.10.1. |
2807
|
|
|
|
|
|
|
and $value <= PIOVER2 |
2808
|
|
|
|
|
|
|
or croak "'$value' is an invalid value for '$name'"; |
2809
|
0
|
|
|
|
|
0
|
$self->{$name} = $value; |
2810
|
0
|
|
|
|
|
0
|
return SET_ACTION_NONE; |
2811
|
|
|
|
|
|
|
} |
2812
|
|
|
|
|
|
|
} |
2813
|
|
|
|
|
|
|
|
2814
|
|
|
|
|
|
|
sub _set_station { |
2815
|
12
|
|
|
12
|
|
34
|
my ( $hash, $attr, $value ) = @_; |
2816
|
12
|
50
|
|
|
|
41
|
if ( defined $value ) { |
2817
|
12
|
50
|
|
|
|
39
|
embodies( $value, 'Astro::Coord::ECI' ) |
2818
|
|
|
|
|
|
|
or croak "Attribute $attr must be undef or an ", |
2819
|
|
|
|
|
|
|
'Astro::Coord::ECI object'; |
2820
|
12
|
50
|
|
|
|
44
|
$value->get( 'station' ) |
2821
|
|
|
|
|
|
|
and croak NO_CASCADING_STATIONS; |
2822
|
|
|
|
|
|
|
} |
2823
|
12
|
|
|
|
|
32
|
$hash->{$attr} = $value; |
2824
|
12
|
|
|
|
|
42
|
return SET_ACTION_RESET; |
2825
|
|
|
|
|
|
|
} |
2826
|
|
|
|
|
|
|
|
2827
|
17
|
|
|
17
|
|
259
|
use constant SUN_CLASS => 'Astro::Coord::ECI::Sun'; |
|
17
|
|
|
|
|
35
|
|
|
17
|
|
|
|
|
56896
|
|
2828
|
|
|
|
|
|
|
|
2829
|
|
|
|
|
|
|
sub _set_sun { |
2830
|
2
|
|
|
2
|
|
6
|
my ( $self, $name, $value ) = @_; |
2831
|
2
|
50
|
|
|
|
18
|
embodies( $value, $self->SUN_CLASS() ) |
2832
|
|
|
|
|
|
|
or croak sprintf 'The sun attribute must be a %s', |
2833
|
|
|
|
|
|
|
$self->SUN_CLASS(); |
2834
|
2
|
50
|
|
|
|
18
|
ref $value |
2835
|
|
|
|
|
|
|
or $value = $value->new(); |
2836
|
2
|
|
|
|
|
6
|
$self->{$name} = $value; |
2837
|
2
|
|
|
|
|
12
|
return SET_ACTION_NONE; |
2838
|
|
|
|
|
|
|
} |
2839
|
|
|
|
|
|
|
|
2840
|
|
|
|
|
|
|
# Unfortunately, the TLE subclass may need objects reblessed if |
2841
|
|
|
|
|
|
|
# the ID changes. So much for factoring. Sigh. |
2842
|
|
|
|
|
|
|
|
2843
|
|
|
|
|
|
|
sub _set_id { |
2844
|
139
|
|
|
139
|
|
321
|
$_[0]{$_[1]} = $_[2]; |
2845
|
139
|
100
|
|
|
|
763
|
$_[0]->rebless () if $_[0]->can ('rebless'); |
2846
|
139
|
|
|
|
|
423
|
return SET_ACTION_NONE; |
2847
|
|
|
|
|
|
|
} |
2848
|
|
|
|
|
|
|
|
2849
|
|
|
|
|
|
|
# If this is a reference ellipsoid name, check it, and if it's |
2850
|
|
|
|
|
|
|
# OK set semimajor and flattening also. Also clear any cached |
2851
|
|
|
|
|
|
|
# geodetic coordinates. |
2852
|
|
|
|
|
|
|
|
2853
|
|
|
|
|
|
|
sub _set_reference_ellipsoid { |
2854
|
31
|
|
|
31
|
|
73
|
my ($self, $name, $value) = @_; |
2855
|
31
|
50
|
|
|
|
85
|
defined $value or croak <
|
2856
|
|
|
|
|
|
|
Error - Can not set reference ellipsoid to undefined. |
2857
|
|
|
|
|
|
|
eod |
2858
|
31
|
50
|
|
|
|
115
|
exists $known_ellipsoid{$value} or croak <
|
2859
|
|
|
|
|
|
|
Error - Reference ellipsoid '$value' is unknown. |
2860
|
|
|
|
|
|
|
eod |
2861
|
|
|
|
|
|
|
|
2862
|
31
|
|
|
|
|
90
|
$self->{semimajor} = $known_ellipsoid{$value}{semimajor}; |
2863
|
31
|
|
|
|
|
76
|
$self->{flattening} = $known_ellipsoid{$value}{flattening}; |
2864
|
31
|
|
|
|
|
59
|
$self->{$name} = $value; |
2865
|
31
|
|
|
|
|
122
|
return SET_ACTION_RESET; |
2866
|
|
|
|
|
|
|
} |
2867
|
|
|
|
|
|
|
|
2868
|
|
|
|
|
|
|
# If this is a vanilla setting, just do it. |
2869
|
|
|
|
|
|
|
|
2870
|
|
|
|
|
|
|
sub _set_value { |
2871
|
1290
|
|
|
1290
|
|
2638
|
$_[0]->{$_[1]} = $_[2]; |
2872
|
1290
|
|
|
|
|
3360
|
return SET_ACTION_NONE; |
2873
|
|
|
|
|
|
|
} |
2874
|
|
|
|
|
|
|
|
2875
|
|
|
|
|
|
|
=item $coord->universal ($time) |
2876
|
|
|
|
|
|
|
|
2877
|
|
|
|
|
|
|
This method sets the time represented by the object, in universal time |
2878
|
|
|
|
|
|
|
(a.k.a. CUT, a.k.a. Zulu, a.k.a. Greenwich). |
2879
|
|
|
|
|
|
|
|
2880
|
|
|
|
|
|
|
This method can also be called as a class method, in which case it |
2881
|
|
|
|
|
|
|
instantiates the desired object. |
2882
|
|
|
|
|
|
|
|
2883
|
|
|
|
|
|
|
=item $time = $coord->universal (); |
2884
|
|
|
|
|
|
|
|
2885
|
|
|
|
|
|
|
This method returns the universal time previously set. |
2886
|
|
|
|
|
|
|
|
2887
|
|
|
|
|
|
|
=cut |
2888
|
|
|
|
|
|
|
|
2889
|
|
|
|
|
|
|
sub universal { |
2890
|
140693
|
|
|
140693
|
1
|
233750
|
my ( $self, $time, @args ) = @_; |
2891
|
|
|
|
|
|
|
|
2892
|
|
|
|
|
|
|
@args |
2893
|
140693
|
50
|
|
|
|
244252
|
and croak <<'EOD'; |
2894
|
|
|
|
|
|
|
Error - The universal() method must be called with either zero |
2895
|
|
|
|
|
|
|
arguments (to retrieve the time) or one argument (to set the |
2896
|
|
|
|
|
|
|
time). |
2897
|
|
|
|
|
|
|
EOD |
2898
|
|
|
|
|
|
|
|
2899
|
140693
|
100
|
|
|
|
237051
|
if ( defined $time ) { |
2900
|
65243
|
|
|
|
|
117805
|
return $self->__universal( $time, 1 ); |
2901
|
|
|
|
|
|
|
} else { |
2902
|
75450
|
50
|
|
|
|
138708
|
ref $self or croak <<'EOD'; |
2903
|
|
|
|
|
|
|
Error - The universal() method may not be called as a class method |
2904
|
|
|
|
|
|
|
unless you specify arguments. |
2905
|
|
|
|
|
|
|
EOD |
2906
|
|
|
|
|
|
|
defined $self->{universal} |
2907
|
75450
|
50
|
|
|
|
137523
|
or croak <<'EOD'; |
2908
|
|
|
|
|
|
|
Error - Object's time has not been set. |
2909
|
|
|
|
|
|
|
EOD |
2910
|
75450
|
|
|
|
|
198677
|
return $self->{universal}; |
2911
|
|
|
|
|
|
|
} |
2912
|
|
|
|
|
|
|
} |
2913
|
|
|
|
|
|
|
|
2914
|
|
|
|
|
|
|
# Set universal time without running model |
2915
|
|
|
|
|
|
|
|
2916
|
|
|
|
|
|
|
sub __universal { |
2917
|
84084
|
|
|
84084
|
|
139762
|
my ( $self, $time, $run_model ) = @_; |
2918
|
84084
|
50
|
|
|
|
149982
|
defined $time |
2919
|
|
|
|
|
|
|
or confess 'Programming error - __universal() requires a defined time'; |
2920
|
84084
|
100
|
|
|
|
158142
|
$self = $self->new () unless ref $self; |
2921
|
|
|
|
|
|
|
defined $self->{universal} |
2922
|
|
|
|
|
|
|
and $time == $self->{universal} |
2923
|
84084
|
100
|
100
|
|
|
319341
|
and return $self; |
2924
|
58330
|
|
|
|
|
126772
|
$self->__clear_time(); |
2925
|
58330
|
|
|
|
|
92714
|
$self->{universal} = $time; |
2926
|
58330
|
100
|
|
|
|
155994
|
$run_model |
2927
|
|
|
|
|
|
|
and $self->_call_time_set(); |
2928
|
58316
|
|
|
|
|
133177
|
return $self; |
2929
|
|
|
|
|
|
|
} |
2930
|
|
|
|
|
|
|
|
2931
|
|
|
|
|
|
|
sub __clear_time { |
2932
|
58344
|
|
|
58344
|
|
86864
|
my ( $self ) = @_; |
2933
|
58344
|
|
|
|
|
90123
|
delete $self->{universal}; |
2934
|
58344
|
|
|
|
|
78906
|
delete $self->{local_mean_time}; |
2935
|
58344
|
|
|
|
|
77535
|
delete $self->{dynamical}; |
2936
|
58344
|
100
|
|
|
|
107489
|
if ($self->{specified}) { |
2937
|
58230
|
100
|
|
|
|
103216
|
if ($self->{inertial}) { |
2938
|
31846
|
|
|
|
|
76795
|
delete $self->{_ECI_cache}{fixed}; |
2939
|
|
|
|
|
|
|
} else { |
2940
|
26384
|
|
|
|
|
42204
|
delete $self->{_ECI_cache}{inertial}; |
2941
|
|
|
|
|
|
|
} |
2942
|
|
|
|
|
|
|
} |
2943
|
58344
|
|
|
|
|
84393
|
return; |
2944
|
|
|
|
|
|
|
} |
2945
|
|
|
|
|
|
|
|
2946
|
|
|
|
|
|
|
sub __event_name { |
2947
|
30
|
|
|
30
|
|
86
|
my ( $self, $event, $tplt ) = @_; |
2948
|
30
|
50
|
|
|
|
89
|
ARRAY_REF eq ref $tplt |
2949
|
|
|
|
|
|
|
or confess 'Programming error - $tplt must be array ref'; |
2950
|
30
|
|
|
|
|
105
|
return __sprintf( $tplt->[$event], $self->__object_name() ); |
2951
|
|
|
|
|
|
|
} |
2952
|
|
|
|
|
|
|
|
2953
|
|
|
|
|
|
|
sub __horizon_name { |
2954
|
10
|
|
|
10
|
|
28
|
my ( $self, $event, $tplt ) = @_; |
2955
|
10
|
|
33
|
|
|
53
|
return $self->__event_name( |
2956
|
|
|
|
|
|
|
$event, |
2957
|
|
|
|
|
|
|
$tplt || $self->__horizon_name_tplt(), |
2958
|
|
|
|
|
|
|
); |
2959
|
|
|
|
|
|
|
} |
2960
|
|
|
|
|
|
|
|
2961
|
|
|
|
|
|
|
sub __horizon_name_tplt { |
2962
|
0
|
|
|
0
|
|
0
|
return [ '%s sets', '%s rises' ]; |
2963
|
|
|
|
|
|
|
} |
2964
|
|
|
|
|
|
|
|
2965
|
|
|
|
|
|
|
sub __transit_name { |
2966
|
10
|
|
|
10
|
|
30
|
my ( $self, $event, $tplt ) = @_; |
2967
|
10
|
|
33
|
|
|
82
|
return $self->__event_name( |
2968
|
|
|
|
|
|
|
$event, |
2969
|
|
|
|
|
|
|
$tplt || $self->__transit_name_tplt(), |
2970
|
|
|
|
|
|
|
); |
2971
|
|
|
|
|
|
|
} |
2972
|
|
|
|
|
|
|
|
2973
|
|
|
|
|
|
|
sub __transit_name_tplt { |
2974
|
6
|
|
|
6
|
|
35
|
return [ undef, '%s transits meridian' ]; |
2975
|
|
|
|
|
|
|
} |
2976
|
|
|
|
|
|
|
|
2977
|
|
|
|
|
|
|
sub __object_name { |
2978
|
50
|
|
|
50
|
|
101
|
my ( $self ) = @_; |
2979
|
|
|
|
|
|
|
return $self->get( 'name' ) || $self->get( 'id' ) || ( |
2980
|
50
|
|
33
|
|
|
117
|
$self->{_name} ||= $self->__object_name_from_class_name() |
2981
|
|
|
|
|
|
|
); |
2982
|
|
|
|
|
|
|
} |
2983
|
|
|
|
|
|
|
|
2984
|
|
|
|
|
|
|
sub __object_name_from_class_name { |
2985
|
5
|
|
|
5
|
|
15
|
my ( $self ) = @_; |
2986
|
5
|
|
33
|
|
|
34
|
( my $name = ref $self || $self ) =~ s/ (?: :: | _ ) XS \z //smx; |
2987
|
5
|
|
|
|
|
45
|
$name =~ s/ .* :: //smx; |
2988
|
5
|
|
|
|
|
17
|
return $name; |
2989
|
|
|
|
|
|
|
} |
2990
|
|
|
|
|
|
|
|
2991
|
|
|
|
|
|
|
sub __object_is_self_named { |
2992
|
20
|
|
|
20
|
|
42
|
my ( $self ) = @_; |
2993
|
20
|
|
66
|
|
|
85
|
$self->{_name_re} ||= do { |
2994
|
5
|
|
|
|
|
26
|
my $re = $self->__object_name_from_class_name(); |
2995
|
5
|
|
|
|
|
90
|
qr< \A \Q$re\E \z >smxi; |
2996
|
|
|
|
|
|
|
}; |
2997
|
20
|
|
|
|
|
70
|
return $self->__object_name() =~ $self->{_name_re}; |
2998
|
|
|
|
|
|
|
} |
2999
|
|
|
|
|
|
|
|
3000
|
|
|
|
|
|
|
####################################################################### |
3001
|
|
|
|
|
|
|
# |
3002
|
|
|
|
|
|
|
# Internal |
3003
|
|
|
|
|
|
|
# |
3004
|
|
|
|
|
|
|
|
3005
|
|
|
|
|
|
|
# $coord->_call_time_set () |
3006
|
|
|
|
|
|
|
|
3007
|
|
|
|
|
|
|
# This method calls the time_set method if it exists and if we are |
3008
|
|
|
|
|
|
|
# not already in it. It is a way to avoid endless recursion if the |
3009
|
|
|
|
|
|
|
# time_set method should happen to set the time. |
3010
|
|
|
|
|
|
|
|
3011
|
|
|
|
|
|
|
sub _call_time_set { |
3012
|
57916
|
|
|
57916
|
|
82595
|
my $self = shift; |
3013
|
57916
|
100
|
|
|
|
172780
|
$self->can ('time_set') or return; |
3014
|
31492
|
|
|
|
|
45575
|
my $exception; |
3015
|
31492
|
100
|
|
|
|
70813
|
unless ($self->{_no_set}++) { |
3016
|
31374
|
100
|
|
|
|
46973
|
eval {$self->time_set (); 1;} |
|
31374
|
|
|
|
|
79711
|
|
|
31360
|
|
|
|
|
65914
|
|
3017
|
|
|
|
|
|
|
or $exception = $@; |
3018
|
|
|
|
|
|
|
} |
3019
|
31492
|
100
|
|
|
|
69919
|
--$self->{_no_set} or delete $self->{_no_set}; |
3020
|
31492
|
100
|
|
|
|
58417
|
if ($exception) { |
3021
|
14
|
|
|
|
|
42
|
$self->__clear_time(); |
3022
|
|
|
|
|
|
|
# Re-raise the exception now that we have cleaned up. |
3023
|
14
|
|
|
|
|
56
|
die $exception; ## no critic (RequireCarping) |
3024
|
|
|
|
|
|
|
} |
3025
|
31478
|
|
|
|
|
46467
|
return; |
3026
|
|
|
|
|
|
|
} |
3027
|
|
|
|
|
|
|
|
3028
|
|
|
|
|
|
|
# $coord->_check_coord (method => \@_) |
3029
|
|
|
|
|
|
|
|
3030
|
|
|
|
|
|
|
# This is designed to be called "up front" for any of the methods |
3031
|
|
|
|
|
|
|
# that retrieve or set coordinates, to be sure the object is in |
3032
|
|
|
|
|
|
|
# a consistent state. |
3033
|
|
|
|
|
|
|
# * If $self is not a reference, it creates a new object if there |
3034
|
|
|
|
|
|
|
# are arguments, or croaks if there are none. |
3035
|
|
|
|
|
|
|
# * If the number arguments passed (after removing self and the |
3036
|
|
|
|
|
|
|
# method name) is one more than a multiple of three, the last |
3037
|
|
|
|
|
|
|
# argument is removed, and used to set the universal time of |
3038
|
|
|
|
|
|
|
# the object. |
3039
|
|
|
|
|
|
|
# * If there are arguments, all coordinate caches are cleared; |
3040
|
|
|
|
|
|
|
# otherwise the coordinates are reset if needed. |
3041
|
|
|
|
|
|
|
# The object itself is returned. |
3042
|
|
|
|
|
|
|
|
3043
|
|
|
|
|
|
|
sub _check_coord { |
3044
|
200567
|
|
|
200567
|
|
330026
|
my ($self, $method, $args, @extra) = @_; |
3045
|
|
|
|
|
|
|
|
3046
|
200567
|
100
|
|
|
|
369006
|
unless (ref $self) { |
3047
|
14
|
50
|
|
|
|
31
|
@$args or croak <
|
3048
|
|
|
|
|
|
|
Error - The $method() method may not be called as a class method |
3049
|
|
|
|
|
|
|
unless you specify arguments. |
3050
|
|
|
|
|
|
|
eod |
3051
|
14
|
|
|
|
|
30
|
$self = $self->new (); |
3052
|
|
|
|
|
|
|
} |
3053
|
|
|
|
|
|
|
|
3054
|
200567
|
50
|
|
|
|
358596
|
$self->{debug} and do { |
3055
|
0
|
|
|
|
|
0
|
require Data::Dumper; |
3056
|
0
|
|
|
|
|
0
|
local $Data::Dumper::Terse = 1; |
3057
|
0
|
|
|
|
|
0
|
print "Debug $method (", Data::Dumper::Dumper (@$args, @extra), ")\n"; |
3058
|
|
|
|
|
|
|
}; |
3059
|
|
|
|
|
|
|
|
3060
|
|
|
|
|
|
|
{ |
3061
|
200567
|
|
|
|
|
251184
|
my $inx = 0; |
|
200567
|
|
|
|
|
247627
|
|
3062
|
200567
|
|
|
|
|
345313
|
foreach (@$args) { |
3063
|
263135
|
50
|
|
|
|
453263
|
defined $_ |
3064
|
|
|
|
|
|
|
and next; |
3065
|
0
|
|
|
|
|
0
|
require Data::Dumper; |
3066
|
0
|
|
|
|
|
0
|
local $Data::Dumper::Terse = 1; |
3067
|
0
|
0
|
|
|
|
0
|
croak <
|
3068
|
0
|
|
|
|
|
0
|
Error - @{[ (caller (1))[3] ]} argument $inx is undefined. |
3069
|
|
|
|
|
|
|
Arguments are (@{[ join ', ', |
3070
|
0
|
|
|
|
|
0
|
map {my $x = Data::Dumper::Dumper $_; chomp $x; $x} @$args ]}) |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
3071
|
|
|
|
|
|
|
eod |
3072
|
0
|
|
|
|
|
0
|
$inx++; |
3073
|
|
|
|
|
|
|
} |
3074
|
|
|
|
|
|
|
} |
3075
|
|
|
|
|
|
|
|
3076
|
200567
|
100
|
|
|
|
375571
|
$self->universal (pop @$args) if @$args % 3 == 1; |
3077
|
|
|
|
|
|
|
|
3078
|
200567
|
100
|
|
|
|
352181
|
if ($self->{specified}) { |
3079
|
198107
|
100
|
|
|
|
381270
|
if (@$args) { |
|
|
100
|
|
|
|
|
|
3080
|
|
|
|
|
|
|
# Cached coordinate deletion moved to ecef(), so it's only done once. |
3081
|
|
|
|
|
|
|
} elsif ($self->{_need_purge}) { |
3082
|
4
|
|
|
|
|
8
|
delete $self->{_need_purge}; |
3083
|
4
|
|
|
|
|
8
|
my $spec = $self->{specified}; |
3084
|
4
|
|
|
|
|
42
|
my $data = [$self->$spec ()]; |
3085
|
4
|
|
|
|
|
9
|
foreach my $key (@kilatr) {delete $self->{$key}} |
|
20
|
|
|
|
|
37
|
|
3086
|
4
|
|
|
|
|
12
|
$self->$spec (@$data); |
3087
|
|
|
|
|
|
|
} |
3088
|
|
|
|
|
|
|
} |
3089
|
|
|
|
|
|
|
|
3090
|
200567
|
|
|
|
|
338302
|
return $self; |
3091
|
|
|
|
|
|
|
} |
3092
|
|
|
|
|
|
|
|
3093
|
|
|
|
|
|
|
# _check_latitude($arg_name => $value) |
3094
|
|
|
|
|
|
|
# |
3095
|
|
|
|
|
|
|
# This subroutine range-checks the given latitude value, |
3096
|
|
|
|
|
|
|
# generating a warning if it is outside the range -PIOVER2 <= |
3097
|
|
|
|
|
|
|
# $value <= PIOVER2. The $arg_name is used in the exception, if |
3098
|
|
|
|
|
|
|
# any. The value is normalized to the range -PI to PI, and |
3099
|
|
|
|
|
|
|
# returned. Not that a value outside the validation range makes |
3100
|
|
|
|
|
|
|
# any sense. |
3101
|
|
|
|
|
|
|
|
3102
|
|
|
|
|
|
|
sub _check_latitude { |
3103
|
6465
|
50
|
33
|
6465
|
|
28143
|
(-&PIOVER2 <= $_[1] && $_[1] <= &PIOVER2) |
3104
|
|
|
|
|
|
|
or carp (ucfirst $_[0], |
3105
|
|
|
|
|
|
|
' must be in radians, between -PI/2 and PI/2'); |
3106
|
6465
|
|
|
|
|
15576
|
return mod2pi($_[1] + PI) - PI; |
3107
|
|
|
|
|
|
|
} |
3108
|
|
|
|
|
|
|
|
3109
|
|
|
|
|
|
|
# $value = _check_longitude($arg_name => $value) |
3110
|
|
|
|
|
|
|
# |
3111
|
|
|
|
|
|
|
# This subroutine range-checks the given longitude value, |
3112
|
|
|
|
|
|
|
# generating a warning if it is outside the range -TWOPI <= $value |
3113
|
|
|
|
|
|
|
# <= TWOPI. The $arg_name is used in the exception, if any. The |
3114
|
|
|
|
|
|
|
# value is normalized to the range -PI to PI, and returned. |
3115
|
|
|
|
|
|
|
|
3116
|
|
|
|
|
|
|
sub _check_longitude { |
3117
|
4212
|
50
|
33
|
4212
|
|
15904
|
(-&TWOPI <= $_[1] && $_[1] <= &TWOPI) |
3118
|
|
|
|
|
|
|
or carp (ucfirst $_[0], |
3119
|
|
|
|
|
|
|
' must be in radians, between -2*PI and 2*PI'); |
3120
|
4212
|
|
|
|
|
8917
|
return mod2pi($_[1] + PI) - PI; |
3121
|
|
|
|
|
|
|
} |
3122
|
|
|
|
|
|
|
|
3123
|
|
|
|
|
|
|
# _check_right_ascension($arg_name => $value) |
3124
|
|
|
|
|
|
|
# |
3125
|
|
|
|
|
|
|
# This subroutine range-checks the given right ascension value, |
3126
|
|
|
|
|
|
|
# generating a warning if it is outside the range 0 <= $value <= |
3127
|
|
|
|
|
|
|
# TWOPI. The $arg_name is used in the exception, if any. The value |
3128
|
|
|
|
|
|
|
# is normalized to the range 0 to TWOPI, and returned. |
3129
|
|
|
|
|
|
|
|
3130
|
|
|
|
|
|
|
sub _check_right_ascension { |
3131
|
2253
|
50
|
33
|
2253
|
|
9009
|
(0 <= $_[1] && $_[1] <= &TWOPI) |
3132
|
|
|
|
|
|
|
or carp (ucfirst $_[0], |
3133
|
|
|
|
|
|
|
' must be in radians, between 0 and 2*PI'); |
3134
|
2253
|
|
|
|
|
4791
|
return mod2pi($_[1]); |
3135
|
|
|
|
|
|
|
} |
3136
|
|
|
|
|
|
|
|
3137
|
|
|
|
|
|
|
# @cartesian = _convert_spherical_to_cartesian( @spherical ) |
3138
|
|
|
|
|
|
|
# |
3139
|
|
|
|
|
|
|
# This subroutine converts spherical coordinates to Cartesian |
3140
|
|
|
|
|
|
|
# coordinates of the same handedness. |
3141
|
|
|
|
|
|
|
# |
3142
|
|
|
|
|
|
|
# The first three arguments are Phi (in the X-Y plane, e.g. azimuth or |
3143
|
|
|
|
|
|
|
# longitude, in radians), Theta (perpendicular to the X-Y plane, e.g. |
3144
|
|
|
|
|
|
|
# elevation or latitude, in radians) and Rho (range). Subsequent |
3145
|
|
|
|
|
|
|
# triplets of arguments are optional, and can represent derivitaves of |
3146
|
|
|
|
|
|
|
# position (velocity, acceleration ... ) at that point. |
3147
|
|
|
|
|
|
|
# |
3148
|
|
|
|
|
|
|
# The return is the corresponding X, Y, and Z coordinates. If more than |
3149
|
|
|
|
|
|
|
# three triplets of arguments are specified, they will be converted to |
3150
|
|
|
|
|
|
|
# spherical coordinates as though measured at that point, and returned |
3151
|
|
|
|
|
|
|
# in the same order. That is, if you supplied Phi, Theta, and Rho |
3152
|
|
|
|
|
|
|
# velocities, you will get back X, Y, and Z velocities. velocities, you |
3153
|
|
|
|
|
|
|
# will get back Phi, Theta, and Rho velocities, in that order. |
3154
|
|
|
|
|
|
|
|
3155
|
|
|
|
|
|
|
sub _convert_spherical_to_cartesian { |
3156
|
15312
|
|
|
15312
|
|
28647
|
my @sph_data = @_; |
3157
|
|
|
|
|
|
|
@sph_data |
3158
|
15312
|
50
|
33
|
|
|
50772
|
and not @sph_data % 3 |
3159
|
|
|
|
|
|
|
or confess( 'Programming error - Want 3 or 6 arguments' ); |
3160
|
|
|
|
|
|
|
|
3161
|
|
|
|
|
|
|
# The first triplet is position. We extract it into its own array, |
3162
|
|
|
|
|
|
|
# then compute the corresponding Cartesian coordinates using the |
3163
|
|
|
|
|
|
|
# definition of the coordinate system. |
3164
|
|
|
|
|
|
|
|
3165
|
15312
|
|
|
|
|
29275
|
my @sph_pos = splice @sph_data, 0, 3; |
3166
|
15312
|
|
|
|
|
25755
|
my $range = $sph_pos[2]; |
3167
|
15312
|
|
|
|
|
23636
|
my $sin_theta = sin $sph_pos[1]; |
3168
|
15312
|
|
|
|
|
21515
|
my $cos_theta = cos $sph_pos[1]; |
3169
|
15312
|
|
|
|
|
24239
|
my $sin_phi = sin $sph_pos[0]; |
3170
|
15312
|
|
|
|
|
21779
|
my $cos_phi = cos $sph_pos[0]; |
3171
|
15312
|
|
|
|
|
20657
|
my $diag = $range * $cos_theta; |
3172
|
|
|
|
|
|
|
|
3173
|
15312
|
|
|
|
|
19999
|
my $X = $diag * $cos_phi; |
3174
|
15312
|
|
|
|
|
20035
|
my $Y = $diag * $sin_phi; |
3175
|
15312
|
|
|
|
|
20079
|
my $Z = $range * $sin_theta; |
3176
|
|
|
|
|
|
|
|
3177
|
|
|
|
|
|
|
# Accumulate results. |
3178
|
|
|
|
|
|
|
|
3179
|
15312
|
|
|
|
|
24263
|
my @rslt = ( $X, $Y, $Z ); |
3180
|
|
|
|
|
|
|
|
3181
|
|
|
|
|
|
|
# If we have velocity (accelelation, etc) components |
3182
|
|
|
|
|
|
|
|
3183
|
15312
|
100
|
|
|
|
29338
|
if ( @sph_data ) { |
3184
|
|
|
|
|
|
|
|
3185
|
|
|
|
|
|
|
# We compute unit vectors in the Cartesian coordinate system. |
3186
|
|
|
|
|
|
|
|
3187
|
|
|
|
|
|
|
# x = cos Theta cos Phi r - sin Theta cos Phi theta - sin Phi phi |
3188
|
|
|
|
|
|
|
# y = cos Theta sin Phi r - sin Theta sin Phi theta - cos Phi phi |
3189
|
|
|
|
|
|
|
# z = sin Theta r + cos Theta theta |
3190
|
|
|
|
|
|
|
|
3191
|
1
|
|
|
|
|
4
|
my $X_hat = [ - $sin_phi, - $sin_theta * $cos_phi, |
3192
|
|
|
|
|
|
|
$cos_theta * $cos_phi ]; |
3193
|
1
|
|
|
|
|
3
|
my $Y_hat = [ $cos_phi, - $sin_theta * $sin_phi, |
3194
|
|
|
|
|
|
|
$cos_theta * $sin_phi ]; |
3195
|
1
|
|
|
|
|
2
|
my $Z_hat = [ 0, $cos_theta, $sin_theta ]; |
3196
|
|
|
|
|
|
|
|
3197
|
1
|
|
|
|
|
3
|
while ( @sph_data ) { |
3198
|
|
|
|
|
|
|
|
3199
|
|
|
|
|
|
|
# Each triplet is then converted by projecting the Cartesian |
3200
|
|
|
|
|
|
|
# vector onto the appropriate unit vector. Azimuth and |
3201
|
|
|
|
|
|
|
# elevation are also converted to length by multiplying by |
3202
|
|
|
|
|
|
|
# the range. NOTE that this is the small-angle |
3203
|
|
|
|
|
|
|
# approximation, but should be OK since we assume we're |
3204
|
|
|
|
|
|
|
# dealing with derivatives of position. |
3205
|
|
|
|
|
|
|
|
3206
|
1
|
|
|
|
|
3
|
my @sph_info = splice @sph_data, 0, 3; |
3207
|
1
|
|
|
|
|
2
|
$sph_info[0] *= $range; |
3208
|
1
|
|
|
|
|
2
|
$sph_info[1] *= $range; |
3209
|
1
|
|
|
|
|
4
|
push @rslt, vector_dot_product( $X_hat, \@sph_info ); |
3210
|
1
|
|
|
|
|
3
|
push @rslt, vector_dot_product( $Y_hat, \@sph_info ); |
3211
|
1
|
|
|
|
|
3
|
push @rslt, vector_dot_product( $Z_hat, \@sph_info ); |
3212
|
|
|
|
|
|
|
} |
3213
|
|
|
|
|
|
|
|
3214
|
|
|
|
|
|
|
} |
3215
|
|
|
|
|
|
|
|
3216
|
15312
|
|
|
|
|
53558
|
return @rslt; |
3217
|
|
|
|
|
|
|
} |
3218
|
|
|
|
|
|
|
|
3219
|
|
|
|
|
|
|
# @cartesian = _convert_spherical_x_to_cartesian( @spherical ) |
3220
|
|
|
|
|
|
|
# |
3221
|
|
|
|
|
|
|
# This subroutine is a convenience wrapper for |
3222
|
|
|
|
|
|
|
# _convert_spherical_to_cartesian(). The only difference is that the |
3223
|
|
|
|
|
|
|
# arguments are Theta (perpendicular to the X-Y plane), Phi (in the X-Y |
3224
|
|
|
|
|
|
|
# plane) and Rho, rather than Phi, Theta, Rho. This subroutine is my |
3225
|
|
|
|
|
|
|
# penance for not having all the methods that involve spherical |
3226
|
|
|
|
|
|
|
# coordinates return their arguments in the same order. |
3227
|
|
|
|
|
|
|
|
3228
|
|
|
|
|
|
|
sub _convert_spherical_x_to_cartesian { |
3229
|
13058
|
|
|
13058
|
|
24290
|
my @args = @_; |
3230
|
13058
|
|
|
|
|
28286
|
for ( my $inx = 0; $inx < @args; $inx += 3 ) { |
3231
|
13058
|
|
|
|
|
39522
|
@args[ $inx, $inx + 1 ] = @args[ $inx + 1, $inx ]; |
3232
|
|
|
|
|
|
|
} |
3233
|
13058
|
|
|
|
|
25692
|
return _convert_spherical_to_cartesian( @args ); |
3234
|
|
|
|
|
|
|
} |
3235
|
|
|
|
|
|
|
|
3236
|
|
|
|
|
|
|
# @spherical = _convert_cartesian_to_spherical( @cartesian ) |
3237
|
|
|
|
|
|
|
# |
3238
|
|
|
|
|
|
|
# This subroutine converts three-dimensional Cartesian coordinates to |
3239
|
|
|
|
|
|
|
# spherical coordinates of the same handedness. |
3240
|
|
|
|
|
|
|
# |
3241
|
|
|
|
|
|
|
# The first three arguments are the X, Y, and Z coordinates, and are |
3242
|
|
|
|
|
|
|
# required. Subsequent triplets af arguments are optional, and can |
3243
|
|
|
|
|
|
|
# represent anything (velocity, acceleration ... ) at that point. |
3244
|
|
|
|
|
|
|
# |
3245
|
|
|
|
|
|
|
# The return is the spherical coordinates Phi (in the X-Y plane, e.g. |
3246
|
|
|
|
|
|
|
# azimuth or longitude, in radians), Theta (perpendicular to the X-Y |
3247
|
|
|
|
|
|
|
# plane, e.g. elevation or latitude, in radians), and Rho (range). If |
3248
|
|
|
|
|
|
|
# more than three triplets of arguments are specified, they will be |
3249
|
|
|
|
|
|
|
# converted to spherical coordinates as though measured at that point, |
3250
|
|
|
|
|
|
|
# and returned in the same order. That is, if you supplied X, Y, and Z |
3251
|
|
|
|
|
|
|
# velocities, you will get back Phi, Theta, and Rho velocities, in that |
3252
|
|
|
|
|
|
|
# order. |
3253
|
|
|
|
|
|
|
|
3254
|
|
|
|
|
|
|
sub _convert_cartesian_to_spherical { |
3255
|
24487
|
|
|
24487
|
|
49225
|
my @cart_data = @_; |
3256
|
|
|
|
|
|
|
@cart_data |
3257
|
24487
|
50
|
33
|
|
|
80874
|
and not @cart_data % 3 |
3258
|
|
|
|
|
|
|
or confess( 'Programming error - Want 3 or 6 arguments' ); |
3259
|
|
|
|
|
|
|
|
3260
|
|
|
|
|
|
|
# The first triplet is position. We extract it into its own array, |
3261
|
|
|
|
|
|
|
# then compute the corresponding spherical coordinates using the |
3262
|
|
|
|
|
|
|
# definition of the coordinate system. |
3263
|
|
|
|
|
|
|
|
3264
|
24487
|
|
|
|
|
47601
|
my @cart_pos = splice @cart_data, 0, 3; |
3265
|
24487
|
|
|
|
|
61998
|
my $range = vector_magnitude( \@cart_pos ); |
3266
|
24487
|
100
|
66
|
|
|
101063
|
my $azimuth = ( $cart_pos[0] || $cart_pos[1] ) ? |
3267
|
|
|
|
|
|
|
mod2pi( atan2 $cart_pos[1], $cart_pos[0] ) : |
3268
|
|
|
|
|
|
|
0; |
3269
|
24487
|
100
|
|
|
|
69154
|
my $elevation = $range ? asin( $cart_pos[2] / $range ) : 0; |
3270
|
|
|
|
|
|
|
|
3271
|
|
|
|
|
|
|
# Accumulate results. |
3272
|
|
|
|
|
|
|
|
3273
|
24487
|
|
|
|
|
46439
|
my @rslt = ( $azimuth, $elevation, $range ); |
3274
|
|
|
|
|
|
|
|
3275
|
|
|
|
|
|
|
# If we have velocity (accelelation, etc) components |
3276
|
|
|
|
|
|
|
|
3277
|
24487
|
100
|
|
|
|
46855
|
if ( @cart_data ) { |
3278
|
|
|
|
|
|
|
|
3279
|
|
|
|
|
|
|
# We compute unit vectors in the spherical coordinate system. |
3280
|
|
|
|
|
|
|
# |
3281
|
|
|
|
|
|
|
# The "Relationships among Unit Vectors" at |
3282
|
|
|
|
|
|
|
# http://plaza.obu.edu/corneliusk/mp/rauv.pdf (and retained in |
3283
|
|
|
|
|
|
|
# the ref/ directory) gives the transformation both ways. With |
3284
|
|
|
|
|
|
|
# x, y, and z being the Cartesian unit vectors, Theta and Phi |
3285
|
|
|
|
|
|
|
# being the elevation (in the range 0 to pi, 0 being along the + |
3286
|
|
|
|
|
|
|
# Z axis) and azimuth (X toward Y, i.e. right-handed), and r, |
3287
|
|
|
|
|
|
|
# theta, and phi being the corresponding unit vectors: |
3288
|
|
|
|
|
|
|
# |
3289
|
|
|
|
|
|
|
# r = sin Theta cos Phi x + sin Theta sin Phi y + cos Theta z |
3290
|
|
|
|
|
|
|
# theta = cos Theta cos Phi x + cos Theta sin Phi y - sin Theta z |
3291
|
|
|
|
|
|
|
# phi = - sin Phi x + cos phi y |
3292
|
|
|
|
|
|
|
# |
3293
|
|
|
|
|
|
|
# and |
3294
|
|
|
|
|
|
|
# |
3295
|
|
|
|
|
|
|
# x = sin Theta cos Phi r + cos Theta cos Phi theta - sin Phi phi |
3296
|
|
|
|
|
|
|
# y = sin Theta sin Phi r + cos Theta sin Phi theta - cos Phi phi |
3297
|
|
|
|
|
|
|
# z = cos Theta r - sin Theta theta |
3298
|
|
|
|
|
|
|
# |
3299
|
|
|
|
|
|
|
# It looks to me like I get the Theta convention I'm using by |
3300
|
|
|
|
|
|
|
# replacing sin Theta with cos Theta and cos Theta by sin Theta |
3301
|
|
|
|
|
|
|
# (because Dr. Cornelius takes 0 as the positive Z axis whereas |
3302
|
|
|
|
|
|
|
# I take zero as the X-Y plane) and changing the sign of theta |
3303
|
|
|
|
|
|
|
# (since Dr. Cornelius' Theta increases in the negative Z |
3304
|
|
|
|
|
|
|
# direction, whereas mine increases in the positive Z |
3305
|
|
|
|
|
|
|
# direction). |
3306
|
|
|
|
|
|
|
# |
3307
|
|
|
|
|
|
|
# The document was found at http://plaza.obu.edu/corneliusk/ |
3308
|
|
|
|
|
|
|
# which is the page for Dr. Kevin Cornelius' Mathematical |
3309
|
|
|
|
|
|
|
# Physics (PHYS 4053) course at Ouachita Baptist University in |
3310
|
|
|
|
|
|
|
# Arkadelphia AR. |
3311
|
|
|
|
|
|
|
|
3312
|
16898
|
|
|
|
|
26515
|
my $diag = sqrt( $cart_pos[0] * $cart_pos[0] + |
3313
|
|
|
|
|
|
|
$cart_pos[1] * $cart_pos[1] ); |
3314
|
16898
|
|
|
|
|
24313
|
my ( $sin_theta, $cos_theta, $sin_phi, $cos_phi ); |
3315
|
16898
|
50
|
|
|
|
28733
|
if ( $range > 0 ) { |
3316
|
16898
|
|
|
|
|
25395
|
$sin_theta = $cart_pos[2] / $range; |
3317
|
16898
|
|
|
|
|
22493
|
$cos_theta = $diag / $range; |
3318
|
16898
|
50
|
|
|
|
27275
|
if ( $diag > 0 ) { |
3319
|
16898
|
|
|
|
|
20569
|
$sin_phi = $cart_pos[1] / $diag; |
3320
|
16898
|
|
|
|
|
22539
|
$cos_phi = $cart_pos[0] / $diag; |
3321
|
|
|
|
|
|
|
} else { |
3322
|
0
|
|
|
|
|
0
|
$sin_phi = 0; |
3323
|
0
|
|
|
|
|
0
|
$cos_phi = 1; |
3324
|
|
|
|
|
|
|
} |
3325
|
|
|
|
|
|
|
|
3326
|
|
|
|
|
|
|
# phi = - sin Phi x + cos phi y |
3327
|
|
|
|
|
|
|
# theta = - sin Theta cos Phi x - sin Theta sin Phi y + cos Theta z |
3328
|
|
|
|
|
|
|
# r = cos Theta cos Phi x + cos Theta sin Phi y + sin Theta z |
3329
|
|
|
|
|
|
|
|
3330
|
16898
|
|
|
|
|
39409
|
my $az_hat = [ - $sin_phi, $cos_phi, 0 ]; |
3331
|
16898
|
|
|
|
|
35563
|
my $el_hat = [ - $sin_theta * $cos_phi, - $sin_theta * $sin_phi, |
3332
|
|
|
|
|
|
|
$cos_theta ]; |
3333
|
16898
|
|
|
|
|
44137
|
my $rng_hat = [ $cos_theta * $cos_phi, $cos_theta * $sin_phi, |
3334
|
|
|
|
|
|
|
$sin_theta ]; |
3335
|
|
|
|
|
|
|
|
3336
|
16898
|
|
|
|
|
34494
|
while ( @cart_data ) { |
3337
|
|
|
|
|
|
|
|
3338
|
|
|
|
|
|
|
# Each triplet is then converted by projecting the |
3339
|
|
|
|
|
|
|
# Cartesian vector onto the appropriate unit vector. |
3340
|
|
|
|
|
|
|
# Azimuth and elevation are also converted to radians by |
3341
|
|
|
|
|
|
|
# dividing by the range. NOTE that this is the |
3342
|
|
|
|
|
|
|
# small-angle approximation, but since we assume we're |
3343
|
|
|
|
|
|
|
# dealing with derivitaves, it's OK. |
3344
|
|
|
|
|
|
|
|
3345
|
16898
|
|
|
|
|
28120
|
my @cart_info = splice @cart_data, 0, 3; |
3346
|
16898
|
|
|
|
|
35636
|
push @rslt, vector_dot_product( $az_hat, \@cart_info ) / $range; |
3347
|
16898
|
|
|
|
|
34084
|
push @rslt, vector_dot_product( $el_hat, \@cart_info ) / $range; |
3348
|
16898
|
|
|
|
|
32654
|
push @rslt, vector_dot_product( $rng_hat, \@cart_info ); |
3349
|
|
|
|
|
|
|
} |
3350
|
|
|
|
|
|
|
} else { |
3351
|
|
|
|
|
|
|
# $sin_theta = $sin_phi = 0; |
3352
|
|
|
|
|
|
|
# $cos_theta = $cos_phi = 1; |
3353
|
|
|
|
|
|
|
# We used to do the above and then drop through and do the |
3354
|
|
|
|
|
|
|
# velocity calculation if needed. But in this case the |
3355
|
|
|
|
|
|
|
# velocity calulation blows up. So for the moment we are |
3356
|
|
|
|
|
|
|
# just going to punt. |
3357
|
|
|
|
|
|
|
} |
3358
|
|
|
|
|
|
|
|
3359
|
|
|
|
|
|
|
} |
3360
|
|
|
|
|
|
|
|
3361
|
24487
|
|
|
|
|
72461
|
return @rslt; |
3362
|
|
|
|
|
|
|
|
3363
|
|
|
|
|
|
|
} |
3364
|
|
|
|
|
|
|
|
3365
|
|
|
|
|
|
|
# @spherical = _convert_cartesian_to_spherical_x( @cartesian ) |
3366
|
|
|
|
|
|
|
# |
3367
|
|
|
|
|
|
|
# This subroutine is a convenience wrapper for |
3368
|
|
|
|
|
|
|
# _convert_cartesian_to_spherical(). The only difference is that the |
3369
|
|
|
|
|
|
|
# return is Theta (perpendicular to the X-Y plane), Phi (in the X-Y |
3370
|
|
|
|
|
|
|
# plane) and Rho, rather than Phi, Theta, Rho. This subroutine is my |
3371
|
|
|
|
|
|
|
# penance for not having all the methods that involve spherical |
3372
|
|
|
|
|
|
|
# coordinates return their arguments in the same order. |
3373
|
|
|
|
|
|
|
|
3374
|
|
|
|
|
|
|
sub _convert_cartesian_to_spherical_x { |
3375
|
1362
|
|
|
1362
|
|
2958
|
my @sph_data = _convert_cartesian_to_spherical( @_ ); |
3376
|
1362
|
|
|
|
|
3285
|
for ( my $inx = 0; $inx < @sph_data; $inx += 3 ) { |
3377
|
1362
|
|
|
|
|
3868
|
@sph_data[ $inx, $inx + 1 ] = @sph_data[ $inx + 1, $inx ]; |
3378
|
|
|
|
|
|
|
} |
3379
|
1362
|
|
|
|
|
7266
|
return @sph_data; |
3380
|
|
|
|
|
|
|
} |
3381
|
|
|
|
|
|
|
|
3382
|
|
|
|
|
|
|
# @eci = $self->_convert_ecef_to_eci () |
3383
|
|
|
|
|
|
|
|
3384
|
|
|
|
|
|
|
# This subroutine converts the object's ECEF setting to ECI, and |
3385
|
|
|
|
|
|
|
# both caches and returns the result. |
3386
|
|
|
|
|
|
|
|
3387
|
|
|
|
|
|
|
sub _convert_ecef_to_eci { |
3388
|
7
|
|
|
7
|
|
14
|
my $self = shift; |
3389
|
7
|
|
|
|
|
14
|
my $thetag = thetag ($self->universal); |
3390
|
7
|
|
|
|
|
22
|
my @data = $self->ecef (); |
3391
|
7
|
50
|
|
|
|
19
|
$self->{debug} and print <
|
3392
|
|
|
|
|
|
|
Debug eci - thetag = $thetag |
3393
|
|
|
|
|
|
|
(x, y) = (@data[0, 1]) |
3394
|
|
|
|
|
|
|
eod |
3395
|
7
|
|
|
|
|
19
|
my $costh = cos ($thetag); |
3396
|
7
|
|
|
|
|
12
|
my $sinth = sin ($thetag); |
3397
|
7
|
|
|
|
|
21
|
@data[0, 1] = ($data[0] * $costh - $data[1] * $sinth, |
3398
|
|
|
|
|
|
|
$data[0] * $sinth + $data[1] * $costh); |
3399
|
7
|
50
|
|
|
|
18
|
$self->{debug} and print <
|
3400
|
|
|
|
|
|
|
Debug eci - after rotation, |
3401
|
|
|
|
|
|
|
(x, y) = (@data[0, 1]) |
3402
|
|
|
|
|
|
|
eod |
3403
|
7
|
50
|
|
|
|
38
|
if ( @data > 5 ) { |
3404
|
|
|
|
|
|
|
|
3405
|
7
|
|
|
|
|
32
|
@data[3, 4] = ($data[3] * $costh - $data[4] * $sinth, |
3406
|
|
|
|
|
|
|
$data[3] * $sinth + $data[4] * $costh); |
3407
|
7
|
|
|
|
|
12
|
$data[3] -= $data[1] * $self->{angularvelocity}; |
3408
|
7
|
|
|
|
|
16
|
$data[4] += $data[0] * $self->{angularvelocity}; |
3409
|
|
|
|
|
|
|
} |
3410
|
|
|
|
|
|
|
|
3411
|
|
|
|
|
|
|
# A bunch of digging says this was added by Subversion commit 697, |
3412
|
|
|
|
|
|
|
# which was first released with Astro::Coord::ECI version 0.014. The |
3413
|
|
|
|
|
|
|
# comment says "Handle equinox in conversion between eci and ecef. |
3414
|
|
|
|
|
|
|
# Correctly, I hope." I'm leaving it in for now, but ... |
3415
|
7
|
|
|
|
|
18
|
$self->set (equinox_dynamical => $self->dynamical); |
3416
|
7
|
|
|
|
|
9
|
return @{$self->{_ECI_cache}{inertial}{eci} = \@data}; |
|
7
|
|
|
|
|
55
|
|
3417
|
|
|
|
|
|
|
} |
3418
|
|
|
|
|
|
|
|
3419
|
|
|
|
|
|
|
# This subroutine converts the object's ECI setting to ECEF, and |
3420
|
|
|
|
|
|
|
# both caches and returns the result. |
3421
|
|
|
|
|
|
|
|
3422
|
|
|
|
|
|
|
our $EQUINOX_TOLERANCE = 365 * SECSPERDAY; |
3423
|
|
|
|
|
|
|
|
3424
|
|
|
|
|
|
|
sub _convert_eci_to_ecef { |
3425
|
29921
|
|
|
29921
|
|
41246
|
my $self = shift; |
3426
|
29921
|
|
|
|
|
47272
|
my $thetag = thetag ($self->universal); |
3427
|
|
|
|
|
|
|
|
3428
|
29921
|
|
|
|
|
62075
|
my $dyn = $self->dynamical; |
3429
|
|
|
|
|
|
|
## my $equi = $self->get ('equinox_dynamical') || do { |
3430
|
|
|
|
|
|
|
## $self->set (equinox_dynamical => $dyn); $dyn}; |
3431
|
29921
|
|
66
|
|
|
65425
|
my $equi = $self->{equinox_dynamical} ||= $dyn; |
3432
|
29921
|
50
|
|
|
|
70401
|
if (abs ($equi - $dyn) > $EQUINOX_TOLERANCE) { |
3433
|
0
|
|
|
|
|
0
|
$self->precess_dynamical ($dyn); |
3434
|
|
|
|
|
|
|
} |
3435
|
|
|
|
|
|
|
|
3436
|
29921
|
|
|
|
|
55211
|
my @ecef = $self->eci (); |
3437
|
29921
|
|
|
|
|
62184
|
my $costh = cos (- $thetag); |
3438
|
29921
|
|
|
|
|
47268
|
my $sinth = sin (- $thetag); |
3439
|
29921
|
|
|
|
|
68589
|
@ecef[0, 1] = ($ecef[0] * $costh - $ecef[1] * $sinth, |
3440
|
|
|
|
|
|
|
$ecef[0] * $sinth + $ecef[1] * $costh); |
3441
|
|
|
|
|
|
|
|
3442
|
29921
|
100
|
|
|
|
58881
|
if ( @ecef > 3 ) { |
3443
|
18309
|
|
|
|
|
32047
|
@ecef[ 3, 4 ] = ( $ecef[3] * $costh - $ecef[4] * $sinth, |
3444
|
|
|
|
|
|
|
$ecef[3] * $sinth + $ecef[4] * $costh ); |
3445
|
18309
|
|
|
|
|
30003
|
$ecef[3] += $ecef[1] * $self->{angularvelocity}; |
3446
|
18309
|
|
|
|
|
24696
|
$ecef[4] -= $ecef[0] * $self->{angularvelocity}; |
3447
|
|
|
|
|
|
|
} |
3448
|
|
|
|
|
|
|
|
3449
|
29921
|
|
|
|
|
60280
|
$self->{_ECI_cache}{fixed}{ecef} = \@ecef; |
3450
|
|
|
|
|
|
|
|
3451
|
|
|
|
|
|
|
defined wantarray |
3452
|
29921
|
50
|
|
|
|
58176
|
or return; |
3453
|
29921
|
|
|
|
|
80929
|
return @ecef; |
3454
|
|
|
|
|
|
|
} |
3455
|
|
|
|
|
|
|
|
3456
|
|
|
|
|
|
|
# my @args = _expand_args_default_station( @_ ) |
3457
|
|
|
|
|
|
|
# |
3458
|
|
|
|
|
|
|
# This subroutine handles the placing of the contents of the |
3459
|
|
|
|
|
|
|
# 'station' attribute into the argument list of methods that, |
3460
|
|
|
|
|
|
|
# prior to the introduction of the 'station' attribute, formerly |
3461
|
|
|
|
|
|
|
# took two Astro::Coord::ECI objects and computed the position of |
3462
|
|
|
|
|
|
|
# the second as seen from the first. |
3463
|
|
|
|
|
|
|
|
3464
|
|
|
|
|
|
|
sub _expand_args_default_station { |
3465
|
39403
|
|
|
39403
|
|
69113
|
my @args = @_; |
3466
|
39403
|
100
|
|
|
|
83229
|
if ( ! embodies( $args[1], 'Astro::Coord::ECI' ) ) { |
3467
|
17
|
|
|
|
|
57
|
unshift @args, $args[0]->get( 'station' ); |
3468
|
17
|
50
|
|
|
|
55
|
embodies( $args[0], 'Astro::Coord::ECI' ) |
3469
|
|
|
|
|
|
|
or croak 'Station not set'; |
3470
|
|
|
|
|
|
|
defined $args[1]->{universal} |
3471
|
17
|
50
|
|
|
|
64
|
and $args[0]->universal( $args[1]->universal() ); |
3472
|
|
|
|
|
|
|
} |
3473
|
39403
|
|
|
|
|
92422
|
return @args; |
3474
|
|
|
|
|
|
|
} |
3475
|
|
|
|
|
|
|
|
3476
|
|
|
|
|
|
|
# $value = $self->__initial_inertial |
3477
|
|
|
|
|
|
|
# |
3478
|
|
|
|
|
|
|
# Return the initial setting of the inertial attribute. At this |
3479
|
|
|
|
|
|
|
# level we are assumed not inertial until we acquire a position. |
3480
|
|
|
|
|
|
|
# This is not part of the public interface, but may be used by |
3481
|
|
|
|
|
|
|
# subclasses to set an initial value for this read-only attribute. |
3482
|
|
|
|
|
|
|
# Setting the coordinates explicitly will still set the {inertial} |
3483
|
|
|
|
|
|
|
# attribute appropriately. |
3484
|
|
|
|
|
|
|
|
3485
|
59
|
|
|
59
|
|
155
|
sub __initial_inertial{ return }; |
3486
|
|
|
|
|
|
|
|
3487
|
|
|
|
|
|
|
# $value = _local_mean_delta ($coord) |
3488
|
|
|
|
|
|
|
|
3489
|
|
|
|
|
|
|
# Calculate the delta from universal to civil time for the object. |
3490
|
|
|
|
|
|
|
# An exception is raised if the coordinates of the object have not |
3491
|
|
|
|
|
|
|
# been set. |
3492
|
|
|
|
|
|
|
|
3493
|
|
|
|
|
|
|
sub _local_mean_delta { |
3494
|
2
|
|
|
2
|
|
7
|
return ($_[0]->geodetic ())[1] * SECSPERDAY / TWOPI; |
3495
|
|
|
|
|
|
|
} |
3496
|
|
|
|
|
|
|
|
3497
|
|
|
|
|
|
|
=begin comment |
3498
|
|
|
|
|
|
|
|
3499
|
|
|
|
|
|
|
# $string = _rad2dms ($angle) |
3500
|
|
|
|
|
|
|
|
3501
|
|
|
|
|
|
|
# Convert radians to degrees, minutes, and seconds of arc. |
3502
|
|
|
|
|
|
|
# Used for debugging. |
3503
|
|
|
|
|
|
|
|
3504
|
|
|
|
|
|
|
sub _rad2dms { |
3505
|
|
|
|
|
|
|
my $angle = rad2deg (shift); |
3506
|
|
|
|
|
|
|
my $deg = floor ($angle); |
3507
|
|
|
|
|
|
|
$angle = ($angle - $deg) * 60; |
3508
|
|
|
|
|
|
|
my $min = floor ($angle); |
3509
|
|
|
|
|
|
|
$angle = ($angle - $min) * 60; |
3510
|
|
|
|
|
|
|
return "$deg degrees $min minutes $angle seconds of arc"; |
3511
|
|
|
|
|
|
|
} |
3512
|
|
|
|
|
|
|
|
3513
|
|
|
|
|
|
|
=end comment |
3514
|
|
|
|
|
|
|
|
3515
|
|
|
|
|
|
|
=cut |
3516
|
|
|
|
|
|
|
|
3517
|
|
|
|
|
|
|
####################################################################### |
3518
|
|
|
|
|
|
|
# |
3519
|
|
|
|
|
|
|
# Package initialization |
3520
|
|
|
|
|
|
|
# |
3521
|
|
|
|
|
|
|
|
3522
|
|
|
|
|
|
|
__PACKAGE__->set (ellipsoid => 'WGS84'); |
3523
|
|
|
|
|
|
|
|
3524
|
|
|
|
|
|
|
%savatr = map {$_ => 1} (keys %static, qw{dynamical id name universal}); |
3525
|
|
|
|
|
|
|
# Note that local_mean_time does not get preserved, because it |
3526
|
|
|
|
|
|
|
# changes if the coordinates change. |
3527
|
|
|
|
|
|
|
|
3528
|
|
|
|
|
|
|
1; |
3529
|
|
|
|
|
|
|
|
3530
|
|
|
|
|
|
|
__END__ |