File Coverage

blib/lib/Astro/Coords.pm
Criterion Covered Total %
statement 806 903 89.2
branch 308 442 69.6
condition 92 139 66.1
subroutine 103 115 89.5
pod 57 57 100.0
total 1366 1656 82.4


line stmt bran cond sub pod time code
1             package Astro::Coords;
2              
3             =head1 NAME
4              
5             Astro::Coords - Class for handling astronomical coordinates
6              
7             =head1 SYNOPSIS
8              
9             use Astro::Coords;
10              
11             $c = new Astro::Coords( name => "My target",
12             ra => '05:22:56',
13             dec => '-26:20:40.4',
14             type => 'B1950'
15             units=> 'sexagesimal');
16              
17             $c = new Astro::Coords( long => '05:22:56',
18             lat => '-26:20:40.4',
19             type => 'galactic');
20              
21             $c = new Astro::Coords( planet => 'mars' );
22              
23             $c = new Astro::Coords( elements => \%elements );
24              
25             $c = new Astro::Coords( az => 345, el => 45 );
26              
27             # Associate with an observer location
28             $c->telescope( new Astro::Telescope( 'JCMT' ));
29              
30             # ...and a reference epoch for all calculations
31             $date = Time::Piece->strptime($string, $format);
32             $c->datetime( $date );
33              
34             # or use DateTime
35             $date = DateTime->from_epoch( epoch => $epoch, time_zone => 'UTC' );
36             $c->datetime( $date );
37              
38             # Return coordinates J2000, for the epoch stored in the datetime
39             # object. This will work for all variants.
40             ($ra, $dec) = $c->radec();
41             $radians = $ra->radians;
42              
43             # or individually
44             $ra = $c->ra(); # returns Astro::Coords::Angle::Hour object
45             $dec = $c->dec( format => 'deg' );
46              
47             # Return coordinates J2000, epoch 2000.0
48             $ra = $c->ra2000();
49             $dec = $c->dec2000();
50              
51             # Return coordinats apparent, reference epoch, from location
52             # In sexagesimal format.
53             ($ra_app, $dec_app) = $c->apparent;
54             $ra_app = $c->ra_app( format => 's');
55             $dec_app = $c->dec_app( format => 's' );
56              
57             # Azimuth and elevation for reference epoch from observer location
58             ($az, $el) = $c->azel;
59             my $az = $c->az;
60             my $el = $c->el;
61              
62             # obtain summary string of object
63             $summary = "$c";
64              
65             # Obtain full summary as an array
66             @summary = $c->array;
67              
68             # See if the target is observable for the current time
69             # and telescope
70             $obs = 1 if $c->isObservable;
71              
72             # Calculate distance to another coordinate (in radians)
73             $distance = $c->distance( $c2 );
74              
75             # Calculate the rise and set time of the source
76             $tr = $c->rise_time;
77             $ts = $c->set_time;
78              
79             # transit elevation
80             $trans = $c->transit_el;
81              
82             # transit time
83             $mtime = $c->meridian_time();
84              
85              
86             =head1 DESCRIPTION
87              
88             Class for manipulating and transforming astronomical coordinates.
89             Can handle the following coordinate types:
90              
91             + Equatorial RA/Dec, galactic (including proper motions and parallax)
92             + Planets
93             + Comets/Asteroids
94             + Fixed locations in azimuth and elevations
95             + interpolated apparent coordinates
96              
97             For time dependent calculations a telescope location and reference
98             time must be provided. See C<Astro::Telescope> and C<DateTime> for
99             details on specifying location and reference epoch.
100              
101             =cut
102              
103 19     19   893134 use 5.006;
  19         111  
104 19     19   142 use strict;
  19         77  
  19         463  
105 19     19   102 use warnings;
  19         57  
  19         687  
106 19     19   116 use warnings::register;
  19         41  
  19         3208  
107 19     19   128 use Carp;
  19         36  
  19         1560  
108 19     19   125 use vars qw/ $DEBUG /;
  19         38  
  19         1633  
109             $DEBUG = 0;
110              
111             our $VERSION = '0.21';
112              
113 19     19   11095 use Math::Trig qw/ acos /;
  19         275992  
  19         1504  
114 19     19   7693 use Astro::PAL ();
  19         43144  
  19         513  
115 19     19   8376 use Astro::Coords::Angle;
  19         46  
  19         603  
116 19     19   9248 use Astro::Coords::Angle::Hour;
  19         60  
  19         599  
117 19     19   9713 use Astro::Coords::Equatorial;
  19         60  
  19         558  
118 19     19   8755 use Astro::Coords::Elements;
  19         58  
  19         615  
119 19     19   8350 use Astro::Coords::Planet;
  19         51  
  19         607  
120 19     19   8156 use Astro::Coords::Interpolated;
  19         52  
  19         684  
121 19     19   7863 use Astro::Coords::Fixed;
  19         46  
  19         666  
122 19     19   8013 use Astro::Coords::Calibration;
  19         49  
  19         693  
123              
124 19     19   128 use Scalar::Util qw/ blessed /;
  19         48  
  19         1375  
125 19     19   17011 use DateTime;
  19         9616872  
  19         1253  
126 19     19   207 use Time::Piece;
  19         43  
  19         208  
127              
128             # Constants for Sun rise/set and twilight definitions
129             # Elevation in radians
130             # See http://aa.usno.navy.mil/faq/docs/RST_defs.html
131 19     19   2097 use constant SUN_RISE_SET => ( - (50 * 60) * Astro::PAL::DAS2R); # 50 arcmin
  19         57  
  19         1755  
132 19     19   155 use constant CIVIL_TWILIGHT => ( - (6 * 3600) * Astro::PAL::DAS2R); # 6 deg
  19         49  
  19         1188  
133 19     19   122 use constant NAUT_TWILIGHT => ( - (12 * 3600) * Astro::PAL::DAS2R); # 12 deg
  19         42  
  19         1038  
134 19     19   129 use constant AST_TWILIGHT => ( - (18 * 3600) * Astro::PAL::DAS2R); # 18 deg
  19         47  
  19         1081  
135              
136             # This is a fudge. Not accurate
137 19     19   149 use constant MOON_RISE_SET => ( 5 * 60 * Astro::PAL::DAS2R);
  19         81  
  19         994  
138              
139             # Number of km in one Astronomical Unit
140 19     19   115 use constant AU2KM => 149.59787066e6;
  19         43  
  19         984  
141              
142             # Speed of light ( km/s )
143 19     19   132 use constant CLIGHT => 2.99792458e5;
  19         56  
  19         1131  
144              
145             # UTC TimeZone
146 19     19   177 use constant UTC => new DateTime::TimeZone( name => 'UTC' );
  19         54  
  19         146  
147              
148             =head1 METHODS
149              
150             =head2 Constructor
151              
152             =over 4
153              
154             =item B<new>
155              
156             This can be treated as an object factory. The object returned
157             by this constructor depends on the arguments supplied to it.
158             Coordinates can be provided as orbital elements, a planet name
159             or an equatorial (or related) fixed coordinate specification (e.g.
160             right ascension and declination).
161              
162             A complete (for some definition of complete) specification for
163             the coordinates in question must be provided to the constructor.
164             The coordinates given as arguments will be converted to an internal
165             format.
166              
167             A planet name can be specified with:
168              
169             $c = new Astro::Coords( planet => "sun" );
170              
171             Orbital elements as:
172              
173             $c = new Astro::Coords( elements => \%elements );
174             $c = new Astro::Coords( elements => \@array );
175              
176             where C<%elements> must contain the names of the elements
177             as used in the PAL routine palPlante, and @array is the
178             contents of the array returned by calling the array() method
179             on another Elements object.
180              
181             Fixed astronomical oordinate frames can be specified using:
182              
183             $c = new Astro::Coords( ra =>
184             dec =>
185             long =>
186             lat =>
187             type =>
188             units =>
189             );
190              
191             C<ra> and C<dec> are used for HMSDeg systems (eg type=J2000). Long and
192             Lat are used for degdeg systems (eg where type=galactic). C<type> can
193             be "galactic", "j2000", "b1950", and "supergalactic".
194             The C<units> can be specified as "sexagesimal" (when using colon or
195             space-separated strings), "degrees" or "radians". The default is
196             determined from context.
197              
198             Fixed (as in fixed on Earth) coordinate frames can be specified
199             using:
200              
201             $c = new Astro::Coords( dec =>
202             ha =>
203             tel =>
204             az =>
205             el =>
206             units =>
207             );
208              
209             where C<az> and C<el> are the Azimuth and Elevation. Hour Angle
210             and Declination require a telescope. Units are as defined above.
211              
212             Finally, if no arguments are given the object is assumed
213             to be of type C<Astro::Coords::Calibration>.
214              
215             Returns C<undef> if an object could not be created.
216              
217             =cut
218              
219             sub new {
220 1562     1562 1 1394284 my $class = shift;
221              
222 1562         6319 my %args = @_;
223              
224 1562         2642 my $obj;
225              
226             # Always try for a planet object first if $args{planet} is used
227             # (it might be that ra/dec are being specified and planet is a target
228             # name - this allows all the keys to be specified at once and the
229             # object can decide the most likely coordinate object to use
230             # This has the distinct disadvantage that planet is always tried
231             # even though it is rare. We want to be able to throw anything
232             # at this without knowing what we are.
233 1562 100 66     6242 if (exists $args{planet} and defined $args{planet}) {
234 511         2056 $obj = new Astro::Coords::Planet( $args{planet} );
235             }
236              
237             # planet did not work. Try something else.
238 1562 100       3881 unless (defined $obj) {
239              
240             # For elements we must not only check for the elements key
241             # but also make sure that that key points to a hash containing
242             # at least the EPOCH or EPOCHPERIH key
243 1051 100 33     7177 if (exists $args{elements} and defined $args{elements}
    100 66        
    100 66        
    100 66        
    50 33        
244             && (UNIVERSAL::isa($args{elements},"HASH") # A hash
245             && ((exists $args{elements}{EPOCH}
246             and defined $args{elements}{EPOCH})
247             || (exists $args{elements}{EPOCHPERIH}
248             and defined $args{elements}{EPOCHPERIH}))) ||
249             ( ref($args{elements}) eq 'ARRAY' && # ->array() ref
250             $args{elements}->[0] eq 'ELEMENTS')
251             ) {
252              
253 5         47 $obj = new Astro::Coords::Elements( %args );
254              
255             } elsif (exists $args{mjd1}) {
256              
257 4         43 $obj = new Astro::Coords::Interpolated( %args );
258              
259             } elsif (exists $args{type} and defined $args{type}) {
260              
261 1029         4522 $obj = new Astro::Coords::Equatorial( %args );
262              
263             } elsif (exists $args{az} or exists $args{el} or exists $args{ha}) {
264              
265 11         79 $obj = new Astro::Coords::Fixed( %args );
266              
267             } elsif ( scalar keys %args == 0 ) {
268              
269 2         32 $obj = new Astro::Coords::Calibration();
270              
271             } else {
272             # unable to work out what you are asking for
273 0         0 return undef;
274              
275             }
276             }
277              
278 1562         5867 return $obj;
279             }
280              
281              
282             =back
283              
284             =head2 Accessor Methods
285              
286             =over 4
287              
288             =item B<name>
289              
290             Name of the target associated with the coordinates.
291              
292             =cut
293              
294             sub name {
295 5057     5057 1 7125 my $self = shift;
296 5057 100       10227 if (@_) {
297 4         10 $self->{name} = shift;
298             }
299 5057         13216 return $self->{name};
300             }
301              
302             =item B<telescope>
303              
304             Telescope object (an instance of Astro::Telescope) to use
305             for obtaining the position of the telescope to use for
306             the determination of source elevation.
307              
308             $c->telescope( new Astro::Telescope( 'JCMT' ));
309             $tel = $c->telescope;
310              
311             This method checks that the argument is of the correct type.
312              
313             =cut
314              
315             sub telescope {
316 43994     43994 1 671970 my $self = shift;
317 43994 100       86062 if (@_) {
318 1534         2693 my $tel = shift;
319 1534 50       5810 return undef unless UNIVERSAL::isa($tel, "Astro::Telescope");
320 1534         3135 $self->{Telescope} = $tel;
321             # Telescope is part of the caching scheme
322 1534         3389 $self->_calc_cache_key();
323             }
324 43994         78424 return $self->{Telescope};
325             }
326              
327              
328             =item B<datetime>
329              
330             Date/Time object to use when determining the source elevation.
331              
332             $c->datetime( new Time::Piece() );
333              
334             Argument must be an object that has the C<mjd> method. Both
335             C<DateTime> and C<Time::Piece> objects are allowed. A value of
336             C<undef> is supported. This will clear the time and force the current
337             time to be used on subsequent calls.
338              
339             $c->datetime( undef );
340              
341             If no argument is specified and no Date/Time object had already been
342             supplied, or C<usenow> is set to true, an object
343             referring to the current time (GMT/UT) is returned. This object may be
344             either a C<Time::Piece> object or a C<DateTime> object depending on
345             current implementation (but in modern versions it will be a
346             C<DateTime> object). If a Date/Time object had already been specified
347             then the object returned will be of the same type as the supplied
348             object (C<DateTime> or C<Time::Piece>).
349             If a new argument is supplied C<usenow> is always set to false.
350              
351             A copy of the input argument is created, guaranteeing a UTC representation.
352              
353             Note that due to the possibility of an optimized caching scheme being
354             used, you should not adjust this object after it has been cloned. The
355             object is assumed to be immutable by the module internals. If you
356             really do require that it be adjusted externally, use the
357             C<datetime_is_unsafe> method to indicate this to the module.
358              
359             =cut
360              
361             sub datetime {
362 76732     76732 1 460644 my $self = shift;
363 76732 100       141264 if (@_) {
364 12330         17084 my $time = shift;
365              
366             # undef is okay
367 12330 0 66     60320 croak "datetime: Argument does not have an mjd() method [class="
    50          
368             . ( ref($time) ? ref($time) : $time) ."]"
369             if (defined $time && !UNIVERSAL::can($time, "mjd"));
370 12330         26548 $self->{DateTime} = _clone_time( $time );
371 12330         170077 $self->usenow(0);
372             # Changing usenow will have forced a recalculation of the cache key
373             # so no update is required here
374             # $self->_calc_cache_key;
375             }
376 76732 100 66     199082 if (defined $self->{DateTime} && ! $self->usenow) {
377 76657         154185 return $self->{DateTime};
378             } else {
379 75         267 return DateTime->now( time_zone => UTC );
380             }
381             }
382              
383             =item B<has_datetime>
384              
385             Returns true if a specific time is stored in the object, returns
386             false if no time is stored. (The value of C<usenow> is
387             ignored).
388              
389             This is required because C<datetime> always returns a time.
390              
391             =cut
392              
393             sub has_datetime {
394 21184     21184 1 28571 my $self = shift;
395 21184         68415 return (defined $self->{DateTime});
396             }
397              
398             =item B<usenow>
399              
400             Flag to indicate whether the current time should be used for calculations
401             regardless of whether an explicit time object is stored in C<datetime>.
402             This is useful when trying to determine the current position of a target
403             without affecting previous settings.
404              
405             $c->usenow( 1 );
406             $usenow = $c->usenow;
407              
408             Defaults to false.
409              
410             =cut
411              
412             sub usenow {
413 103535     103535 1 147024 my $self = shift;
414 103535 100       179129 if (@_) {
415 12331         20547 $self->{UseNow} = shift;
416             # Since this affects caching we need to force a key check if this value
417             # is updated
418 12331         24843 $self->_calc_cache_key();
419             }
420 103535         267692 return $self->{UseNow};
421             }
422              
423             =item B<datetime_is_unsafe>
424              
425             If true, indicates that the DateTime object stored in this object is
426             not guranteed to be immutable by the externally user. This effectively
427             turns off the internal cache.
428              
429             $c->datetime_is_unsafe();
430              
431             =cut
432              
433             sub datetime_is_unsafe {
434 17849     17849 1 25376 my $self = shift;
435 17849 100       32795 if (@_) {
436 2202         3860 $self->{DATETIME_IS_UNSAFE} = shift;
437 2202         3966 $self->_calc_cache_key;
438             }
439 17849         42660 return $self->{DATETIME_IS_UNSAFE};
440             }
441              
442             =item B<comment>
443              
444             A textual comment associated with the coordinate (optional).
445             Defaults to the empty string.
446              
447             $comment = $c->comment;
448             $c->comment("An inaccurate coordinate");
449              
450             Always returns an empty string if undefined.
451              
452             =cut
453              
454             sub comment {
455 7     7 1 11 my $self = shift;
456 7 50       14 if (@_) {
457 0         0 $self->{Comment} = shift;
458             }
459 7         11 my $com = $self->{Comment};
460 7 50       19 $com = '' unless defined $com;
461 7         11 return $com;
462             }
463              
464             =item B<native>
465              
466             Returns the name of the method that should be called to return the
467             coordinates in a form as close as possible to those that were supplied
468             to the constructor. This method is useful if, say, the object is created
469             from Galactic coordinates but internally represented in a different
470             coordinate frame.
471              
472             $native_method = $c->native;
473              
474             This method can then be called to retrieve the coordinates:
475              
476             ($c1, $c2) = $c->$native_method();
477              
478             Currently, the native form will not exactly match the supplied form
479             if a non-standard equinox has been used, or if proper motions and parallax
480             are present, but the resulting answer can be used as a guide.
481              
482             If no native method is obvious (e.g. for a planet), 'apparent' will
483             be returned.
484              
485             =cut
486              
487             sub native {
488 1043     1043 1 1837 my $self = shift;
489 1043 100       2409 if (@_) {
490 1042         2614 $self->{NativeMethod} = shift;
491             }
492 1043 50       2933 return (defined $self->{NativeMethod} ? $self->{NativeMethod} : 'apparent' );
493             }
494              
495              
496             =back
497              
498             =head2 General Methods
499              
500             =over 4
501              
502             =item B<azel>
503              
504             Return Azimuth and elevation for the currently stored time and telescope.
505             If no telescope is present the equator is used. Returns the Az and El
506             as C<Astro::Coords::Angle> objects.
507              
508             ($az, $el) = $c->azel();
509              
510             =cut
511              
512             sub azel {
513 6158     6158 1 8914 my $self = shift;
514              
515 6158         11508 my ($az,$el) = $self->_cache_read( "AZ", "EL" );
516              
517 6158 100 66     14935 if (!defined $az || !defined $el) {
518              
519 6157         12136 my $ha = $self->ha;
520 6157         17993 my $dec = $self->dec_app;
521 6157         19509 my $tel = $self->telescope;
522 6157 100       20253 my $lat = ( defined $tel ? $tel->lat : 0.0);
523 6157         53537 ($az, $el) = Astro::PAL::palDe2h( $ha, $dec, $lat );
524 6157         17594 $az = new Astro::Coords::Angle( $az, units => 'rad', range => '2PI' );
525 6157         14561 $el = new Astro::Coords::Angle( $el, units => 'rad' );
526              
527 6157         14629 $self->_cache_write( "AZ" => $az, "EL" => $el );
528             }
529 6158         15823 return ($az, $el);
530             }
531              
532              
533             =item B<ra_app>
534              
535             Apparent RA for the current time.
536              
537             $ra_app = $c->ra_app( format => "s" );
538              
539             See L<"NOTES"> for details on the supported format specifiers
540             and default calling convention.
541              
542             =cut
543              
544             sub ra_app {
545 11639     11639 1 19868 my $self = shift;
546 11639         20425 my %opt = @_;
547              
548 11639         23734 my $ra = $self->_cache_read( "RA_APP" );
549 11639 100       23452 if (!defined $ra) {
550 7510         21515 $ra = ($self->apparent)[0];
551             }
552 11639         49093 return $ra->in_format( $opt{format} );
553             }
554              
555              
556             =item B<dec_app>
557              
558             Apparent Dec for the currently stored time.
559              
560             $dec_app = $c->dec_app( format => "s" );
561              
562             See L<"NOTES"> for details on the supported format specifiers
563             and default calling convention.
564              
565              
566             =cut
567              
568             sub dec_app {
569 9257     9257 1 15262 my $self = shift;
570 9257         15502 my %opt = @_;
571 9257         18605 my $dec = $self->_cache_read( "DEC_APP" );
572 9257 100       18625 if (!defined $dec) {
573 6037         15164 $dec = ($self->apparent)[1];
574             }
575 9257         36475 return $dec->in_format( $opt{format} );
576             }
577              
578             =item B<ha>
579              
580             Get the hour angle for the currently stored LST. By default HA is returned
581             as an C<Astro::Coords::Angle::Hour> object.
582              
583             $ha = $c->ha;
584             $ha = $c->ha( format => "h" );
585              
586             By default the Hour Angle will be normalised to +/- 12h if an explicit
587             format is specified.
588              
589             See L<"NOTES"> for details on the supported format specifiers
590             and default calling convention.
591              
592             =cut
593              
594             # normalize key was supported but should its absence imply no normalization?
595              
596             sub ha {
597 6194     6194 1 8535 my $self = shift;
598 6194         9902 my %opt = @_;
599              
600 6194         9858 my $ha = $self->_cache_read( "HA" );
601 6194 100       12204 if (!defined $ha) {
602 6160         12133 $ha = $self->_lst - $self->ra_app;
603             # Always normalize?
604 6160         26511 $ha = new Astro::Coords::Angle::Hour( $ha, units => 'rad', range => 'PI' );
605 6160         13418 $self->_cache_write( "HA" => $ha );
606             }
607 6194         20351 return $ha->in_format( $opt{format} );
608             }
609              
610             =item B<az>
611              
612             Azimuth of the source for the currently stored time at the current
613             telescope. See L<"NOTES"> for details on the supported format specifiers
614             and default calling convention.
615              
616             $az = $c->az();
617              
618             If no telescope is defined the equator is used.
619              
620             =cut
621              
622             sub az {
623 47     47 1 171 my $self = shift;
624 47         146 my %opt = @_;
625 47         145 my $az = $self->_cache_read( "AZ" );
626 47 100       217 if (!defined $az) {
627 14         47 ($az, my $el) = $self->azel();
628             }
629 47         164 return $az->in_format( $opt{format} );
630             }
631              
632             =item B<el>
633              
634             Elevation of the source for the currently stored time at the current
635             telescope. See L<"NOTES"> for details on the supported format specifiers
636             and default calling convention.
637              
638             $el = $c->el();
639              
640             If no telescope is defined the equator is used.
641              
642             =cut
643              
644             sub el {
645 6172     6172 1 12352 my $self = shift;
646 6172         10662 my %opt = @_;
647 6172         12890 my $el = $self->_cache_read( "EL" );
648              
649 6172 100       13209 if (!defined $el) {
650 6126         12511 (my $az, $el) = $self->azel();
651             }
652 6172         23519 return $el->in_format( $opt{format} );
653             }
654              
655             =item B<airmass>
656              
657             Airmass of the source for the currently stored time at the current
658             telescope.
659              
660             $am = $c->airmass();
661              
662             Value determined from the current elevation.
663              
664             =cut
665              
666             sub airmass {
667 0     0 1 0 my $self = shift;
668 0         0 my $el = $self->el;
669 0         0 my $zd = Astro::PAL::DPIBY2 - $el;
670 0         0 return Astro::PAL::palAirmas( $zd );
671             }
672              
673             =item B<radec>
674              
675             Return the J2000 Right Ascension and Declination for the target. Unless
676             overridden by a subclass, this converts from the apparent RA/Dec to J2000.
677             Returns two C<Astro::Coords::Angle> objects.
678              
679             ($ra, $dec) = $c->radec();
680              
681             =cut
682              
683             sub radec {
684 9     9 1 17 my $self = shift;
685 9   100     67 my ($sys, $equ) = $self->_parse_equinox( shift || 'J2000' );
686 9         50 my ($ra_app, $dec_app) = $self->apparent;
687 9         30 my $mjd = $self->_mjd_tt;
688 9         26 my ($rm, $dm);
689 9 50       33 if ($sys eq 'FK5') {
    0          
690             # Julian epoch
691 9         54 ($rm, $dm) = Astro::PAL::palAmp($ra_app, $dec_app, $mjd, $equ);
692             } elsif ($sys eq 'FK4') {
693             # Convert to J2000 and then convert to Besselian epoch
694 0         0 ($rm, $dm) = Astro::PAL::palAmp($ra_app, $dec_app, $mjd, 2000.0);
695              
696 0         0 ($rm, $dm) = $self->_j2000_to_byyyy( $equ, $rm, $dm);
697             }
698              
699 9         57 return (new Astro::Coords::Angle::Hour( $rm, units => 'rad', range => '2PI'),
700             new Astro::Coords::Angle( $dm, units => 'rad' ));
701             }
702              
703             =item B<ra>
704              
705             Return the J2000 Right ascension for the target. Unless overridden
706             by a subclass this converts the apparent RA/Dec to J2000.
707              
708             $ra2000 = $c->ra( format => "s" );
709              
710             Calls the C<radec> method. See L<"NOTES"> for details on the supported
711             format specifiers and default calling convention.
712              
713             =cut
714              
715             sub ra {
716 0     0 1 0 my $self = shift;
717 0         0 my %opt = @_;
718 0         0 my ($ra,$dec) = $self->radec;
719 0         0 return $ra->in_format( $opt{format} );
720             }
721              
722             =item B<dec>
723              
724             Return the J2000 declination for the target. Unless overridden
725             by a subclass this converts the apparent RA/Dec to J2000.
726              
727             $dec2000 = $c->dec( format => "s" );
728              
729             Calls the C<radec> method. See L<"NOTES"> for details on the supported
730             format specifiers and default calling convention.
731              
732             =cut
733              
734             sub dec {
735 0     0 1 0 my $self = shift;
736 0         0 my %opt = @_;
737 0         0 my ($ra,$dec) = $self->radec;
738 0         0 return $dec->in_format( $opt{format} );
739             }
740              
741             =item B<glong>
742              
743             Return Galactic longitude. See L<"NOTES"> for details on the supported
744             format specifiers and default calling convention.
745              
746             $glong = $c->glong( format => "s" );
747              
748             =cut
749              
750             sub glong {
751 0     0 1 0 my $self = shift;
752 0         0 my %opt = @_;
753 0         0 my ($glong,$glat) = $self->glonglat();
754 0         0 return $glong->in_format( $opt{format} );
755             }
756              
757             =item B<glat>
758              
759             Return Galactic latitude. See L<"NOTES"> for details on the supported
760             format specifiers and default calling convention.
761              
762              
763             $glat = $c->glat( format => "s" );
764              
765             =cut
766              
767             sub glat {
768 0     0 1 0 my $self = shift;
769 0         0 my %opt = @_;
770 0         0 my ($glong,$glat) = $self->glonglat();
771 0         0 return $glat->in_format( $opt{format} );
772             }
773              
774             =item B<sglong>
775              
776             Return SuperGalactic longitude. See L<"NOTES"> for details on the
777             supported format specifiers and default calling convention.
778              
779             $sglong = $c->sglong( format => "s" );
780              
781             =cut
782              
783             sub sglong {
784 0     0 1 0 my $self = shift;
785 0         0 my %opt = @_;
786 0         0 my ($sglong,$sglat) = $self->sglonglat();
787 0         0 return $sglong->in_format( $opt{format} );
788             }
789              
790             =item B<sglat>
791              
792             Return SuperGalactic latitude. See L<"NOTES"> for details on the supported format specifiers and default calling convention.
793              
794             $glat = $c->sglat( format => "s" );
795              
796             =cut
797              
798             sub sglat {
799 0     0 1 0 my $self = shift;
800 0         0 my %opt = @_;
801 0         0 my ($sglong,$sglat) = $self->sglonglat();
802 0         0 return $sglat->in_format( $opt{format} );
803             }
804              
805             =item B<ecllong>
806              
807             Return Ecliptic longitude. See L<"NOTES"> for details on the supported
808             format specifiers and default calling convention.
809              
810             $eclong = $c->ecllong( format => "s" );
811              
812             =cut
813              
814             sub ecllong {
815 0     0 1 0 my $self = shift;
816 0         0 my %opt = @_;
817 0         0 my ($eclong,$eclat) = $self->ecllonglat();
818 0         0 return $eclong->in_format( $opt{format} );
819             }
820              
821             =item B<ecllat>
822              
823             Return ecliptic latitude. See L<"NOTES"> for details on the supported
824             format specifiers and default calling convention.
825              
826             $eclat = $c->ecllat( format => "s" );
827              
828             =cut
829              
830             sub ecllat {
831 0     0 1 0 my $self = shift;
832 0         0 my %opt = @_;
833 0         0 my ($eclong,$eclat) = $self->ecllonglat();
834 0         0 return $eclat->in_format( $opt{format} );
835             }
836              
837             =item B<glonglat>
838              
839             Calculate Galactic longitude and latitude. Position is calculated for
840             the current ra/dec position (as returned by the C<radec> method).
841              
842             ($long, $lat) = $c->glonglat;
843              
844             Answer is returned as two C<Astro::Coords::Angle> objects.
845              
846             =cut
847              
848             sub glonglat {
849 6     6 1 26 my $self = shift;
850 6         103 my ($ra,$dec) = $self->radec;
851 6         34 my ($long, $lat) = Astro::PAL::palEqgal( $ra, $dec );
852 6         21 return (new Astro::Coords::Angle($long, units => 'rad', range => '2PI'),
853             new Astro::Coords::Angle($lat, units => 'rad'));
854             }
855              
856             =item B<sglonglat>
857              
858             Calculate Super Galactic longitude and latitude.
859              
860             ($slong, $slat) = $c->sglonglat;
861              
862             Answer is returned as two C<Astro::Coords::Angle> objects.
863              
864             =cut
865              
866             sub sglonglat {
867 0     0 1 0 my $self = shift;
868 0         0 my ($glong, $glat) = $self->glonglat();
869 0         0 my ($sglong, $sglat) = Astro::PAL::palGalsup( $glong, $glat );
870 0         0 return (new Astro::Coords::Angle($sglong, units => 'rad', range => '2PI'),
871             new Astro::Coords::Angle($sglat, units => 'rad'));
872             }
873              
874             =item B<ecllonglat>
875              
876             Calculate the ecliptic longitude and latitude for the epoch stored in
877             the object. Position is calculated for the current ra/dec position (as
878             returned by the C<radec> method.
879              
880             ($long, $lat) = $c->ecllonglat();
881              
882             Answer is returned as two C<Astro::Coords::Angle> objects.
883              
884             =cut
885              
886             sub ecllonglat {
887 2     2 1 6 my $self = shift;
888 2         9 my ($ra, $dec) = $self->radec;
889 2         79 my ($long, $lat) = Astro::PAL::palEqecl( $ra, $dec, $self->_mjd_tt );
890 2         11 return (new Astro::Coords::Angle($long, units => 'rad', range => '2PI'),
891             new Astro::Coords::Angle($lat, units => 'rad'));
892             }
893              
894             =item B<radec2000>
895              
896             Convenience wrapper routine to return the J2000 coordinates for epoch
897             2000.0. This is not the same as calling the C<radec> method with
898             equinox J2000.0.
899              
900             ($ra2000, $dec2000) = $c->radec2000;
901              
902             It is equivalent to setting the epoch in the object to 2000.0
903             (ie midday on 2000 January 1) and then calling C<radec>.
904              
905             The answer will be location dependent in most cases.
906              
907             Results are returned as two C<Astro::Coords::Angle> objects.
908              
909             =cut
910              
911             sub radec2000 {
912 3     3 1 6 my $self = shift;
913              
914             # store current configuration
915 3 50       17 my $reftime = ( $self->has_datetime ? $self->datetime : undef);
916              
917             # Create new time
918 3         30 $self->datetime( DateTime->new( year => 2000, month => 1,
919             day => 1, hour => 12) );
920              
921             # Ask for the answer
922 3         32 my ($ra, $dec) = $self->radec( 'J2000' );
923              
924             # restore the date state
925 3         37 $self->datetime( $reftime );
926              
927 3         1433 return ($ra, $dec);
928             }
929              
930             =item B<radec1950>
931              
932             Convenience wrapper to return the FK4 B1950 coordinates for the
933             currently defined epoch. Since the FK4 to FK5 conversion requires an
934             epoch, the J2000 coordinates are first calculated for the current
935             epoch and the frame conversion is done to epoch B1950.
936              
937             This is technically not the same as calling the radec() method with
938             equinox B1950 since that would use the current epoch associated with
939             the coordinates when converting from FK4 to FK5.
940              
941             In the base class these are calculated by precessing the J2000 RA/Dec
942             for the current date and time, which are themselves derived from the
943             apparent RA/Dec for the current time.
944              
945             ($ra, $dec) = $c->radec1950;
946              
947             Results are returned as two C<Astro::Coords::Angle> objects.
948              
949             =cut
950              
951             sub radec1950 {
952 3     3 1 21 my $self = shift;
953 3         10 my ($ra, $dec) = $self->radec;
954              
955             # No E-terms or precession since we are going to B1950 epoch 1950
956 3         22 my ($r1950, $d1950, $dr1950, $dd1950) = Astro::PAL::palFk54z($ra,$dec,1950.0);
957 3         13 return (new Astro::Coords::Angle::Hour( $r1950, units => 'rad', range => '2PI'),
958             new Astro::Coords::Angle( $d1950, units => 'rad' ));
959             }
960              
961             =item B<pa>
962              
963             Parallactic angle of the source for the currently stored time at the
964             current telescope. See L<"NOTES"> for details on the supported format
965             specifiers and default calling convention.
966              
967             $pa = $c->pa();
968             $padeg = $c->pa( format => 'deg' );
969              
970             If no telescope is defined the equator is used.
971              
972             =cut
973              
974             sub pa {
975 25     25 1 38 my $self = shift;
976 25         46 my %opt = @_;
977 25         57 my $ha = $self->ha;
978 25         95 my $dec = $self->dec_app;
979 25         73 my $tel = $self->telescope;
980 25 50       53 my $lat = ( defined $tel ? $tel->lat : 0.0);
981 25         87 my $pa = Astro::PAL::palPa($ha, $dec, $lat);
982 25         79 $pa = new Astro::Coords::Angle( $pa, units => 'rad' );
983 25         92 return $pa->in_format( $opt{format} );
984             }
985              
986              
987             =item B<isObservable>
988              
989             Determine whether the coordinates are accessible for the current
990             time and telescope.
991              
992             $isobs = $c->isObservable;
993              
994             Returns false if a telescope has not been specified (see
995             the C<telescope> method) or if the specified telescope does not
996             know its own limits.
997              
998             =cut
999              
1000             sub isObservable {
1001 15     15 1 13249 my $self = shift;
1002              
1003             # Get the telescope
1004 15         38 my $tel = $self->telescope;
1005 15 50       61 return 0 unless defined $tel;
1006              
1007             # Get the limits hash
1008 15         54 my %limits = $tel->limits;
1009              
1010 15 50       558 if (exists $limits{type}) {
1011              
1012 15 100       60 if ($limits{type} eq 'AZEL') {
    50          
1013              
1014             # Get the current elevation of the source
1015 12         52 my $el = $self->el;
1016              
1017 12 100 66     59 if ($el > $limits{el}{min} and $el < $limits{el}{max}) {
1018 8         68 return 1;
1019             } else {
1020 4         24 return 0;
1021             }
1022              
1023             } elsif ($limits{type} eq 'HADEC') {
1024              
1025             # Get the current HA
1026 3         11 my $ha = $self->ha( normalize => 1 );
1027              
1028 3 100 66     14 if ( $ha > $limits{ha}{min} and $ha < $limits{ha}{max}) {
1029 2         17 my $dec= $self->dec_app;
1030              
1031 2 100 66     9 if ($dec > $limits{dec}{min} and $dec < $limits{dec}{max}) {
1032 1         10 return 1;
1033             } else {
1034 1         7 return 0;
1035             }
1036              
1037             } else {
1038 1         7 return 0;
1039             }
1040              
1041             } else {
1042             # have no idea
1043 0         0 return 0;
1044             }
1045              
1046             } else {
1047 0         0 return 0;
1048             }
1049              
1050             }
1051              
1052              
1053             =item B<array>
1054              
1055             Return a summary of this object in the form of an array containing
1056             the following:
1057              
1058             coordinate type (eg PLANET, RADEC, MARS)
1059             ra2000 (J2000 RA in radians [for equatorial])
1060             dec2000 (J2000 dec in radians [for equatorial])
1061             elements (up to 8 orbital elements)
1062              
1063             =cut
1064              
1065             sub array {
1066 0     0 1 0 my $self = shift;
1067 0         0 croak "The method array() must be subclassed\n";
1068             }
1069              
1070             =item B<distance>
1071              
1072             Calculate the distance (on the tangent plane) between the current
1073             coordinate and a supplied coordinate.
1074              
1075             $dist = $c->distance( $c2 );
1076             @dist = $c->distance( $c2 );
1077              
1078             In scalar context the distance is returned as an
1079             C<Astro::Coords::Angle> object In list context returns the individual
1080             "x" and "y" offsets (as C<Astro::Coords::Angle> objects).
1081              
1082             Returns undef if there was an error during the calculation (e.g. because
1083             the new coordinate was too far away).
1084              
1085             =cut
1086              
1087             sub distance {
1088 1     1 1 11 my $self = shift;
1089 1         2 my $offset = shift;
1090              
1091 1         3 my( $ra, $dec ) = $self->radec;
1092 1         4 my( $ra_off, $dec_off ) = $offset->radec;
1093              
1094 1         6 my ($xi, $eta, $j) = Astro::PAL::palDs2tp($ra_off, $dec_off,
1095             $ra, $dec );
1096              
1097 1 50       4 return () unless $j == 0;
1098              
1099 1 50       4 if (wantarray) {
1100 0         0 return (new Astro::Coords::Angle($xi, units => 'rad'),
1101             new Astro::Coords::Angle($eta, units => 'rad'));
1102             } else {
1103 1         7 my $dist = ($xi**2 + $eta**2)**0.5;
1104 1         4 return new Astro::Coords::Angle( $dist, units => 'rad' );
1105             }
1106             }
1107              
1108              
1109             =item B<status>
1110              
1111             Return a status string describing the current coordinates.
1112             This consists of the current elevation, azimuth, hour angle
1113             and declination. If a telescope is defined the observability
1114             of the target is included.
1115              
1116             $status = $c->status;
1117              
1118             =cut
1119              
1120             sub status {
1121 6     6 1 92452 my $self = shift;
1122 6         15 my $string;
1123              
1124 6 100       35 $string .= "Target name: " . $self->name . "\n"
1125             if $self->name;
1126              
1127 6         35 $string .= "Coordinate type:" . $self->type ."\n";
1128              
1129 6 50       20 if ($self->type ne 'CAL') {
1130              
1131 6         23 my ($az,$el) = $self->azel;
1132 6         43 $string .= "Elevation: " . $el->degrees ." deg\n";
1133 6         34 $string .= "Azimuth : " . $az->degrees ." deg\n";
1134 6         27 my $ha = $self->ha->hours;
1135 6         42 $string .= "Hour angle: " . $ha ." hrs\n";
1136 6         26 my ($ra_app, $dec_app) = $self->apparent;
1137 6         33 $string .= "Apparent RA : " . $ra_app->string . "\n";
1138 6         26 $string .= "Apparent dec: " . $dec_app->string ."\n";
1139              
1140             # Transit time
1141 6         41 my $mt = $self->meridian_time;
1142 6 50       59 $string .= "Time of next transit: " .
1143             (defined $mt ? $mt->datetime : "<never>") ."\n";
1144              
1145 6         423 my $t_el = $self->transit_el(format=>'d');
1146 6 50       85 $string .= "Transit El: " . (defined $t_el ? "$t_el deg"
1147             : "<undefined>") ."\n";
1148 6         36 my $ha_set = $self->ha_set( format => 'hour');
1149 6 50       67 $string .= "Hour Ang. (set):" . (defined $ha_set ? $ha_set : '??')." hrs\n";
1150              
1151 6         29 my $t = $self->rise_time;
1152 6 50       63 $string .= "Next Rise time: " . $t->datetime . "\n" if defined $t;
1153 6         217 $t = $self->set_time;
1154 6 50       50 $string .= "Next Set time: " . $t->datetime . "\n" if defined $t;
1155              
1156             # This check was here before we added a RA/Dec to the
1157             # base class.
1158 6 50       272 if ($self->can('radec')) {
1159 6         29 my ($ra, $dec) = $self->radec;
1160 6         24 $string .= "RA (J2000): " . $ra->string . "\n";
1161 6         27 $string .= "Dec(J2000): " . $dec->string . "\n";
1162             }
1163             }
1164              
1165 6 50       25 if (defined $self->telescope) {
1166 6 50       18 my $name = (defined $self->telescope->fullname ?
1167             $self->telescope->fullname : $self->telescope->name );
1168 6         40 $string .= "Telescope: $name\n";
1169 6 100       37 if ($self->isObservable) {
1170 3         10 $string .= "The target is currently observable\n";
1171             } else {
1172 3         9 $string .= "The target is not currently observable\n";
1173             }
1174             }
1175              
1176 6         27 $string .= "For time ". $self->datetime->datetime ."\n";
1177 6         176 my $fmt = 's';
1178 6         27 $string .= "LST: ". $self->_lst->hours ."\n";
1179              
1180 6         3910 return $string;
1181             }
1182              
1183             =item B<calculate>
1184              
1185             Calculate target positions for a range of times.
1186              
1187             @data = $c->calculate( start => $start,
1188             end => $end,
1189             inc => $increment,
1190             units => 'deg'
1191             );
1192              
1193             The start and end times are either C<Time::Piece> or C<DateTime>
1194             objects and the increment is either a C<Time::Seconds> object, a
1195             C<DateTime::Duration> object (in fact, an object that implements the
1196             C<seconds> method) or an integer. If the end time will not necessarily
1197             be used explictly if the increment does not divide into the total time
1198             gap exactly. None of the returned times will exceed the end time. The
1199             increment must be greater than zero but the start and end times can be
1200             identical.
1201              
1202             Returns an array of hashes. Each hash contains
1203              
1204             time [same object class as provided as argument]
1205             elevation
1206             azimuth
1207             parang
1208             lst [always in radians]
1209              
1210             The angles are in the units specified (radians, degrees or sexagesimal). They
1211             will be Angle objects if no units are specified.
1212              
1213             Note that this method returns C<DateTime> objects if it was given C<DateTime>
1214             objects, else it returns C<Time::Piece> objects.
1215              
1216             After running, the original time associated with the object will be retained.
1217              
1218             =cut
1219              
1220             sub calculate {
1221 1     1 1 256 my $self = shift;
1222              
1223 1         5 my %opt = @_;
1224              
1225 1 50       5 croak "No start time specified" unless exists $opt{start};
1226 1 50       3 croak "No end time specified" unless exists $opt{end};
1227 1 50       2 croak "No time increment specified" unless exists $opt{inc};
1228              
1229             # Get the increment as an integer (DateTime::Duration or Time::Seconds)
1230 1         41 my $inc = $opt{inc};
1231 1 50       11 if (UNIVERSAL::can($inc, "seconds")) {
1232 0         0 $inc = $inc->seconds;
1233             }
1234 1 50       9 croak "Increment must be greater than zero" unless $inc > 0;
1235              
1236             # Determine date class to use for calculations
1237 1         6 my $dateclass = blessed( $opt{start} );
1238 1 50 33     7 croak "Start time must be either Time::Piece or DateTime object"
      33        
1239             if (!$dateclass ||
1240             ($dateclass ne "Time::Piece" && $dateclass ne 'DateTime' ));
1241              
1242 1         3 my @data;
1243              
1244             # Get the current datetime
1245             my $original;
1246 1         5 my $usenow = $self->usenow;
1247 1 50       8 $original = $self->datetime() unless $usenow;
1248              
1249             # Get a private copy of the date object for calculations
1250             # (copy constructor)
1251 1         706 my $current = _clone_time( $opt{start} );
1252              
1253 1         73 while ( $current->epoch <= $opt{end}->epoch ) {
1254              
1255             # Hash for storing the data
1256 25         321 my %timestep;
1257              
1258             # store a copy of the time
1259 25         57 $timestep{time} = _clone_time( $current );
1260              
1261             # Set the time in the object
1262             # [standard problem with knowing whether we are overriding
1263             # another setting]
1264 25         1578 $self->datetime( $current );
1265              
1266             # Now calculate the positions
1267 25         67 ($timestep{azimuth}, $timestep{elevation}) = $self->azel();
1268 25         64 $timestep{parang} = $self->pa;
1269              
1270 25 50       84 if (defined $opt{units} ) {
1271 25         67 $timestep{azimuth} = $timestep{azimuth}->in_format( $opt{units} );
1272 25         59 $timestep{elevation} = $timestep{elevation}->in_format( $opt{units} );
1273 25         63 $timestep{parang} = $timestep{parang}->in_format( $opt{units} );
1274             }
1275              
1276 25         93 $timestep{lst} = $self->_lst();
1277              
1278             # store the timestep
1279 25         69 push(@data, \%timestep);
1280              
1281             # increment the time
1282 25         65 $current = _inc_time( $current, $inc );
1283              
1284             }
1285              
1286             # Restore the time
1287 1 50       16 if ($usenow) {
1288 0         0 $self->datetime( undef );
1289             } else {
1290 1         4 $self->datetime( $original );
1291             }
1292 1         5 $self->usenow( $usenow );
1293              
1294 1         42 return @data;
1295              
1296             }
1297              
1298             =item B<rise_time>
1299              
1300             Time at which the target will appear above the horizon. By default the
1301             calculation is for the next rise time relative to the current
1302             reference time. If the "event" key is used, this can control which
1303             rise time will be returned. For event=1, this indicates the following
1304             rise (the default), event=-1 indicates a previous rise and event=0
1305             indicates the nearest source rising to the current reference time.
1306              
1307             If the "nearest" key is set in the argument hash, this is synonymous
1308             with event=0 and supercedes the event key.
1309              
1310             Returns C<undef> if the target is never visible or never sets. An
1311             optional argument can be given specifying a different elevation to the
1312             horizon (in radians).
1313              
1314             $t = $c->set_time();
1315             $t = $c->set_time( horizon => $el );
1316             $t = $c->set_time( nearest => 1 );
1317             $t = $c->set_time( event => -1 );
1318              
1319             Returns a C<Time::Piece> object or a C<DateTime> object depending on the
1320             type of object that is returned by the C<datetime> method.
1321              
1322             For some occasions the calculation will be performed twice, once for
1323             the meridian transit before the reference time and once for the
1324             transit after the reference time.
1325              
1326             Does not distinguish a source that never rises from a source that
1327             never sets. Both will return undef for the rise time.
1328              
1329             Next and previous depend on the adjacent transits. The routine will
1330             not step forward multiple days looking for a rise time if the source is
1331             not going to rise before the next or previous transit.
1332              
1333             =cut
1334              
1335             sub rise_time {
1336 1529     1529 1 13871 my $self = shift;
1337 1529         4066 return $self->_rise_set_time( 1, @_);
1338             }
1339              
1340             =item B<set_time>
1341              
1342             Time at which the target will set below the horizon. By default the
1343             calculation is the next set time from the current reference time. If
1344             the "event" key is used, this can control which set time will be
1345             returned. For event=1, this indicates the following set (the default),
1346             event=-1 indicates a previous set and event=0 indicates the nearest
1347             source setting to the current reference time.
1348              
1349             If the "nearest" key is set in the argument hash, this is synonymous
1350             with event=0 and supercedes the event key.
1351              
1352             Returns C<undef> if the target is never visible or never sets. An
1353             optional argument can be given specifying a different elevation to the
1354             horizon (in radians).
1355              
1356             $t = $c->set_time();
1357             $t = $c->set_time( horizon => $el );
1358             $t = $c->set_time( nearest => 1 );
1359             $t = $c->set_time( event => -1 );
1360              
1361             Returns a C<Time::Piece> object or a C<DateTime> object depending on the
1362             type of object that is returned by the C<datetime> method.
1363              
1364             For some occasions the calculation will be performed twice, once for
1365             the meridian transit before the reference time and once for the
1366             transit after the reference time.
1367              
1368             Does not distinguish a source that never rises from a source that
1369             never sets. Both will return undef for the set time.
1370              
1371             Next and previous depend on the adjacent transits. The routine will
1372             not step forward multiple days looking for a set time if the source is
1373             not going to set following the next or previous transit.
1374              
1375             =cut
1376              
1377             sub set_time {
1378 1529     1529 1 16434 my $self = shift;
1379 1529         3542 return $self->_rise_set_time( 0, @_);
1380             }
1381              
1382              
1383             =item B<ha_set>
1384              
1385             Hour angle at which the target will set. Negate this value to obtain
1386             the rise time. By default assumes the target sets at an elevation of 0
1387             degrees (except for the Sun and Moon which are special-cased). An
1388             optional hash can be given with key of "horizon" specifying a
1389             different elevation (in radians).
1390              
1391             $ha = $c->ha_set;
1392             $ha = $c->ha_set( horizon => $el );
1393              
1394             Returned by default as an C<Astro::Coords::Angle::Hour> object unless
1395             an explicit "format" is specified.
1396              
1397             $ha = $c->ha_set( horizon => $el, format => 'h');
1398              
1399             There are predefined elevations for events such as
1400             Sun rise/set and Twilight (only relevant if your object
1401             refers to the Sun). See L<"CONSTANTS"> for more information.
1402              
1403             Returns C<undef> if the target never reaches the specified horizon.
1404             (maybe it is circumpolar).
1405              
1406             For the Sun and moon this calculation will not be very accurate since
1407             it depends on the time for which the calculation is to be performed
1408             (the time is not used by this routine) and the rise Hour Angle and
1409             setting Hour Angle will differ (especially for the moon) . These
1410             effects are corrected for by the C<rise_time> and C<set_time>
1411             methods.
1412              
1413             In some cases for the Moon, an iterative technique is used to calculate
1414             the hour angle when the Moon is near transit (the simple geometrical
1415             arguments do not correctly calculate the transit elevation).
1416              
1417             =cut
1418              
1419             sub ha_set {
1420 3064     3064 1 4407 my $self = shift;
1421              
1422             # Get the reference horizon elevation
1423 3064         8142 my %opt = @_;
1424              
1425             my $horizon = (defined $opt{horizon} ? $opt{horizon} :
1426 3064 100       7857 $self->_default_horizon );
1427              
1428             # Get the telescope position
1429 3064         6220 my $tel = $self->telescope;
1430              
1431             # Get the longitude (in radians)
1432 3064 100       9396 my $lat = (defined $tel ? $tel->lat : 0.0 );
1433              
1434             # Declination
1435 3064         22071 my $dec = $self->dec_app;
1436              
1437             # Calculate the hour angle for this elevation
1438             # See http://www.faqs.org/faqs/astronomy/faq/part3/section-5.html
1439 3064         18434 my $cos_ha0 = ( sin($horizon) - sin($lat)*sin( $dec ) ) /
1440             ( cos($lat) * cos($dec) );
1441              
1442             # Make sure we have a valid number for the cosine
1443 3064 100 100     7215 if (defined $self->name && lc($self->name) eq 'moon' && abs($cos_ha0) > 1) {
      100        
1444             # for the moon this routine can incorrectly determine
1445             # cos HA near transit [in fact it always will be inaccurate
1446             # but near transit it won't return any value at all]
1447             # Calculate transit elevation and if it is greater than the
1448             # requested "horizon" use an iterative technique to find the
1449             # set time.
1450 2 50       11 if ($self->transit_el > $horizon) {
1451 2 50       11 my $oldtime = ( $self->has_datetime ? $self->datetime : undef);
1452 2         13 my $mt = $self->meridian_time();
1453 2         11 $self->datetime( $mt );
1454 2 50       12 print "# Calculating iterative set time for HA determination\n"
1455             if $DEBUG;
1456 2         11 my $convok = $self->_iterative_el( $horizon, -1 );
1457 2 50       12 return undef unless $convok;
1458 2         8 my $seconds = $self->datetime->epoch - $mt->epoch;
1459 2         25 $cos_ha0 = cos( $seconds * Astro::PAL::DS2R );
1460 2         9 $self->datetime( $oldtime );
1461             }
1462             }
1463              
1464 3064 100       8831 return undef if abs($cos_ha0) > 1;
1465              
1466             # Work out the hour angle for this elevation
1467 2546         8180 my $ha0 = acos( $cos_ha0 );
1468              
1469             # If we are the Sun we need to convert this to solar time
1470             # time from sidereal time
1471 2546 100 100     19514 $ha0 *= 365.2422/366.2422
      66        
1472             unless (defined $self->name &&
1473             lc($self->name) eq 'sun' && $self->isa("Astro::Coords::Planet"));
1474              
1475              
1476             # print "HA 0 is $ha0\n";
1477             # print "#### in hours: ". ( $ha0 * Astro::PAL::DR2S / 3600)."\n";
1478              
1479             # return the result (converting if necessary)
1480             return Astro::Coords::Angle::Hour->new( $ha0, units => 'rad',
1481 2546         7502 range => 'PI')->in_format($opt{format});
1482              
1483             }
1484              
1485             =item B<meridian_time>
1486              
1487             Calculate the meridian time for this target (the time at which
1488             the source transits).
1489              
1490             MT(UT) = apparent RA - LST(UT=0)
1491              
1492             By default the next transit following the current time is calculated and
1493             returned as a C<Time::Piece> or C<DateTime> object (depending on what
1494             is stored in C<datetime>).
1495              
1496             If you want control over which transit should be calculated this can be
1497             specified using the "event" hash key:
1498              
1499             $mt = $c->meridian_time( event => 1 );
1500             $mt = $c->meridian_time( event => 0 );
1501             $mt = $c->meridian_time( event => -1 );
1502              
1503             A value of "1" indicates the next transit following the current
1504             reference time (this is the default behaviour for reasons of backwards
1505             compatibility). A value of "-1" indicates that the closest transit
1506             event before the reference time should be used. A valud of "0"
1507             indicates that the nearest transit event should be returned. If the parameter
1508             value is not one of the above, it will default to "1".
1509              
1510             A synonym for event=>0 is provided by using the "nearest" key. If
1511             present and true, this key overrides "event". If present and false the
1512             "event" key is used (defaulting to "1" if event indicates "nearest"=1).
1513              
1514             $mt = $c->meridian_time( nearest => 1 );
1515              
1516             =cut
1517              
1518             sub meridian_time {
1519 2564     2564 1 18008 my $self = shift;
1520 2564         6374 my %opt = @_;
1521              
1522             # extract the true value of "event" given all the backwards compatibility
1523             # problems
1524 2564         6953 my $event = $self->_extract_event( %opt );
1525              
1526             # Get the current time (do not modify it since we need to put it back)
1527 2564 50       5253 my $oldtime = ( $self->has_datetime ? $self->datetime : undef);
1528              
1529             # Need a reference time for the calculation
1530 2564         5237 my $reftime = $self->datetime;
1531              
1532             # For fast moving objects such as planets, we need to calculate
1533             # the transit time iteratively since the apparent RA/Dec will change
1534             # slightly during the night so we need to adjust the internal clock
1535             # to get it close to the actual transit time. We also need to make sure
1536             # that we are starting at the correct reference time so start at the
1537             # current time and look forward until we get a transit time > than
1538             # our start time
1539              
1540             # calculate the required times
1541 2564         3550 my $mtime;
1542 2564 100 100     7607 if ($event == 1 || $event == -1) {
    50          
1543             # previous or next
1544 2561         6754 $mtime = $self->_calc_mtime( $reftime, $event );
1545              
1546             # Reset the clock
1547 2561         7047 $self->datetime( $oldtime );
1548              
1549             } elsif ($event == 0) {
1550             # nearest requires both
1551 3         8 my $prev = $self->_calc_mtime( $reftime, -1 );
1552 3         13 my $next = $self->_calc_mtime( $reftime, 1 );
1553              
1554             # Reset the clock (before we possibly croak)
1555 3         22 $self->datetime( $oldtime );
1556              
1557             # calculate the diff
1558 3 50 33     46 if (defined $prev && defined $next) {
    0          
    0          
1559 3         13 my $prev_diff = abs( $reftime->epoch - $prev->epoch );
1560 3         34 my $next_diff = abs( $reftime->epoch - $next->epoch );
1561              
1562 3 50       31 if ($prev_diff < $next_diff) {
1563 3         11 $mtime = $prev;
1564             } else {
1565 0         0 $mtime = $next;
1566             }
1567              
1568             } elsif (defined $prev) {
1569 0         0 $mtime = $prev;
1570             } elsif (defined $next) {
1571 0         0 $mtime = $next;
1572             } else {
1573 0         0 croak "Should not occur in meridian_time\n";
1574             }
1575             } else {
1576 0         0 croak "Unrecognized value for event: $event\n";
1577             }
1578              
1579 2564         7101 return $mtime;
1580             }
1581              
1582             sub _calc_mtime {
1583 1075     1075   1671 my $self = shift;
1584 1075         2377 my ($reftime, $event ) = @_;
1585              
1586             # event must be 1 or -1
1587 1075 50 66     4352 if (!defined $event || ($event != 1 && $event != -1)) {
      33        
1588 0         0 croak "Event must be either +1 or -1";
1589             }
1590              
1591             # do we have DateTime objects
1592 1075         2055 my $dtime = $self->_isdt();
1593              
1594             # Somewhere to store the previous time so we can make sure things
1595             # are iterating nicely
1596 1075         1772 my $prevtime;
1597              
1598             # The current best guess of the meridian time
1599             my $mtime;
1600              
1601             # Number of times we want to loop before aborting
1602 1075         1433 my $max = 10;
1603              
1604             # Tolerance for good convergence
1605 1075         1530 my $tol = 1;
1606              
1607             # Increment (in hours) to jump forward each loop
1608             # Need to make sure we lock onto the correct transit so I'm
1609             # wary of jumping forward by exactly 24 hours
1610 1075         1592 my $inc = 12 * $event;
1611 1075 100 66     2877 $inc /= 2 if (defined $self->name && lc($self->name) eq 'moon');
1612              
1613             # Loop until mtime is greater than the reftime
1614             # and (mtime - prevtime) is smaller than a second
1615             # and we have not looped more than $max times
1616             # There is probably an analytical solution. The problem is that
1617             # the apparent RA depends on the current time yet the apparent RA
1618             # varies with time
1619 1075         1948 my $count = 0;
1620 1075 50       2211 print "Looping..............".$reftime->datetime."\n" if $DEBUG;
1621 1075         2273 while ( $count <= $max ) {
1622 3978         22749 $count++;
1623              
1624 3978 100       7619 if (defined $mtime) {
1625 2903         6128 $prevtime = _clone_time( $mtime );
1626 2903         40319 $self->datetime( $mtime );
1627             }
1628 3978         8736 $mtime = $self->_local_mtcalc();
1629 3978 50       3561707 print "New meridian time: ".$mtime->datetime ."\n" if $DEBUG;
1630              
1631             # make sure we are going the correct way
1632             # since we are enforced to only find a transit in the direction
1633             # governed by "event"
1634              
1635             # Calculate the difference in epoch seconds before the current
1636             # object reference time and the calculate transit time.
1637             # Use ->epoch rather than overload since I'm having problems
1638             # with Duration objects
1639 3978         11266 my $diff = $reftime->epoch - $mtime->epoch;
1640 3978         43836 $diff *= $event; # make the comparison correct sense
1641 3978 100       9091 if ($diff > 0) {
1642 1048 50       2547 print "Need to offset....[diff = $diff sec]\n" if $DEBUG;
1643             # this is an earlier transit time
1644             # Need to keep jumping forward until we lock on to a meridian
1645             # time that ismore recent than the ref time
1646 1048 100       2364 if ($dtime) {
1647 1023         2847 $mtime->add( hours => ($count * $inc));
1648             } else {
1649 25         72 $mtime = $mtime + ($count * $inc * Time::Seconds::ONE_HOUR);
1650             }
1651             }
1652              
1653             # End loop if the difference between meridian time and calculated
1654             # previous time is less than the acceptable tolerance
1655 3978 100 66     887080 if (defined $prevtime && defined $mtime) {
1656 2903 100       6877 last if (abs($mtime->epoch - $prevtime->epoch) <= $tol);
1657             }
1658             }
1659              
1660             # warn if we did not converge
1661 1075 50       10388 warnings::warnif "Meridian time calculation failed to converge"
1662             if $count > $max;
1663              
1664             # return the time
1665 1075         3739 return $mtime;
1666             }
1667              
1668             # Returns true if
1669             # time - reftime is negative
1670              
1671             # Returns RA-LST added on to reference time
1672             sub _local_mtcalc {
1673 5470     5470   7586 my $self = shift;
1674              
1675             # Now calculate the offset from the RA of the source.
1676             # Note that RA should be apparent RA and so the time should
1677             # match the actual time stored in the object.
1678             # Make sure the LST and Apparent RA are -PI to +PI
1679             # so that we do not jump whole days
1680 5470         10916 my $lst = Astro::PAL::palDrange($self->_lst);
1681 5470         12952 my $ra_app = Astro::PAL::palDrange( $self->ra_app );
1682 5470         15806 my $offset = $ra_app - $lst;
1683              
1684             # This is in radians. Need to convert it to seconds
1685 5470         8681 my $offset_sec = $offset * Astro::PAL::DR2S;
1686              
1687             # print "LST: $lst\n";
1688             # print "RA App: ". $self->ra_app ."\n";
1689             # print "Offset radians: $offset\n";
1690             # print "Offset seconds: $offset_sec\n";
1691              
1692             # If we are not the Sun we need to convert this to sidereal
1693             # time from solar time
1694 5470 100 100     12219 $offset_sec *= 365.2422/366.2422
      66        
1695             unless (defined $self->name &&
1696             lc($self->name) eq 'sun' && $self->isa("Astro::Coords::Planet"));
1697              
1698 5470         13339 my $datetime = $self->datetime;
1699 5470 100       12269 if ($self->_isdt()) {
1700 5370         13937 return $datetime->clone->add( seconds => $offset_sec );
1701             } else {
1702 100         309 return ($datetime + $offset_sec);
1703             }
1704              
1705             # return $mtime;
1706             }
1707              
1708             =item B<transit_el>
1709              
1710             Elevation at transit. This is just the elevation at Hour Angle = 0.0.
1711             (ie at C<meridian_time>).
1712              
1713             Format is supported as for the C<el> method. See L<"NOTES"> for
1714             details on the supported format specifiers and default calling
1715             convention.
1716              
1717             $el = $c->transit_el( format => 'deg' );
1718              
1719             =cut
1720              
1721             sub transit_el {
1722 8     8 1 30 my $self = shift;
1723              
1724             # Get meridian time
1725 8         40 my $mtime = $self->meridian_time();
1726              
1727             # Cache the current time if required
1728             # Note that we can leave $cache as undef if there is no
1729             # real time.
1730 8         24 my $cache;
1731 8 50       27 $cache = $self->datetime if $self->has_datetime;
1732              
1733             # set the new time
1734 8         57 $self->datetime( $mtime );
1735              
1736             # calculate the elevation
1737 8         61 my $el = $self->el( @_ );
1738              
1739             # fix the time back to what it was (including an undef value
1740             # if we did not read the cache).
1741 8         49 $self->datetime( $cache );
1742              
1743 8         50 return $el;
1744             }
1745              
1746             =item B<apply_offset>
1747              
1748             Applies the offsets of an C<Astro::Coords::Offset> object.
1749              
1750             my $coords_offset = $coords->apply_offset($offset);
1751              
1752             The current implementation works by calling C<radec2000> or C<glonglat>
1753             on the original object and will return a new C<Astro::Coords::Equatorial>
1754             object.
1755              
1756             =cut
1757              
1758             sub apply_offset {
1759 7     7 1 23 my $self = shift;
1760 7         10 my $offset = shift;
1761              
1762             # Check that the current implementation can handle the situation.
1763              
1764 7 50       25 croak 'apply_offet: argument should be an Astro::Coords::Offset object'
1765             unless UNIVERSAL::isa($offset, 'Astro::Coords::Offset');
1766              
1767 7 50       19 croak 'apply_offset: can only use TAN projection'
1768             unless $offset->projection() eq 'TAN';
1769              
1770 7         12 my $coordsoffset = undef;
1771              
1772 7 100       16 if ($offset->system() eq 'J2000') {
    50          
1773             # Apply offsets to J2000 coordinates and create a new object from them.
1774              
1775 5         14 my ($dra, $ddec) = map {$_->radians()} $offset->offsets_rotated();
  10         21  
1776              
1777             my ($ra, $dec) = Astro::PAL::palDtp2s($dra, $ddec,
1778 5         27 map {$_->radians()} $self->radec2000());
  10         54  
1779              
1780 5         22 $coordsoffset = new Astro::Coords(type => 'J2000',
1781             ra => $ra,
1782             dec => $dec,
1783             units => 'radians');
1784             }
1785             elsif ($offset->system() eq 'GAL') {
1786             # Apply offsets to galactic coordinates and create a new object from them.
1787              
1788 2         7 my ($dlon, $dlat) = map {$_->radians()} $offset->offsets_rotated();
  4         9  
1789              
1790             my ($lon, $lat) = Astro::PAL::palDtp2s($dlon, $dlat,
1791 2         13 map {$_->radians()} $self->glonglat());
  4         20  
1792              
1793 2         10 $coordsoffset = new Astro::Coords(type => 'GAL',
1794             long => $lon,
1795             lat => $lat,
1796             units => 'radians');
1797             }
1798             else {
1799 0         0 croak 'apply_offset: can only use J2000 or GAL system';
1800             }
1801              
1802 7 50       19 croak 'apply_offset: failed to create object' unless defined $coordsoffset;
1803              
1804             # Copy across additional data.
1805              
1806 7         18 foreach my $method (qw/name telescope datetime comment/) {
1807 28         82 my $value = $self->$method();
1808 28 100       1997 next unless defined $value;
1809 20 100 66     58 next if $method eq 'comment' && $value eq '';
1810 13         30 $coordsoffset->$method($value);
1811             }
1812              
1813 7         52 return $coordsoffset;
1814             }
1815              
1816             =back
1817              
1818             =head2 Velocities
1819              
1820             This sections describes the available methods for determining the velocities
1821             of each of the standard velocity frames in the direction of the reference
1822             target relative to the current observer position and reference time.
1823              
1824             =over 4
1825              
1826             =item B<rv>
1827              
1828             Return the radial velocity of the target (not the observer) in km/s.
1829             This will be used for parallax corrections (if relevant) and for
1830             calculating the doppler correction factor.
1831              
1832             $rv = $c->rv();
1833              
1834             If the velocity was originally specified as a redshift it will be
1835             returned here as optical velocity (and may not be a physical value).
1836              
1837             If no radial velocity has been specified, returns 0 km/s.
1838              
1839             =cut
1840              
1841             sub rv {
1842 10     10 1 17 my $self = shift;
1843 10 100       40 return (defined $self->{RadialVelocity} ? $self->{RadialVelocity} : 0 );
1844             }
1845              
1846             # internal set routine
1847             sub _set_rv {
1848 3     3   6 my $self = shift;
1849 3         7 $self->{RadialVelocity} = shift;
1850             }
1851              
1852             =item B<redshift>
1853              
1854             Redshift is defined as the optical velocity as a fraction of the speed of light:
1855              
1856             v(opt) = c z
1857              
1858             Returns the reshift if the velocity definition is optical. If the
1859             velocity definition is radio, redshift can only be calculated for
1860             small radio velocities. An attempt is made to calculate redshift from
1861             radio velocity using
1862              
1863             v(opt) = v(radio) / ( 1 - v(radio) / c )
1864              
1865             but only if v(radio)/c is small. Else returns undef.
1866              
1867             =cut
1868              
1869             sub redshift {
1870 2     2 1 8 my $self = shift;
1871 2         5 my $vd = $self->vdefn;
1872 2 50 33     16 if ($vd eq 'REDSHIFT' || $vd eq 'OPTICAL') {
    0          
1873 2         8 return ( $self->rv / CLIGHT );
1874             } elsif ($vd eq 'RELATIVISTIC') {
1875             # need to add
1876 0         0 return undef;
1877             } else {
1878 0         0 my $rv = $self->rv;
1879             # 1% of light speed
1880 0 0       0 if ( $rv > ( 0.01 * CLIGHT) ) {
1881 0         0 my $vopt = $rv / ( 1 - ( $rv / CLIGHT ) );
1882 0         0 return ( $vopt / CLIGHT );
1883             } else {
1884 0         0 return undef;
1885             }
1886             }
1887             }
1888              
1889             # internal set routine
1890             sub _set_redshift {
1891 1     1   3 my $self = shift;
1892 1         2 my $z = shift;
1893 1 50       2 $z = 0 unless defined $z;
1894 1         12 $self->_set_rv( CLIGHT * $z );
1895 1         5 $self->_set_vdefn( 'REDSHIFT' );
1896 1         5 $self->_set_vframe( 'HEL' );
1897             }
1898              
1899             =item B<vdefn>
1900              
1901             The velocity definition used to specify the target radial velocity.
1902             This is a readonly parameter set at object creation (depending on
1903             subclass) and can be one of RADIO, OPTICAL, RELATIVISTIC or REDSHIFT
1904             (which is really optical but specified in a different way).
1905              
1906             $vdefn = $c->vdefn();
1907              
1908             Required for calculating the doppler correction. Defaults to 'OPTICAL'.
1909              
1910             =cut
1911              
1912             sub vdefn {
1913 9     9 1 18 my $self = shift;
1914 9 50       30 return (defined $self->{VelocityDefinition} ? $self->{VelocityDefinition} : 'OPTICAL' );
1915             }
1916              
1917             # internal set routine
1918             sub _set_vdefn {
1919 2     2   2 my $self = shift;
1920 2         4 my $defn = shift;
1921             # undef resets to default
1922 2 50       5 if (defined $defn) {
1923 2         18 $defn = $self->_normalise_vdefn( $defn );
1924             }
1925 2         7 $self->{VelocityDefinition} = $defn;
1926             }
1927              
1928             =item B<vframe>
1929              
1930             The velocity frame used to specify the radial velocity. This attribute
1931             is readonly and set during object construction. Abbreviations are used
1932             for the first 3 characters of the standard frames (4 to distinguish
1933             LSRK from LSRD):
1934              
1935             HEL - Heliocentric (the Sun)
1936             BAR - Barycentric (the Solar System barycentre)
1937             GEO - Geocentric (Centre of the Earth)
1938             TOP - Topocentric (Surface of the Earth)
1939             LSR - Kinematical Local Standard of Rest
1940             LSRK - As for LSR
1941             LSRD - Dynamical Local Standard of Rest
1942              
1943             The usual definition for star catalogues is Heliocentric. Default is
1944             Heliocentric.
1945              
1946             =cut
1947              
1948             sub vframe {
1949 11     11 1 21 my $self = shift;
1950 11 100       46 return (defined $self->{VelocityFrame} ? $self->{VelocityFrame} : 'HEL' );
1951             }
1952              
1953             # internal set routine
1954             sub _set_vframe {
1955 2     2   2 my $self = shift;
1956 2         4 my $frame = shift;
1957 2 50       5 if (defined $frame) {
1958             # undef resets to default
1959 2         4 $frame = $self->_normalise_vframe( $frame );
1960             }
1961 2         6 $self->{VelocityFrame} = $frame;
1962             }
1963              
1964             =item B<obsvel>
1965              
1966             Calculates the observed velocity of the target as seen from the
1967             observer's location. Includes both the observer velocity and target
1968             velocity.
1969              
1970             $rv = $c->obsvel;
1971              
1972             Note that the source velocity and observer velocity are simply added
1973             without any regard for relativistic effects for high redshift sources.
1974              
1975             =cut
1976              
1977             sub obsvel {
1978 3     3 1 7 my $self = shift;
1979 3         8 my $vdefn = $self->vdefn;
1980 3         6 my $vframe = $self->vframe;
1981 3         6 my $rv = $self->rv;
1982              
1983             # Now we need to calculate the observer velocity in the
1984             # target frame
1985 3         8 my $vobs = $self->vdiff( '', 'TOP' );
1986              
1987             # Total velocity between observer and target
1988 3         6 my $vtotal = $vobs + $rv;
1989              
1990 3         9 return $vtotal;
1991             }
1992              
1993             =item B<doppler>
1994              
1995             Calculates the doppler factor required to correct a rest frequency to
1996             an observed frequency. This correction is calculated for the observer
1997             location and specified date and uses the velocity definition provided
1998             to the object constructor. Both the observer radial velocity, and the
1999             target radial velocity are taken into account (see the C<obsvel>
2000             method).
2001              
2002             $dopp = $c->doppler;
2003              
2004             Default definitions and frames will be used if none were specified.
2005              
2006             The doppler factors (defined as frequency/rest frequency or
2007             rest wavelength / wavelength) are calculated as follows:
2008              
2009             RADIO: 1 - v / c
2010              
2011             OPTICAL 1 - v / ( v + c )
2012              
2013             REDSHIFT ( 1 / ( 1 + z ) ) * ( 1 - v(hel) / ( v(hel) + c ) )
2014              
2015             ie in order to observe a line in the astronomical target, multiply the
2016             rest frequency by the doppler correction to select the correct frequency
2017             at the telescope to tune the receiver.
2018              
2019             For high velocity optical sources ( v(opt) << c ) and those sources
2020             specified using redshift, the doppler correction is properly
2021             calculated by first correcting the rest frequency to a redshifted
2022             frequency (dividing by 1 + z) and then separately correcting for the
2023             telescope motion relative to the new redshift corrected heliocentric
2024             rest frequency. The REDSHIFT equation, above, is used in this case and
2025             is used if the source radial velocity is > 0.01 c. ie the Doppler
2026             correction is calculated for a source at 0 km/s Heliocentric and
2027             combined with the redshift correction.
2028              
2029             The Doppler correction is invalid for large radio velocities.
2030              
2031             =cut
2032              
2033             sub doppler {
2034 2     2 1 4 my $self = shift;
2035 2         10 my $vdefn = $self->vdefn;
2036 2         9 my $obsvel = $self->obsvel;
2037              
2038             # Doppler correction depends on definition
2039 2         3 my $doppler;
2040 2 100 33     15 if ( $vdefn eq 'RADIO' ) {
    50          
    0          
2041 1         10 $doppler = 1 - ( $obsvel / CLIGHT );
2042             } elsif ( $vdefn eq 'OPTICAL' || $vdefn eq 'REDSHIFT' ) {
2043 1 50       3 if ( $obsvel > (0.01 * CLIGHT)) {
2044             # Relativistic velocity
2045             # First calculate the redshift correction
2046 1         4 my $zcorr = 1 / ( 1 + $self->redshift );
2047              
2048             # Now the observer doppler correction to Heliocentric frame
2049 1         3 my $vhel = $self->vhelio;
2050 1         4 my $obscorr = 1 - ( $vhel / ( CLIGHT * $vhel) );
2051              
2052 1         2 $doppler = $zcorr * $obscorr;
2053              
2054             } else {
2055             # small radial velocity, use standard doppler formula
2056 0         0 $doppler = 1 - ( $obsvel / ( CLIGHT + $obsvel ) );
2057             }
2058             } elsif ( $vdefn eq 'RELATIVISTIC' ) {
2059             # do we need to use the same correction as for OPTICAL and REDSHIFT?
2060             # presumably
2061 0         0 $doppler = sqrt( ( CLIGHT - $obsvel ) / ( CLIGHT + $obsvel ) );
2062             } else {
2063 0         0 croak "Can not calculate doppler correction for unsupported definition $vdefn\n";
2064             }
2065 2         238 return $doppler;
2066             }
2067              
2068             =item B<vdiff>
2069              
2070             Simple wrapper around the individual velocity methods (C<vhelio>, C<vlsrk> etc)
2071             to report the difference in velocity between two arbitrary frames.
2072              
2073             $vd = $c->vdiff( 'HELIOCENTRIC', 'TOPOCENTRIC' );
2074             $vd = $c->vdiff( 'HEL', 'LSRK' );
2075              
2076             Note that the velocity methods all report their velocity relative to the
2077             observer (ie topocentric correction), equivalent to specifiying 'TOP'
2078             as the second argument to vdiff.
2079              
2080             The two arguments are mandatory but if either are 'undef' they are converted
2081             to the target velocity frame (see C<vdefn> method).
2082              
2083             The second example is simply equivalent to
2084              
2085             $vd = $c->vhelio - $c->vlsrk;
2086              
2087             but the usefulness of this method really comes into play when defaulting to
2088             the target frame since it removes the need for logic in the main program.
2089              
2090             $vd = $c->vdiff( 'HEL', '' );
2091              
2092             =cut
2093              
2094             sub vdiff {
2095 4     4 1 7 my $self = shift;
2096 4   66     12 my $f1 = ( shift || $self->vframe );
2097 4   33     11 my $f2 = ( shift || $self->vframe );
2098              
2099             # convert the arguments to standardised frames
2100 4         12 $f1 = $self->_normalise_vframe( $f1 );
2101 4         20 $f2 = $self->_normalise_vframe( $f2 );
2102              
2103 4 50       26 return 0 if $f1 eq $f2;
2104              
2105             # put all the supported answers in a hash relative to TOP
2106 4         6 my %vel;
2107 4         8 $vel{TOP} = 0;
2108 4         9 $vel{GEO} = $self->verot();
2109 4         18 $vel{HEL} = $self->vhelio;
2110 4         9 $vel{BAR} = $self->vbary;
2111 4         20 $vel{LSRK} = $self->vlsrk;
2112 4         11 $vel{LSRD} = $self->vlsrd;
2113 4         14 $vel{GAL} = $self->vgalc;
2114 4         33 $vel{LG} = $self->vlg;
2115              
2116             # now the difference is easy
2117 4         17 return ( $vel{$f1} - $vel{$f2} );
2118             }
2119              
2120             =item B<verot>
2121              
2122             The velocity component of the Earth's rotation in the direction of the
2123             target (in km/s).
2124              
2125             $vrot = $c->verot();
2126              
2127             Current time will be assumed if none is set. If no observer location
2128             is specified, the equator at 0 deg lat will be used.
2129              
2130             =cut
2131              
2132             sub verot {
2133 41     41 1 432 my $self = shift;
2134              
2135             # Local Sidereal Time
2136 41         80 my $lst = $self->_lst;
2137              
2138             # Observer location
2139 41         88 my $tel = $self->telescope;
2140 41 100       108 my $lat = (defined $tel ? $tel->lat : 0 );
2141              
2142             # apparent ra dec
2143 41         204 my ($ra, $dec) = $self->apparent();
2144              
2145 41         144 return Astro::PAL::palRverot( $lat, $ra, $dec, $lst );
2146             }
2147              
2148             =item B<vorb>
2149              
2150             Velocity component of the Earth's orbit in the direction of the target
2151             (in km/s) for the current date and time.
2152              
2153             $vorb = $c->vorb;
2154              
2155             By default calculates the velocity component relative to the Sun.
2156             If an optional parameter is true, the calculation will be relative to
2157             the solary system barycentre.
2158              
2159             $vorb = $c->vorb( $usebary );
2160              
2161             =cut
2162              
2163             sub vorb {
2164 36     36 1 62 my $self = shift;
2165 36         65 my $usebary = shift;
2166              
2167             # Earth velocity (and position)
2168 36         89 my ($vbr,$pbr,$vhr,$phr) = Astro::PAL::palEvp($self->_mjd_tt(), 2000.0);
2169              
2170             # Barycentric or heliocentric velocity?
2171 36 100       143 my @v = ( $usebary ? @$vbr : @$vhr );
2172              
2173             # Convert spherical source coords to cartesian
2174 36         114 my ($ra, $dec) = $self->radec;
2175 36         133 my @cart = Astro::PAL::palDcs2c($ra,$dec);
2176              
2177             # Velocity due to Earth's orbit is scalar product of the star position
2178             # with the Earth's velocity
2179 36         243 my $vorb = - Astro::PAL::palDvdv(\@cart,\@v)* AU2KM;
2180 36         342 return $vorb;
2181             }
2182              
2183             =item B<vhelio>
2184              
2185             Velocity of the observer with respect to the Sun in the direction of
2186             the target (ie the heliocentric frame). This is simply the sum of the
2187             component due to the Earth's orbit and the component due to the
2188             Earth's rotation.
2189              
2190             $vhel = $c->vhelio;
2191              
2192             =cut
2193              
2194             sub vhelio {
2195 31     31 1 59 my $self = shift;
2196 31         58 return ($self->verot + $self->vorb);
2197             }
2198              
2199             =item B<vbary>
2200              
2201             Velocity of the observer with respect to the Solar System Barycentre in
2202             the direction of the target (ie the barycentric frame). This is
2203             simply the sum of the component due to the Earth's orbit and the
2204             component due to the Earth's rotation.
2205              
2206             $vhel = $c->vbary;
2207              
2208             =cut
2209              
2210             sub vbary {
2211 5     5 1 10 my $self = shift;
2212 5         10 return ($self->verot + $self->vorb(1));
2213             }
2214              
2215             =item B<vlsrk>
2216              
2217             Velocity of the observer with respect to the kinematical Local Standard
2218             of Rest in the direction of the target.
2219              
2220             $vlsrk = $c->vlsrk();
2221              
2222             =cut
2223              
2224             sub vlsrk {
2225 7     7 1 24 my $self = shift;
2226 7         21 my ($ra, $dec) = $self->radec;
2227 7         24 return (Astro::PAL::palRvlsrk( $ra, $dec ) + $self->vhelio);
2228             }
2229              
2230             =item B<vlsrd>
2231              
2232             Velocity of the observer with respect to the dynamical Local Standard
2233             of Rest in the direction of the target.
2234              
2235             $vlsrd = $c->vlsrd();
2236              
2237             =cut
2238              
2239             sub vlsrd {
2240 11     11 1 19 my $self = shift;
2241 11         25 my ($ra, $dec) = $self->radec;
2242 11         36 return (Astro::PAL::palRvlsrd( $ra, $dec ) + $self->vhelio);
2243             }
2244              
2245             =item B<vgalc>
2246              
2247             Velocity of the observer with respect to the centre of the Galaxy
2248             in the direction of the target.
2249              
2250             $vlsrd = $c->vgalc();
2251              
2252             =cut
2253              
2254             sub vgalc {
2255 5     5 1 15 my $self = shift;
2256 5         12 my ($ra, $dec) = $self->radec;
2257 5         17 return (Astro::PAL::palRvgalc( $ra, $dec ) + $self->vlsrd);
2258             }
2259              
2260             =item B<vlg>
2261              
2262             Velocity of the observer with respect to the Local Group in the
2263             direction of the target.
2264              
2265             $vlsrd = $c->vlg();
2266              
2267             =cut
2268              
2269             sub vlg {
2270 5     5 1 11 my $self = shift;
2271 5         12 my ($ra, $dec) = $self->radec;
2272 5         26 return (Astro::PAL::palRvlg( $ra, $dec ) + $self->vhelio);
2273             }
2274              
2275             =back
2276              
2277             =begin __PRIVATE_METHODS__
2278              
2279             =head2 Private Methods
2280              
2281             The following methods are not part of the public interface and can be
2282             modified or removed for any release of this module.
2283              
2284             =over 4
2285              
2286             =item B<_lst>
2287              
2288             Calculate the LST for the current date/time and
2289             telescope and return it (in radians).
2290              
2291             If no date/time is specified the current time will be used.
2292             If no telescope is defined the LST will be from Greenwich.
2293              
2294             This is labelled as an internal routine since it is not clear whether
2295             the method to determine LST should be here or simply placed into
2296             C<DateTime>. In practice this simply calls the
2297             C<Astro::PAL::ut2lst> function with the correct args (and therefore
2298             does not need the MJD). It will need the longitude though so we
2299             calculate it here.
2300              
2301             No correction for DUT1 is applied.
2302              
2303             =cut
2304              
2305             sub _lst {
2306 11716     11716   17852 my $self = shift;
2307              
2308 11716         21870 my $cachedlst = $self->_cache_read_global_lst();
2309 11716 100       30984 return $cachedlst if defined $cachedlst;
2310              
2311 8758         16163 my $time = $self->datetime;
2312 8758         23982 my $tel = $self->telescope;
2313              
2314             # Get the longitude (in radians)
2315 8758 100       27688 my $long = (defined $tel ? $tel->long : 0.0 );
2316              
2317             # Seconds can be floating point.
2318 8758         67826 my $sec = $time->sec;
2319 8758 100       59440 if ($time->can("nanosecond")) {
2320 8649         18670 $sec += 1E-9 * $time->nanosecond;
2321             }
2322              
2323             # Return the first arg
2324             # Note that we guarantee a UT time representation
2325 8758         55303 my $lst = (Astro::PAL::ut2lst( $time->year, $time->mon,
2326             $time->mday, $time->hour,
2327             $time->min, $sec, $long))[0];
2328              
2329 8758         2140188 my $lstobject = new Astro::Coords::Angle::Hour( $lst, units => 'rad', range => '2PI');
2330              
2331 8758         25185 $self->_cache_write_global_lst($lstobject);
2332              
2333 8758         25701 return $lstobject;
2334             }
2335              
2336             =item B<_mjd_tt>
2337              
2338             Internal routine to retrieve the MJD in TT (Terrestrial time) rather than UTC time.
2339              
2340             =cut
2341              
2342             sub _mjd_tt {
2343 13641     13641   19995 my $self = shift;
2344 13641         30337 my $mjd = $self->datetime->mjd;
2345 13641         178404 my $offset = Astro::PAL::palDtt( $mjd );
2346 13641         22331 $mjd += ($offset / (86_400));
2347 13641         99471 return $mjd;
2348             }
2349              
2350             =item B<_clone_time>
2351              
2352             Internal routine to copy a Time::Piece or DateTime object
2353             into a new object for internal storage.
2354              
2355             $clone = _clone_time( $orig );
2356              
2357             =cut
2358              
2359             sub _clone_time {
2360 15259     15259   21912 my $input = shift;
2361 15259 100       29434 return unless defined $input;
2362              
2363 15256 100       73780 if (UNIVERSAL::isa($input, "Time::Piece")) {
    50          
2364 319         783 return Time::Piece::gmtime( $input->epoch );
2365             } elsif (UNIVERSAL::isa($input, "DateTime")) {
2366             # Use proper clone method
2367 14937         37092 return $input->clone;
2368             }
2369 0         0 return;
2370             }
2371              
2372             =item B<_inc_time>
2373              
2374             Internal routine to increment the supplied time by the supplied
2375             number of seconds (either as an integer or the native duration class).
2376              
2377             $date = _inc_time( $date, $seconds );
2378              
2379             Does return the date object since not all date objects can be modified inplace.
2380             Does not guarantee to return a new cloned object though.
2381              
2382             =cut
2383              
2384             sub _inc_time {
2385 25     25   44 my $date = shift;
2386 25         40 my $delta = shift;
2387              
2388             # convert to integer seconds
2389 25 50       112 $delta = $delta->seconds() if UNIVERSAL::can( $delta, "seconds" );
2390              
2391             # Check for Time::Piece, else assume DateTime
2392 25 50       122 if (UNIVERSAL::isa( $date, "Time::Piece" ) ) {
2393             # can not add time in place
2394 25         94 $date += $delta;
2395             } else {
2396             # increment the time (this happens in place)
2397 0 0       0 if (abs($delta) > 1) {
2398 0         0 $date->add( seconds => "$delta" );
2399             } else {
2400 0         0 $date->add( nanoseconds => ( $delta * 1E9 ) );
2401             }
2402             }
2403              
2404 25         1849 return $date;
2405             }
2406              
2407             =item B<_cmp_time>
2408              
2409             Internal routine to Compare two times (assuming the same class)
2410              
2411             $cmp = _cmp_time( $a, $b );
2412              
2413             Returns 1 if $a > $b (epoch)
2414             -1 if $a < $b (epoch)
2415             0 if $a == $b (epoch)
2416              
2417             Currently assumes epoch is enough for comparison and so works
2418             for both DateTime and Time::Piece objects.
2419              
2420             =cut
2421              
2422             sub _cmp_time {
2423 0     0   0 my $t1 = shift;
2424 0         0 my $t2 = shift;
2425 0 0       0 my $e1 = (defined $t1 ? $t1->epoch : 0);
2426 0 0       0 my $e2 = (defined $t2 ? $t2->epoch : 0);
2427 0         0 return $e1 <=> $e2;
2428             }
2429              
2430             =item B<_default_horizon>
2431              
2432             Returns the default horizon to use for rise/set calculations.
2433             Normally, a value is supplied to the relevant routines.
2434              
2435             In the base class, returns 0. Can be overridden by subclasses (in particular
2436             the moon and sun).
2437              
2438             =cut
2439              
2440             sub _default_horizon {
2441 10     10   18 return 0;
2442             }
2443              
2444             =item B<_rise_set_time>
2445              
2446             Return either the rise time or set time associated with a specific horizon.
2447              
2448             $time = $c->_rise_set_time( $rise, %opt );
2449              
2450             Where the options hash controls the horizon and whether the event
2451             should follow the reference time, come before the reference time or be
2452             the nearest event.
2453              
2454             Supported keys for event control are processed by the _extract_event
2455             private method.
2456              
2457             $rise should be true if the source is rising, and false if the source
2458             is setting.
2459              
2460             =cut
2461              
2462             sub _rise_set_time {
2463 3058     3058   4731 my $self = shift;
2464 3058         4333 my $rise = shift;
2465 3058         10079 my %opt = @_;
2466 3058         7574 my $period = $self->_sidereal_period();
2467              
2468             # Calculate the HA required for setting
2469 3058         8998 my $ha_set = $self->ha_set( %opt, format=> 'radians' );
2470 3058 100       10889 return if ! defined $ha_set;
2471              
2472             # extract the event information
2473 2540         7860 my $event = $self->_extract_event( %opt );
2474             croak "Unrecognized event specifier in set_time"
2475 2540 50       5652 unless grep {$_ == $event} (-1, 0, 1);
  7620         17396  
2476              
2477             # and convert to seconds
2478 2540         4405 $ha_set *= Astro::PAL::DR2S;
2479              
2480             # and thence to a duration if required
2481 2540 100       5377 if ($self->_isdt()) {
2482 2526         8176 $ha_set = new DateTime::Duration( seconds => $ha_set );
2483 2526         279405 $period = new DateTime::Duration( seconds => $period );
2484             }
2485              
2486             # Determine whether we have to remember the cache
2487 2540         235304 my $havetime = $self->has_datetime;
2488              
2489             # Need the requested horizon
2490             my $refel = (defined $opt{horizon} ? $opt{horizon} :
2491 2540 100       7544 $self->_default_horizon );
2492              
2493             # somewhere to store the times
2494 2540         3666 my @event;
2495 2540   100     7430 my $direction = $event || 1;
2496 2540         3298 my $event_time;
2497              
2498             # Do not bother with iterative method for comparisons if the
2499             # values differ by more than this amount. (Ideally this would
2500             # be tuned by source depending on how fast it moves.)
2501 2540         3705 my $safety_seconds = 3600;
2502              
2503             # To be able to defer interative calculations until necessary,
2504             # we need a data structure which can store the time and whether
2505             # it has been iterated. For convenience we can add the epoch and
2506             # end up with an arrayref like:
2507             #
2508             # $event = [$datetime_or_timepiece, $epoch_seconds, $iterated]
2509             #
2510             # So save the reference time in this format:
2511             #
2512             # Get the current time (do not modify it since we need to put it back)
2513 2540         4824 my $reftime = $self->datetime;
2514 2540         7437 $reftime = [$reftime, $reftime->epoch(), 1];
2515              
2516             # And define 2 convient subroutines for these arrayrefs:
2517             #
2518             # Subroutine to perform the iteration on the event and
2519             # check for convergence.
2520             my $iterate = sub {
2521 2645     2645   3914 my $estimate = shift;
2522 2645 50       5255 return if $estimate->[2];
2523 2645         7799 $self->datetime( $estimate->[0] );
2524 2645 100       9274 if ($self->_iterative_el( $refel, ($rise ? 1 : -1) )) {
    100          
2525 2644         5618 my $dt = $self->datetime();
2526 2644         12965 $estimate->[0] = $dt;
2527 2644         7248 $estimate->[1] = $dt->epoch();
2528 2644         15089 $estimate->[2] = 1;
2529             } else {
2530 1 50       6 print "**** Failed to converge\n" if $DEBUG;
2531 1         13 $estimate->[0] = undef;
2532 1         3 $estimate->[1] = undef;
2533 1         4 $estimate->[2] = 1;
2534             }
2535 2540         24976 };
2536              
2537             # Subroutine to compare two event arrayrefs. Iterate them
2538             # if they are within $safety_seconds.
2539             my $compare = sub {
2540 4848     4848   8632 my ($a, $b) = @_;
2541 4848 50 33     18066 return undef unless (defined $a->[0] and defined $b->[0]);
2542 4848 100 66     17296 return $a->[1] <=> $b->[1] if (abs($b->[1] - $a->[1]) > $safety_seconds)
      100        
2543             or ($b->[2] && $a->[2]);
2544 143 50       628 $iterate->($a) unless $a->[2];
2545 143 50       731 $iterate->($b) unless $b->[2];
2546 143 50 33     835 return undef unless (defined $a->[0] and defined $b->[0]);
2547 143         390 return $a->[1] <=> $b->[1];
2548 2540         8591 };
2549              
2550             # Calculate the set or rise time for the meridian time in the direction
2551             # in which we want to go, and then step the meridian time by one "day"
2552             # so that we can work out which set time to return. There is probably
2553             # an analytic short cut that can be used to decide whether the
2554             # correct set time will be related to the previous or next meridian
2555             # time.
2556              
2557             # Calculate the transit time
2558 2540 0       6005 print "Calculating " . ($direction == -1 ? "previous" : "next") .
    50          
2559             " meridian time\n" if $DEBUG;
2560              
2561 2540         6011 my $mt = $self->meridian_time( event => $direction );
2562              
2563             # First guestimate for the event
2564 2540 100       5255 if ($rise) {
2565 1270         4331 $event_time = $mt - $ha_set;
2566             } else {
2567 1270         4467 $event_time = $mt + $ha_set;
2568             }
2569              
2570 2540         2217237 $event[0] = [$event_time, $event_time->epoch(), 0];
2571              
2572 2540 0       24228 print "Determined approximate " . ($rise ? "rise" : "set") . " time of ".
    50          
2573             $event_time . "\n" if $DEBUG;
2574              
2575             # Change direction if we wanted the nearest,
2576             # or there was no real event the way we tried,
2577             # or we went the right way.
2578 2540 100       5984 if (! $event) {
2579 856         1915 $direction = - $direction
2580             } else {
2581 1684         4432 my $cmp = $compare->($event[0], $reftime);
2582 1684 100 66     7976 $direction = - $direction if (! defined $cmp)
2583             or ($cmp * $event > 0);
2584             }
2585              
2586 2540 0       5299 print 'Calculating meridian time stepped ' .
    50          
2587             ($direction == -1 ? 'back' : 'forward') . "\n" if $DEBUG;
2588              
2589 2540 100       4801 if ($direction > 0) {
2590 856         2615 $event_time = $event_time + $period;
2591             } else {
2592 1684         4836 $event_time = $event_time - $period;
2593             }
2594              
2595 2540         2275380 $event[1] = [$event_time, $event_time->epoch(), 0];
2596              
2597             # store the event time
2598 2540 100       24720 if ($direction < 0) {
2599 1684         3179 @event = reverse @event;
2600             }
2601              
2602 2540 0       5362 print "Determined approximate " . ($rise ? "rise" : "set") . " time of ".
    50          
2603             $event_time . "\n" if $DEBUG;
2604              
2605             # choose a value
2606 2540         3945 $event_time = undef;
2607              
2608 2540 100       5292 unless ($event) {
2609             # Need to find the "nearest" event.
2610             # First check neither is already undefined.
2611 856 50 33     4459 unless (defined $event[0]->[0] and defined $event[1]->[0]) {
2612 0 0       0 if (defined $event[0]->[0]) {
    0          
2613 0         0 $iterate->($event[0]);
2614 0         0 $event_time = $event[0]->[0];
2615             }
2616             elsif (defined $event[1]->[0]) {
2617 0         0 $iterate->($event[1]);
2618 0         0 $event_time = $event[1]->[0];
2619             }
2620             } else {
2621             # Otherwise we can safely compute the differences.
2622 856         1752 my @diff = map {abs($_->[1] - $reftime->[1])} @event;
  1712         4670  
2623 856 100       2570 if (abs($diff[0] - $diff[1]) < $safety_seconds) {
2624             # Too close to call based on the estimated times, so iterate both.
2625 35         139 $iterate->($_) foreach grep {not $_->[2]} @event;
  70         268  
2626              
2627 35 50       284 if (defined $event[0]->[0]) {
    0          
2628 35 50       142 if (defined $event[1]->[0]) {
2629             # Both events were real, need to compare again.
2630 35         120 @diff = map {abs($_->[1] - $reftime->[1])} @event;
  70         237  
2631 35 100       155 $event_time = $diff[0] < $diff[1]
2632             ? $event[0]->[0]
2633             : $event[1]->[0];
2634             } else {
2635             # Only the first event was real, so return it.
2636 0         0 $event_time = $event[0]->[0];
2637             }
2638             }
2639             elsif (defined $event[1]->[0]) {
2640             # Only the second event was real, so return it.
2641 0         0 $event_time = $event[1]->[0];
2642             }
2643             }
2644             else {
2645             # Differences were sufficiently close to decide immediately.
2646 821 100       1919 my $closer = $diff[0] < $diff[1] ? 0 : 1;
2647 821         2483 $iterate->($event[$closer]);
2648              
2649             # However we need to check if it wasn't real, and try the other one.
2650 821 50       2780 unless (defined $event[$closer]->[0]) {
2651 0         0 $closer = 1 - $closer;
2652 0         0 $iterate->($event[$closer]);
2653             }
2654              
2655             # If the other one wasn't real either, we want
2656             # to return undef anyway, so no need to check at this point.
2657 821         1853 $event_time = $event[$closer]->[0];
2658             }
2659             }
2660             } else {
2661             # For "before", we want the nearest time that is earlier than the
2662             # reference time, so reverse the list to start from the end
2663             # the end and jump out when we find an event that is before the
2664             # reference time. For "after" we do not reverse the list.
2665 1684 100       4491 @event = reverse @event if $event == -1;
2666              
2667 1684         3706 for my $t (@event) {
2668 3164 50       6891 next unless defined $t->[0];
2669 3164         5546 my $cmp = $compare->($t, $reftime);
2670 3164 50       6460 next unless defined $cmp;
2671 3164 100 66     9260 if ($cmp == $event or $cmp == 0) {
2672 1684 100       5711 $iterate->($t) unless $t->[2];
2673 1684         3145 $event_time = $t->[0];
2674 1684         3246 last;
2675             }
2676             }
2677             }
2678              
2679             # and restore the reference date to reset the clock
2680 2540 50       8357 $self->datetime( ( $havetime ? $reftime->[0] : undef ) );
2681              
2682 2540         56153 return $event_time;
2683             }
2684              
2685              
2686             =item B<_iterative_el>
2687              
2688             Use an iterative technique to calculate the time the object passes through
2689             a specified elevation. This routine is used for non-sidereal objects (especially
2690             the moon and fast asteroids) where a simple calculation assuming a sidereal
2691             object may lead to inaccuracies of a few minutes (maybe even 10s of minutes).
2692             It is called by both C<set_time> and C<rise_time> to converge on an accurate
2693             time of elevation.
2694              
2695             $status = $self->_iterative_el( $refel, $grad );
2696              
2697             The required elevation must be supplied (in radians). The second
2698             argument indicates whether we are looking for a solution with a
2699             positive (source is rising) or negative (source is setting)
2700             gradient. +1 indicates a rising source, -1 indicates a setting source.
2701              
2702             On entry, the C<datetime> method must return a time that is to be used
2703             as the starting point for convergence (the closer the better) On exit,
2704             the C<datetime> method will return the calculated time for that
2705             elevation.
2706              
2707             The algorithm used for this routine is very simple. Try not to call it
2708             repeatedly.
2709              
2710             Returns undef if the routine did not converge.
2711              
2712             WARNING: this method is overriden by one in Astro::Coords::Equatorial
2713             if there is no proper motion of parallax. The overriding method assumes
2714             that the starting point has been accurately calculated and therefore
2715             does nothing. As a result this method should not be called without
2716             a very good starting point in the case of equatorial coordinates.
2717              
2718             =cut
2719              
2720             sub _iterative_el {
2721 1101     1101   1929 my $self = shift;
2722 1101         1859 my $refel = shift;
2723 1101         1551 my $grad = shift;
2724              
2725             # See what type of date object we are dealing with
2726 1101         2309 my $use_dt = $self->_isdt();
2727              
2728             # We are tweaking DateTime objects without the cache knowing about
2729             # it (because it is faster than lots of object cloning) so we must
2730             # turn off caching
2731 1101         2218 my $old_unsafe = $self->datetime_is_unsafe;
2732 1101         2453 $self->datetime_is_unsafe( 1 );
2733              
2734             # Calculate current elevation
2735 1101         2555 my $el = $self->el;
2736              
2737             # Tolerance (in arcseconds)
2738 1101         2568 my $tol = 5 * Astro::PAL::DAS2R;
2739              
2740             # smallest support increment
2741 1101         1669 my $smallinc = 0.2;
2742              
2743             # Get the estimated time for this elevation
2744 1101         2272 my $time = $self->datetime;
2745 1101         3340 my $epoch = $time->epoch();
2746              
2747             # now compare the requested elevation with the actual elevation for the
2748             # previously calculated rise time
2749 1101 100       7503 if (abs($el - $refel) > $tol ) {
2750 1054 50       2421 if ($DEBUG) {
2751 0         0 print "# ================================ -> ".$self->name."\n";
2752 0         0 print "# Requested elevation: " . (Astro::PAL::DR2D * $refel) ."\n";
2753 0         0 print "# Elevation out of range: ". $self->el(format => 'deg')."\n";
2754 0 0       0 print "# For " . ($grad > 0 ? "rise" : "set")." time: ". $time->datetime ."\n";
2755             }
2756              
2757             # use 1 minute for all except the moon
2758 1054         1589 my $inc = 60; # seconds
2759 1054 100 100     2507 $inc *= 10 if (defined $self->name && lc($self->name) eq 'moon');
2760 1054         2112 my $maxinc = $inc * 10;
2761              
2762 1054 100       2377 my $sign = (($el <=> $refel) != $grad ? 1 : -1); # incrementing or decrementing time
2763 1054         4720 my $prevel; # previous elevation
2764             my $prevepoch; # 'epoch' value of previous time
2765              
2766             # Indicate whether we have straddled the correct answer
2767 1054         0 my $has_been_high;
2768 1054         0 my $has_been_low;
2769 1054         0 my $has_straddled;
2770              
2771             # This is a very simple convergence algorithm.
2772             # Newton-Raphson is used until we straddle the value, given that the function
2773             # is almost linear for most elevations.
2774 1054         2244 while (abs($el-$refel) > $tol) {
2775 3435 100       7576 if (defined $prevel) {
2776              
2777             # should check sign of gradient to make sure we are not
2778             # running away to an incorrect gradient. Note that this function
2779             # can be highly curved if we are near ante transit.
2780              
2781             # There are two constraints that have to be used to control
2782             # the convergence
2783             # - make sure we are not diverging with each iteration
2784             # - make sure we are not bouncing around missing the final
2785             # value due to inaccuracies in the time resolution (this is
2786             # important for fast moving objects like the sun). Currently
2787             # we are not really doing anything useful by tweaking by less
2788             # than a second
2789              
2790             # The problem is that we can not stop on bracketing the correct
2791             # value if we are never going to reach the value itself because
2792             # it never rises or never sets
2793              
2794             # \ /
2795             # \ /
2796             # - <-- minimum elevation
2797             # <-- required elevation
2798              
2799             # in the above case each step will diverge but will never
2800             # straddle
2801              
2802             # The solution is to first iterate by using a divergence
2803             # criterion. Then, if we determine that we have straddled the
2804             # correct result, we switch to a stopping criterion that
2805             # uses the time resolution if we never fully converge (the
2806             # important thing is to return a time not an elevation)
2807              
2808 2381         5101 my $diff_to_prev = $prevel - $refel;
2809 2381         5106 my $diff_to_curr = $el - $refel;
2810              
2811             # set the straddle parameters
2812 2381 100       5675 if ($diff_to_curr < 0) {
2813 909         1411 $has_been_low = 1;
2814             } else {
2815 1472         2717 $has_been_high = 1;
2816             }
2817              
2818             # if we have straddled, stop on increment being small
2819              
2820             # now determine whether we should reverse
2821             # if we know we are bouncing around the correct value
2822             # we can reverse as soon as the previous el and the current el
2823             # are either side of the correct answer
2824 2381         3196 my $reverse;
2825 2381 100       4121 if ($has_straddled) {
2826             # we can abort if we are below the time resolution
2827 52 100       164 last if $inc < $smallinc;
2828              
2829             # reverse
2830 50 100       147 $reverse = 1 if ($diff_to_prev / $diff_to_curr < 0);
2831             } else {
2832             # abort if the inc is too small
2833 2329 100       5002 if ($inc < $smallinc) {
2834 1         134 $self->datetime_is_unsafe( $old_unsafe );
2835 1         13 return undef;
2836             }
2837              
2838 2328         3619 my $deltat = $epoch - $prevepoch;
2839 2328         4686 my $deltael = $el - $prevel;
2840 2328 100 66     9315 if (abs($deltat) > 0 and abs($deltael) > 0) {
2841             # in the linear approximation
2842             # we know the gradient
2843 2324         4088 my $gradient = $deltael / $deltat;
2844 2324         3777 my $newinc = - ($diff_to_curr) / $gradient;
2845 2324         3808 my $newsign = $newinc <=> 0;
2846 2324         3234 $newinc = abs($newinc);
2847              
2848 2324 100       4143 if ($newinc < $maxinc) {
2849 1938         2676 $sign = $newsign;
2850 1938         2623 $inc = $newinc;
2851 1938 50       4188 $inc = $smallinc if $inc < $smallinc;
2852             } else {
2853             # The gradient approach might be running away,
2854             # so duplicate the non-gradient behaviour instead.
2855 386 100       1086 $reverse = 1 if abs($diff_to_prev) < abs($diff_to_curr);
2856             }
2857             } else {
2858             # if we have not straddled, we reverse if the previous el
2859             # is closer than this el
2860 4 100       20 $reverse = 1 if abs($diff_to_prev) < abs($diff_to_curr);
2861             }
2862             }
2863              
2864             # Defer straddled test so we have one gradient-based step past straddling.
2865 2378   100     5625 $has_straddled = $has_been_low && $has_been_high;
2866              
2867             # reverse
2868 2378 100       4558 if ( $reverse ) {
2869              
2870             # the gap between the previous measurement and the reference
2871             # is smaller than the current gap. We seem to be diverging.
2872             # Change direction
2873 241         364 $sign *= -1;
2874             # and use half the step size
2875 241         387 $inc /= 3;
2876             }
2877             }
2878              
2879             # Now calculate a new time
2880 3432         5706 my $delta = $sign * $inc;
2881 3432 100       5819 if (!$use_dt) {
2882 36         95 $time = $time + $delta;
2883             # we have created a new object so need to store it for next time
2884             # round
2885 36         2178 $self->datetime( $time );
2886             } else {
2887             # increment the time (this happens in place so we do not need to
2888             # register the change with the datetime method
2889 3396 100       6213 if (abs($delta) > 1) {
2890 2835         15090 $time->add( seconds => "$delta" );
2891             } else {
2892 561         1884 $time->add( nanoseconds => ( $delta * 1E9 ) );
2893             }
2894              
2895             }
2896             # recalculate the elevation, storing the previous as reference
2897 3432         3168542 $prevel = $el;
2898 3432         11813 $el = $self->el;
2899 3432 50       10142 print "# New elevation: ". $self->el(format=>'deg')." \t@ ".$time->datetime." with delta $delta sec\n"
2900             if $DEBUG;
2901 3432         5480 $prevepoch = $epoch;
2902 3432         9767 $epoch = $time->epoch();
2903             }
2904              
2905             }
2906              
2907             # reset the unsafeness level
2908 1100         3643 $self->datetime_is_unsafe( $old_unsafe );
2909              
2910 1100         3711 return 1;
2911             }
2912              
2913             =item B<_isdt>
2914              
2915             Internal method. Returns true if the C<datetime> method contains a
2916             DateTime object. Returns false otherwise (assumed to be
2917             Time::Piece). If an optional argument is supplied that argument is
2918             tested instead.
2919              
2920             $isdt = $self->_isdt();
2921             $isdt = $self->_isdt( $dt );
2922              
2923             =cut
2924              
2925             sub _isdt {
2926 11678     11678   16334 my $self = shift;
2927 11678         15700 my $test = shift;
2928              
2929 11678 50       28467 $test = $self->datetime unless defined $test;
2930              
2931             # Check Time::Piece first since there is a possibility that
2932             # this is really a subclass of DateTime
2933 11678         16928 my $dtime;
2934 11678 100       58824 if ($test->isa( "Time::Piece")) {
    50          
2935 151         217 $dtime = 0;
2936             } elsif ($test->isa("DateTime")) {
2937 11527         16406 $dtime = 1;
2938             } else {
2939 0         0 croak "Unknown DateTime object class ". blessed($test);
2940             }
2941              
2942 11678         22778 return $dtime;
2943             }
2944              
2945             =item B<_extract_event>
2946              
2947             Parse an options hash to determine the correct value of the "event" key
2948             given backwards compatibility requirements.
2949              
2950             $event = $self->_extract_event( %opt );
2951              
2952             - "nearest" trumps "event" if "nearest" is true. Returns event=0
2953              
2954             - If "event" is not defined, it defaults to "1"
2955              
2956             - If "nearest" is false, "event" is used unless "event"=0 in
2957             which case "nearest" trumps "event" and returns "1".
2958              
2959             =cut
2960              
2961             sub _extract_event {
2962 5104     5104   7122 my $self = shift;
2963              
2964             # make sure the $opt{event} always exists
2965 5104         7385 my $default = 1;
2966 5104         13563 my %opt = (event => $default, @_);
2967              
2968 5104 100       10665 if (exists $opt{nearest}) {
2969 5 50       14 if ($opt{nearest}) {
2970             # request for nearest
2971 5         14 return 0;
2972             } else {
2973             # request for not nearest
2974 0 0       0 if ($opt{event} == 0) {
2975             # this is incompatible with nearest=0 so return default
2976 0         0 return $default;
2977             } else {
2978 0         0 return $opt{event};
2979             }
2980             }
2981              
2982             } else {
2983 5099         13415 return $opt{event};
2984             }
2985             }
2986              
2987             =item B<_normalise_vframe>
2988              
2989             Convert an input string representing a velocity frame, to
2990             a standardised form recognized by the software. In most cases,
2991             the string is upper cased and reduced two the first 3 characters.
2992             LSRK and LSRD are special-cased. LSR is converted to LSRK.
2993              
2994             $frame = $c->_normalise_vframe( $in );
2995              
2996             Unrecognized or undefined frames trigger an exception.
2997              
2998             =cut
2999              
3000             sub _normalise_vframe {
3001 10     10   13 my $self = shift;
3002 10         16 my $in = shift;
3003              
3004 10 50       26 croak "Velocity frame not defined. Can not normalise" unless defined $in;
3005              
3006             # upper case
3007 10         14 $in = uc( $in );
3008              
3009             # LSRK or LSRD need no normalisation
3010 10 100 100     42 return $in if ($in eq 'LSRK' || $in eq 'LSRD' || $in eq 'LG');
      66        
3011              
3012             # Truncate
3013 6         12 my $trunc = substr( $in, 0, 3 );
3014              
3015             # Verify
3016 6 50       28 croak "Unrecognized velocity frame '$trunc'"
3017             unless $trunc =~ /^(GEO|TOP|HEL|LSR|GAL|BAR)/;
3018              
3019             # special case
3020 6 100       13 $trunc = 'LSRK' if $trunc eq 'LSR';
3021              
3022             # okay
3023 6         14 return $trunc;
3024             }
3025              
3026             =item B<_normalise_vdefn>
3027              
3028             Convert an input string representing a velocity definition, to
3029             a standardised form recognized by the software. In all cases the
3030             string is truncated to 3 characters and upper-cased before validating
3031             against known types.
3032              
3033             $defn = $c->_normalise_vdefn( $in );
3034              
3035             Unrecognized or undefined frames trigger an exception.
3036              
3037             =cut
3038              
3039             sub _normalise_vdefn {
3040 2     2   4 my $self = shift;
3041 2         3 my $in = shift;
3042              
3043 2 50       4 croak "Velocity definition not defined. Can not normalise" unless defined $in;
3044              
3045             # upper case
3046 2         5 $in = uc( $in );
3047              
3048             # Truncate
3049 2         4 my $trunc = substr( $in, 0, 3 );
3050              
3051             # Verify
3052 2 100       14 if ($trunc eq 'RAD') {
    50          
    50          
    0          
3053 1         3 return 'RADIO';
3054             } elsif ($trunc eq 'OPT') {
3055 0         0 return 'OPTICAL';
3056             } elsif ($trunc eq 'RED') {
3057 1         4 return 'REDSHIFT';
3058             } elsif ($trunc eq 'REL') {
3059 0         0 return 'RELATIVISTIC';
3060             } else {
3061 0         0 croak "Unrecognized velocity definition '$trunc'";
3062             }
3063             }
3064              
3065             =item B<_parse_equinox>
3066              
3067             Given an equinox string of the form JYYYY.frac or BYYYY.frac
3068             return the epoch of the equinox and the system of the equinox.
3069              
3070             ($system, $epoch ) = $c->_parse_equinox( 'B1920.34' );
3071              
3072             If no leading letter, Julian epoch is assumed. If the string does not
3073             match the reuquired pattern, J2000 will be assumed and a warning will
3074             be issued.
3075              
3076             System is returned as 'FK4' for Besselian epoch and 'FK5' for
3077             Julian epoch.
3078              
3079             =cut
3080              
3081             sub _parse_equinox {
3082 116     116   191 my $self = shift;
3083 116         174 my $str = shift;
3084 116         265 my ($sys, $epoch) = ('FK5', 2000.0);
3085 116 50       773 if ($str =~ /^([BJ]?)(\d+(\.\d+)?)$/i) {
3086 116         327 my $typ = $1;
3087 116 100       277 $sys = ($typ eq 'B' ? 'FK4' : 'FK5' );
3088 116         255 $epoch = $2;
3089             } else {
3090 0         0 warnings::warnif( "Supplied equinox '$str' does not look like an equinox");
3091             }
3092 116         443 return ($sys, $epoch);
3093             }
3094              
3095             =item B<_j2000_to_byyyy>
3096              
3097             Since we always store in J2000 internally, converting between
3098             different Julian equinoxes is straightforward. This routine takes a
3099             J2000 coordinate pair (with proper motions and parallax already
3100             applied) and converts them to Besselian equinox for the current epoch.
3101              
3102             ($bra, $bdec) = $c->_j2000_to_BYYY( $equinox, $ra2000, $dec2000);
3103              
3104             The equinox is the epoch year. It is assumed to be Besselian.
3105              
3106             =cut
3107              
3108             sub _j2000_to_byyyy {
3109 2     2   4 my $self = shift;
3110 2         4 my ($equ, $ra2000, $dec2000) = @_;
3111              
3112             # First to 1950
3113 2         6 my ($rb, $db, $drb, $drd) = Astro::PAL::palFk54z($ra2000, $dec2000,
3114             Astro::PAL::palEpb( $self->_mjd_tt ) );
3115              
3116             # Then preces to reference epoch frame
3117             # I do not know whether fictitious proper motions should be included
3118             # here with palPm or whether it is enough to use non-1950 epoch
3119             # in palFk54z and then preces 1950 to the same epoch. Not enough test
3120             # data for this rare case.
3121 2 100       9 if ($equ != 1950) {
3122             # Add E-terms
3123 1         6 my ($rnoe, $dnoe) = Astro::PAL::palSubet( $rb, $db, 1950.0 );
3124              
3125             # preces
3126 1         8 ($rnoe, $dnoe) = Astro::PAL::palPreces( 'FK4', 1950, $equ, $rnoe, $dnoe);
3127              
3128             # Add E-terms
3129 1         5 ($rb, $db) = Astro::PAL::palAddet( $rnoe, $dnoe, $equ );
3130              
3131             }
3132 2         7 return ($rb, $db);
3133             }
3134              
3135             =item B<_sidereal_period>
3136              
3137             Returns the length of the source's "day" in seconds, i.e. how long it takes
3138             return to the same position in the sky. This implementation
3139             returns one sidereal day, but it must be overriden for fast-moving
3140             objects such as the Sun and the Moon.
3141              
3142             To be used internally for rise / set time calculation.
3143              
3144             =cut
3145              
3146             sub _sidereal_period {
3147 3488     3488   7082 return 24 * 3600 * 365.2422/366.2422;
3148             }
3149              
3150             =back
3151              
3152             =head2 Caching Routines
3153              
3154             These methods provide a means of caching calculated answers for a fixed
3155             epoch. It is usually faster to lookup a pre-calculated value for a given
3156             time than it is to calculate that time.
3157              
3158             The cache is per-object.
3159              
3160             =over 4
3161              
3162             =item B<_cache_write>
3163              
3164             Add the supplied values to the cache. Values can be provided in a hash.
3165             The choice of key names is up to the caller.
3166              
3167             $c->_cache_write( AZ => $az, EL => $el );
3168              
3169             Nothing is stored if the date stored in the object is not fixed.
3170              
3171             =cut
3172              
3173             sub _cache_write {
3174 25899     25899   39804 my $self = shift;
3175 25899         70726 my %add = @_;
3176              
3177 25899         54601 my $primary = $self->_cache_key;
3178 25899 100       80912 return () unless defined $primary;
3179              
3180 7741         15807 my $C = $self->_cache_ref;
3181              
3182 7741 100       15848 if (!exists $C->{$primary}) {
3183 4486         10305 $C->{$primary} = {};
3184             }
3185              
3186 7741         11971 my $local = $C->{$primary};
3187              
3188 7741         20074 for my $key (keys %add) {
3189 13851         26000 $local->{$key} = $add{$key};
3190             }
3191 7741         21945 return;
3192             }
3193              
3194             =item B<_cache_read>
3195              
3196             Retrieve value(s) from the cache. Returns undef if no value is available.
3197              
3198             ($az, $el) = $c->_cache_read( "AZ", "EL" );
3199              
3200             In scalar context, returns the first value.
3201              
3202             =cut
3203              
3204             sub _cache_read {
3205 53079     53079   71696 my $self = shift;
3206 53079         94614 my @keys = @_;
3207              
3208 53079         85029 my $primary = $self->_cache_key;
3209 53079 100       121347 return () unless defined $primary;
3210              
3211 21322         36670 my $C = $self->_cache_ref;
3212              
3213 21322 100       54684 return () unless exists $C->{$primary};
3214 7515         11862 my $local = $C->{$primary};
3215 7515 50       12923 return () unless defined $local;
3216              
3217 7515         14380 my @answer = map { $local->{$_} } @keys;
  7555         23039  
3218             # print "Caller: ". join(":", caller() ) ."\n";
3219             # print "Getting cache values for ". join(":",@keys) ."\n";
3220             # print "Getting cache values for ". join(":",@answer) ."\n";
3221 7515 100       21207 return (wantarray() ? @answer : $answer[0] );
3222             }
3223              
3224             =item B<_calc_cache_key>
3225              
3226             Internal (to the cache system) function for calculating the cache
3227             key for the current epoch.
3228              
3229             $key = $c->_calc_cache_key;
3230              
3231             Use the _cache_key() method to return the result.
3232              
3233             Caching is disabled if C<datetime_is_unsafe>, C<usenow> or no
3234             DateTime object is available.
3235              
3236             =cut
3237              
3238             sub _calc_cache_key {
3239 16067     16067   21914 my $self = shift;
3240             # return; # This will disable caching
3241              
3242             # if we have a floating clock we do not want to cache
3243 16067 100 66     30978 if (!$self->has_datetime || $self->usenow || $self->datetime_is_unsafe) {
      100        
3244 2658         6602 $self->_cache_key( undef );
3245 2658         4413 return;
3246             }
3247              
3248             # The cache key currently uses the time and the telescope name
3249 13409         30688 my $dt = $self->datetime;
3250 13409         27694 my $tel = $self->telescope;
3251 13409 100       42810 my $telName = (defined $tel ? $tel->name : "NONE" );
3252              
3253 13409 50       86259 print "# Calculating cache key using $telName and ". $dt->epoch ."\n"
3254             if $DEBUG;
3255              
3256             # usually epoch is quicker to generate than ISO date but we have to
3257             # be careful about fractional seconds in DateTime (Time::Piece can
3258             # not do it)
3259              
3260 13409         20776 my $addendum = "";
3261 13409 100       54599 $addendum = $dt->nanosecond if $dt->can("nanosecond");
3262              
3263             # Use date + telescope name as key
3264 13409         84928 $self->_cache_key($telName . "_" . $dt->epoch. $addendum);
3265             }
3266              
3267             =item B<_cache_key>
3268              
3269             Retrieve the current key suitable for caching results.
3270             The key should have been calculated using _calc_cache_key().
3271             Can be undef if caching is disabled.
3272              
3273             $key = $c->_cache_key;
3274              
3275             =cut
3276              
3277             sub _cache_key {
3278 115519     115519   228616 my $self = shift;
3279 115519 100       209036 if (@_) {
3280 16067         31659 $self->{_CACHE_KEY} = shift;
3281             }
3282 115519         204133 return $self->{_CACHE_KEY};
3283             }
3284              
3285              
3286 19     19   205067 use constant GLOBAL_CACHE_MAX => 1000;
  19         74  
  19         7355  
3287             our %_cache_global_lst = ();
3288             our @_cache_global_lst = ();
3289              
3290             =item B<_cache_read_global_lst>
3291              
3292             Retrieves a value from the global cache of LST values.
3293             Returns undef if no value is available.
3294              
3295             $lst = $self->_cache_read_global_lst();
3296              
3297             =cut
3298              
3299             sub _cache_read_global_lst {
3300 11716     11716   15896 my $self = shift;
3301 11716         19939 my $key = $self->_cache_key();
3302 11716 100       24659 return undef unless defined $key;
3303 7160         19407 return $_cache_global_lst{$key};
3304             }
3305              
3306             =item B<_cache_write_global_lst>
3307              
3308             Writes a value into the global cache of LST values.
3309              
3310             $self->_cache_write_global_lst($lst);
3311              
3312             =cut
3313              
3314             sub _cache_write_global_lst {
3315 8758     8758   12293 my $self = shift;
3316 8758         11388 my $value = shift;
3317 8758 50       16845 return unless defined $value;
3318 8758         16781 my $key = $self->_cache_key();
3319 8758 100       18666 return unless defined $key;
3320 4202 100       9959 if (GLOBAL_CACHE_MAX < scalar @_cache_global_lst) {
3321 2072         6257 my $remove = shift @_cache_global_lst;
3322 2072         16676 delete $_cache_global_lst{$remove};
3323             }
3324 4202         11201 $_cache_global_lst{$key} = $value;
3325 4202         9241 push @_cache_global_lst, $key;
3326             }
3327              
3328             =item B<_cache_ref>
3329              
3330             Returns reference to cache hash. Internal internal.
3331              
3332             =cut
3333              
3334             {
3335             my $KEY = "__AC_CACHE";
3336             sub _cache_ref {
3337 29063     29063   38590 my $self = shift;
3338 29063 100       55896 if (!exists $self->{$KEY} ) {
3339 1530         4332 $self->{$KEY} = {};
3340             }
3341 29063         47952 return $self->{$KEY};
3342             }
3343             }
3344              
3345             =back
3346              
3347             =end __PRIVATE_METHODS__
3348              
3349             =head1 NOTES
3350              
3351             Many of the methods described in these classes return results as
3352             either C<Astro::Coords::Angle> and C<Astro::Coords::Angle::Hour>
3353             objects. This provides to the caller much more control in how to
3354             represent the answer, especially when the default stringification may
3355             not be suitable. Whilst methods such as C<radec> and C<apparent>
3356             always return objects, methods to return individual coordinate values
3357             such as C<ra>, C<dec>, and C<az> can return the result in a variety of
3358             formats. The default format is simply to return the underlying
3359             C<Angle> object but an explicit format can be specified if you are
3360             simply interested in the value in degrees, say, or are instantly
3361             stringifying it. The supported formats are all documented in the
3362             C<in_format> method documentation in the C<Astro::Coords::Angle> man
3363             page but include all the standard options that have been available in
3364             early versions of C<Astro::Coords>: 'sexagesimal', 'radians',
3365             'degrees'.
3366              
3367             $radians = $c->ra( format => 'rad' );
3368             $string = $c->ra( format => 'sex' );
3369             $deg = $c->ra( format => 'deg' );
3370             $object = $c->ra();
3371              
3372             =head1 CONSTANTS
3373              
3374             In some cases when calculating events such as sunrise, sunset or
3375             twilight time it is useful to have predefined constants containing
3376             the standard elevations. These are available in the C<Astro::Coords>
3377             namespace as:
3378              
3379             SUN_RISE_SET: Position of Sun for sunrise or sunset (-50 arcminutes)
3380             CIVIL_TWILIGHT: Civil twilight (-6 degrees)
3381             NAUT_TWILIGHT: Nautical twilight (-12 degrees)
3382             AST_TWILIGHT: Astronomical twilight (-18 degrees)
3383              
3384             For example:
3385              
3386             $set = $c->set_time( horizon => Astro::Coords::AST_TWILIGHT );
3387              
3388             These are usually only relevant for the Sun. Note that refraction
3389             effects may affect the actual answer and these are simply average
3390             definitions.
3391              
3392             For the Sun and Moon the expected defaults are used if no horizon
3393             is specified (ie SUN_RISE_SET is used for the Sun).
3394              
3395             =head1 REQUIREMENTS
3396              
3397             C<Astro::PAL> is used for all internal astrometric calculations.
3398              
3399             =head1 SEE ALSO
3400              
3401             L<Astro::Telescope> and L<DateTime> are used to specify observer
3402             location and reference epoch respectively.
3403              
3404             L<Astro::Coords::Equatorial>,
3405             L<Astro::Coords::Planet>,
3406             L<Astro::Coords::Fixed>,
3407             L<Astro::Coords::Interpolated>,
3408             L<Astro::Coords::Calibration>,
3409             L<Astro::Coords::Angle>,
3410             L<Astro::Coords::Angle::Hour>.
3411              
3412             =head1 AUTHOR
3413              
3414             Tim Jenness E<lt>tjenness@cpan.orgE<gt>
3415              
3416             =head1 COPYRIGHT
3417              
3418             Copyright (C) 2008, 2010, 2012 Science & Technology Facilities Council.
3419             Copyright (C) 2001-2005 Particle Physics and Astronomy Research Council.
3420             All Rights Reserved.
3421              
3422             This program is free software; you can redistribute it and/or modify it under
3423             the terms of the GNU General Public License as published by the Free Software
3424             Foundation; either version 3 of the License, or (at your option) any later
3425             version.
3426              
3427             This program is distributed in the hope that it will be useful,but WITHOUT ANY
3428             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
3429             PARTICULAR PURPOSE. See the GNU General Public License for more details.
3430              
3431             You should have received a copy of the GNU General Public License along with
3432             this program; if not, write to the Free Software Foundation, Inc., 59 Temple
3433             Place,Suite 330, Boston, MA 02111-1307, USA
3434              
3435             =cut
3436