File Coverage

blib/lib/Astro/Coords/Elements.pm
Criterion Covered Total %
statement 101 122 82.7
branch 29 56 51.7
condition 17 39 43.5
subroutine 16 20 80.0
pod 11 11 100.0
total 174 248 70.1


line stmt bran cond sub pod time code
1             package Astro::Coords::Elements;
2              
3              
4             =head1 NAME
5              
6             Astro::Coords::Elements - Specify astronomical coordinates using orbital elements
7              
8             =head1 SYNOPSIS
9              
10             $c = new Astro::Coords::Elements( elements => \%elements );
11              
12             =head1 DESCRIPTION
13              
14             This class is used by C<Astro::Coords> for handling coordinates
15             specified as orbital elements.
16              
17             =cut
18              
19 19     19   9138542 use 5.006;
  19         82  
20 19     19   122 use strict;
  19         43  
  19         441  
21 19     19   97 use warnings;
  19         83  
  19         579  
22 19     19   131 use Carp;
  19         48  
  19         1869  
23              
24             our $VERSION = '0.21';
25              
26             # Need working palPlante
27 19     19   676 use Astro::PAL 0.95 ();
  19         4871  
  19         534  
28 19     19   614 use Astro::Coords::Angle;
  19         60  
  19         580  
29 19     19   7736 use Time::Piece qw/ :override /;
  19         141925  
  19         135  
30              
31 19     19   1857 use base qw/ Astro::Coords /;
  19         57  
  19         2828  
32              
33 19     19   126 use overload '""' => "stringify";
  19         65  
  19         158  
34              
35             =head1 METHODS
36              
37              
38             =head2 Constructor
39              
40             =over 4
41              
42             =item B<new>
43              
44             Instantiate a new object using the supplied options.
45              
46             $c = new Astro::Coords::Elements( elements => \%elements );
47             $c = new Astro::Coords::Elements( elements => \@array );
48              
49             Returns undef on error.
50              
51             The elements can be specified either by using a reference to an array
52             returned by the C<array()> method of another elements object or in a
53             reference to a hash containing the following keys:
54              
55             suitable for the major planets:
56              
57             EPOCH = epoch of elements t0 (TT MJD)
58             ORBINC = inclination i (radians)
59             ANODE = longitude of the ascending node [$\Omega$] (radians)
60             PERIH = longitude of perihelion [$\varpi$] (radians)
61             AORQ = mean distance a (AU)
62             E = eccentricity e
63             AORL = mean longitude L (radians)
64             DM = daily motion n (radians)
65              
66             suitable for minor planets:
67              
68              
69             EPOCH = epoch of elements t0 (TT MJD)
70             ORBINC = inclination i (radians)
71             ANODE = longitude of the ascending node [$\Omega$] (radians)
72             PERIH = argument of perihelion [$\omega$] (radians)
73             AORQ = mean distance a (AU)
74             E = eccentricity e
75             AORL = mean anomaly M (radians)
76              
77             suitable for comets:
78              
79              
80             EPOCH = epoch of elements t0 (TT MJD)
81             ORBINC = inclination i (radians)
82             ANODE = longitude of the ascending node [$\Omega$] (radians)
83             PERIH = argument of perihelion [$\omega$] (radians)
84             AORQ = perihelion distance q (AU)
85             E = eccentricity e
86             EPOCHPERIH = epoch of perihelion T (TT MJD)
87              
88             See the documentation to palPlante() and palPertel() for more information.
89             Keys must be upper case.
90              
91             For comets if the only one epoch is specified it is assumed that the
92             epochs are identical. This may cause problems if the epochs are not
93             really close to each other.
94              
95             In order to better match normal usage, EPOCH can also be specified
96             as a string of the form 'YYYY mmm D.frac' (e.g. '1997 Apr 1.567').
97             (no decimal place after the month). This is the format used by JPL.
98              
99             =cut
100              
101             sub new {
102 5     5 1 13 my $proto = shift;
103 5   33     22 my $class = ref($proto) || $proto;
104              
105 5         20 my %opts = @_;
106              
107             # "elements" key must be defined and point to a reference
108 5 50 33     31 return undef unless (exists $opts{elements} && ref($opts{elements}));
109              
110             # We allow use of an array reference or a hash
111             # The array ref is converted to a hash
112 5         14 my %elements;
113 5 100 33     39 if (ref($opts{elements}) eq 'HASH') {
    50          
114 4         11 %elements = %{ $opts{elements} };
  4         23  
115             } elsif (ref($opts{elements}) eq 'ARRAY' &&
116             $opts{elements}->[0] eq 'ELEMENTS') {
117              
118 1         4 my $i = 3;
119 1         4 for my $key (qw/ EPOCH ORBINC ANODE PERIH AORQ E AORL /) {
120 7         14 $elements{$key} = $opts{elements}->[$i];
121 7         11 $i++;
122             }
123             # assign EPOCHPERIH
124 1 50 33     30 if (!defined $elements{AORL} && defined $opts{elements}->[$i]) {
125 1         6 $elements{EPOCHPERIH} = $opts{elements}->[$i];
126             } else {
127 0         0 $elements{DM} = $opts{elements}->[$i];
128             }
129              
130             } else {
131 0         0 return undef;
132             }
133              
134             # Sanity check
135 5         22 for (qw/ ORBINC ANODE PERIH AORQ E/) {
136             #return undef unless exists $elements{$_};
137             croak "Must supply element $_ to constructor"
138 25 50       56 unless exists $elements{$_};
139             }
140              
141             # Fix up EPOCHs if it has been specified as a string
142 5         19 for my $key (qw/ EPOCH EPOCHPERIH / ) {
143 10 50       27 next unless exists $elements{$key};
144 10         21 my $epoch = $elements{$key};
145              
146             # if we are missing one of the EPOCHs that is okay
147             # so just skip
148 10 50       38 if (! defined $epoch) {
149             # and delete it from the hash as if it was never supplied
150             # this avoids complications later
151 0         0 delete $elements{$key};
152 0         0 next;
153             }
154              
155 10 100 100     117 if ($epoch =~ /^\d+\.\d+$/ || $epoch =~ /^\d+$/) {
    50          
156             # an MJD so do not modify
157             } elsif ($epoch =~ /\d\d\d\d \w\w\w \d+\.\d+/) {
158             # has letters in it so try to parse
159             # Split on decimal point
160 2         9 my ($date, $frac) = split(/\./,$epoch,2);
161 2         6 $frac = "0.". $frac; # preserve as decimal fraction
162 2         5 my $format = '%Y %b %d';
163             #print "EPOCH : $epoch and $date and $frac\n";
164 2         11 my $obj = Time::Piece->strptime($date, $format);
165 2         167 my $tzoffset = $obj->tzoffset;
166 2 50       42 $tzoffset = $tzoffset->seconds if defined $tzoffset;
167 2         51 $obj = gmtime($obj->epoch() + $tzoffset);
168              
169             # get the MJD and add on the fraction
170 2         120 my $mjd = $obj->mjd() + $frac;
171 2         83 $elements{$key} = $mjd;
172             #print "MJD: $mjd\n";
173              
174             } else {
175             # do not understand the format so return undef
176 0         0 warn "Unable to recognize format for elements $key [$epoch]";
177 0         0 return undef;
178             }
179              
180             # Convert JD to MJD
181 10 50       39 if ($elements{$key} > 2400000.5) {
182 0         0 $elements{$key} -= 2400000.5;
183             }
184              
185             }
186              
187             # but complain if we do not have one of them
188             croak "Must supply one of EPOCH or EPOCHPERIH - both were undefined"
189             if (!exists $elements{EPOCH} &&
190 5 0 33     37 !exists $elements{EPOCHPERIH});
191              
192             # create the object
193 5         42 bless { elements => \%elements, name => $opts{name} }, $class;
194              
195             }
196              
197              
198              
199             =back
200              
201             =head2 Accessor Methods
202              
203             =over 4
204              
205             =item B<elements>
206              
207             Returns the hash containing the elements.
208              
209             %el = $c->elements;
210              
211             =cut
212              
213             sub elements {
214 26     26 1 262 my $self = shift;
215 26         43 return %{ $self->{elements}};
  26         242  
216             }
217              
218             =back
219              
220             =head1 General Methods
221              
222             =over 4
223              
224             =item B<array>
225              
226             Return back 11 element array with first element containing the
227             string "ELEMENTS", the next two elements as undef and up to 8
228             following elements containing the orbital elements in the order
229             presented in the documentation of the constructor.
230              
231             This method returns a standardised set of elements across all
232             types of coordinates.
233              
234             Note that for JFORM=3 (Comet) case the epoch of perihelion
235             is stored as the 8th element (the epoch of the elements is still
236             returned as the first element) [corresponding to array index 10].
237             This usage of the final element can be determined by noting that
238             the element before it (AORL) will be undefined in the case of JFORM=3.
239             If AORL is defined then the Epoch of perihelion will not be written
240             even if it is defined.
241              
242             =cut
243              
244             sub array {
245 1     1 1 117 my $self = shift;
246 1         4 my %el = $self->elements;
247              
248             # use EPOCHPERIH if EPOCH is not defined
249 1         3 my $epoch = $el{EPOCH};
250 1 50       4 $epoch = $el{EPOCHPERIH} unless $epoch;
251              
252             # the 8th element can be the EPOCHPERIH or DM field
253             # dependent on the element type.
254 1         2 my $lastel;
255 1 50 33     11 if (defined $el{EPOCHPERIH} && !defined $el{DM}
      33        
256             && !defined $el{AORL}) {
257 1         3 $lastel = $el{EPOCHPERIH};
258             } else {
259 0         0 $lastel = $el{DM};
260             }
261              
262             return ( $self->type, undef, undef,
263             $epoch, $el{ORBINC}, $el{ANODE}, $el{PERIH},
264 1         4 $el{AORQ}, $el{E}, $el{AORL}, $lastel);
265             }
266              
267             =item B<type>
268              
269             Returns the generic type associated with the coordinate system.
270             For this class the answer is always "ELEMENTS".
271              
272             This is used to aid construction of summary tables when using
273             mixed coordinates.
274              
275             It could be done using isa relationships.
276              
277             =cut
278              
279             sub type {
280 3     3 1 30 return "ELEMENTS";
281             }
282              
283             =item B<stringify>
284              
285             Stringify overload. Returns comma-separated list of
286             the elements.
287              
288             =cut
289              
290             sub stringify {
291 4     4 1 439 my $self = shift;
292 4         14 my %el = $self->elements;
293 4 100       18 my $str = join(",", map { (defined $_ ? $_ : 'UNDEF' ) } values %el);
  29         117  
294 4         18 return $str;
295             }
296              
297             =item B<summary>
298              
299             Return a one line summary of the coordinates.
300             In the future will accept arguments to control output.
301              
302             $summary = $c->summary();
303              
304             =cut
305              
306             sub summary {
307 0     0 1 0 my $self = shift;
308 0         0 my $name = $self->name;
309 0 0       0 $name = '' unless defined $name;
310 0         0 return sprintf("%-16s %-12s %-11s ELEMENTS",$name,'','');
311             }
312              
313             =item B<apparent>
314              
315             Return the apparent RA and Dec (as two C<Astro::Coords::Angle>
316             objects) for the current coordinates and time. Includes perterbation
317             corrections to convert the elements to the required epoch.
318              
319             Returns empty list on error.
320              
321             =cut
322              
323             sub apparent {
324 23     23 1 38 my $self = shift;
325              
326 23         91 my ($ra_app,$dec_app) = $self->_cache_read( "RA_APP", "DEC_APP" );
327              
328             # not in cache so must calculate it
329 23 100 66     100 if (!defined $ra_app || !defined $dec_app) {
330              
331 20         56 my $tel = $self->telescope;
332 20 100       95 my $long = (defined $tel ? $tel->long : 0.0 );
333 20 100       177 my $lat = (defined $tel ? $tel->lat : 0.0 );
334 20         157 my %el = $self->elements;
335 20         50 my $jform;
336 20 50 33     96 if (exists $el{DM} and defined $el{DM}) {
    50 66        
337             # major planets
338 0         0 $jform = 1;
339             } elsif (exists $el{AORL} and defined $el{AORL}) {
340             # minor planets
341 0         0 $jform = 2;
342 0         0 $el{DM} = 0;
343             } else {
344             # comets
345 20         34 $jform = 3;
346 20         41 $el{DM} = 0;
347 20         36 $el{AORL} = 0;
348             }
349              
350             # synch epoch if need be
351 20 50 33     110 if (!exists $el{EPOCH} || !exists $el{EPOCHPERIH}) {
352 0 0       0 if (exists $el{EPOCH}) {
353 0         0 $el{EPOCHPERIH} = $el{EPOCH};
354             } else {
355 0         0 $el{EPOCH} = $el{EPOCHPERIH};
356             }
357             }
358              
359             # First have to perturb the elements to the current epoch
360             # if we have a minor planet or comet
361 20 50 33     86 if ( $jform == 2 || $jform == 3) {
362             # for now we do not have enough information for jform=3
363             # so just assume the EPOCH is the same
364             # use Data::Dumper;
365             # print "Before perturbing: ". Dumper(\%el);
366             # print "MJD ref : " . $self->_mjd_tt . " and jform = $jform\n";
367             ($el{EPOCH},$el{ORBINC}, $el{ANODE},
368             $el{PERIH},$el{AORQ},$el{E},$el{AORL},
369             my $jstat) = Astro::PAL::palPertel($jform,$el{EPOCH},$self->_mjd_tt,
370             $el{EPOCHPERIH},$el{ORBINC},$el{ANODE},
371 20         78 $el{PERIH},$el{AORQ},$el{E},$el{AORL} );
372              
373             # print "After perturbing: " .Dumper(\%el);
374 20 0       119 croak "Error perturbing elements for target ".
    50          
375             (defined $self->name ? $self->name : '' )
376             ." [status=$jstat]"
377             if $jstat != 0;
378             }
379              
380              
381             # Print out the values
382             #print "EPOCH: $el{EPOCH}\n";
383             #print "ORBINC: ". ($el{ORBINC}*Astro::PAL::DR2D) . "\n";
384             #print "ANODE: ". ($el{ANODE}*Astro::PAL::DR2D) . "\n";
385             #print "PERIH : ". ($el{PERIH}*Astro::PAL::DR2D) . "\n";
386             #print "AORQ: $el{AORQ}\n";
387             #print "E: $el{E}\n";
388              
389             my ($ra, $dec, $dist, $j) = Astro::PAL::palPlante($self->_mjd_tt, $long, $lat, $jform,
390             $el{EPOCH}, $el{ORBINC}, $el{ANODE}, $el{PERIH},
391 20         73 $el{AORQ}, $el{E}, $el{AORL}, $el{DM} );
392              
393 20 0       97 croak "Error determining apparent RA/Dec for target ".
    50          
394             (defined $self->name ? $self->name : '' )
395             ."[status=$j]" if $j != 0;
396              
397             # Convert to angle object
398 20         85 $ra_app = new Astro::Coords::Angle::Hour($ra, units => 'rad', range => '2PI');
399 20         77 $dec_app = new Astro::Coords::Angle($dec, units => 'rad');
400              
401             # Store in cache
402 20         75 $self->_cache_write( "RA_APP" => $ra_app, "DEC_APP" => $dec_app );
403             }
404              
405 23         65 return( $ra_app, $dec_app );
406             }
407              
408             =item B<rv>
409              
410             Radial velocity of the planet relative to the Earth geocentre.
411              
412             =cut
413              
414             sub rv {
415 0     0 1 0 croak "Not yet implemented element radial velocities";
416             }
417              
418             =item B<vdefn>
419              
420             Velocity definition. Always 'RADIO'.
421              
422             =cut
423              
424             sub vdefn {
425 0     0 1 0 return 'RADIO';
426             }
427              
428             =item B<vframe>
429              
430             Velocity reference frame. Always 'GEO'.
431              
432             =cut
433              
434             sub vframe {
435 0     0 1 0 return 'GEO';
436             }
437              
438             =item B<apply_offset>
439              
440             Overrided method to warn if C<Astro::Coords::apply_offset> is
441             called on this subclass.
442              
443             =cut
444              
445             sub apply_offset {
446 1     1 1 34 my $self = shift;
447 1         11 warn "apply_offset: applying offset to orbital elements position for a specific time.\n";
448 1         26 return $self->SUPER::apply_offset(@_);
449             }
450              
451             =back
452              
453             =head1 NOTES
454              
455             Usually called via C<Astro::Coords>.
456              
457             =head1 LINKS
458              
459             Useful sources of orbital elements can be found at
460             http://ssd.jpl.nasa.gov and http://cfa-www.harvard.edu/iau/Ephemerides/
461              
462             =head1 REQUIREMENTS
463              
464             C<Astro::PAL> is used for all internal astrometric calculations.
465              
466             =head1 AUTHOR
467              
468             Tim Jenness E<lt>tjenness@cpan.orgE<gt>
469              
470             =head1 COPYRIGHT
471              
472             Copyright (C) 2001-2005 Particle Physics and Astronomy Research Council.
473             All Rights Reserved.
474              
475             This program is free software; you can redistribute it and/or modify it under
476             the terms of the GNU General Public License as published by the Free Software
477             Foundation; either version 3 of the License, or (at your option) any later
478             version.
479              
480             This program is distributed in the hope that it will be useful,but WITHOUT ANY
481             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
482             PARTICULAR PURPOSE. See the GNU General Public License for more details.
483              
484             You should have received a copy of the GNU General Public License along with
485             this program; if not, write to the Free Software Foundation, Inc., 59 Temple
486             Place,Suite 330, Boston, MA 02111-1307, USA
487              
488             =cut
489              
490             1;