File Coverage

blib/lib/Astro/Coords/Offset.pm
Criterion Covered Total %
statement 100 118 84.7
branch 31 50 62.0
condition 3 6 50.0
subroutine 17 19 89.4
pod 11 11 100.0
total 162 204 79.4


line stmt bran cond sub pod time code
1             package Astro::Coords::Offset;
2              
3             =head1 NAME
4              
5             Astro::Coords::Offset - Represent an offset from a base position
6              
7             =head1 SYNOPSIS
8              
9             use Astro::Coords::Offset;
10              
11             my $offset = new Astro::Coords::Offset( 10, 20,
12             system => 'J2000',
13             projection => "TAN" );
14              
15             my $offset = new Astro::Coords::Offset( $ang1, $ang2,
16             system => 'J2000',
17             projection => "TAN" );
18              
19             my ($a1, $a2) = $offset->offsets;
20             my $arcsec = $a1->arcsec;
21              
22             =head1 DESCRIPTION
23              
24             Sometimes, it is necessary for a position to be specified that is
25             offset from the base tracking system. This class provides a means of
26             specifying an offset in a particular coordinate system and using a
27             specified projection.
28              
29             =cut
30              
31 2     2   11373349 use 5.006;
  2         18  
32 2     2   20 use strict;
  2         5  
  2         98  
33 2     2   11 use warnings;
  2         12  
  2         163  
34 2     2   26 use Carp;
  2         18  
  2         332  
35              
36 2     2   587 use Astro::PAL;
  2         4742  
  2         865  
37 2     2   604 use Astro::Coords::Angle;
  2         5  
  2         78  
38              
39 2     2   12 use constant PAZERO => new Astro::Coords::Angle( 0.0, units => 'radians' );
  2         47  
  2         16  
40              
41 2     2   14 use vars qw/ @PROJ @SYSTEMS /;
  2         4  
  2         2959  
42              
43             our $VERSION = '0.21';
44              
45             # Allowed projections
46             @PROJ = qw| SIN TAN ARC DIRECT |;
47              
48             # Allowed coordinate systems J\d+ and B\d+ are also allowed by the
49             # PTCS - these are pattern matches
50             @SYSTEMS = (qw|
51             TRACKING
52             GAL
53             ICRS
54             ICRF
55             |,
56             qr|J\d+(\.\d)?|,
57             qr|B\d+(\.\d)?|,
58             qw|
59             APP
60             HADEC
61             AZEL
62             MOUNT
63             OBS
64             FPLANE
65             |);
66              
67             =head1 METHODS
68              
69             =head2 Constructor
70              
71             =over 4
72              
73             =item B<new>
74              
75             Create a new Offset object. The first two arguments must be the
76             offsets in arcseconds or C<Astro::Coords::Angle> objects. The
77             projection and tracking system can be specified as optional hash
78             arguments (defaulting to TAN and J2000 respectively).
79              
80             my $off = new Astro::Coords::Offset( 10, -20 );
81              
82             my $off = new Astro::Coords::Offset( @off, system => "AZEL",
83             projection => "SIN");
84              
85             my $off = new Astro::Coords::Offset( @off, system => "AZEL",
86             projection => "SIN",
87             posang => $pa,
88             );
89              
90             =cut
91              
92             sub new {
93 11     11 1 3672 my $proto = shift;
94 11   66     45 my $class = ref($proto) || $proto;
95              
96 11         16 my $dc1 = shift;
97 11         17 my $dc2 = shift;
98              
99 11 50 33     37 croak "Offsets must be supplied to constructor"
100             if (!defined $dc1 || !defined $dc2);
101              
102 11         33 my %options = @_;
103              
104             # Aim for case-insensitive keys
105 11         41 my %merged = (
106             system => "J2000",
107             projection => 'TAN',
108             tracking_system => undef,
109             posang => undef );
110              
111 11         35 for my $k (keys %options) {
112 12         22 my $lk = lc($k);
113 12 50       27 if (exists $merged{$lk}) {
114 12         25 $merged{$lk} = $options{$k};
115             }
116             }
117              
118             # Store the offsets as Angle objects if they are not already
119 11 100       49 $dc1 = new Astro::Coords::Angle( $dc1, units => 'arcsec' )
120             unless UNIVERSAL::isa( $dc1, 'Astro::Coords::Angle');
121 11 100       37 $dc2 = new Astro::Coords::Angle( $dc2, units => 'arcsec' )
122             unless UNIVERSAL::isa( $dc2, 'Astro::Coords::Angle');
123              
124              
125             # Create the object
126 11         52 my $off = bless {
127             OFFSETS => [ $dc1, $dc2 ],
128             PROJECTION => undef,
129             POSANG => PAZERO,
130             SYSTEM => undef,
131             TRACKING_SYSTEM => undef,
132             }, $class;
133              
134             # Use accessor to set so that we get validation
135 11         33 $off->projection( $merged{projection} );
136 11         27 $off->system( $merged{system} );
137             $off->tracking_system( $merged{tracking_system} )
138 11 50       25 if defined $merged{tracking_system};
139             $off->posang( $merged{posang} )
140 11 100       25 if defined $merged{posang};
141              
142 11         76 return $off;
143             }
144              
145             =back
146              
147             =head2 Accessor Methods
148              
149             =over 4
150              
151             =item B<offsets>
152              
153             Return the X and Y offsets.
154              
155             @offsets = $self->offsets;
156              
157             as C<Astro::Coords::Angle> objects.
158              
159             =cut
160              
161             sub offsets {
162 12     12 1 21 my $self = shift;
163 12         13 return @{$self->{OFFSETS}};
  12         38  
164             }
165              
166             =item B<xoffset>
167              
168             Returns just the X offset.
169              
170             $x = $off->xoffset;
171              
172             =cut
173              
174             sub xoffset {
175 1     1 1 3 my $self = shift;
176 1         3 my @xy = $self->offsets;
177 1         6 return $xy[0];
178             }
179              
180             =item B<yoffset>
181              
182             Returns just the Y offset.
183              
184             $x = $off->yoffset;
185              
186             =cut
187              
188             sub yoffset {
189 1     1 1 2 my $self = shift;
190 1         3 my @xy = $self->offsets;
191 1         4 return $xy[1];
192             }
193              
194             =item B<system>
195              
196             Coordinate system of this offset. Can be different to the coordinate
197             system of the base position.
198              
199             Allowed values are J2000, B1950, AZEL plus others specified by the
200             JAC TCS XML (see L<"SEE ALSO"> section at end). TRACKING is special
201             since it can change, depending on which output coordinate frame is
202             in use. See the C<tracking_system> attribute for more details.
203              
204             "Az/El" is treated as "AZEL" for backwards compatibility reasons.
205              
206             =cut
207              
208             sub system {
209 24     24 1 1064 my $self = shift;
210 24 100       50 if (@_) {
211 11         18 my $p = shift;
212 11         18 $p = uc($p);
213 11 50       23 $p = "AZEL" if $p eq 'AZ/EL';
214              
215             # need to make sure that we convert the input system into
216             # a TCS system
217 11         14 my $match;
218 11         20 for my $compare (@SYSTEMS) {
219 132 100       1096 if ($p =~ /^$compare/) {
220 11 50       30 if (!defined $match) {
221 11 100       21 if (ref($compare)) {
222             # regex so we just take the input
223 8         18 $match = $p;
224             } else {
225             # exact match to start of string so take the TCS value
226 3         7 $match = $compare;
227             }
228             } else {
229 0         0 croak "Multiple matches for system '$p'";
230             }
231             }
232             }
233 11 50       23 croak "Unknown system '$p'" unless defined $match;
234 11         22 $self->{SYSTEM} = $match;
235             }
236 24         80 return $self->{SYSTEM};
237             }
238              
239             =item B<posang>
240              
241             Position angle of this offset as an C<Astro::Coords::Angle> object.
242             Position angle follows the normal "East of North" convention.
243              
244             $off->posang( 45 );
245             $pa = $off->posang;
246              
247             If a number is supplied it is assumed to be in degrees (this
248             matches the common usage in the JCMT TCS XML DTD).
249              
250             By default returns a position angle of 0 deg.
251              
252             =cut
253              
254             sub posang {
255 15     15 1 26 my $self = shift;
256 15 100       36 if (@_) {
257 4         6 my $pa = shift;
258 4 50       25 if (!defined $pa) {
    100          
    50          
259 0         0 $self->{POSANG} = PAZERO;
260             } elsif (UNIVERSAL::isa($pa, "Astro::Coords::Angle")) {
261 2         5 $self->{POSANG} = $pa;
262             } elsif ($pa =~ /\d/) {
263 2         7 $self->{POSANG} = new Astro::Coords::Angle( $pa, units => 'deg');
264             } else {
265 0         0 croak "Position angle for offset supplied in non-recognizable form ('$pa')";
266             }
267             }
268 15         33 return $self->{POSANG};
269             }
270              
271             =item B<projection>
272              
273             Return (or set) the projection that should be used for this offset.
274             Defaults to tangent plane. Allowed options are TAN, SIN or ARC.
275              
276             =cut
277              
278             sub projection {
279 20     20 1 41 my $self = shift;
280 20 100       50 if (@_) {
281 11         17 my $p = shift;
282 11         23 $p = uc($p);
283 11         34 my $match = join("|",@PROJ);
284 11 50       90 croak "Unknown projection '$p'"
285             unless $p =~ /^$match$/;
286 11         33 $self->{PROJECTION} = $p;
287             }
288 20         51 return $self->{PROJECTION};
289             }
290              
291              
292              
293             # From the TCS:
294             # if (otype == direct)
295             # {
296             # *dc1 = t1 - b1;
297             # *dc2 = t2 - b2;
298             # }
299             # else if (otype == tan_offset)
300             # {
301             # slaDs2tp(t1,t2,b1,b2,dc1,dc2,&jstat);
302             # }
303             # else if (otype == sin_offset)
304             # {
305             # da = t1 - b1;
306             # cd = cos(t2);
307             # *dc1 = cd * sin(da);
308             # *dc2 = sin(t2)*cos(b2) - cd * sin(b2) * cos(da);
309             # }
310             # else if (otype == arc_offset)
311             # {
312             # da = t1 - b1;
313             # cd = cos(t2);
314             # sd = sin(t2);
315             # cd0 = cos(b2);
316             # sd0 = sin(b2);
317             # cda = cos(da);
318             # theta = acos(sd*sd0 + cd*cd0*cda);
319             # to = theta/(sin(theta));
320             # *dc1 = to*cd*sin(da);
321             # *dc2 = to*(sd*cd0 - cd*sd0*cda);
322             # }
323              
324             =item B<tracking_system>
325              
326             In some cases, the offset can be specified to be relative to the
327             system that the telescope is currently using to track the source.
328             This does not necessarily have to be the same as the coordinate
329             frame that was originally used to specify the target. For example,
330             it is perfectly acceptable to ask a telescope to go to a certain
331             Az/El and then ask it to track in RA/Dec.
332              
333             This method allows the tracking system to be specified
334             independenttly of the offset coordinate system. It will only
335             be used if the offset is specified to use "TRACKING" (but it allows
336             the system to disambiguate an offset that was defined as "TRACKING B1950"
337             from an offset that is simply "B1950".
338              
339             The allowed types are the same as for C<system> except that "TRACKING"
340             is not permitted.
341              
342             =cut
343              
344             sub tracking_system {
345 0     0 1 0 my $self = shift;
346 0 0       0 if (@_) {
347 0         0 my $p = shift;
348 0         0 $p = uc($p);
349 0 0       0 croak "Tracking System can not itself be 'TRACKING'"
350             if $p eq 'TRACKING';
351 0         0 my $match = join("|",@SYSTEMS);
352 0 0       0 croak "Unknown system '$p'"
353             unless $p =~ /^$match$/;
354 0         0 $self->{TRACKING_SYSTEM} = $p;
355             }
356 0         0 return $self->{TRACKING_SYSTEM};
357             }
358              
359             =back
360              
361             =head2 General Methods
362              
363             =over 4
364              
365             =item B<invert>
366              
367             Return a new offset object with the sense of the offset inverted.
368              
369             $inv = $offset->invert;
370              
371             =cut
372              
373             # We could do this by adding 180 deg to posang but people really
374             # expect the sign to change
375              
376             sub invert {
377 1     1 1 486 my $self = shift;
378              
379 1         4 my @xy = map { $_->negate } $self->offsets;
  2         8  
380 1         4 my $pa = $self->posang->clone;
381 1 50       5 $pa = undef if $pa->radians == 0;
382 1         3 return $self->new( @xy, system => $self->system,
383             projection => $self->projection,
384             posang => $pa);
385             }
386              
387             =item B<clone>
388              
389             Create a cloned copy of this offset.
390              
391             $clone = $offset->clone;
392              
393             =cut
394              
395             sub clone {
396 0     0 1 0 my $self = shift;
397 0         0 my @xy = map { $_->clone() } $self->offsets;
  0         0  
398 0         0 my $pa = $self->posang->clone;
399 0 0       0 $pa = undef if $pa->radians == 0;
400 0         0 return $self->new( @xy, posang => $pa,
401             system => $self->system,
402             projection => $self->projection
403             );
404             }
405              
406             =item B<offsets_rotated>
407              
408             This can be thought of as a version of C<offsets> which returns offsets which
409             have been rotated through the position angle. It uses the C<offsets> method
410             internally to fetch the stored values. Results are C<Astro::Coords::Angle>
411             objects.
412              
413             ($x_rotated, $y_rotated) = $offset->offsets_rotated();
414              
415             It is assumed that the coordinate system has the first coordinate being
416             positive to the East in order to match the definiton of the
417             C<posang> given above.
418              
419             =cut
420              
421             sub offsets_rotated {
422 9     9 1 23 my $self = shift;
423 9         19 my $paobj = $self->posang();
424              
425             # If position angle not specified, assume zero.
426 9 50       20 return $self->offsets() unless defined $paobj;
427              
428             # Also do nothing if the angle is zero.
429 9         21 my $pa = $paobj->radians();
430 9 100       41 return $self->offsets() if $pa == 0.0;
431              
432 2         6 my ($x, $y) = map {$_->arcsec()} $self->offsets();
  4         11  
433              
434             # This code taken from OMP::Translator::Base::PosAngRot
435             # which could now be defined in terms of this method,
436             # except that it does not use an Astro::Coords::Offset.
437              
438 2         31 my $cospa = cos($pa);
439 2         7 my $sinpa = sin($pa);
440              
441 2         4 my $xr = $x * $cospa + $y * $sinpa;
442 2         4 my $yr = - $x * $sinpa + $y * $cospa;
443              
444 2         3 return map {new Astro::Coords::Angle($_, units => 'arcsec')} ($xr, $yr);
  4         11  
445             }
446              
447             =back
448              
449             =head1 SEE ALSO
450              
451             The allowed offset types are designed to match the specification used
452             by the Portable Telescope Control System configuration XML.
453             See L<http://www.jach.hawaii.edu/JACdocs/JCMT/OCS/ICD/006> for more
454             on this.
455              
456             =head1 AUTHOR
457              
458             Tim Jenness E<lt>tjenness@cpan.orgE<gt>
459              
460             =head1 COPYRIGHT
461              
462             Copyright 2002-2006 Particle Physics and Astronomy Research Council.
463             All Rights Reserved.
464              
465             This program is free software; you can redistribute it and/or modify it under
466             the terms of the GNU General Public License as published by the Free Software
467             Foundation; either version 3 of the License, or (at your option) any later
468             version.
469              
470             This program is distributed in the hope that it will be useful,but WITHOUT ANY
471             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
472             PARTICULAR PURPOSE. See the GNU General Public License for more details.
473              
474             You should have received a copy of the GNU General Public License along with
475             this program; if not, write to the Free Software Foundation, Inc., 59 Temple
476             Place,Suite 330, Boston, MA 02111-1307, USA
477              
478             =cut
479              
480             1;