File Coverage

blib/lib/Astro/FITS/HdrTrans/JCMT.pm
Criterion Covered Total %
statement 157 171 91.8
branch 41 62 66.1
condition 19 36 52.7
subroutine 23 23 100.0
pod 14 14 100.0
total 254 306 83.0


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Astro::FITS::HdrTrans::JCMT - class combining common behaviour for modern JCMT instruments
4              
5             =cut
6              
7             package Astro::FITS::HdrTrans::JCMT;
8              
9 13     13   41153862 use strict;
  13         40  
  13         429  
10 13     13   86 use warnings;
  13         49  
  13         488  
11              
12 13     13   3525 use Astro::Coords;
  13         1738898  
  13         334  
13 13     13   3040 use Astro::Telescope;
  13         17676  
  13         306  
14 13     13   86 use DateTime;
  13         60  
  13         332  
15 13     13   81 use DateTime::TimeZone;
  13         45  
  13         583  
16              
17             our $VERSION = '1.63';
18              
19 13     13   84 use base qw/ Astro::FITS::HdrTrans::JAC /;
  13         55  
  13         7324  
20              
21             # Unit mapping implies that the value propogates directly
22             # to the output with only a keyword name change.
23             my %UNIT_MAP =
24             (
25             AIRMASS_START => 'AMSTART',
26             AZIMUTH_START => 'AZSTART',
27             ELEVATION_START => 'ELSTART',
28             FILENAME => 'FILE_ID',
29             DR_RECIPE => "RECIPE",
30             HUMIDITY => 'HUMSTART',
31             LATITUDE => 'LAT-OBS',
32             LONGITUDE => 'LONG-OBS',
33             OBJECT => 'OBJECT',
34             OBSERVATION_NUMBER => 'OBSNUM',
35             PROJECT => 'PROJECT',
36             SCAN_PATTERN => 'SCAN_PAT',
37             STANDARD => 'STANDARD',
38             TAI_UTC_CORRECTION => 'DTAI',
39             UT1_UTC_CORRECTION => 'DUT1',
40             WIND_BLIND => 'WND_BLND',
41             X_APERTURE => 'INSTAP_X',
42             Y_APERTURE => 'INSTAP_Y',
43             );
44              
45             my %CONST_MAP = ();
46              
47             # Create the translation methods
48             __PACKAGE__->_generate_lookup_methods( \%CONST_MAP, \%UNIT_MAP );
49              
50             our $COORDS;
51              
52             =head1 METHODS
53              
54             =over 4
55              
56             =item B<translate_from_FITS>
57              
58             This routine overrides the base class implementation to enable the
59             caches to be cleared for target location.
60              
61             This means that some conversion methods (in particular those using time in
62             a base class) may not work properly outside the context of a full translation
63             unless they have been subclassed locally.
64              
65             Date fixups are handled in a super class.
66              
67             =cut
68              
69             sub translate_from_FITS {
70 4     4 1 10 my $class = shift;
71 4         8 my $headers = shift;
72              
73             # clear cache
74 4         49 $COORDS = undef;
75              
76             # Go to the base class
77 4         37 return $class->SUPER::translate_from_FITS( $headers, @_ );
78             }
79              
80             =item B<to_UTDATE>
81              
82             Converts the date in a date-obs header into a number of form YYYYMMDD.
83              
84             =cut
85              
86             sub to_UTDATE {
87 4     4 1 6 my $class = shift;
88 4         7 my $FITS_headers = shift;
89              
90 4         12 $class->_fix_dates( $FITS_headers );
91 4         29 return $class->SUPER::to_UTDATE( $FITS_headers, @_ );
92             }
93              
94             =item B<to_UTEND>
95              
96             Converts UT date in a date-end header into C<Time::Piece> object
97              
98             =cut
99              
100             sub to_UTEND {
101 5     5 1 36 my $class = shift;
102 5         10 my $FITS_headers = shift;
103              
104 5         24 $class->_fix_dates( $FITS_headers );
105 5         32 return $class->SUPER::to_UTEND( $FITS_headers, @_ );
106             }
107              
108             =item B<to_UTSTART>
109              
110             Converts UT date in a date-obs header into C<Time::Piece> object.
111              
112             =cut
113              
114             sub to_UTSTART {
115 9     9 1 20 my $class = shift;
116 9         15 my $FITS_headers = shift;
117              
118 9         29 $class->_fix_dates( $FITS_headers );
119 9         46 return $class->SUPER::to_UTSTART( $FITS_headers, @_ );
120             }
121              
122             =item B<to_RA_BASE>
123              
124             Uses the elevation, azimuth, telescope name, and observation start
125             time headers (ELSTART, AZSTART, TELESCOP, and DATE-OBS headers,
126             respectively) to calculate the base RA.
127              
128             Returns the RA in degrees.
129              
130             =cut
131              
132             sub to_RA_BASE {
133 4     4 1 11 my $self = shift;
134 4         11 my $FITS_headers = shift;
135              
136 4         13 my $coords = $self->_calc_coords( $FITS_headers );
137 4 50       14 return undef unless defined $coords;
138 4         28 return $coords->ra( format => 'deg' );
139             }
140              
141             =item B<to_DEC_BASE>
142              
143             Uses the elevation, azimuth, telescope name, and observation start
144             time headers (ELSTART, AZSTART, TELESCOP, and DATE-OBS headers,
145             respectively) to calculate the base declination.
146              
147             Returns the declination in degrees.
148              
149             =cut
150              
151             sub to_DEC_BASE {
152 4     4 1 18 my $self = shift;
153 4         10 my $FITS_headers = shift;
154              
155 4         45 my $coords = $self->_calc_coords( $FITS_headers );
156              
157 4 50       18 return undef unless defined $coords;
158 4         33 return $coords->dec( format => 'deg' );
159             }
160              
161             =item B<to_TAU>
162              
163             Use the average WVM tau measurements.
164              
165             =cut
166              
167             sub to_TAU {
168 4     4 1 13 my $self = shift;
169 4         7 my $FITS_headers = shift;
170              
171 4         10 my $tau = 0.0;
172 4         12 for my $src (qw/ TAU225 WVMTAU /) {
173 6         74 my $st = $src . "ST";
174 6         16 my $en = $src . "EN";
175              
176 6         18 my @startvals = $self->via_subheader_undef_check( $FITS_headers, $st );
177 6         22 my @endvals = $self->via_subheader_undef_check( $FITS_headers, $en );
178 6         18 my $startval = $startvals[0];
179 6         16 my $endval = $endvals[-1];
180              
181 6 100 66     45 if (defined $startval && defined $endval) {
    50          
    50          
182 4         28 $tau = ($startval + $endval) / 2;
183 4         25 last;
184             } elsif (defined $startval) {
185 0         0 $tau = $startval;
186             } elsif (defined $endval) {
187 0         0 $tau = $endval;
188             }
189             }
190 4         12 return $tau;
191             }
192              
193             =item B<to_SEEING>
194              
195             Use the average seeing measurements.
196              
197             =cut
198              
199             sub to_SEEING {
200 4     4 1 10 my $self = shift;
201 4         10 my $FITS_headers = shift;
202              
203 4         11 my $seeing = 0.0;
204              
205              
206 4         17 my @startvals = $self->via_subheader_undef_check( $FITS_headers, "SEEINGST" );
207 4         20 my @endvals = $self->via_subheader_undef_check( $FITS_headers, "SEEINGEN" );
208 4         14 my $startval = $startvals[0];
209 4         14 my $endval = $endvals[-1];
210              
211 4 100 66     32 if (defined $startval && defined $endval) {
    50          
    50          
212 3         17 $seeing = ($startval + $endval) / 2;
213             } elsif (defined $startval) {
214 0         0 $seeing = $startval;
215             } elsif (defined $endval) {
216 0         0 $seeing = $endval;
217             }
218              
219 4         16 return $seeing;
220             }
221              
222              
223              
224              
225             =item B<to_OBSERVATION_ID_SUBSYSTEM>
226              
227             Returns the subsystem observation IDs associated with the header.
228             Returns a reference to an array. Will be empty if the OBSIDSS header
229             is missing.
230              
231             =cut
232              
233             sub to_OBSERVATION_ID_SUBSYSTEM {
234 4     4 1 13 my $self = shift;
235 4         10 my $FITS_headers = shift;
236             # Try multiple headers since the database is different to the file
237 4         8 my @obsidss;
238 4         14 for my $h (qw/ OBSIDSS OBSID_SUBSYSNR /) {
239 5         18 my @found = $self->via_subheader( $FITS_headers, $h );
240 5 100       32 if (@found) {
241 3         11 @obsidss = @found;
242 3         7 last;
243             }
244             }
245 4         9 my @all;
246 4 100       14 if (@obsidss) {
247             # Remove duplicates
248 3         10 my %seen;
249 3         8 @all = grep { ! $seen{$_}++ } @obsidss;
  3         20  
250             }
251 4         14 return \@all;
252             }
253              
254             =item B<to_SUBSYSTEM_IDKEY>
255              
256             =cut
257              
258             sub to_SUBSYSTEM_IDKEY {
259 4     4 1 12 my $self = shift;
260 4         10 my $FITS_headers = shift;
261              
262 4         13 for my $try ( qw/ OBSIDSS OBSID_SUBSYSNR / ) {
263 5         18 my @results = $self->via_subheader( $FITS_headers, $try );
264 5 100       35 return $try if @results;
265             }
266 1         4 return;
267             }
268              
269             =item B<to_DOME_OPEN>
270              
271             Uses the roof and door status at start and end of observation headers
272             to generate a combined value which, if true, confirms that the dome
273             was fully open throughout. (Unless it closed and reopened during
274             the observation.)
275              
276             =cut
277              
278             sub to_DOME_OPEN {
279 4     4 1 25 my $self = shift;
280 4         11 my $FITS_headers = shift;
281              
282 4         13 my ($n_open, $n_closed, $n_other) = (0, 0, 0);
283              
284 4         16 foreach my $header (qw/DOORSTST DOORSTEN ROOFSTST ROOFSTEN/) {
285 16         49 foreach my $value ($self->via_subheader($FITS_headers, $header)) {
286 12 100       61 if ($value =~ /^open$/i) {
    50          
287 4         9 $n_open ++;
288             }
289             elsif ($value =~ /^closed$/i) {
290 8         19 $n_closed ++;
291             }
292             else {
293 0         0 $n_other ++;
294             }
295             }
296             }
297              
298 4 100 33     26 if ($n_open and not ($n_closed or $n_other)) {
      66        
299 1         4 return 1;
300             }
301              
302 3 100 33     23 if ($n_closed and not ($n_open or $n_other)) {
      66        
303 2         9 return 0;
304             }
305              
306 1         3 return undef;
307             }
308              
309             =item B<from_DOME_OPEN>
310              
311             Converts the DOME_OPEN value back to individual roof and door
312             status headers.
313              
314             =cut
315              
316             sub from_DOME_OPEN {
317 2     2 1 6 my $self = shift;
318 2         3 my $generic_headers = shift;
319              
320 2         3 my $value = undef;
321              
322 2 100       6 if (exists $generic_headers->{'DOME_OPEN'}) {
323 1         3 my $dome = $generic_headers->{'DOME_OPEN'};
324 1 50       4 if (defined $dome) {
325 1 50       5 $value = $dome ? 'Open' : 'Closed';
326             }
327             }
328              
329 2         4 return map {$_ => $value} qw/DOORSTST DOORSTEN ROOFSTST ROOFSTEN/;
  8         28  
330             }
331              
332             =item B<to_REMOTE>
333              
334             Convert between the JCMT's OPER_LOC header and a standardised 'REMOTE value'.
335              
336             REMOTE = 1
337             LOCAL = 0
338              
339             If not defined or has a different value, return 'undef'
340             =cut
341              
342             sub to_REMOTE {
343 4     4 1 11 my $self = shift;
344 4         10 my $FITS_headers = shift;
345 4         6 my $remote;
346 4 50       16 if (exists( $FITS_headers->{'REMOTE'})) {
347 0         0 $remote = $FITS_headers->{'REMOTE'};
348             } else {
349 4         48 $remote = ''
350             }
351 4 50       24 if (uc($remote) =~ /REMOTE/) {
    50          
352 0         0 $remote = 1;
353             } elsif (uc($remote) =~ /LOCAL/) {
354 0         0 $remote = 0;
355             } else {
356 4         21 $remote = undef;
357             }
358              
359 4         13 return $remote;
360             }
361              
362              
363             =item B<from_REMOTE>
364              
365             Converts the REMOTE value back to the OPER_LOC header
366             if REMOTE=1, oper_loc='REMOTE'
367             if REMOTE=0, oper_loc='LOCAL'
368             if REMOTE is anything else, return undef;
369              
370             =cut
371              
372             sub from_REMOTE {
373 2     2 1 4 my $self = shift;
374 2         5 my $generic_headers = shift;
375              
376 2         4 my $value = undef;
377              
378 2 50       5 if (exists $generic_headers->{'REMOTE'}) {
379 0         0 my $remote = $generic_headers->{'REMOTE'};
380 0 0       0 if (defined $remote) {
381 0 0       0 $value = $remote ? 'REMOTE' : 'LOCAL';
382             }
383             }
384              
385 2         25 return (OPER_LOC => $value);
386             }
387              
388              
389              
390             =back
391              
392             =head1 PRIVATE METHODS
393              
394             =over 4
395              
396             =item B<_calc_coords>
397              
398             Function to calculate the coordinates at the start of the observation by using
399             the elevation, azimuth, telescope, and observation start time. Caches
400             the result if it's already been calculated.
401              
402             Returns an Astro::Coords object.
403              
404             =cut
405              
406             sub _calc_coords {
407 8     8   19 my $self = shift;
408 8         12 my $FITS_headers = shift;
409              
410             # Force dates to be standardized
411 8         33 $self->_fix_dates( $FITS_headers );
412              
413             # Here be dragons. Possibility that cache will not be cleared properly
414             # if a user comes in outside of the translate_from_FITS() method.
415 8 100 66     48 if ( defined( $COORDS ) &&
416             UNIVERSAL::isa( $COORDS, "Astro::Coords" ) ) {
417 4         14 return $COORDS;
418             }
419              
420 4         15 my $telescope = $FITS_headers->{'TELESCOP'};
421              
422             # We can try DATE-OBS and AZEL START or DATE-END and AZEL END
423 4         279 my ($dateobs, $az, $el);
424              
425 4         48 my @keys = ( { date => "DATE-OBS", az => "AZSTART", el => "ELSTART" },
426             { date => "DATE-END", az => "AZEND", el => "ELEND" } );
427              
428 4         24 for my $keys_to_try ( @keys ) {
429              
430             # We might have subheaders, especially for the AZEL
431             # values so we read into arrays and check them.
432              
433 5         35 my @dateobs = $self->via_subheader( $FITS_headers, $keys_to_try->{date} );
434 5         23 my @azref = $self->via_subheader( $FITS_headers, $keys_to_try->{az} );
435 5         23 my @elref = $self->via_subheader( $FITS_headers, $keys_to_try->{el} );
436              
437             # try to ensure that we use the same index everywhere
438 5         10 my $idx;
439 5         19 ($idx, $dateobs) = _middle_value(\@dateobs, $idx);
440 5         16 ($idx, $az) = _middle_value(\@azref, $idx);
441 5         28 ($idx, $el) = _middle_value(\@elref, $idx);
442              
443             # if we have a set of values we can stop looking
444 5 100 66     71 last if (defined $dateobs && defined $az && defined $el);
      66        
445             }
446              
447             # only proceed if we have a defined value
448 4 50 33     68 if (defined $dateobs && defined $telescope
      33        
      33        
449             && defined $az && defined $el ) {
450 4         46 my $coords = new Astro::Coords( az => $az,
451             el => $el,
452             units => 'degrees',
453             );
454 4         1321 $coords->telescope( new Astro::Telescope( $telescope ) );
455              
456             # convert ISO date to object
457 4         50681 my $dt = Astro::FITS::HdrTrans::Base->_parse_iso_date( $dateobs );
458 4 50       16 return unless defined $dt;
459              
460 4         37 $coords->datetime( $dt );
461              
462 4         1083 $COORDS = $coords;
463 4         31 return $COORDS;
464             }
465              
466 0         0 return undef;
467             }
468              
469             =item B<_middle_value>
470              
471             Returns the value from the middle of an array reference. If that is
472             not defined we start from the beginning until we find a defined
473             value. Return undef if we can not find anything.
474              
475             =cut
476              
477             sub _middle_value {
478 15     15   24 my $arr = shift;
479 15         23 my $idx = shift;
480              
481 15 100       47 $idx = int ((scalar @$arr) / 2) unless defined $idx;
482              
483 15 100       54 return ($idx, $arr->[$idx]) if (defined $arr->[$idx]);
484              
485             # No luck scan them all
486 2         6 for my $idx (0..$#$arr) {
487 0         0 my $val = $arr->[$idx];
488 0 0       0 return ($idx, $val) if defined $val;
489             }
490 2         6 return (undef, undef);
491             }
492              
493             =back
494              
495             =head1 SEE ALSO
496              
497             C<Astro::FITS::HdrTrans>,
498             C<Astro::FITS::HdrTrans::Base>,
499             C<Astro::FITS::HdrTrans::JAC>.
500              
501             =head1 AUTHORS
502              
503             Anubhav E<lt>a.agarwal@jach.hawawii.eduE<gt>,
504             Brad Cavanagh E<lt>b.cavanagh@jach.hawaii.eduE<gt>,
505             Tim Jenness E<lt>t.jenness@jach.hawaii.eduE<gt>.
506              
507             =head1 COPYRIGHT
508              
509             Copyright (C) 2009, 2011, 2012, 2014 Science and Technology Facilities Council.
510             Copyright (C) 2016 East Asian Observatory.
511             All Rights Reserved.
512              
513             This program is free software; you can redistribute it and/or modify it under
514             the terms of the GNU General Public License as published by the Free Software
515             Foundation; either version 2 of the License, or (at your option) any later
516             version.
517              
518             This program is distributed in the hope that it will be useful,but WITHOUT ANY
519             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
520             PARTICULAR PURPOSE. See the GNU General Public License for more details.
521              
522             You should have received a copy of the GNU General Public License along with
523             this program; if not, write to the Free Software Foundation, Inc., 59 Temple
524             Place,Suite 330, Boston, MA 02111-1307, USA
525              
526             =cut
527              
528             1;