1package Astro::Telescope;
2
3=head1 NAME
4
5Astro::Telescope - class for obtaining telescope information
6
7=head1 SYNOPSIS
8
9  use Astro::Telescope;
10
11  $tel = new Astro::Telescope( 'UKIRT' );
12
13  $latitude = $tel->lat;
14  $longitude = $tel->long;
15  $altitude = $tel->alt;
16
17  %limits = $tel->limits;
18
19  @telescopes = Astro::Telescope->telNames();
20
21=head1 DESCRIPTION
22
23A class for handling properties of individual telescopes such
24as longitude, latitude, height and observational limits.
25
26=cut
27
28use 5.006;
29use warnings;
30use warnings::register;
31use strict;
32
33our $ASTRO_PAL = 0;
34eval { require Astro::PAL; };
35if( ! $@ ) {
36  $ASTRO_PAL = 1;
37}
38
39use Astro::Telescope::MPC;
40
41use File::Spec;
42use Carp;
43
44use vars qw/ $VERSION /;
45$VERSION = '0.71';
46
47# separator to use for output sexagesimal notation
48our $Separator = " ";
49
50# Decimal degrees to radians conversion factor.
51use constant DD2R => 0.017453292519943295769236907684886127134428718885417;
52
53# Decimal hours to radians conversion factor.
54use constant DH2R => 0.26179938779914943653855361527329190701643078328126;
55
56# Radians to degrees conversion factor.
57use constant DR2D => 57.295779513082320876798154814105170332405472466564;
58
59# Earth's equatorial radius in metres.
60use constant EQU_RAD => 6378100;
61
62# Earth's flattening parameter (actually 1-f).
63use constant E => 0.996647186;
64
65# Related to flattening parameter (sqrt(1-(1-f)^2)).
66use constant EPS => 0.081819221;
67
68# Pi.
69use constant PI => 4 * atan2( 1, 1 );
70
71# AU to metre conversion factor.
72use constant AU2METRE => 149598000000;
73
74# Hash table containing mapping from PAL telescope name to
75# MPC observatory code.
76our %pal2obs = ( 'AAT' => '260',
77                 'LPO4.2' => '950',
78                 'LPO2.5' => '950',
79                 'LP01' => '950',
80                 'LICK120' => '662',
81                 'MMT' => '696',
82                 'DAO72' => '658',
83                 'DUPONT' => '304',
84                 'MTHOP1.5' => '696',
85                 'STROMLO74' => '414',
86                 'ANU2.3' => '413',
87                 'GBVA140' => '256',
88                 'TOLOLO4M' => 'I02',
89                 'TOLOLO1.5M' => 'I02',
90                 'BLOEMF' => '074',
91                 'BOSQALEGRE' => '821',
92                 'FLAGSTF61' => '689',
93                 'LOWELL72' => '688',
94                 'OKAYAMA' => '371',
95                 'KPNO158' => '691',
96                 'KPNO90' => '691',
97                 'KPNO84' => '691',
98                 'KPNO36FT' => '697',
99                 'KOTTAMIA' => '088',
100                 'ESO3.6' => '809',
101                 'MAUNAK88' => '568',
102                 'UKIRT' => '568',
103                 'QUEBEC1.6' => '301',
104                 'MTEKAR' => '098',
105                 'MTLEMMON60' => '686',
106                 'MCDONLD2.7' => '711',
107                 'MCDONLD2.1' => '711',
108                 'PALOMAR200' => '261',
109                 'PALOMAR60' => '644',
110                 'DUNLAP74' => '779',
111                 'HPROV1.93' => '511',
112                 'HPROV1.52' => '511',
113                 'SANPM83' => '679',
114                 'SAAO74' => '079',
115                 'TAUTNBG' => '033',
116                 'CATALINA61' => '693',
117                 'STEWARD90' => '691',
118                 'USSR6' => '115',
119                 'ARECIBO' => '251',
120                 'CAMB5KM' => '503',
121                 'CAMB1MILE' => '503',
122                 'GBVA300' => '256',
123                 'JCMT' => '568',
124                 'ESONTT' => '809',
125                 'ST.ANDREWS' => '482',
126                 'APO3.5' => '645',
127                 'KECK1' => '568',
128                 'TAUTSCHM' => '033',
129                 'PALOMAR48' => '644',
130                 'UKST' => 'E12',
131                 'KISO' => '381',
132                 'ESOSCHM' => '809',
133                 'SUBARU' => '568',
134                 'CFHT' => '568',
135                 'KECK2' => '568',
136                 'GEMININ' => '568',
137                 'IRTF' => '568',
138                 'CSO' => '568',
139                 'VLT1' => '309',
140                 'VLT2' => '309',
141                 'VLT3' => '309',
142                 'VLT4' => '309',
143                 'MAGELLAN1' => '304',
144                 'MAGELLAN2' => '304',
145               );
146
147
148=head1 METHODS
149
150=head2 Constructor
151
152=over
153
154=item B<new>
155
156Create a new telescope object. Takes the telescope abbreviation
157as the single argument.
158
159  $tel = new Astro::Telescope( 'VLA' );
160
161An argument must be supplied. Returns C<undef> if the telescope
162is not recognized.
163
164If more than one argument is supplied the assumption
165is that the user is supplying telescope details. In that case,
166"Name" and "Long" must be supplied, and either the geodetic latitude and
167altitude ("Lat" and "Alt" -- but if "Alt" is not supplied it will
168default to zero and this class will issue a warning), the geocentric
169latitude and distance
170("GeocLat" and "GeocDist"), or the parallax coefficients ("Parallax")
171must be supplied. Latitudes and longitudes must be given in radians,
172altitude and distance in metres, and the parallax constants in units
173of Earth radii.
174
175  $tel = new Astro::Telescope('telescope');
176  $tel = new Astro::Telescope(Name => 'JCMT', Long => $long, Lat => $lat );
177
178
179=cut
180
181sub new {
182  my $proto = shift;
183  my $class = ref($proto) || $proto;
184
185  return undef unless @_;
186
187  # Create the new object
188  my $tel = bless {}, $class;
189
190  # Configure it with the supplied telescope name
191  # or other arguments
192  $tel->_configure( @_ ) or return undef;
193
194  return $tel;
195}
196
197=back
198
199=head2 Accessor Methods
200
201=over 4
202
203=item B<name>
204
205Returns the abbreviated name of the telescope. This is the same as
206that given to the constructor (although it will be upper-cased).
207
208The object can be reconfigured to a new telescope by supplying
209a new abbreviation to this method.
210
211  $tel->name('JCMT');
212
213The object will not change state if the name is not known.
214
215=cut
216
217sub name {
218  my $self = shift;
219  if (@_) {
220    my $name = shift;
221    $self->_configure( $name );
222  }
223  return $self->{Name};
224}
225
226=item B<fullname>
227
228Returns the full name of the telescope. For example, if the abbreviated
229name is "JCMT" this will return "JCMT 15 metre".
230
231=cut
232
233sub fullname {
234  my $self = shift;
235  return $self->{FullName};
236}
237
238=item B<obscode>
239
240Returns or sets the IAU observatory code as listed at
241http://cfa-www.harvard.edu/iau/lists/ObsCodes.html. The object will
242not change state if the observatory code is not known.
243
244=cut
245
246sub obscode {
247  my $self = shift;
248  if( @_ ) {
249    my $obscode = shift;
250    $self->_configure( $obscode );
251  }
252  return $self->{ObsCode};
253}
254
255=item B<long>
256
257Longitude of the telescope (east +ve). By default this is in radians.
258
259An argument of "d" or "s" can be supplied to retrieve the value
260in decimal degrees or sexagesimal string format respectively.
261
262 $string = $tel->long("s");
263
264=cut
265
266sub long {
267  my $self = shift;
268  my $long = $self->{Long};
269  $long = $self->_cvt_fromrad( $long, shift ) if @_;
270  return $long
271}
272
273=item B<lat>
274
275Geodetic latitude of the telescope. By default this is in radians.
276
277An argument of "d" or "s" can be supplied to retrieve the value
278in decimal degrees or sexagesimal string format respectively.
279
280  $deg = $tel->lat("d");
281
282=cut
283
284sub lat {
285  my $self = shift;
286  my $lat = $self->{Lat};
287  $lat = $self->_cvt_fromrad( $lat, shift ) if @_;
288  return $lat
289}
290
291=item B<alt>
292
293Altitude of the telescope in metres above mean sea level.
294
295=cut
296
297sub alt {
298  my $self = shift;
299  return $self->{Alt};
300}
301
302=item B<parallax>
303
304Return the parallax constants, rho*sin(phi') and rho*cos(phi'),
305where rho is the geocentric radius in Earth radii and phi' is
306the geocentric latitude. Returned as a hash where 'Par_C' is
307rho*sin(phi') and 'Par_S' is rho*cos(phi').
308
309  @parallax = $tel->parallax;
310
311=cut
312
313sub parallax {
314  my $self = shift;
315  return %{$self->{Parallax}};
316}
317
318=item B<geoc_lat>
319
320Return the geocentric latitude. By default this is in radians.
321
322An argument of "d" or "s" can be supplied to retrieve the value
323in decimal degrees or sexagesimal string format respectively.
324
325  $deg = $tel->geoc_lat("d");
326
327=cut
328
329sub geoc_lat {
330  my $self = shift;
331  my $lat = $self->{GeocLat};
332  $lat = $self->_cvt_fromrad( $lat, shift ) if @_;
333  return $lat;
334}
335
336=item B<geoc_dist>
337
338Return the distance from the centre of the Earth. By default
339this is in metres.
340
341  $geoc_dist = $tel->geoc_dist;
342
343=cut
344
345sub geoc_dist {
346  my $self = shift;
347  return $self->{GeocDist};
348}
349
350=item B<obsgeo>
351
352Return the cartesian coordinates of the observatory. These are the form required
353for specifying coordinates in the FITS OBSGEO-X, OBSGEO-Y and OBSGEO-Z header
354items.
355
356  ($x, $y, $z) = $tel->obsgeo;
357
358Values are returned in metres.
359
360=cut
361
362sub obsgeo {
363  my $self = shift;
364  my $long = $self->long;
365  my $gclat  = $self->geoc_lat;
366  my $dist = $self->geoc_dist;
367
368# Could use the PAL versions but we have local copies of these routines.
369# Seem to give identical answers to PAL within about 50 m.
370#  my $gdlat = $self->lat;
371#  Astro::PAL::palGeoc( $gdlat, $self->alt, my $pal_r, my $pal_z);
372#  $pal_r *= $AU2METRE;
373#  $pal_z *= $AU2METRE;
374
375  # calculate distance from observatory to centre of Earth projected onto the equator
376  my $r = $dist * cos( $gclat );
377
378  # calculate height above the equator
379  my $z = $dist * sin( $gclat );
380
381#  $z = $pal_z; $r = $pal_r;
382
383  # now calculate coordinates projected from the longitude
384  my $x = $r * cos( $long );
385  my $y = $r * sin( $long );
386
387  return ($x, $y, $z);
388}
389
390=item B<limits>
391
392Return the telescope limits.
393
394  %limits = $tel->limits;
395
396The limits are returned as a hash with the following keys:
397
398=over 4
399
400=item type
401
402Specifies the way in which the limits are specified. Effectively the
403telescope mount. Values of "AZEL" (for altaz telescopes) and "HADEC"
404(for equatorial telescopes) are currently supported.
405
406=item el
407
408Elevation limit of the telescope. Value is a hash with keys C<max>
409and C<min>. Units are in radians. Only used if C<type> is C<AZEL>.
410
411=item ha
412
413Hour angle limit of the telescope. Value is a hash with keys C<max>
414and C<min>. Units are in radians. Only used if C<type> is C<HADEC>.
415
416=item dec
417
418Declination limit of the telescope. Value is a hash with keys C<max>
419and C<min>. Units are in radians. Only used if C<type> is C<HADEC>.
420
421=back
422
423Only some telescopes have limits defined (please send patches with new
424limits if you know them). If limits are not available for this
425telescope limits corresponding to "above the horizon" are returned.
426
427If limits have been explicitly associated with this object using the
428C<setlimits> method then those limits will be returned.
429
430=cut
431
432sub limits {
433  my $self = shift;
434  croak "Limits() method does not (yet) accept any arguments!" if @_;
435  return %{$self->{LIMITS}} if defined $self->{LIMITS};
436
437  # Just put them all in a big hash (this could come outside
438  # the method since it does not change)
439  my %limits = (
440		JCMT => {
441			 type => "AZEL",
442			 el => { # 5 to 88 deg
443				max => 88 * DD2R,
444				min => 5 * DD2R,
445			       },
446			},
447		UKIRT => {
448			  type => "HADEC",
449			  ha => { # +/- 4.5 hours
450				max => 4.5 * DH2R,
451				min => -4.5 * DH2R,
452				},
453			  dec=> { # -42 to +60 deg
454				max => 60 * DD2R,
455				min => -42 * DD2R,
456				},
457			 },
458
459	       );
460
461  # Return the hash if it exists
462  if (exists $limits{ $self->name }) {
463    return %{ $limits{ $self->name } };
464  } else {
465    # fudge something for simple observability
466    return ( type => 'AZEL',
467	     el   => {
468		      max => 90 * DD2R,
469		      min => 0,
470		     }
471	   );
472  }
473
474}
475
476=item B<setlimits>
477
478This method allows limits for this telescope object to be set explicitly.
479The contents of the limits hash must be those described by the C<limits> method
480and will be returned by the C<limits> method). Limits set
481in this way will override built-in limits.
482
483  $tel->setlimits( %limits );
484
485Limits will be cleared if the object is reconfigured (eg by setting the obscode).
486
487=cut
488
489sub setlimits {
490  my $self = shift;
491  my %limits = @_;
492  croak "Supplied limits do not seem to contain a type key"
493    unless exists $limits{type};
494  $self->{LIMITS} = \%limits;
495  return;
496}
497
498=back
499
500=head2 Class Methods
501
502=over 4
503
504=item B<telNames>
505
506Obtain a sorted list of all supported telescope names.
507
508  @names = Astro::Telescope->telNames;
509
510Currently only returns the PAL names, and only if Astro::PAL is
511available. If it is not available, return an empty list.
512
513=cut
514
515sub telNames {
516  my @names;
517  if( $ASTRO_PAL ) {
518    my $i = 1;
519    my $ident = '';
520    while (defined $ident) {
521      my ($ident, $name, $w, $p, $h) = Astro::PAL::palObs($i);
522      last unless defined $ident;
523      $i++;
524      push(@names, $ident);
525    }
526  }
527  return sort @names;
528}
529
530=back
531
532=begin __PRIVATE__
533
534=head2 Private Methods
535
536=over 4
537
538=item B<_configure>
539
540Reconfigure the object for a new telescope. Called automatically
541by the constructor or if a new telescope name or observatory
542code is provided.
543
544Returns C<undef> if the telescope was not supported.
545
546If more than one argument is supplied the assumption
547is that the user is supplying telescope details. In that case,
548"Name" and "Long" must be supplied, and either the geodetic latitude and
549altitude ("Lat" and "Alt" -- but if "Alt" is not supplied it will
550default to zero and this class will issue a warning), the geocentric
551latitude and distance
552("GeocLat" and "GeocDist"), or the parallax coefficients ("Parallax")
553must be supplied. Latitudes and longitudes must be given in radians,
554altitude and distance in metres, and the parallax constants in units
555of Earth radii.
556
557  $t->_configure('telescope');
558  $t->_configure( $obscode );
559  $t->_configure(Name => 'JCMT', Long => $long, Lat => $lat );
560
561Any user defined limits are cleared by this routine.
562
563=cut
564
565sub _configure {
566  my $self = shift;
567  $self->{LIMITS} = undef; # reset user-supplied limits
568  if (scalar(@_) == 1) {
569
570    my $name = uc(shift);
571
572    &Astro::Telescope::MPC::parse_table;
573
574    if( exists( $Astro::Telescope::MPC::obs_codes{$name} ) ) {
575
576      $self->{Name} = $Astro::Telescope::MPC::obs_codes{$name}->{Name};
577      $self->{FullName} = $Astro::Telescope::MPC::obs_codes{$name}->{Name};
578      $self->{ObsCode} = $name;
579      $self->{Long} = $Astro::Telescope::MPC::obs_codes{$name}->{Long};
580      $self->{Parallax}->{Par_C} = $Astro::Telescope::MPC::obs_codes{$name}->{Par_C};
581      $self->{Parallax}->{Par_S} = $Astro::Telescope::MPC::obs_codes{$name}->{Par_S};
582
583      ( $self->{GeocLat}, $self->{GeocDist} ) = $self->_par2geoc();
584      ( $self->{Lat}, $self->{Alt} ) = $self->_geoc2geod();
585
586    } elsif( $ASTRO_PAL ) {
587
588      my ($ident, $fullname, $w, $p, $h) = Astro::PAL::palObs($name);
589
590      if( defined $fullname ) {
591
592        # Correct for East positive
593        $w *= -1;
594
595        $self->{Name} = $ident;
596        $self->{FullName} = $fullname;
597        $self->{Long} = $w;
598        $self->{Lat} = $p;
599        $self->{Alt} = $h;
600
601        ( $self->{GeocLat}, $self->{GeocDist} ) = $self->_geod2geoc();
602        $self->{Parallax} = $self->_geoc2par();
603
604        $self->{ObsCode} = $pal2obs{$name};
605
606      } else {
607        return undef;
608      }
609
610    } else {
611      return undef;
612    }
613
614    return 1;
615
616  } else {
617    my %args = @_;
618
619    return undef unless exists $args{Name} && exists $args{Long};
620
621    if( exists( $args{Lat} ) ) {
622
623      if( !exists( $args{Alt} ) ) {
624        warnings::warnif( "Warning: Altitude not given. Defaulting to zero." );
625        $self->{Alt} = 0;
626      } else {
627        $self->{Alt} = $args{Alt};
628      }
629      $self->{Lat} = $args{Lat};
630
631      if( !exists( $args{GeocLat} ) || !exists( $args{GeocDist} ) ) {
632        ( $self->{GeocLat}, $self->{GeocDist} ) = $self->_geod2geoc();
633      }
634
635      if( !exists( $args{Parallax} ) ) {
636        $self->{Parallax} = $self->_geoc2par();
637      }
638    } elsif( exists( $args{Parallax} ) ) {
639
640      $self->{Parallax} = $args{Parallax};
641
642      if( !exists( $args{GeocLat} ) || !exists( $args{GeocDist} ) ) {
643        ( $self->{GeocLat}, $self->{GeocDist} ) = $self->_par2geoc();
644      }
645
646      if( !exists( $args{Lat} ) || !exists( $args{Alt} ) ) {
647        ( $self->{Lat}, $self->{Alt} ) = $self->_geoc2geod();
648      }
649    } elsif( exists( $args{GeocLat} ) && exists( $args{GeocDist} ) ) {
650
651      $self->{GeocLat} = $args{GeocLat};
652      $self->{GeocDist} = $args{GeocDist};
653
654      if( !exists( $args{Lat} ) || !exists( $args{Alt} ) ) {
655        ( $self->{Lat}, $self->{Alt} ) = $self->_geoc2geod();
656      }
657      if( !exists( $args{Parallax} ) ) {
658        $self->{Parallax} = $self->_geoc2par();
659      }
660    } else {
661      return undef;
662    }
663
664    for my $key (qw/ Name Long FullName ObsCode / ) {
665      $self->{$key} = $args{$key} if exists $args{$key};
666    }
667    return 1;
668  }
669}
670
671=item B<_cvt_fromrad>
672
673Convert radians to either degrees ("d") or sexagesimal string ("s").
674
675  $converted = $self->_cvt_fromrad($rad, "s");
676
677If the second argument is not supplied the string is returned
678unmodified.
679
680The string is space separated by default but this can be overridden
681by setting the variable $Astro::Telescope::Separator to a new value.
682
683=cut
684
685sub _cvt_fromrad {
686  my $self = shift;
687  my $rad = shift;
688  my $format = shift;
689  return $rad unless defined $format;
690  my $degrees = $rad * DR2D;
691  my $out;
692  if ($format =~ /^d/) {
693    $out = $degrees;
694  } elsif ($format =~ /^s/) {
695
696    my $deg = int( $degrees );
697    my $rem = abs( $degrees - $deg );
698    my $min = int( 60 * $rem );
699    $rem = 60 * $rem - $min;
700    my $sec = int( 60 * $rem );
701    $rem = 60 * $rem - $sec;
702    my $frac = int( $rem * 100 );
703
704    $out = join($Separator,$deg,$min,$sec) . ".$frac";
705  }
706  return $out;
707}
708
709=item B<_geod2geoc>
710
711Convert geodetic latitude and altitude to geocentric latitude and
712distance from centre of earth.
713
714  ( $geoc_lat, $geoc_dist ) = $self->_geod2geoc();
715
716Returns latitude in radians and distance in metres.
717
718=cut
719
720sub _geod2geoc {
721  my $self = shift;
722
723  return undef unless ( defined $self->lat &&
724                        defined $self->alt );
725
726  my $lat = $self->lat;
727  my $alt = $self->alt;
728
729  my $lambda_sl = atan2( E * E * sin( $lat ) / cos( $lat ), 1 );
730  my $sin_lambda_sl = sin( $lambda_sl );
731  my $cos_lambda_sl = cos( $lambda_sl );
732  my $sin_mu = sin( $lat );
733  my $cos_mu = cos( $lat );
734  my $sl_radius = sqrt( EQU_RAD * EQU_RAD / ( 1 + ( ( 1 / ( E * E ) ) - 1 ) * $sin_lambda_sl * $sin_lambda_sl ) );
735
736  my $py  = $sl_radius * $sin_lambda_sl + $alt * $sin_mu;
737  my $px = $sl_radius * $cos_lambda_sl + $alt * $cos_mu;
738  my $geoc_lat = atan2( $py, $px );
739
740  my $geoc_dist = sqrt( $py * $py + $px * $px );
741
742  return( $geoc_lat, $geoc_dist );
743}
744
745=item B<_geoc2geod>
746
747Convert geocentric latitude and distance from centre of Earth to
748geodetic latitude and altitude.
749
750  ( $geod_lat, $geod_alt ) = $self->_geoc2geod();
751
752Returns latitude in radians and altitude in metres.
753
754=cut
755
756sub _geoc2geod {
757  my $self = shift;
758
759  return undef unless ( defined $self->{GeocLat} &&
760                        defined $self->{GeocDist} );
761
762  my $geoc_lat = $self->{GeocLat};
763  my $geoc_dist = $self->{GeocDist};
764
765  my $t_lat = sin( $geoc_lat ) / cos( $geoc_lat );
766  my $x_alpha = E * EQU_RAD / sqrt( $t_lat * $t_lat + E * E );
767  my $mu_alpha = atan2( sqrt( EQU_RAD * EQU_RAD - $x_alpha * $x_alpha ), E * $x_alpha );
768  if( $geoc_lat < 0 ) {
769    $mu_alpha = 0 - $mu_alpha;
770  }
771  my $sin_mu_a = sin( $mu_alpha );
772  my $delt_lambda = $mu_alpha - $geoc_lat;
773  my $r_alpha = $x_alpha / cos( $geoc_lat );
774  my $l_point = $geoc_dist - $r_alpha;
775  my $alt = $l_point * cos( $delt_lambda );
776  my $denom = sqrt( 1 - EPS * EPS * $sin_mu_a * $sin_mu_a );
777  my $rho_alpha = EQU_RAD * ( 1 - EPS ) / ( $denom * $denom * $denom );
778  my $delt_mu = atan2( $l_point * sin( $delt_lambda ), $rho_alpha + $alt );
779  my $geod_lat = $mu_alpha - $delt_mu;
780  my $lambda_sl = atan2( E * E * sin( $geod_lat ) / cos( $geod_lat ), 1 );
781  my $sin_lambda_sl = sin( $lambda_sl );
782  my $sea_level_r = sqrt( EQU_RAD * EQU_RAD / ( 1 + ( ( 1 / ( E * E ) ) - 1 ) * $sin_lambda_sl * $sin_lambda_sl ) );
783
784  return ( $geod_lat, $alt );
785}
786
787=item B<_geoc2par>
788
789Convert geocentric latitude and distance from centre of Earth to
790parallax constants.
791
792  $parallax = $self->_geoc2par();
793
794Returns a hash reference, where keys are 'Par_C' and 'Par_S' for
795C and S constants, respectively.
796
797=cut
798
799sub _geoc2par {
800  my $self = shift;
801
802  return undef unless ( defined $self->{GeocLat} &&
803                        defined $self->{GeocDist} );
804
805  my %return;
806
807  my $geoc_lat = $self->{GeocLat};
808  my $geoc_dist = $self->{GeocDist};
809
810  my $rho = $geoc_dist / EQU_RAD;
811
812  $return{Par_C} = $rho * sin( $geoc_lat );
813  $return{Par_S} = $rho * cos( $geoc_lat );
814
815  return \%return;
816
817}
818
819=item B<_par2geoc>
820
821Convert parallax constants to geocentric latitude and distance from
822centre of Earth.
823
824  ( $geoc_lat, $geoc_dist ) = $self->_par2geoc();
825
826=cut
827
828sub _par2geoc {
829  my $self = shift;
830
831  return undef unless ( defined $self->{Parallax} );
832
833  my $par_S = $self->{Parallax}->{Par_S};
834  my $par_C = $self->{Parallax}->{Par_C};
835
836  my $geoc_lat = atan2( $par_C, $par_S );
837  my $geoc_dist = sqrt( $par_S * $par_S + $par_C * $par_C ) * EQU_RAD;
838
839  return( $geoc_lat, $geoc_dist );
840
841}
842
843=back
844
845=head2 Backwards Compatibility
846
847These methods are provided for programs that used the original
848interface:
849
850  lat_by_rad, long_by_rad, lat_by_deg, long_by_deg, alt_by_deg,
851  alt_by_rad
852
853=cut
854
855sub lat_by_rad {
856  my $self = shift;
857  return $self->lat;
858}
859
860sub long_by_rad {
861  my $self = shift;
862  return $self->long;
863}
864
865sub alt_by_rad {
866  my $self = shift;
867  return $self->alt;
868}
869
870sub lat_by_deg {
871  my $self = shift;
872  return $self->lat('d');
873}
874
875sub long_by_deg {
876  my $self = shift;
877  return $self->long('d');
878}
879
880sub alt_by_deg {
881  my $self = shift;
882  return $self->alt('d');
883}
884
885=end __PRIVATE__
886
887=head1 REQUIREMENTS
888
889The list of telescope properties is currently obtained from those
890provided by PAL (C<Astro::PAL>) and also from the Minor Planet
891Center (http://www.cfa.harvard.edu/iau/lists/ObsCodes.html).
892
893=head1 AUTHORS
894
895Tim Jenness E<lt>t.jenness@jach.hawaii.eduE<gt>,
896Brad Cavanagh E<lt>b.cavanagh@jach.hawaii.eduE<gt>
897
898=head1 COPYRIGHT
899
900Copyright (C) 2007, 2008, 2010, 2012 Science and Technology Facilities Council.
901Copyright (C) 1998-2005 Particle Physics and Astronomy Research Council.
902All Rights Reserved. This program is free software; you can
903redistribute it and/or modify it under the same terms as Perl itself.
904
905=cut
906
9071;
908
909