File Coverage

blib/lib/Astro/Telescope.pm
Criterion Covered Total %
statement 226 255 88.6
branch 44 74 59.4
condition 6 30 20.0
subroutine 36 42 85.7
pod 14 20 70.0
total 326 421 77.4


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