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