File Coverage

blib/lib/Astro/Coord.pm
Criterion Covered Total %
statement 306 533 57.4
branch 50 188 26.6
condition 29 162 17.9
subroutine 30 41 73.1
pod 22 27 81.4
total 437 951 45.9


line stmt bran cond sub pod time code
1             package Astro::Coord;
2 1     1   575 use strict;
  1         2  
  1         33  
3              
4             =head1 NAME
5              
6             Astro::Coord - Astronomical coordinate transformations
7              
8             =head1 SYNOPSIS
9              
10             use Astro::Coord;
11              
12             ($l, $b) = fk4gal($ra, $dec);
13             ($az, $el) = eqazel($ha, $dec, $latitude);
14              
15             =head1 DESCRIPTION
16              
17             Astro::Coord contains an assorted set Perl routines for coordinate
18             conversions, such as hour angle to elevation and J2000 to B1950.
19              
20             =head1 AUTHOR
21              
22             Chris Phillips Chris.Phillips@csiro.au
23              
24             =head1 FUNCTIONS
25              
26             =cut
27              
28              
29             BEGIN {
30 1     1   3 use Exporter ();
  1         1  
  1         19  
31 1         86 use vars qw( $VERSION @ISA @EXPORT @EXPORT_OK @EXPORT_FAIL
32 1     1   2 $bepoch );
  1         1  
33 1     1   1 $VERSION = '1.43';
34              
35 1         8 @ISA = qw(Exporter);
36              
37 1         2 @EXPORT = qw( xy2azel azel2xy eqazel J2000todate
38             fk4fk5 fk5fk4 fk4gal galfk4 j2gal
39             coord_convert
40             haset_ewxy ewxy_tlos haset_azel azel_tlos
41             antenna_rise pol2r r2pol
42             );
43 1         2 @EXPORT_OK = qw ( fk4fk5r fk5fk4r fk4galr galfk4r
44             ephem_vars nutate precsn $bepoch );
45 1         23 @EXPORT_FAIL = qw ( );
46              
47 1     1   3 use Carp;
  1         1  
  1         44  
48 1     1   3 use POSIX qw( asin acos fmod tan );
  1         2  
  1         4  
49              
50 1     1   52 use Astro::Time qw( $PI rad2turn turn2rad mjd2lst );
  1         1  
  1         89  
51             }
52              
53             $bepoch = 1950.0;
54              
55 1     1   4 use constant JULIAN_DAY_J2000 => 2451545.0;
  1         1  
  1         48  
56 1     1   4 use constant JULIAN_DAYS_IN_CENTURY => 36525.0;
  1         0  
  1         61  
57              
58             # The E-terms vector for FK4 <--> other coordinate system transforms
59             # (used in fk4fk5 fk5fk4 fk4gal galfk4)
60             my @eterm = (-1.62557E-06, -0.31919E-06, -0.13843E-06);
61              
62             ## The precession matrix for FK4 <--> FK5 conversions (used in
63             ## fk4fk5 and fk5fk4)
64             #my @btoj = ([+0.999925678186902,-0.011182059642247,-0.004857946558960],
65             # [+0.011182059571766,+0.999937478448132,-0.000027176441185],
66             # [+0.004857946721186,-0.000027147426498,+0.999988199738770]);
67              
68             # The precession matrix for FK4 <--> Galactic conversions (used in
69             # fk4gal and galfk4)
70             my @etog = ([-0.066988739415,-0.872755765852,-0.483538914632],
71             [+0.492728466075,-0.450346958020,+0.744584633283],
72             [-0.867600811151,-0.188374601723,+0.460199784784]);
73              
74             # Values used in SLALIB routines
75              
76 1     1   4 use constant D2PI => 6.283185307179586476925287;
  1         1  
  1         41  
77              
78             # Radians per year to arcsec per century
79 1     1   3 use constant PMF => 100*60*60*360/D2PI;
  1         1  
  1         32  
80              
81             # Small number to avoid arithmetic problems
82 1     1   3 use constant TINY => 1e-30;
  1         1  
  1         29  
83              
84             # Km per sec to AU per tropical century
85             # = 86400 * 36524.2198782 / 149597870
86 1     1   3 use constant VF => 21.095;
  1         1  
  1         4234  
87              
88             # Vectors A and Adot, and matrix M
89             my @a = ( -1.62557e-6, -0.31919e-6, -0.13843e-6,
90             +1.245e-3, -1.580e-3, -0.659e-3);
91              
92             my @ad =(+1.245e-3, -1.580e-3, -0.659e-3);
93              
94             my @em = ([+0.9999256782, -0.0111820611, -0.0048579477],
95             [+0.0111820610, +0.9999374784, -0.0000271765],
96             [+0.0048579479, -0.0000271474, +0.9999881997],
97             [-0.000551, -0.238565, +0.435739],
98             [+0.238514, -0.002667, -0.008541],
99             [-0.435623, +0.012254, +0.002117]);
100              
101             my @emi = ([+0.9999256795, +0.0111814828, +0.0048590039,
102             -0.00000242389840, -0.00000002710544, -0.00000001177742],
103             [-0.0111814828, +0.9999374849, -0.0000271771,
104             +0.00000002710544, -0.00000242392702, +0.00000000006585],
105             [-0.0048590040, -0.0000271557, +0.9999881946,
106             +0.00000001177742, +0.00000000006585, -0.00000242404995],
107             [-0.000551, +0.238509, -0.435614,
108             +0.99990432, +0.01118145, +0.00485852],
109             [-0.238560, -0.002667, +0.012254,
110             -0.01118145, +0.99991613, -0.00002717],
111             [+0.435730, -0.008541, +0.002117,
112             -0.00485852, -0.00002716, +0.99996684]);
113              
114             =item B
115              
116             ($x, $y, $z) = pol2r($polar1, $polar2);
117              
118             Converts a position in polar coordinates into rectangular coordinates
119             $polar1, $polar2 The polar coordinates to convert (turns)
120             $x, $y, $z The rectangular coordinates
121              
122             =cut
123              
124             sub pol2r ($$) {
125 7     7 1 6 my ($p1, $p2) = @_;
126              
127             # Converts polar coordinates into rectangluar
128 7         6 my @rect;
129 7         8 $rect[0] = cos(turn2rad($p1))*cos(turn2rad($p2));
130 7         9 $rect[1] = sin(turn2rad($p1))*cos(turn2rad($p2));
131 7         9 $rect[2] = sin(turn2rad($p2));
132 7         13 return(@rect);
133             }
134              
135             =item B
136              
137             ($polar1, $polar2) = r2pol($x, $y, $z);
138              
139             Converts a position in rectangular coordinates into polar coordinates
140             $x, $y, $y The rectangular coordinates to convert
141             $polar1, $polar2 The polar coordinates (turns);
142             Returns undef if too few or too many arguments are passed.
143              
144             =cut
145              
146             sub r2pol (@) {
147             # First check that we have 3 arguments
148 7 50   7 1 12 if (scalar @_ != 3) {
149 0         0 carp 'Inconsistent arguments';
150 0         0 return undef ;
151             }
152 7         6 my ($x, $y, $z) = @_;
153              
154             # Converts rectangular coordinates to polar
155 7         7 my ($tmp, $left, $right);
156 7         24 $tmp = atan2($y, $x)/(2.0*$PI);
157              
158 7 50       19 if (ref($tmp) =~ /PDL/ ) { # Allow to work with PDL
    100          
159 0         0 $tmp -> where($tmp<0.0) .= $tmp -> where($tmp<0.0) + 1.0;
160             } elsif ($tmp < 0.0) {
161 5         4 $tmp += 1.0;
162             }
163              
164 7         5 $left = $tmp;
165 7         10 $tmp = sqrt($x*$x + $y*$y + $z*$z);
166              
167 7 50       10 if (ref($tmp) =~ /PDL/) { # Allow to work with PDL
168 0         0 $right = &PDL::Math::asin($z/$tmp)/(2.0*$PI);
169             } else {
170 7         17 $right = asin($z/$tmp)/(2.0*$PI);
171             }
172              
173 7         19 return ($left, $right);
174             }
175              
176             =item B
177              
178             ($az, $el) = xy2azel($x, $y);
179              
180             Converts a telescope position in X,Y coordinates into Az,El coordinates
181             $x, $y The X and Y coordinates (turns)
182             $az, $el The azimuth and elevation (turns)
183              
184             =cut
185              
186             sub xy2azel ($$) {
187 1     1 1 4 my ($x, $y) = @_;
188              
189             # Convert a position in X,Y to Az,El
190 1         3 my @polar = pol2r($x, $y);
191 1         1 my $temp = $polar[0];
192 1         1 $polar[0] = $polar[1];
193 1         2 $polar[1] = $polar[2];
194 1         1 $polar[2] = $temp;
195 1         2 return (r2pol(@polar));
196             }
197              
198             =item B
199              
200             ($x, $y) = azel2xy($az, $el);
201              
202             Converts a position in Az,El coordinates into X,Y coordinates
203             $az, $el The azimuth and elevation (turns)
204             $x, $y The X and Y coordinate (turns)
205              
206             =cut
207              
208             sub azel2xy ($$) {
209 1     1 1 4 my ($az, $el) = @_;
210              
211             # Convert a position in Az,El to X,Y
212 1         2 my @polar = pol2r($az, $el);
213 1         1 my $temp = $polar[1];
214 1         1 $polar[1] = $polar[0];
215 1         2 $polar[0] = $polar[2];
216 1         1 $polar[2] = $temp;
217 1         1 my ($x, $y) = r2pol(@polar);
218 1 50       3 if ($x>0.5) {
219 1         2 $x -= 1.0;
220             }
221 1 50       2 if ($y>0.5) {
222 0         0 $y -= 1.0;
223             }
224 1         2 return ($x, $y);
225             }
226              
227             =item B
228              
229             ($ha, $dec) = eqazel($az, $el, $latitude);
230             ($az, $el) = eqazel($ha, $dec, $latitude);
231             ($ha, $dec) = eqazel($az, $el, $latitude, $allownegative);
232              
233             Converts HA/Dec coordinates to Az/El and vice versa.
234             $ha, $dec Hour angle and declination of source (turns)
235             $az, $el Azimuth and elevation of source (turns)
236             $latitude Latitude of the observatory (turns)
237             $allownegative If true, allow negative $ha or $az on return (Optional)
238             Note:
239             The ha,dec and az,el conversion is symmetrical
240              
241             =cut
242              
243             sub eqazel ($$$;$) {
244 4     4 1 12 my $sphi = sin(turn2rad($_[2]));
245 4         9 my $cphi = cos(turn2rad($_[2]));
246 4         6 my $sleft = sin(turn2rad($_[0]));
247 4         6 my $cleft = cos(turn2rad($_[0]));
248 4         5 my $sright = sin(turn2rad($_[1]));
249 4         6 my $cright = cos(turn2rad($_[1]));
250 4         9 my $left_out = atan2(-$sleft,-$cleft*$sphi+$sright*$cphi/$cright)/(2.0*$PI);
251 4 100 66     14 $left_out = ($left_out < 0.0) ? $left_out + 1.0 : $left_out
    100          
252             if (!(defined $_[3] && $_[3]));
253 4         14 my $right_out= asin($cleft*$cright*$cphi + $sright*$sphi)/(2.0*$PI);
254              
255 4         7 return($left_out, $right_out);
256              
257             }
258              
259             =item B
260              
261             ($JRA, $JDec) = fk4fk5($BRA, $BDec);
262             (@fk5) = fk4fk5(@fk4);
263              
264             Converts an FK4 (B1950) position to the equivalent FK5 (J2000)
265             position.
266             $BRA,$BDec fk4/B1950 position (turns)
267             $JRA,$Dec fk5/J2000 position (turns)
268             @fk4 fk4/B1950 position (as a 3-vector)
269             @fk5 fk5/J2000 position (as a 3-vector)
270             Note:
271             This code is based on similar routines from the Fortran SLALIB
272             package, so are quite accurate, but subject to a restrictive
273             license (see README).
274              
275             =cut
276              
277             sub fk4fk5 (@) {
278             # - - - - - -
279             # F K 4 5 Z
280             # - - - - - -
281             #
282             # Convert B1950.0 FK4 star data to J2000.0 FK5 assuming zero
283             # proper motion in the FK5 frame (double precision)
284             #
285             # This routine converts stars from the old, Bessel-Newcomb, FK4
286             # system to the new, IAU 1976, FK5, Fricke system, in such a
287             # way that the FK5 proper motion is zero. Because such a star
288             # has, in general, a non-zero proper motion in the FK4 system,
289             # the routine requires the epoch at which the position in the
290             # FK4 system was determined.
291             #
292             # The method is from Appendix 2 of Ref 1, but using the constants
293             # of Ref 4.
294             #
295             # Given:
296             # R1950,D1950 dp B1950.0 FK4 RA,Dec at epoch (rad)
297             # BEPOCH dp Besselian epoch (e.g. 1979.3D0)
298             #
299             # Returned:
300             # R2000,D2000 dp J2000.0 FK5 RA,Dec (rad)
301             #
302             # Notes:
303             #
304             # 1) The epoch BEPOCH is strictly speaking Besselian, but
305             # if a Julian epoch is supplied the result will be
306             # affected only to a negligible extent.
307             #
308             # 2) Conversion from Besselian epoch 1950.0 to Julian epoch
309             # 2000.0 only is provided for. Conversions involving other
310             # epochs will require use of the appropriate precession,
311             # proper motion, and E-terms routines before and/or
312             # after FK45Z is called.
313             #
314             # 3) In the FK4 catalogue the proper motions of stars within
315             # 10 degrees of the poles do not embody the differential
316             # E-term effect and should, strictly speaking, be handled
317             # in a different manner from stars outside these regions.
318             # However, given the general lack of homogeneity of the star
319             # data available for routine astrometry, the difficulties of
320             # handling positions that may have been determined from
321             # astrometric fields spanning the polar and non-polar regions,
322             # the likelihood that the differential E-terms effect was not
323             # taken into account when allowing for proper motion in past
324             # astrometry, and the undesirability of a discontinuity in
325             # the algorithm, the decision has been made in this routine to
326             # include the effect of differential E-terms on the proper
327             # motions for all stars, whether polar or not. At epoch 2000,
328             # and measuring on the sky rather than in terms of dRA, the
329             # errors resulting from this simplification are less than
330             # 1 milliarcsecond in position and 1 milliarcsecond per
331             # century in proper motion.
332             #
333             # References:
334             #
335             # 1 Aoki,S., et al, 1983. Astron.Astrophys., 128, 263.
336             #
337             # 2 Smith, C.A. et al, 1989. "The transformation of astrometric
338             # catalog systems to the equinox J2000.0". Astron.J. 97, 265.
339             #
340             # 3 Yallop, B.D. et al, 1989. "Transformation of mean star places
341             # from FK4 B1950.0 to FK5 J2000.0 using matrices in 6-space".
342             # Astron.J. 97, 274.
343             #
344             # 4 Seidelmann, P.K. (ed), 1992. "Explanatory Supplement to
345             # the Astronomical Almanac", ISBN 0-935702-68-7.
346             #
347             # Called: sla_DCS2C, sla_EPJ, sla_EPB2D, sla_DCC2S, sla_DRANRM
348             #
349             # P.T.Wallace Starlink 21 September 1998
350             #
351             # Copyright (C) 1998 Rutherford Appleton Laboratory
352              
353              
354 1     1 1 39 my ($rect, $w, $i, $j);
355 0         0 my (@r0, @a1, @v1, @v2); # Position and position+velocity vectors
356              
357 1 50       4 if (@_==3) { # Rectangular coordinates passed
    50          
    0          
358 0         0 @r0 = @_;
359 0         0 $rect = 1;
360             } elsif (@_==2) { # Sperical coordinates
361 1         2 @r0 = pol2r($_[0],$_[1]); # Spherical to Cartesian
362 1         1 $rect = 0;
363             } elsif (@_>3) {
364 0         0 croak "Too many arguments for Astro::fk4fk5 ";
365             } else {
366 0         0 croak "Not enough arguments for Astro::fk4fk5 ";
367             }
368              
369             # Adjust vector A to give zero proper motion in FK5
370 1         2 $w=($bepoch-1950)/PMF;
371 1         2 for ($i=0; $i<3; $i++) {
372 3         6 $a1[$i]=$a[$i]+$w*$ad[$i];
373             }
374             # Remove e-terms
375 1         2 $w=$r0[0]*$a1[0]+$r0[1]*$a1[1]+$r0[2]*$a1[2];
376 1         3 for ($i=0; $i<3; $i++) {
377 3         5 $v1[$i]=$r0[$i]-$a1[$i]+$w*$r0[$i];
378             }
379              
380             # Convert position vector to Fricke system
381 1         2 for ($i=0; $i<6; $i++) {
382 6         3 $w=0;
383 6         6 for ($j=0; $j<3; $j++) {
384             #warn "DEBUG: [$i,$j]\n";
385 18         17 $w=$w+$em[$i][$j]*$v1[$j];
386 18         25 $v2[$i]=$w
387             }
388             }
389              
390             # Allow for fictitious proper motion in FK4
391 1         3 $w=(epj(epb2d($bepoch))-2000)/PMF;
392 1         2 for ($i=0; $i<3; $i++) {
393 3         5 $v2[$i]=$v2[$i]+$w*$v2[$i+3];
394             }
395              
396 1 50       2 if ($rect) {
397 0         0 return @v2[0..2];
398             } else {
399             # Revert to spherical coordinates
400 1         2 return r2pol(@v2[0..2]);
401             }
402             }
403              
404             =item B
405              
406             @fk5 = fk4fk5r(@fk4);
407              
408             Converts an FK4 (B1950) position to the equivalent FK5 (J2000) position.
409             Note: Convert equitoral positions to/from 3-vectors using pol2r and r2pol.
410             @fk4 fk4 position (as a 3-vector, turns)
411             @fk5 fk5 position (as a 3-vector, turns)
412             Note:
413             Just a wrapper to fk4fk5 which now handler polar and rectangular
414             arguments
415              
416             =cut
417              
418             sub fk4fk5r (@) {
419 0     0 1 0 return fk4fk5(@_);
420             }
421              
422             #sub fk4fk5r (@) {
423             # # First check that we have 3 arguments
424             # if (scalar @_ < 3) {
425             # croak 'Not enough arguments for Astro::Coord::fk4fk5r at ';
426             # } elsif (scalar @_ > 3) {
427             # croak 'Too many arguments for Astro::Coord::fk4fk5r at ';
428             # }
429             #
430             # my ($i, $j, @temp, @fk5);
431             # my $w = 0.0;
432             #
433             # # Add the eterms
434             # for ($i=0 ; $i<3 ; $i++) {
435             # $w += $_[$i] * $eterm[$i];
436             # }
437             # for ($i=0 ; $i<3 ; $i++) {
438             # $temp[$i] = $_[$i] - $eterm[$i] + $w * $_[$i];
439             # }
440             #
441             # # Precess from FK4 to FK5
442             # for ($i=0 ; $i<3 ; $i++) {
443             # $fk5[$i] = 0.0;
444             # for ($j=0 ; $j<3 ; $j++) {
445             # $fk5[$i] += $btoj[$i][$j] * $temp[$j];
446             # }
447             # }
448             #
449             # return(@fk5);
450             #}
451              
452             =item B
453              
454             ($JRA, $JDec) = fk4fk5($BRA, $BDec);
455             ($@fk5) = fk4fk5(@fk4);
456              
457             Converts an FK5 (J2000) position to the equivalent FK4 (B1950) position.
458             $JRA,$Dec fk5/J2000 position (turns)
459             $BRA,$BDec fk4/B1950 position (turns)
460             @fk5 fk5/J2000 position (as a 3-vector)
461             @fk4 fk4/B1950 position (as a 3-vector)
462             Note:
463             This code is based on similar routines from the Fortran SLALIB
464             package, so are quite accurate, but subject to a restrictive
465             license (see README).
466              
467             =cut
468              
469             sub fk5fk4 (@) {
470             #+
471             # - - - - - -
472             # F K 5 2 4
473             # - - - - - -
474             #
475             # Convert J2000.0 FK5 star data to B1950.0 FK4 (double precision)
476             #
477             # This routine converts stars from the new, IAU 1976, FK5, Fricke
478             # system, to the old, Bessel-Newcomb, FK4 system. The precepts
479             # of Smith et al (Ref 1) are followed, using the implementation
480             # by Yallop et al (Ref 2) of a matrix method due to Standish.
481             # Kinoshita's development of Andoyer's post-Newcomb precession is
482             # used. The numerical constants from Seidelmann et al (Ref 3) are
483             # used canonically.
484             #
485             # Given: (all J2000.0,FK5)
486             # R2000,D2000 dp J2000.0 RA,Dec (rad)
487             # DR2000,DD2000 dp J2000.0 proper motions (rad/Jul.yr)
488             # P2000 dp parallax (arcsec)
489             # V2000 dp radial velocity (km/s, +ve = moving away)
490             #
491             # Returned: (all B1950.0,FK4)
492             # R1950,D1950 dp B1950.0 RA,Dec (rad)
493             # DR1950,DD1950 dp B1950.0 proper motions (rad/trop.yr)
494             # P1950 dp parallax (arcsec)
495             # V1950 dp radial velocity (km/s, +ve = moving away)
496             #
497             # Notes:
498             #
499             # 1) The proper motions in RA are dRA/dt rather than
500             # cos(Dec)#dRA/dt, and are per year rather than per century.
501             #
502             # 2) Note that conversion from Julian epoch 2000.0 to Besselian
503             # epoch 1950.0 only is provided for. Conversions involving
504             # other epochs will require use of the appropriate precession,
505             # proper motion, and E-terms routines before and/or after
506             # FK524 is called.
507             #
508             # 3) In the FK4 catalogue the proper motions of stars within
509             # 10 degrees of the poles do not embody the differential
510             # E-term effect and should, strictly speaking, be handled
511             # in a different manner from stars outside these regions.
512             # However, given the general lack of homogeneity of the star
513             # data available for routine astrometry, the difficulties of
514             # handling positions that may have been determined from
515             # astrometric fields spanning the polar and non-polar regions,
516             # the likelihood that the differential E-terms effect was not
517             # taken into account when allowing for proper motion in past
518             # astrometry, and the undesirability of a discontinuity in
519             # the algorithm, the decision has been made in this routine to
520             # include the effect of differential E-terms on the proper
521             # motions for all stars, whether polar or not. At epoch 2000,
522             # and measuring on the sky rather than in terms of dRA, the
523             # errors resulting from this simplification are less than
524             # 1 milliarcsecond in position and 1 milliarcsecond per
525             # century in proper motion.
526             #
527             # References:
528             #
529             # 1 Smith, C.A. et al, 1989. "The transformation of astrometric
530             # catalog systems to the equinox J2000.0". Astron.J. 97, 265.
531             #
532             # 2 Yallop, B.D. et al, 1989. "Transformation of mean star places
533             # from FK4 B1950.0 to FK5 J2000.0 using matrices in 6-space".
534             # Astron.J. 97, 274.
535             #
536             # 3 Seidelmann, P.K. (ed), 1992. "Explanatory Supplement to
537             # the Astronomical Almanac", ISBN 0-935702-68-7.
538             #
539             # P.T.Wallace Starlink 19 December 1993
540             #
541             # Copyright (C) 1995 Rutherford Appleton Laboratory
542             #-
543 1     1 1 3 my ($rect, @v1, @v2);
544 1 50       4 if (@_==3) { # Rectangular coordinates passed
    50          
    0          
545 0         0 @v1 = @_;
546 0         0 $rect = 1;
547             } elsif (@_==2) { # Sperical coordinates
548 1         2 @v1 = pol2r($_[0],$_[1]); # Spherical to Cartesian
549 1         1 $rect = 0;
550             } elsif (@_>2) {
551 0         0 croak "Too many arguments for Astro::fk5fk4 ";
552             } else {
553 0         0 croak "Not enough arguments for Astro::fk5fk4 ";
554             }
555              
556             # Miscellaneous
557 1         2 my ($w, $x, $y, $z, $wd, $rxyz);
558 0         0 my ($ur, $ud, $xd, $yd, $zd);
559 0         0 my ($i,$j);
560              
561             # Convert position+velocity vector to BN system
562 1         6 for ($i=0; $i<6; $i++) {
563 6         3 $w=0.0;
564             ##for ($j=0; $j<6; $j++) {
565 6         8 for ($j=0; $j<3; $j++) {
566 18         28 $w=$w+$emi[$i][$j]*$v1[$j];
567             }
568 6         11 $v2[$i]=$w;
569             }
570              
571             # Position vector components and magnitude
572 1         1 $x=$v2[0];
573 1         2 $y=$v2[1];
574 1         1 $z=$v2[2];
575 1         3 $rxyz=sqrt($x*$x+$y*$y+$z*$z);
576              
577             # Apply E-terms to position
578 1         2 $w=$x*$a[0]+$y*$a[1]+$z*$a[2];
579 1         3 $x=$x+$a[0]*$rxyz-$w*$x;
580 1         2 $y=$y+$a[1]*$rxyz-$w*$y;
581 1         1 $z=$z+$a[2]*$rxyz-$w*$z;
582              
583             # Recompute magnitude
584 1         2 $rxyz=sqrt($x*$x+$y*$y+$z*$z);
585              
586             # Apply E-terms to both position and velocity
587 1         1 $x=$v2[0];
588 1         1 $y=$v2[1];
589 1         2 $z=$v2[2];
590 1         2 $w=$x*$a[0]+$y*$a[1]+$z*$a[2];
591 1         2 $wd=$x*$a[3]+$y*$a[4]+$z*$a[5];
592 1         3 $x=$x+$a[0]*$rxyz-$w*$x;
593 1         1 $y=$y+$a[1]*$rxyz-$w*$y;
594 1         2 $z=$z+$a[2]*$rxyz-$w*$z;
595 1         3 $xd=$v2[3]+$a[3]*$rxyz-$wd*$x;
596 1         2 $yd=$v2[4]+$a[4]*$rxyz-$wd*$y;
597 1         1 $zd=$v2[5]+$a[5]*$rxyz-$wd*$z;
598              
599 1         2 my @r;
600 1 50       3 if ($rect) {
601 0         0 @r = ($x, $y, $z);
602             } else {
603 1         6 @r= r2pol($x, $y, $z);
604             }
605              
606             # my $rxysq =$x*$x+$y*$y;
607             # my $rxy = sqrt($rxysq);
608             # if ($rxy>TINY) {
609             # $ur=($x*$yd-$y*$xd)/$rxysq;
610             # $ud=($zd*$rxysq-$z*($x*$xd+$y*$yd))/(($rxysq+$z*$z)*$rxy);
611             # }
612             #
613             ## Return results
614             # my $dr1950=$ur/PMF;
615             # my $dd1950=$ud/PMF;
616              
617 1         4 return(@r);
618             }
619              
620              
621             =item B
622              
623             @fk4 = fk5fk4r(@fk5);
624              
625             Converts an FK5 (J2000) position to the equivalent FK4 (B1950) position.
626             Note: Convert equitoral positions to/from 3-vectors using pol2r and r2pol.
627             @fk4 fk4 position (as a 3-vector, turns)
628             @fk5 fk5 position (as a 3-vector, turns)
629             Note:
630             Just a wrapper to fk5fk4 which now handler polar and rectangular
631             arguments
632              
633             =cut
634              
635             sub fk5fk4r (@) {
636 0     0 1 0 return fk5fk4(@_);
637             }
638              
639              
640             #sub fk5fk4 (@) {
641             ## - - - - - -
642             ## F K 5 4 Z
643             ## - - - - - -
644             ##
645             ## Convert a J2000.0 FK5 star position to B1950.0 FK4 assuming
646             ## zero proper motion and parallax (double precision)
647             ##
648             ## This routine converts star positions from the new, IAU 1976,
649             ## FK5, Fricke system to the old, Bessel-Newcomb, FK4 system.
650             ##
651             ## Given:
652             ## R2000,D2000 dp J2000.0 FK5 RA,Dec (rad)
653             ## BEPOCH dp Besselian epoch (e.g. 1950D0)
654             ##
655             ## Returned:
656             ## R1950,D1950 dp B1950.0 FK4 RA,Dec (rad) at epoch BEPOCH
657             ## DR1950,DD1950 dp B1950.0 FK4 proper motions (rad/trop.yr)
658             ##
659             ## Notes:
660             ##
661             ## 1) The proper motion in RA is dRA/dt rather than cos(Dec)#dRA/dt.
662             ##
663             ## 2) Conversion from Julian epoch 2000.0 to Besselian epoch 1950.0
664             ## only is provided for. Conversions involving other epochs will
665             ## require use of the appropriate precession routines before and
666             ## after this routine is called.
667             ##
668             ## 3) Unlike in the sla_FK524 routine, the FK5 proper motions, the
669             ## parallax and the radial velocity are presumed zero.
670             ##
671             ## 4) It is the intention that FK5 should be a close approximation
672             ## to an inertial frame, so that distant objects have zero proper
673             ## motion; such objects have (in general) non-zero proper motion
674             ## in FK4, and this routine returns those fictitious proper
675             ## motions.
676             ##
677             ## 5) The position returned by this routine is in the B1950
678             ## reference frame but at Besselian epoch BEPOCH. For
679             ## comparison with catalogues the BEPOCH argument will
680             ## frequently be 1950D0.
681             ##
682             ## Called: sla_FK524, sla_PM
683             ##
684             ## P.T.Wallace Starlink 10 April 1990
685             ##
686             ## Copyright (C) 1995 Rutherford Appleton Laboratory
687             #
688             # my $bepoch = 1950.0;
689             #
690             # my $rect;
691             # if (@_>3) {
692             # croak "Too many arguments for Astro::fk5fk4 ";
693             # } elsif (@_<2) {
694             # croak "Not enough arguments for Astro::fk5fk4 ";
695             # }
696             # my @r2000 = @_;
697             #
698             # # fk5 equinox j2000 (any epoch) to fk4 equinox b1950 epoch b1950
699             # my (@r1950) = fk524(@r2000);
700             # my $dd1950 = pop @r1950;
701             # my $dr1950 = pop @r1950;
702             #
703             # ## fictitious proper motion to epoch bepoch
704             # #my ($r1950, $d1950) = pm($r,$d,$dr1950,$dd1950,0.0,0.0,1950,$bepoch);
705             # return @r1950;
706             #}
707              
708             #=item B
709             #
710             # @fk4 = fk5fk4r(@fk5);
711             #
712             # Converts an FK5 (J2000) position to the equivalent FK4 (B1950) position.
713             # Note: Convert equitoral positions to/from 3-vectors using pol2r and r2pol.
714             # @fk5 fk5 position (as a 3-vector, turns)
715             # @fk4 fk4 position (as a 3-vector, turns)
716             #
717             #=cut
718             #
719             #sub fk5fk4r(@) {
720             #
721             # # First check that we have 3 arguments
722             # if (scalar @_ < 3) {
723             # croak 'Not enough arguments for Astro::Coord::fk5fk4r at ';
724             # } elsif (scalar @_ > 3) {
725             # croak 'Too many arguments for Astro::Coord::fk5fk4r at ';
726             # }
727             #
728             # my ($i, $j, @fk4);
729             # my $w = 0.0;
730             #
731             # # Precess. Note : the same matrix is used as for the FK4 -> FK5
732             # # transformation, but we have transposed it within the
733             # # for loop
734             #
735             # for ($i=0 ; $i<3 ; $i++) {
736             # $fk4[$i] = 0.0;
737             # for ($j=0 ; $j<3 ; $j++) {
738             # $fk4[$i] += $btoj[$j][$i] * $_[$j];
739             # }
740             # }
741             #
742             # # Allow for e-terms
743             # for ($i=0 ; $i<3 ; $i++) {
744             # $w += $_[$i] * $eterm[$i];
745             # }
746             # $w += 1.0;
747             # for ($i=0 ; $i<3 ; $i++) {
748             # $fk4[$i] = ($fk4[$i] + $eterm[$i])/$w;
749             # }
750             #
751             # return(@fk4);
752             #}
753              
754             =item B
755              
756             @gal = fk4galr(@fk4)
757              
758             Converts an FK4 position (B1950.0) to the IAU 1958 Galactic
759             coordinate system
760             Note: convert equitoral positions to/from 3-vectors using pol2r and r2pol.
761             @fk4 fk4 position to convert (as a 3-vector, turns)
762             @gal Galactic position (as a 3-vector, turns)
763             Returns undef if too few or two many arguments are passed.
764             Reference : Blaauw et al., 1960, MNRAS, 121, 123.
765              
766             =cut
767              
768             # Within 1e-7 arcsec of SLALIB slaEg50
769             sub fk4galr(@) {
770             # First check that we have 3 arguments
771 1 50   1 1 8 if (scalar @_ < 3) {
    50          
772 0         0 croak 'Not enough arguments for Astro::Coord::fk4galr at ';
773             } elsif (scalar @_ > 3) {
774 0         0 croak 'Too many arguments for Astro::Coord::fk4galr at ';
775             }
776              
777 1         1 my ($i, $j, @temp, @gal);
778 1         1 my $w = 0.0;
779              
780             # Allow for e-terms
781 1         3 for ($i=0 ; $i<3 ; $i++) {
782 3         6 $w += $_[$i] * $eterm[$i];
783             }
784 1         2 for ($i=0 ; $i<3 ; $i++) {
785 3         6 $temp[$i] = $_[$i] - $eterm[$i] + $w * $_[$i];
786             }
787              
788              
789             # Precess
790 1         3 for ($i=0 ; $i<3 ; $i++) {
791 3         4 $gal[$i] = 0.0;
792 3         4 for ($j=0 ; $j<3 ; $j++) {
793 9         13 $gal[$i] += $etog[$i][$j] * $temp[$j];
794             }
795             }
796              
797 1         2 return(@gal);
798             }
799              
800             =item B
801              
802             ($bRA, $bDec) = galfk4($l, $b);
803             @fk4 = galfk4(@gal);
804              
805             Converts an IAU 1958 Galactic position to the FK4 coordinate system (B1950)
806             Notes: Converts equitoral positions to/from 3-vectors using pol2r and r2pol.
807             $BRA,$BDec fk4/B1950 position (turns)
808             $l, $b Galactic longitude and latitude
809             @gal Galactic position (as a 3-vector, turns)
810             @fk4 fk4 position (as a 3-vector, turns)
811             Reference : Blaauw et al., 1960, MNRAS, 121, 123.
812              
813             =cut
814              
815             # Within 1e-7 arcsec of SLALIB slaGe50
816             sub galfk4(@) {
817 1     1 1 3 my (@r, $rect);
818              
819 1 50       5 if (@_==3) { # Rectangular coordinates passed
    50          
    0          
820 0         0 @r = @_;
821 0         0 $rect = 1;
822             } elsif (@_==2) { # Sperical coordinates
823 1         2 @r = pol2r($_[0],$_[1]); # Spherical to Cartesian
824 1         2 $rect = 0;
825             } elsif (@_>3) {
826 0         0 croak "Too many arguments for Astro::galfk4 at";
827             } else {
828 0         0 croak "Not enough arguments for Astro::galfk4 at";
829             }
830              
831 1         1 my ($i, $j, @fk4);
832 1         1 my $w = 0.0;
833              
834             # Precess. Note : the same matrix is used as for the FK4 -> Galactic
835             # transformation, but we have transposed it within the
836             # for loop
837 1         7 for ($i=0 ; $i<3 ; $i++) {
838 3         2 $fk4[$i] = 0.0;
839 3         6 for ($j=0 ; $j<3 ; $j++) {
840 9         15 $fk4[$i] += $etog[$j][$i] * $r[$j];
841             }
842             }
843              
844             # Allow for e-terms */
845 1         2 for ($i=0 ; $i<3 ; $i++) {
846 3         5 $w += $r[$i] * $eterm[$i];
847             }
848 1         1 $w += 1.0;
849 1         2 for ($i=0 ; $i<3 ; $i++) {
850 3         5 $fk4[$i] = ($fk4[$i] + $eterm[$i])/$w;
851             }
852              
853 1 50       2 if ($rect) {
854 0         0 return @fk4;
855             } else {
856 1         2 return r2pol(@fk4);
857             }
858             }
859              
860 0     0 0 0 sub galfk4r(@) {galfk4(@_)};
861              
862             #=item B
863             #
864             # ($JRA, $JDec) = fk4fk5($BRA, $BDec);
865             #
866             # Converts an FK4 (B1950) position to the equivalent FK5 (J2000) position.
867             # **LOW PRECISION**
868             # $BRA,$BDec fk4/B1950 position (turns)
869             # $JRA,$Dec fk5/J2000 position (turns)
870             #
871             #=cut
872             #
873             #sub fk4fk5 ($$) {
874             # return r2pol(fk4fk5r(pol2r(shift,shift)));
875             #}
876              
877             #=item B
878             #
879             # ($BRA, $BDec) = fk5fk4($JRA, $JDec);
880             #
881             # Converts an FK5 (J2000) position to the equivalent FK4 (B1950) position.
882             # **LOW PRECISION**
883             # $JRA,$Dec fk5/J2000 position (turns)
884             # $BRA,$BDec fk4/B1950 position (turns)
885             #
886             #=cut
887             #
888             #sub fk5fk4 ($$) {
889             # return r2pol(fk5fk4r(pol2r(shift,shift)));
890             #}
891              
892             =item B
893              
894             ($l, $b) = fk4gal($ra, $dec);
895              
896             Converts an FK4 position (B1950) to the IAU 1958 Galactic
897             coordinate system
898             ($ra, $dec) fk4 position to convert (turns)
899             ($l, $b) Galactic position (turns)
900             Reference : Blaauw et al., 1960, MNRAS, 121, 123.
901              
902             =cut
903              
904             sub fk4gal ($$) {
905 1     1 1 5 return r2pol(fk4galr(pol2r(shift,shift)));
906             }
907              
908             #=item B
909             #
910             # ($ra, $dec) = galfk4($l, $b);
911             #
912             # Converts an IAU 1958 Galactic coordinate system position
913             # to FK4 (B1950).
914             # ($l, $b) Galactic position (turns)
915             # ($ra, $dec) fk4 position to convert (turns)
916             # Reference : Blaauw et al., 1960, MNRAS, 121, 123.
917             #
918             #=cut
919             #
920             #sub galfk4 ($$) {
921             # return r2pol(galfk4r(pol2r(shift,shift)));
922             #}
923              
924             =item B
925              
926             ($omega, $rma, $mlanom, $F, $D, $eps0) = ephem_vars($jd)
927              
928             Given the Julian day ($jd) this routine calculates the ephemeris
929             values required by the prcmat and nutate routines
930              
931             The returned values are :
932             $omega - Longitude of the ascending node of the Moons mean orbit on
933             the ecliptic, measured from the mean equinox of date.
934             $rma - Mean anomaly of the Sun.
935             $mlanom - Mean anomaly of the Moon.
936             $F - L - omega, where L is the mean longitude of the Moon.
937             $D - Mean elongation of the Moon from the Sun.
938             $eps0 - Mean obilquity of the ecliptic.
939              
940             =cut
941              
942             =item B
943              
944              
945             ($DRA, $DDec) = J2000todate($JRA, $JDec, $mjd);
946             @date = J2000todate(@J2000, $mjd);
947              
948             Converts an J2000 position date coordinate
949              
950             $DRA,$DDec Date coordinate (turns)
951             $JRA,$Dec J2000 position (turns)
952             @date Date coordinate (as a 3-vector)
953             @J2000 J2000 position (as a 3-vector)
954              
955             =cut
956              
957             # Untested
958             sub J2000todate(@) {
959              
960 0     0 1 0 my ($rect);
961 0         0 my (@J2000, @date); # Position vectors
962              
963 0         0 my $mjd = pop @_;
964 0 0       0 if (@_==3) { # Rectangular coordinates passed
    0          
    0          
965 0         0 @J2000 = @_;
966 0         0 $rect = 1;
967             } elsif (@_==2) { # Sperical coordinates
968 0         0 @J2000 = pol2r($_[0],$_[1]); # Spherical to Cartesian
969 0         0 $rect = 0;
970             } elsif (@_>3) {
971 0         0 croak "Too many arguments for Astro::Coord::J2000todate ";
972             } else {
973 0         0 croak "Not enough arguments for Astro::Coord::J2000todate ";
974             }
975              
976             # compute the general precession matrix.
977 0         0 my @gp = precsn(JULIAN_DAY_J2000, $mjd+2400000.5);
978              
979             # Determine ephemeris quantities
980 0         0 my ($deps, $dpsi);
981 0         0 my @nu = ();
982 0         0 my ($omega, $rma, $mlanom, $F, $D, $eps0) = ephem_vars($mjd+2400000.5);
983 0         0 ($deps, $dpsi, @nu) = nutate($omega, $F, $D, $rma, $mlanom, $eps0);
984              
985 0         0 my @prcmat = ();
986 0         0 for (my $i=0 ; $i<3 ; $i++) {
987 0         0 for (my $j=0 ; $j<3 ; $j++) {
988 0         0 my $xx = 0.0;
989 0         0 for (my $k=0 ; $k<3 ; $k++) {
990 0         0 $xx = $xx + $gp[$i][$k] * $nu[$k][$j];
991             }
992 0         0 $prcmat[$i][$j] = $xx;
993             }
994             }
995              
996 0         0 for (my $i=0 ; $i<3 ; $i++) {
997 0         0 $date[$i] = 0.0;
998 0         0 for (my $j=0 ; $j<3 ; $j++) {
999 0         0 $date[$i] += $prcmat[$i][$j] * $J2000[$j];
1000             }
1001             }
1002              
1003 0 0       0 if ($rect) {
1004 0         0 return @date;
1005             } else {
1006             # Revert to spherical coordinates
1007 0         0 return r2pol(@date);
1008             }
1009             }
1010              
1011             sub ephem_vars ($) {
1012 1     1 1 2 my $epoch = shift;
1013              
1014             # Calculates values required internally by prcmat and for nutate from
1015             # the passed Julian Day
1016              
1017             # Calculate the interval to/from J2000 in Julian Centuries
1018 1         3 my $jcents = ($epoch-(JULIAN_DAY_J2000))/JULIAN_DAYS_IN_CENTURY;
1019              
1020             # Calculate the longitude of the mean ascending node of the lunar
1021             # orbit on the ecliptic [A.A. Suppl. 1984, p S26]
1022 1         3 my $omega = (((0.000000039 * $jcents + 0.000036143) *
1023             $jcents - 33.757045934) *
1024             $jcents + 2.182438624)/(2.0*$PI);
1025 1         3 $omega = fmod($omega,1.0);
1026 1 50       3 if ($omega < 0.0) {
1027 1         1 $omega += 1.0;
1028             }
1029              
1030             # Calculate the mean anomaly. [A.A suppl. 1984, p S26]
1031 1         3 my $manom = (6.240035939 - ((5.818e-8 * $jcents +2.797e-6) * $jcents -
1032             628.301956024) * $jcents)/(2.0*$PI);
1033 1         4 $manom = fmod($manom,1.0);
1034 1 50       2 if ($manom < 0.0) {
1035 0         0 $manom += 1.0;
1036             }
1037              
1038             # Calculate the mean anomaly of the Moon. [A.A. Suppl, 1984, p S26]
1039 1         3 my $mlanom = (((0.000000310 * $jcents + 0.000151795) * $jcents
1040             +8328.691422884) * $jcents + 2.355548394)/(2.0*$PI);
1041 1         2 $mlanom = fmod($mlanom,1.0);
1042 1 50       3 if ($mlanom < 0.0) {
1043 0         0 $mlanom += 1.0;
1044             }
1045              
1046             # Calculate the longitude of the moon from ascending node.
1047             # [A.A. Suppl, 1984, p S26]
1048 1         2 my $F = (((0.000000053 * $jcents - 0.000064272) * $jcents + 8433.466158318)
1049             * $jcents + 1.627901934)/(2.0*$PI);
1050 1         1 $F = fmod($F,1.0);
1051 1 50       2 if ($F < 0.0) {
1052 0         0 $F += 1.0;
1053             }
1054              
1055             # Calculate the mean elongation of the moon from the sun.
1056             # [A.A suppl. 1984, p S26]
1057 1         2 my $D = (((0.000000092 * $jcents + 0.000033409) * $jcents + 7771.377146171)
1058             * $jcents + 5.198469514)/(2.0*$PI);
1059 1         1 $D = fmod($D,1.0);
1060 1 50       7 if ($D < 0.0) {
1061 0         0 $D += 1.0;
1062             }
1063              
1064             # Calculate the mean obliquity of the ecliptic = mean obliquity.
1065             # [A.A suppl. 1984, p S26]
1066 1         3 my $eps0 = (((0.000000009 * $jcents - 0.000000003) * $jcents - 0.000226966)
1067             * $jcents + 0.409092804)/(2.0*$PI);
1068 1         2 return($omega, $manom, $mlanom, $F, $D, $eps0)
1069             }
1070              
1071             =item B
1072              
1073             ($deps, $dpsi, @nu) = nutate($omega, $F, $D, $rma, $mlanom, $eps0);
1074              
1075             To calculate the nutation in longitude and obliquity according to
1076             the 1980 IAU Theory of Nutation including terms with amplitudes
1077             greater than 0.01 arcsecond. The nutation matrix is used to
1078             compute true place from mean place: true vector = N x mean place
1079             vector where the three components of each vector are the direction
1080             cosines wrt the mean equinox and equator.
1081              
1082             / 1 -dpsi.cos(eps) -dpsi.sin(eps) \
1083             | |
1084             N = | dpsi.cos(eps) 1 -deps |
1085             | |
1086             \ dpsi.sin(eps) deps 1 /
1087              
1088             The required inputs are : (NOTE: these are the values returned by ephem_vars)
1089             $omega - Longitude of the ascending node of the Moons mean orbit on
1090             the ecliptic, measured from the mean equinox of date.
1091             $rma - Mean anomaly of the Sun.
1092             $mlanom - Mean anomaly of the Moon.
1093             $F - L - omega, where L is the mean longitude of the Moon.
1094             $D - Mean elongation of the Moon from the Sun.
1095             $eps0 - Mean obilquity of the ecliptic.
1096              
1097             The returned values are :
1098             $deps - nutation in obliquity
1099             $dpsi - nutation in longitude (scalar)
1100             @nu - nutation matrix (array [3][3])
1101              
1102             =cut
1103              
1104             sub nutate ($$$$$$) {
1105 1     1 1 2 my ($omega, $F, $D, $manom, $mlanom, $eps0) = @_;
1106              
1107 1         1 my $arg1 = $omega;
1108 1         1 my $arg2 = 2.0 * $omega;
1109 1         2 my $arg9 = 2.0 * ($F-$D+$omega);
1110 1         1 my $arg10 = $manom;
1111 1         1 my $arg11 = $arg9 + $arg10;
1112 1         1 my $arg12 = $arg9 - $arg10;
1113 1         1 my $arg13 = $arg9 - $arg1;
1114 1         3 my $arg31 = 2.0 * ($F+$omega);
1115 1         1 my $arg32 = $mlanom;
1116 1         1 my $arg33 = $arg31 - $arg1;
1117 1         1 my $arg34 = $arg31 + $arg32;
1118 1         2 my $arg35 = $mlanom - 2.0 * $D;
1119 1         1 my $arg36 = $arg31 - $arg32;
1120              
1121 1         15 my $dpsi = (-0.000083386 * sin($arg1*2.0*$PI)
1122             +0.000001000 * sin($arg2*2.0*$PI)
1123             -0.000006393 * sin($arg9*2.0*$PI)
1124             +0.000000691 * sin($arg10*2.0*$PI)
1125             -0.000000251 * sin($arg11*2.0*$PI)
1126             +0.000000105 * sin($arg12*2.0*$PI)
1127             +0.000000063 * sin($arg13*2.0*$PI)
1128             -0.000001102 * sin($arg31*2.0*$PI)
1129             +0.000000345 * sin($arg32*2.0*$PI)
1130             -0.000000187 * sin($arg33*2.0*$PI)
1131             -0.000000146 * sin($arg34*2.0*$PI)
1132             -0.000000077 * sin($arg35*2.0*$PI)
1133             +0.000000060 * sin($arg36*2.0*$PI))/(2.0*$PI);
1134              
1135 1         8 my $deps = ( 0.000044615 * cos($arg1*2.0*$PI)
1136             -0.000000434 * cos($arg2*2.0*$PI)
1137             +0.000002781 * cos($arg9*2.0*$PI)
1138             +0.000000109 * cos($arg11*2.0*$PI)
1139             +0.000000474 * cos($arg31*2.0*$PI)
1140             +0.000000097 * cos($arg33*2.0*$PI)
1141             +0.000000063 * cos($arg34*2.0*$PI))/(2.0*$PI);
1142 1         4 my $eps = $eps0 + $deps;
1143              
1144 1         6 my @N = ([1.0, -($dpsi)*(2.0*$PI)*cos($eps*2.0*$PI),
1145             -($dpsi)*(2.0*$PI)*sin($eps*2.0*$PI)],
1146             [0.0, 1.0, -($deps)*(2.0*$PI)],
1147             [0.0, ($deps)*(2.0*$PI), 1.0]);
1148 1         2 $N[1][0] = -1.0*$N[0][1];
1149 1         2 $N[2][0] = -1.0*$N[0][2];
1150 1         2 return($deps, $dpsi, @N);
1151             }
1152              
1153             =item B
1154              
1155             @gp = precsn($jd_start, $jd_stop);
1156              
1157             To calculate the precession matrix P for dates AFTER 1984.0 (JD =
1158             2445700.5) Given the position of an object referred to the equator
1159             and equinox of the epoch $jd_start its position referred to the
1160             equator and equinox of epoch $jd_stop can be calculated as follows :
1161              
1162             1) Express the position as a direction cosine 3-vector (V1)
1163             (use pol2r to do this).
1164             2) The corresponding vector V2 for epoch jd_end is V2 = P.V1
1165              
1166             The required inputs are :
1167             $jd_start - The Julian day of the current epoch of the coordinates.
1168             $jd_end - The Julian day at the required epoch for the conversion.
1169              
1170             The returned values are :
1171             @gp - The required precession matrix (array [3][3])
1172              
1173             =cut
1174              
1175             sub precsn ($$) {
1176 1     1 1 2 my ($jd_start, $jd_end) = @_;
1177              
1178 1         2 my @a = (0.011180860865024,
1179             0.000006770713945,
1180             -0.000000000673891,
1181             0.000001463555541,
1182             -0.000000001667759,
1183             0.000000087256766);
1184 1         2 my @b = (0.011180860865024,
1185             0.000006770713945,
1186             -0.000000000673891,
1187             0.000005307158404,
1188             0.000000000319977,
1189             0.000000088250634);
1190 1         1 my @d = (0.009717173455170,
1191             -0.000004136915141,
1192             -0.000000001052046,
1193             0.000002068457570,
1194             0.000000001052046,
1195             -0.000000202812107);
1196              
1197 1         2 my $t = ($jd_start - JULIAN_DAY_J2000)/JULIAN_DAYS_IN_CENTURY;
1198 1         1 my $st = ($jd_end - $jd_start)/JULIAN_DAYS_IN_CENTURY;
1199 1         1 my $t2 = $t * $t;
1200 1         1 my $st2 = $st * $st;
1201 1         2 my $st3 = $st2 * $st;
1202              
1203             # Calculate the Equatorial precession parameters
1204             # (ref. USNO Circular no. 163 1981,
1205             # Lieske et al., Astron. & Astrophys., 58, 1 1977)
1206              
1207 1         3 my $zeta = ($a[0] + $a[1]*$t + $a[2]*$t2) * $st +
1208             ($a[3] + $a[4]*$t) * $st2 + $a[5] * $st3;
1209 1         4 my $z = ($b[0] + $b[1]*$t + $b[2]*$t2) * $st +
1210             ($b[3] + $b[4]*$t) * $st2 + $b[5] * $st3;
1211 1         2 my $theta = ($d[0] + $d[1]*$t + $d[2]*$t2) * $st -
1212             ($d[3] + $d[4]*$t) * $st2 + $d[5] * $st3;
1213              
1214             # Calculate the P matrix
1215 1         4 my @precession = ([0.0, 0.0, 0.0],
1216             [0.0, 0.0, 0.0],
1217             [0.0, 0.0, 0.0]);
1218 1         3 $precession[0][0] = cos($zeta)*cos($z)*cos($theta) - sin($zeta)*sin($z);
1219 1         5 $precession[0][1] = -sin($zeta)*cos($z)*cos($theta) - cos($zeta)*sin($z);
1220 1         1 $precession[0][2] = -cos($z)*sin($theta);
1221 1         2 $precession[1][0] = cos($zeta)*sin($z)*cos($theta) + sin($zeta)*cos($z);
1222 1         2 $precession[1][1] = -sin($zeta)*sin($z)*cos($theta) + cos($zeta)*cos($z);
1223 1         1 $precession[1][2] = -sin($z)*sin($theta);
1224 1         2 $precession[2][0] = cos($zeta)*sin($theta);
1225 1         3 $precession[2][1] = -sin($zeta)*sin($theta);
1226 1         1 $precession[2][2] = cos($theta);
1227              
1228 1         3 return(@precession);
1229             }
1230              
1231             =item B
1232              
1233             ($output_left, $output_right) = coord_convert($input_left, $input_right,
1234             $input_mode, $output_mode,
1235             $mjd, $longitude, $latitude,
1236             $ref0);
1237              
1238             A routine for converting between any of the following coordinate systems :
1239             Coordinate system input/output mode
1240             ----------------- -----------------
1241             X, Y (East-West mounted) 0
1242             Azimuth, Elevation 1
1243             Hour Angle, Declination 2
1244             Right Ascension, Declination (date, J2000 or B1950) 3,4,5
1245             Galactic (B1950) 6
1246              
1247             The last four parameters in the call ($mjd, $longitude, $latitude
1248             and $ref0) are not always required for the coordinate conversion.
1249             In particular if the conversion is between two coordinate systems
1250             which are fixed with respect to the celestial sphere (RA/Dec J2000,
1251             B1950 or Galactic), or two coordinate systems which are fixed with
1252             respect to the antenna (X/Y and Az/El) then these parameters are not
1253             used (NOTE: they must always be passed, even if they only hold 0 or
1254             undef as the routine will return undef if it is not passed 8
1255             parameters). The RA/Dec date system is defined with respect to the
1256             celestial sphere, but varies with time. The table below shows which
1257             of $mjd, $longitude, $latitude and $ref0 are used for a given
1258             conversion. If in doubt you should determing the correct values for
1259             all input parameters, no checking is done in the routine that the
1260             passed values are sensible.
1261              
1262             Conversion $mjd $longitude $latitude $ref0
1263             ------------------------------------------------------------------------
1264             Galactic, Galactic,
1265             RA/Dec J2000,B1950 <->RA/Dec J2000, B1950 N N N N
1266              
1267             Galactic,
1268             RA/Dec J2000,B1950 <->RA/Dec date Y N N N
1269              
1270             Galactic,
1271             RA/Dec J2000,B1950,<->HA/Dec Y Y N N
1272             date
1273              
1274             Galactic,
1275             RA/Dec J2000,B1950,<->X/Y, Az/El Y Y Y Y
1276             date
1277              
1278             X/Y, Az/El <->X/Y, Az/El N N N N
1279              
1280             X/Y, Az/El <->HA/Dec N N Y Y
1281              
1282              
1283             NOTE : The method used for refraction correction is asymptotic at
1284             an elevation of 0 degrees.
1285              
1286             The required inputs are :
1287             $input_left - The left/longitude input coordinate (turns)
1288             $input_right - The right/latitude input coordinate (turns)
1289             $input_mode - The mode of the input coordinates (0-6)
1290             $output_mode - The mode to convert the coordinates to.
1291             $mjd - The time as modified Julian day (if necessary) at
1292             which to perform the conversion
1293             $longitude - The longitude of the location/observatory (if necessary)
1294             at which to perform the conversion (turns)
1295             $latitude - The latitude of the location/observatory (if necessary)
1296             at which to perform the conversion (turns)
1297             $ref0 - The refraction constant (if in doubt use 0.00005).
1298              
1299             The returned values are :
1300             $output_left - The left/longitude output coordinate (turns)
1301             $output_right - The right/latitude output coordinate (turns)
1302              
1303             =cut
1304              
1305             sub coord_convert ($$$$;$$$$) {
1306 1     1 1 6 my ($input_left, $input_right, $input_mode, $output_mode, $mjd, $longitude,
1307             $latitude, $ref0) = @_;
1308              
1309             # Some required constants
1310 1         2 my ($EWXY, $AZEL, $HADEC, $DATE, $J2000, $B1950, $GALACTIC) = 0..6;
1311              
1312             # First check what the input and output modes are.
1313 1 50 33     4 if (($input_mode < $EWXY) || ($input_mode > $GALACTIC)) {
1314 0         0 carp "Invalid input coordinate mode : $input_mode\n".
1315             "Valid inputs are numbers in the range 0-6, which corrspond to X/Y, ".
1316             "Az/El,\n HA/Dec, RA/Dec (date), RA/Dec (J2000), RA/Dec (B1950), ".
1317             "Galactic.";
1318 0         0 return undef;
1319             }
1320 1 50 33     5 if (($output_mode < $EWXY) || ($output_mode > $GALACTIC)) {
1321 0         0 carp "Invalid output coordinate mode : $output_mode\n".
1322             "Valid outputs are numbers in the range 0-6, which corrspond to X/Y, ".
1323             "Az/El,\n HA/Dec, RA/Dec (date), RA/Dec (J2000), RA/Dec (B1950), ".
1324             "Galactic.";
1325 0         0 return undef;
1326             }
1327              
1328             # Check we have the correct parameters passed
1329              
1330             # Need mjd
1331 1 50 33     7 if ((($input_mode>=$DATE && $output_mode<=$DATE) ||
      33        
1332             ($input_mode<=$DATE && $output_mode>=$DATE)) &&
1333             !(defined($mjd))) {
1334 0         0 carp '$mjd parametr missing';
1335 0         0 return undef;
1336             }
1337             # Need longitude
1338 1 50 33     11 if ((($input_mode>=$HADEC && $output_mode<=$AZEL) ||
      33        
1339             ($input_mode<=$HADEC && $output_mode>=$HADEC)) &&
1340             !(defined($longitude))) {
1341 0         0 carp '$longitude parametr missing';
1342 0         0 return undef;
1343             }
1344             # Need latitude
1345 1 50 33     13 if ((($input_mode>=$HADEC && $output_mode<$HADEC) ||
      33        
1346             ($input_mode<=$AZEL && $output_mode>$AZEL)) &&
1347             !(defined($latitude))) {
1348 0         0 carp '$latitude parameter missing';
1349 0         0 return undef;
1350             }
1351             # Need ref0
1352 1 50 33     7 if ((($input_mode>=$HADEC && $output_mode<$HADEC) ||
      33        
1353             ($input_mode<=$AZEL && $output_mode>$AZEL)) &&
1354             !(defined($ref0))) {
1355 0         0 carp '$ref0 parameter missing';
1356 0         0 return undef;
1357             }
1358              
1359             # If necessary determine ephemeris quantities (if either of the modes are
1360             # date, HA/Dec, AzEl or EWXY).
1361 1         1 my ($omega, $rma, $mlanom, $F, $D, $eps0, $deps, $dpsi);
1362 1         1 my @nu = ();
1363              
1364 1 50 33     8 if (($input_mode<=$DATE && $output_mode>=$DATE) ||
      33        
      33        
1365             ($input_mode>=$DATE && $output_mode<=$DATE)) {
1366 1         2 ($omega, $rma, $mlanom, $F, $D, $eps0) = ephem_vars($mjd+2400000.5);
1367 1         3 ($deps, $dpsi, @nu) = nutate($omega, $F, $D, $rma, $mlanom, $eps0);
1368             }
1369              
1370 1         2 my @vonc = ();
1371 1 50 33     7 if (($input_mode<=$HADEC && $output_mode>=$DATE) ||
      33        
      33        
1372             ($input_mode>=$DATE && $output_mode<=$HADEC)) {
1373              
1374             # Calculate the interval to/from J2000 in Julian Centuries
1375 1         2 my $jcents = ($mjd+2400000.5-(JULIAN_DAY_J2000))/JULIAN_DAYS_IN_CENTURY;
1376              
1377             # Compute the eccentricity of the Earth's orbit (in radians)
1378             # [Explanatory supplement to the Astronomical Ephemeris 1961, p 98]
1379 1         2 my $e = (-0.000000126 * $jcents - 0.00004205) * $jcents + 0.016709114;
1380              
1381             # Compute the eccentric anomaly, by iteratively solving :
1382             # ea = e*sin(ea) - rma
1383 1         1 my $ea = $rma;
1384 1         1 my $xx;
1385 1         0 do {
1386 3         2 $xx = $ea;
1387 3         7 $ea = $xx + ($rma - $xx + $e*sin($xx)) / (1.0 - $e*cos($xx));
1388             } while (abs($ea -$xx) > 1.0e-9);
1389              
1390             # Compute the mean longitude of perihelion, in radians
1391             # (reference as for `e').
1392 1         3 my $perihl = ((0.00000005817764*$jcents + 0.000008077) * $jcents
1393             + 0.030010190) * $jcents + 1.796613066;
1394              
1395             # Calculate the equation of the equinoxes
1396             #my $eqenx = $dpsi * cos(($eps0+$deps)*2.0*$PI);
1397              
1398             # Compute the abberation vector
1399 1         1 my $eps = $eps0 + $deps;
1400 1         2 $xx = 0.00009936508 / (1.0 - $e*cos($ea));
1401 1         1 my $efac = sqrt(1.0 - $e*$e);
1402 1         3 $vonc[0] = $xx * (-cos($perihl)*sin($ea) - $efac*sin($perihl)*cos($ea));
1403 1         2 $vonc[1] = $xx * (-sin($perihl)*cos($eps)*sin($ea) +
1404             $efac*cos($perihl)*cos($eps)*cos($ea));
1405 1         2 $vonc[2] = $xx * (-sin($perihl)*sin($eps)*sin($ea) +
1406             $efac*cos($perihl)*sin($eps)*cos($ea));
1407              
1408             }
1409              
1410 1         1 my @prcmat = ();
1411 1 50 33     10 if (($input_mode<=$DATE && $output_mode>=$J2000) ||
      33        
      33        
1412             ($input_mode>=$J2000 && $output_mode<=$DATE)) {
1413              
1414             # compute the general precession matrix. */
1415 1         2 my @gp = precsn(JULIAN_DAY_J2000, $mjd+2400000.5);
1416              
1417             # The matrices returned from nutate (nu) and precsn (gp) can be used
1418             # to convert J2000 coordinates to date by :
1419             # (coords at date) = gp * nu * (coords at J2000)
1420             # gp and nu can be combined to give the required precession matrix
1421              
1422 1         2 for (my $i=0 ; $i<3 ; $i++) {
1423 3         4 for (my $j=0 ; $j<3 ; $j++) {
1424 9         7 my $xx = 0.0;
1425 9         10 for (my $k=0 ; $k<3 ; $k++) {
1426 27         35 $xx = $xx + $gp[$i][$k] * $nu[$k][$j];
1427             }
1428 9         13 $prcmat[$i][$j] = $xx;
1429             }
1430             }
1431             }
1432              
1433 1         1 my $lmst;
1434 1 50 33     9 if (($input_mode<=$HADEC && $output_mode>=$DATE) ||
      33        
      33        
1435             ($output_mode<=$HADEC && $input_mode>=$DATE)) {
1436 1         3 $lmst = mjd2lst($mjd, $longitude);
1437             }
1438              
1439             # Perform the conversion
1440 1         1 my (@lb, @b1950, @j2000, @date, $ra, $ha, $dec, $az, $el, $x, $y);
1441 1 50       5 if ($input_mode == $GALACTIC) {
    50          
    50          
    0          
    0          
    0          
1442 0         0 @lb = pol2r($input_left, $input_right);
1443             } elsif ($input_mode == $B1950) {
1444 0         0 @b1950 = pol2r($input_left, $input_right);
1445             } elsif ($input_mode == $J2000) {
1446 1         2 @j2000 = pol2r($input_left, $input_right);
1447             } elsif ($input_mode == $DATE) {
1448 0         0 @date = pol2r($input_left, $input_right);
1449             } elsif ($input_mode == $HADEC) {
1450 0         0 $ha = $input_left;
1451 0         0 $dec = $input_right;
1452             } elsif ($input_mode == $AZEL) {
1453 0         0 $az = $input_left;
1454 0         0 $el = $input_right;
1455             } else {
1456 0         0 $x = $input_left;
1457 0         0 $y = $input_right;
1458             }
1459              
1460             # Conversion is to a "lower" mode
1461 1 50       6 if ($output_mode < $input_mode) {
1462              
1463             # Convert from Galactic to B1950
1464 1 50       2 if ($input_mode == $GALACTIC) {
1465 0         0 @b1950 = galfk4r(@lb);
1466             }
1467              
1468             # Convert from B1950 to J2000
1469 1 50 33     3 if (($input_mode >= $B1950) && ($output_mode < $B1950)) {
1470 0         0 @j2000 = fk4fk5r(@b1950);
1471             }
1472              
1473             # Precess from J2000 to date
1474 1 50 33     4 if (($input_mode >= $J2000) && ($output_mode < $J2000)) {
1475 1         2 for (my $i=0 ; $i<3 ; $i++) {
1476 3         4 $date[$i] = 0.0;
1477 3         4 for (my $j=0 ; $j<3 ; $j++) {
1478 9         23 $date[$i] += $prcmat[$i][$j] * $j2000[$j];
1479             }
1480             }
1481             }
1482              
1483             # Convert from date to HA/Dec
1484 1 50 33     6 if (($input_mode >= $DATE) && ($output_mode < $DATE)) {
1485              
1486             # Convert to geometrical equitorial coordinates
1487 1         5 for (my $i=0 ; $i<3 ; $i++) {
1488 3         7 $date[$i] += $vonc[$i];
1489             }
1490              
1491             # Convert from retangular back to polar coordinates
1492 1         2 ($ra, $dec) = r2pol(@date);
1493              
1494             # Convert to hour angle
1495 1         4 $ha = $lmst - $ra;
1496 1 50       3 if ($ha < 0.0) {
1497 1         2 $ha += 1.0;
1498             }
1499             }
1500              
1501             # Convert from HA/Dec to Az/El
1502 1 50 33     3 if (($input_mode >= $HADEC) && ($output_mode < $HADEC)) {
1503 1         2 ($az, $el) = eqazel($ha, $dec, $latitude);
1504              
1505             # Correct for refraction
1506 1         10 $el += $ref0/tan($el*2.0*$PI);
1507             }
1508              
1509             # Convert from Az/El to X/Y
1510 1 50 33     4 if (($input_mode >= $AZEL) && ($output_mode < $AZEL)) {
1511 0         0 ($x, $y) = azel2xy($az, $el);
1512             }
1513             } else {
1514             # Convert from X/Y to Az/El
1515 0 0 0     0 if (($input_mode == $EWXY) && ($output_mode > $EWXY)) {
1516 0         0 ($az, $el) = xy2azel($x, $y);
1517             }
1518              
1519             # Convert from Az/El to HA/Dec
1520 0 0 0     0 if (($input_mode <= $AZEL) && ($output_mode > $AZEL)) {
1521              
1522             # First numerically invert the refraction correction
1523 0         0 my $upper = $el - $ref0/tan($el*2.0*$PI);
1524 0         0 my $lower = $el - 1.5*$ref0/tan($el*2.0*$PI);
1525 0         0 my $root = ($lower+$upper)/2.0;
1526 0         0 my $niter = 0;
1527 0   0     0 do {
1528 0 0       0 if ($root + $ref0/tan($root*2.0*$PI) - $el > 0.0) {
1529 0         0 $upper = $root;
1530             } else {
1531 0         0 $lower = $root;
1532             }
1533 0         0 $root = ($lower+$upper)/2.0;
1534 0         0 $niter++;
1535             } while (($niter <= 10) && (($upper-$root) > 7.0e-8));
1536 0         0 $el = $root;
1537              
1538             # Now do the conversion
1539 0         0 ($ha, $dec) = eqazel($az, $el, $latitude);
1540             }
1541              
1542             # Convert from HA/Dec to date
1543 0 0 0     0 if (($input_mode <= $HADEC) && ($output_mode > $HADEC)) {
1544 0         0 $ra = $lmst - $ha;
1545 0 0       0 if ($ra < 0.0) {
1546 0         0 $ra += 1.0;
1547             }
1548 0         0 @date = pol2r($ra, $dec);
1549              
1550             # Remove the abberation vector
1551 0         0 for (my $i=0 ; $i<3 ; $i++) {
1552 0         0 $date[$i] -= $vonc[$i];
1553             }
1554             }
1555              
1556             # precess from date to J2000
1557 0 0 0     0 if (($input_mode <= $DATE) && ($output_mode > $DATE)) {
1558 0         0 for (my $i=0 ; $i<3 ; $i++) {
1559 0         0 $j2000[$i] = 0.0;
1560 0         0 for (my $j=0 ; $j<3 ; $j++) {
1561 0         0 $j2000[$i] += $prcmat[$j][$i] * $date[$j];
1562             }
1563             }
1564             }
1565              
1566             # Convert from J2000 to B1950
1567 0 0 0     0 if (($input_mode <= $J2000) && ($output_mode > $J2000)) {
1568 0         0 @b1950 = fk5fk4(@j2000);
1569             }
1570              
1571             # Convert from B1950 to Galactic
1572 0 0 0     0 if (($input_mode <= $B1950) && ($output_mode >= $B1950)) {
1573 0         0 @lb = fk4galr(@b1950);
1574             }
1575             }
1576              
1577 1 50       3 if ($output_mode == $EWXY) {
    50          
    0          
    0          
    0          
    0          
    0          
1578 0         0 return($x, $y);
1579             } elsif ($output_mode == $AZEL) {
1580 1         4 return($az, $el);
1581             } elsif ($output_mode == $HADEC) {
1582 0         0 return($ha, $dec);
1583             } elsif ($output_mode == $DATE) {
1584 0         0 return(r2pol(@date));
1585             } elsif ($output_mode == $J2000) {
1586 0         0 return(r2pol(@j2000));
1587             } elsif ($output_mode == $B1950) {
1588 0         0 return(r2pol(@b1950));
1589             } elsif ($output_mode == $GALACTIC) {
1590 0         0 return(r2pol(@lb));
1591             }
1592             }
1593              
1594             =item B
1595              
1596             $haset = haset_ewxy($declination, $latitude, %limits);
1597              
1598             This routine takes the $declination of the source, and the $latitude of the
1599             EWXY mounted antenna and calculates the hour angle at which the source
1600             will set. It is then trivial to calculate the time until the source
1601             sets, simply by subtracting the current hour angle of the source from
1602             the hour angle at which it sets.
1603              
1604             The required inputs are :
1605             $declination - The declination of the source (turns)
1606             $latitude - The latitude of the observatory (turns)
1607             %limits - A reference to a hash holding the EWXY antenna limits
1608             The following keys must be defined XLOW, XLOW_KEYHOLE,
1609             XHIGH, XHIGH_KEYHOLE, YLOW, YLOW_KEYHOLE, YHIGH,
1610             YHIGH_KEYHOLE (all values shoule be in turns)
1611              
1612             The returned value is :
1613             $haset - The hour angle (turns) at which a source at this
1614             declination sets for an EWXY mounted antenna with the
1615             given limits at the given latitude
1616              
1617             NOTE: returns undef if %limits hash is missing any of the required keys
1618              
1619             =cut
1620              
1621             sub haset_ewxy($$\%) {
1622              
1623 0     0 1 0 my ($declination, $latitude, $limitsref) = @_;
1624              
1625             # Check that all the required keys are present
1626 0 0 0     0 if ((!exists $limitsref->{XLOW}) || (!exists $limitsref->{XLOW_KEYHOLE}) ||
      0        
      0        
      0        
      0        
      0        
      0        
1627             (!exists $limitsref->{XHIGH}) || (!exists $limitsref->{XHIGH_KEYHOLE}) ||
1628             (!exists $limitsref->{YLOW}) || (!exists $limitsref->{YLOW_KEYHOLE}) ||
1629             (!exists $limitsref->{YHIGH}) || (!exists $limitsref->{YHIGH_KEYHOLE})) {
1630 0         0 carp 'Missing key in %limits';
1631 0         0 return undef;
1632             }
1633              
1634             # Local variables
1635 0         0 my ($pole, $pxlim, $exlim, $hix, $hixk, $lowx, $lowxk);
1636              
1637 0 0       0 if ($latitude < 0.0) {
1638 0         0 $pole = -90.0/360.0;
1639 0         0 $pxlim = $limitsref->{XLOW};
1640 0         0 $exlim = $limitsref->{XHIGH};
1641             } else {
1642 0         0 $pole = 90.0/360.0;
1643 0         0 $pxlim = $limitsref->{XHIGH};
1644 0         0 $exlim = $limitsref->{XLOW};
1645             }
1646 0         0 my $dec_never = $latitude + $exlim;
1647 0         0 my $dec_always = $pole - ($latitude + $pxlim - $pole);
1648              
1649 0 0 0     0 if ((($latitude < 0.0) && ($declination > $dec_never)) ||
    0 0        
      0        
      0        
      0        
      0        
1650             (($latitude > 0.0) && ($declination < $dec_never))) {
1651              
1652             # Source is never up
1653 0         0 return(0.0);
1654             } elsif ((($latitude < 0.0) && ($declination < $dec_always)) ||
1655             (($latitude > 0.0) && ($declination > $dec_always))) {
1656              
1657             # Source is always up
1658 0         0 return(1.0);
1659             } else {
1660              
1661             # Up some of the time - calculate the ghastly constants and
1662             # do everything in radians from here on.
1663 0         0 $declination = 2.0 * $PI * $declination;
1664 0         0 $latitude = 2.0 * $PI * $latitude;
1665 0         0 my $k0 = -cos($declination);
1666 0         0 my $k1 = sin($declination)*cos($latitude);
1667 0         0 my $k2 = sin($declination)*sin($latitude);
1668 0         0 my $k3 = cos($declination)*sin($latitude);
1669 0         0 my $k4 = cos($declination)*cos($latitude);
1670 0         0 my $k5 = $k4 * $k1 + $k2 * $k3;
1671 0         0 my $x = 2.0 * $PI * $limitsref->{XLOW_KEYHOLE};
1672 0         0 my $dec_split = asin(cos(2.0 * $PI * $limitsref->{YLOW}) *
1673             (cos($x) * sin($latitude) + sin($x) *
1674             cos($latitude)));
1675 0 0       0 if ($latitude > 0.0) {
1676            
1677             # Set up for northern antenna
1678 0         0 $hix = $limitsref->{XLOW};
1679 0         0 $hixk = $limitsref->{XLOW_KEYHOLE};
1680 0         0 $lowx = $limitsref->{XHIGH};
1681 0         0 $lowxk = $limitsref->{XHIGH_KEYHOLE};
1682            
1683             } else {
1684            
1685             # Set up for southern antenna
1686 0         0 $hix = $limitsref->{XHIGH};
1687 0         0 $hixk = $limitsref->{XHIGH_KEYHOLE};
1688 0         0 $lowx = $limitsref->{XLOW};
1689 0         0 $lowxk = $limitsref->{XLOW_KEYHOLE};
1690             }
1691              
1692 0 0 0     0 if ((($declination > $dec_split) && ($latitude < 0.0)) ||
      0        
      0        
1693             (($declination < $dec_split) && ($latitude > 0.0))) {
1694            
1695             # We are on the equatorial side
1696 0         0 my $x = 2.0 * $PI * $hix;
1697 0         0 my $y = -1.0 * abs(acos($k5 / ($k4 * sin($x) + $k3 * cos($x))));
1698 0 0       0 if (abs($y) < abs(2.0 * $PI * $limitsref->{YLOW_KEYHOLE})) {
1699 0         0 return(acos(($k1 - $k2 + cos($x) * cos($y) - sin($x) * cos($y))/
1700             ($k3 + $k4))/(2.0 * $PI));
1701             } else {
1702 0         0 my $x = 2.0 * $PI * $hixk;
1703 0         0 my $y = -1.0 * abs(acos($k5 / ($k4 * sin($x) + $k3 * cos($x))));
1704 0 0       0 if (abs($y) < abs(2.0 * $PI * $limitsref->{YLOW_KEYHOLE})) {
    0          
1705 0         0 return(asin(sin(2.0 * $PI * $limitsref->{YLOW_KEYHOLE}) /
1706             $k0)/(2.0 * $PI));
1707             } elsif (abs($y) < abs(2.0 * $PI * $limitsref->{YLOW})) {
1708 0         0 return(acos(($k1 - $k2 + cos($x) * cos($y) - sin($x) * cos($y)) /
1709             ($k3 + $k4))/(2.0 * $PI));
1710             } else {
1711 0         0 return(asin(sin(2.0 * $PI*$limitsref->{YLOW}) / $k0) /
1712             (2.0 * $PI));
1713             }
1714             }
1715             } else {
1716            
1717             # We are on the polar side
1718 0         0 my $x = 2.0 * $PI * $lowx;
1719 0         0 my $y = abs(acos($k5 / ($k4 * sin($x) + $k3 * cos($x))));
1720 0 0       0 if (abs($y) < abs(2.0 * $PI * $limitsref->{YLOW_KEYHOLE})) {
1721 0         0 return(acos(($k1 - $k2 + cos($x) * cos($y) - sin($x) * cos($y)) /
1722             ($k3 + $k4))/(2.0 * $PI));
1723             } else {
1724 0         0 my $x = 2.0 * $PI * $lowxk;
1725 0         0 my $y = -1.0 * abs(acos($k5 /($k4 * sin($x) + $k3 * cos($x))));
1726 0 0       0 if (abs($y) < abs(2.0 * $PI* $limitsref->{YLOW_KEYHOLE})) {
    0          
1727 0         0 return(asin(sin(2.0 * $PI * $limitsref->{YLOW_KEYHOLE}) /
1728             $k0)/(2.0 * $PI));
1729             } elsif (abs($y) < abs(2.0 * $PI * $limitsref->{YLOW})) {
1730 0         0 return(acos(($k1 - $k2 + cos($x) * cos($y) - sin($x) * cos($y)) /
1731             ($k3 + $k4))/(2.0 * $PI));
1732             } else {
1733 0         0 return(asin(sin(2.0 * $PI * $limitsref->{YLOW}) / $k0)/
1734             (2.0 * $PI));
1735             }
1736             }
1737             }
1738             }
1739             }
1740              
1741             =item B
1742              
1743             $tlos = ewxy_tlos($hour_angle, $declination, $latitude, %limits);
1744              
1745             This routine calculates the time left on-source (tlos) for a source
1746             at $hour_angle, $declination for an EWXY mount antenna at $latitude.
1747              
1748             The required inputs are :
1749             $hour_angle - The current hour angle of the source (turns)
1750             $declination - The declination of the source (turns)
1751             $latitude - The latitude of the observatory (turns)
1752             \%limits - A reference to a hash holding the EWXY antenna limits
1753             The following keys must be defined XLOW, XLOW_KEYHOLE,
1754             XHIGH, XHIGH_KEYHOLE, YLOW, YLOW_KEYHOLE, YHIGH,
1755             YHIGH_KEYHOLE (all values should be in turns)
1756              
1757             The returned value is :
1758             $tlos - The time left on-source (turns)
1759              
1760             =cut
1761              
1762             sub ewxy_tlos($$$\%) {
1763              
1764 0     0 1 0 my ($hour_angle, $declination, $latitude, $limitsref) = @_;
1765              
1766 0         0 my $haset = haset_ewxy($declination, $latitude, %$limitsref);
1767 0 0       0 return(undef) if (!defined $haset);
1768 0 0 0     0 $haset -= $hour_angle if (($haset > 0.0) && ($haset < 1.0));
1769 0 0       0 $haset += 1.0 if ($haset < 0.0);
1770              
1771 0         0 return $haset;
1772             }
1773              
1774             =item B
1775              
1776             $haset = haset_azel($declination, $latitude, %limits);
1777              
1778             This routine takes the $declination of the source, and the
1779             $latitude of the Az/El mounted antenna and calculates the hour
1780             angle at which the source will set. It is then trivial to
1781             calculate the time until the source sets, simply by subtracting the
1782             current hour angle of the source from the hour angle at which it
1783             sets. This routine assumes that the antenna is able to rotate
1784             through 360 degrees in azimuth.
1785              
1786             The required inputs are :
1787             $declination - The declination of the source (turns)
1788             $latitude - The latitude of the observatory (turns)
1789             \%limits - A reference to a hash holding the Az/El antenna limits
1790             The following keys must be defined ELLOW (all values should
1791             be in turns)
1792              
1793             The returned value is :
1794             $haset - The hour angle (turns) at which a source at this
1795             declination sets for an Az/El mounted antenna with the
1796             given limits at the given latitude
1797              
1798             NOTE: returns undef if the %limits hash is missing any of the required keys
1799              
1800             =cut
1801              
1802             sub haset_azel($$\%) {
1803              
1804 0     0 1 0 my ($declination, $latitude, $limitsref) = @_;
1805              
1806             # Check that all the required keys are present
1807 0 0       0 if (!exists $limitsref->{ELLOW}) {
1808 0         0 carp 'Missing key in %limits';
1809 0         0 return undef ;
1810             }
1811              
1812 0         0 my $cos_haset = (cos($PI / 2.0 - $limitsref->{ELLOW} * 2.0 *
1813             $PI) - sin($latitude * 2.0 * $PI) *
1814             sin($declination * 2.0 * $PI))/
1815             (cos($declination * 2.0 * $PI)
1816             *cos($latitude * 2.0 * $PI));
1817 0 0       0 if ($cos_haset > 1.0) {
    0          
1818              
1819             # The source never rises
1820 0         0 return(0.0);
1821             } elsif ($cos_haset < -1.0) {
1822              
1823             # The source never sets
1824 0         0 return(1.0);
1825             } else {
1826              
1827 0         0 return(acos($cos_haset)/(2.0*$PI));
1828             }
1829             }
1830              
1831             =item B
1832              
1833             $tlos = azel_tlos($hour_angle, $declination, $latitude, \%limits);
1834              
1835             This routine calculates the time left on-source (tlos) for a source
1836             at $hour_angle, $declination for an Az/El mount antenna at $latitude.
1837              
1838             The required inputs are :
1839             $hour_angle - The current hour angle of the source (turns)
1840             $declination - The declination of the source (turns)
1841             $latitude - The latitude of the observatory (turns)
1842             %limits - A reference to a hash holding the Az/El antenna limits
1843             The following keys must be defined ELLOW (all values
1844             should be in turns)
1845              
1846             The returned value is :
1847             $tlos - The time left on-source (turns)
1848              
1849             =cut
1850              
1851             sub azel_tlos($$$\%) {
1852 0     0 1 0 my ($hour_angle, $declination, $latitude, $limitsref) = @_;
1853              
1854             # Calculate the time left onsource
1855 0         0 my $haset = haset_azel($declination, $latitude, %$limitsref);
1856 0 0       0 if (!defined $haset) {return(undef)};
  0         0  
1857 0 0 0     0 if (($haset > 0.0) && ($haset < 1.0)) { $haset -= $hour_angle; }
  0         0  
1858 0 0       0 if ($haset < 0.0) { $haset += 1.0; }
  0         0  
1859              
1860 0         0 return($haset);
1861             }
1862              
1863             =item B
1864              
1865             $ha_set = antenna_rise($declination, $latitude, $mount, \%limits);
1866              
1867             Given the $declination of the source, the $latitude of the antenna,
1868             the type of the antenna $mount and a reference to a hash holding
1869             information on the antenna limits, this routine calculates the hour
1870             angle at which the source sets for the antenna. The hour angle at
1871             which it rises is simply the negative of that at which it sets.
1872             These values in turn can be used to calculate the LMST at which the
1873             source rises/sets and from that the UT at which the source
1874             rises/sets on a given day, or to calculate the native coordinates
1875             at which the source rises/sets.
1876              
1877             If you want to calculate source rise times above arbitrary elevation,
1878             use the routine rise.
1879              
1880             The required inputs are :
1881             $declination - The declination of the source (turns)
1882             $latitude - The latitude of the observatory (turns)
1883             $mount - The type of antenna mount, 0 => EWXY mount, 1 => Az/El,
1884             any other number will cause the routine to return
1885             undef
1886             %limits - A reference to a hash holding the antenna limits
1887             For an EWXY antenna there must be keys for all the
1888             limits (i.e. XLOW, XLOW_KEYHOLE, XHIGH, XHIGH_KEYHOLE,
1889             YLOW, YLOW_KEYHOLE, YHIGH, YHIGH_KEYHOLE). For an Az/El
1890             antenna there must be a key for ELLOW (all values should
1891             be in turns).
1892              
1893             The returned values are :
1894             $ha_set - The hour angle at which the source sets (turns). The hour
1895             angle at which the source rises is simply the negative of this
1896             value.
1897              
1898             =cut
1899              
1900             sub antenna_rise($$$$) {
1901              
1902 0     0 1 0 my ($declination, $latitude, $mount, $limitsref) = @_;
1903              
1904             # Check that the mount type is either EWXY (0) or AZEL (1)
1905 0 0 0     0 if (($mount != 0) && ($mount != 1)) {
1906 0         0 carp 'mount must equal 0 or 1';
1907 0         0 return undef;
1908             }
1909              
1910 0 0       0 if ($mount == 0) {
    0          
1911 0         0 return(haset_ewxy($declination, $latitude, %$limitsref));
1912             } elsif ($mount == 1) {
1913 0         0 return(haset_azel($declination, $latitude, %$limitsref));
1914             }
1915             }
1916              
1917             my @b2g = ([-0.054875539726, 0.494109453312, -0.867666135858],
1918             [-0.873437108010, -0.444829589425, -0.198076386122],
1919             [-0.483834985808, 0.746982251810, 0.455983795705]);
1920              
1921             #my @b2g = ([ -0.0548777621, +0.4941083214, -0.8676666398],
1922             # [ -0.8734369591, -0.4448308610, -0.1980741871],
1923             # [ -0.4838350026, +0.7469822433, +0.4559837919]);
1924              
1925             sub j2gal($$) {
1926 0     0 0 0 my ($ra,$dec) = @_;
1927 0         0 my @r = pol2r($ra,$dec);
1928 0         0 my @g = (0,0,0);
1929 0         0 for (my $i=0; $i<3; $i++) {
1930 0         0 for (my $j=0; $j<3; $j++) {
1931 0         0 $g[$i]+= $b2g[$j][$i] * $r[$j];
1932             }
1933             }
1934 0         0 return r2pol(@g);
1935             }
1936              
1937             # SLALIB support routines
1938              
1939             sub epb2d ($) {
1940             # - - - - - -
1941             # E P B 2 D
1942             # - - - - - -
1943             #
1944             # Conversion of Besselian Epoch to Modified Julian Date
1945             # (double precision)
1946             #
1947             # Given:
1948             # EPB dp Besselian Epoch
1949             #
1950             # The result is the Modified Julian Date (JD - 2400000.5).
1951             #
1952             # Reference:
1953             # Lieske,J.H., 1979. Astron.Astrophys.,73,282.
1954             #
1955             # P.T.Wallace Starlink February 1984
1956             #
1957             # Copyright (C) 1995 Rutherford Appleton Laboratory
1958              
1959 1     1 0 1 my $epb = shift;
1960              
1961 1         3 return 15019.81352 + ($epb-1900)*365.242198781;
1962             }
1963              
1964             sub epj ($) {
1965             # - - - -
1966             # E P J
1967             # - - - -
1968             #
1969             # Conversion of Modified Julian Date to Julian Epoch (double precision)
1970             #
1971             # Given:
1972             # DATE dp Modified Julian Date (JD - 2400000.5)
1973             #
1974             # The result is the Julian Epoch.
1975             #
1976             # Reference:
1977             # Lieske,J.H., 1979. Astron.Astrophys.,73,282.
1978             #
1979             # P.T.Wallace Starlink February 1984
1980             #
1981             # Copyright (C) 1995 Rutherford Appleton Laboratory
1982 1     1 0 2 my $date = shift;
1983              
1984 1         2 return 2000 + ($date-51544.5)/365.25;
1985             }
1986              
1987             sub pm ($$$$$$$$$$) {
1988             # - - -
1989             # P M
1990             # - - -
1991             #
1992             # Apply corrections for proper motion to a star RA,Dec
1993             # (double precision)
1994             #
1995             # References:
1996             # 1984 Astronomical Almanac, pp B39-B41.
1997             # (also Lederle & Schwan, Astron. Astrophys. 134,
1998             # 1-6, 1984)
1999             #
2000             # Given:
2001             # R0,D0 dp RA,Dec at epoch EP0 (rad)
2002             # PR,PD dp proper motions: RA,Dec changes per year of epoch
2003             # EP0 dp start epoch in years (e.g. Julian epoch)
2004             # EP1 dp end epoch in years (same system as EP0)
2005             #
2006             # Returned:
2007             # R1,D1 dp RA,Dec at epoch EP1 (rad)
2008             #
2009             # Called:
2010             # sla_DCS2C spherical to Cartesian
2011             # sla_DCC2S Cartesian to spherical
2012             # sla_DRANRM normalize angle 0-2Pi
2013             #
2014             # Note:
2015             # The proper motions in RA are dRA/dt rather than
2016             # cos(Dec)*dRA/dt, and are in the same coordinate
2017             # system as R0,D0.
2018             #
2019             # P.T.Wallace Starlink 23 August 1996
2020             #
2021             # Copyright (C) 1996 Rutherford Appleton Laboratory
2022              
2023 0     0 0   my ($r0, $d0, $pr, $pd, $ep0, $ep1) = @_;
2024              
2025             # Km/s to AU/year multiplied by arc seconds to radians
2026 1     1   5 use constant VFR => 0.21094502*0.484813681109535994e-5;
  1         1  
  1         121  
2027              
2028 0           my (@em, $t);
2029              
2030             # Spherical to Cartesian
2031 0           my @p = pol2r($r0,$d0);
2032              
2033             # Space motion (radians per year)
2034 0           $em[0]=-$pr*$p[1]-$pd*cos($r0)*sin($d0);
2035 0           $em[1]= $pr*$p[0]-$pd*sin($r0)*sin($d0);
2036 0           $em[2]= $pd*cos($d0);
2037              
2038             # Apply the motion
2039 0           $t=$ep1-$ep0;
2040 0           for (my $i = 0; $i<3; $i++) {
2041 0           $p[$i]=$p[$i]+$t*$em[$i];
2042             }
2043              
2044             # Cartesian to spherical
2045 0           return r2pol(@p);
2046             }
2047              
2048              
2049             1;
2050              
2051             __END__