File Coverage

blib/lib/Astro/WaveBand.pm
Criterion Covered Total %
statement 214 233 91.8
branch 97 138 70.2
condition 14 36 38.8
subroutine 29 30 96.6
pod 15 15 100.0
total 369 452 81.6


line stmt bran cond sub pod time code
1             package Astro::WaveBand;
2              
3             =head1 NAME
4              
5             Astro::WaveBand - Transparently work in waveband, wavelength or filter
6              
7             =head1 SYNOPSIS
8              
9             use Astro::WaveBand;
10              
11             $w = new Astro::WaveBand( Filter => $filter );
12             $w = new Astro::WaveBand( Wavelength => $wavelength );
13              
14             $w = new Astro::WaveBand( Wavelength => $wavelength,
15             Instrument => 'CGS4' );
16              
17             $filter = $w->filter;
18             $wave = $w->wavelength;
19             $band = $w->waveband; # radio, xray, submm
20             $freq = $w->frequency;
21             $wnum = $w->wavenumber;
22              
23             $natural= $w->natural;
24             $natural = "$w";
25              
26             $w->natural_unit("wavelength");
27              
28             if( $w1 > $w2 ) { ... }
29             if( $w1 == $w2 ) { ... }
30              
31             =head1 DESCRIPTION
32              
33             Class to transparently deal with the conversion between filters,
34             wavelength, frequency and other methods of specifying a location
35             in the electro-magentic spectrum.
36              
37             The class tries to determine the natural form of the numbers such that
38             a request for a summary of the object when it contains 2.2 microns
39             would return the filter name but would return the wavelength if it was
40             not a standard filter. In ambiguous cases an instrument name is
41             required to decide what to return. In really ambiguous cases the user
42             can specify the unit in which to display the numbers on
43             stringification.
44              
45             Used mainly as a way of storing a single number in a database table
46             but using logic to determine the number that an observer is most likely
47             to understand.
48              
49             Numerical comparison operators can be used to compare two C
50             objects. When checking equality, the "natural" and "instrument" methods are
51             used, so if two C objects return the same value from those
52             methods, they are considered to be equal. When checking other comparisons
53             such as greater than, the wavelength is used.
54              
55             =cut
56              
57 1     1   26930 use 5.006;
  1         4  
  1         49  
58 1     1   7 use strict;
  1         2  
  1         43  
59 1     1   6 use warnings;
  1         8  
  1         36  
60 1     1   7 use Carp;
  1         1  
  1         121  
61              
62             # Register an Astro::WaveBand warning category
63 1     1   6 use warnings::register;
  1         3  
  1         254  
64              
65             # CVS version: $Revision$
66             our $VERSION = 0.09;
67              
68             # Overloading
69 1         8 use overload '""' => "natural",
70             '==' => "equals",
71             '!=' => "not_equals",
72             '<=>' => "compare",
73 1     1   2174 'fallback' => 1;
  1         1216  
74              
75             # Constants
76              
77             # Speed of light in m/s
78 1     1   128 use constant CLIGHT => 299792458;
  1         2  
  1         3475  
79              
80             # list of instruments specific to a telescope
81             my %TELESCOPE = (
82             UKIRT => [ "IRCAM", "UFTI", "UIST", "MICHELLE", "WFCAM" ],
83             JCMT => [ "SCUBA", "RXA3", "RXA3M", "RXB3", "RXW", "DAS" ] );
84              
85             # Continuum Filters are keyed by instrument
86             # although if an instrument is not specified the filters
87             # hash will be searched for a match if none is available in
88             # GENERIC
89             my %FILTERS = (
90             GENERIC => {
91             U => 0.365,
92             B => 0.44,
93             V => 0.55,
94             R => 0.70,
95             I => 0.90,
96             J => 1.25,
97             H => 1.65,
98             K => 2.2,
99             L => 3.45,
100             M => 4.7,
101             N =>10.2,
102             Q =>20.0,
103             up => 0.355,
104             gp => 0.470,
105             rp => 0.620,
106             ip => 0.750,
107             zp => 0.880,
108             Pu => 0.355,
109             Pg => 0.470,
110             Pr => 0.620,
111             Pi => 0.750,
112             Pz => 0.880,
113             Y => 1.020, # this will get incorrectly classed as infrared
114             w => 0.608,
115             SO => 0.600,
116             },
117             WFCAM => {
118             "Z" => 0.83,
119             "Y" => 0.97,
120             "J" => 1.17,
121             "H" => 1.49,
122             "K" => 2.03,
123             "1-0S1" => 2.111,
124             "BGamma"=> 2.155,
125             "1.205nbJ" => 1.205,
126             "1.619nbH" => 1.619,
127             "1.644FeII" => 1.631,
128             "Blank" => 0,
129             },
130             IRCAM => {
131             "J98" => "1.250" ,
132             "H98" => "1.635" ,
133             "K98" => "2.150" ,
134             "Lp98" => "3.6" ,
135             "Mp98" => "4.800" ,
136             "2.1c" => "2.100" ,
137             "2.122S1" => "2.122" ,
138             "BrG" => "2.0" ,
139             "2.2c" => "2.200" ,
140             "2.248S1" => "2.248" ,
141             "3.6nbLp" => "3.6" ,
142             "4.0c" => "4.000" ,
143             "BrA" => "4.0" ,
144             "Ice" => "3.1" ,
145             "Dust" => "3.28" ,
146             "3.4nbL" => "3.4" ,
147             "3.5mbL" => "3.5" ,
148             },
149             UFTI => {
150             "Y_MK" => "1.022",
151             "I" => "0.9" ,
152             "Z" => "1.033",
153             "J98" => "1.250",
154             "H98" => "1.635",
155             "K98" => "2.150",
156             "Kprime" =>"2.120",
157             "1.644" => "1.644",
158             '1.69CH4_l' => '1.690',
159             "1.57" => "1.57" ,
160             "2.122" => "2.122",
161             "2.122MK" => "2.122",
162             "BrG" => "2.166",
163             "BrGz" => "2.173",
164             "2.248S(1)" => "2.248",
165             "2.27" => "2.270",
166             "Blank" => "-2.222",# -ve version of OT wavelength
167             "Mask" => "-2.32", # ditto
168             },
169             UIST => {
170             # "K-target" => 1.64, # old
171             "Y_MK" => 1.022,
172             "ZMK" => 1.033,
173             "Hartmann" => 1.64,
174             "J98" => 1.25,
175             "H98" => 1.64,
176             "1.57" => 1.573,
177             "1.66" => 1.664, # old
178             "1.58CH4_s" => 1.604,
179             "1.69CH4_l" => 1.674,
180             "1.644Fe" => 1.643, #
181             "K98" => 2.20,
182             "Kshort" => 2.159,
183             "Klong" => 2.227,
184             "2.122S(1)" => 2.121, #
185             "2.122MK" => 2.127,
186             "2.248S(1)" => 2.248,
187             "2.248MK" => 2.263,
188             "BrG" => 2.166,
189             "2.27" => 2.274,
190             "2.32CO" => 2.324, # old
191             "2.42CO" => 2.425,
192             "3.05ice" => 3.048,
193             "Dust" => 3.278,
194             "3.30PAH" => 3.286,
195             "3.4nbL" => 3.415,
196             "3.5mbL" => 3.489,
197             "3.6nbLp" => 3.593,
198             "3.99" => 3.990,
199             "BrA" => 4.053,
200             "Lp98" => 3.77,
201             "Mp98" => 4.69,
202             },
203             MICHELLE => {
204             "F105B53" => 10.5,
205             "F79B10" => 7.9,
206             "F88B10" => 8.8,
207             "F97B10" => 9.7,
208             "F103B10" => 10.3,
209             "F116B9" => 11.6,
210             "F125B9" => 12.5,
211             "F107B4" => 10.7,
212             "F122B3" => 12.2,
213             "F128B2" => 12.8,
214             "F209B42" => 20.9,
215             "F185B9" => 18.5,
216             "NBlock" => 10.6,
217             "QBlock" => 20.9,
218             "F22B15" => 2.2,
219             "F34B9" => 3.4,
220             "F47B5" => 4.7,
221             },
222             SCUBA => {
223             "850W" => 863,
224             "450W" => 443,
225             "450N" => 442,
226             "850N" => 862,
227             "750N" => 741,
228             "350N" => 344,
229             "P2000" => 2000,
230             "P1350" => 1350,
231             "P1100" => 1100,
232             # This is a kluge until the class can
233             # be extended to support multiple wavelength
234             # instruments.
235             "850S:PHOT" => 1100,
236             "450W:850W" => 443,
237             "450N:850N" => 442,
238             "350N:750N" => 344,
239             },
240             'SCUBA-2' => {
241             850 => 863, # guesses
242             450 => 445,
243             },
244             );
245              
246             # Instruments that have natural units
247             my %NATURAL = (
248             WFCAM => 'filter',
249             CGS4 => 'wavelength',
250             SCUBA => 'filter',
251             'SCUBA-2' => 'filter',
252             UFTI => 'filter',
253             IRCAM => 'filter',
254             MICHELLE => 'filter',
255             ACSIS => 'frequency',
256             DAS => 'frequency',
257             RXA3 => 'frequency',
258             RXA3M => 'frequency',
259             RXB3 => 'frequency',
260             RXW => 'frequency',
261             RXWB => 'frequency',
262             RXWC => 'frequency',
263             RXWD => 'frequency',
264             RXWD2 => 'frequency',
265             HARP => 'frequency',
266             UIST => 'filter',
267             );
268              
269              
270             =head1 METHODS
271              
272             =head2 Constructor
273              
274             =over 4
275              
276             =item B
277              
278             Create a new instance of an C object.
279              
280             $w = new Astro::WaveBand( Filter => $filter );
281              
282             Allowed keys for constructor are one of:
283              
284             Filter - filter name
285             Wavelength - wavelength in microns
286             Frequency - frequency in Hertz
287             Wavenumber - wavenumber in cm^-1
288              
289             plus optionally:
290              
291             Instrument - name of associated instrument
292              
293             In the future there may be a C key to allow the units to be
294             supplied in alternative forms.
295              
296             If a mandatory key is missing or there is more than one
297             mandatory key the constructor will fail and return C.
298             Additionally a warning (of class C) will
299             be issued.
300              
301             =cut
302              
303             sub new {
304 28     28 1 15535 my $proto = shift;
305 28   33     162 my $class = ref($proto) || $proto;
306              
307 28         93 my %args = @_;
308              
309             # Check the hash contains one of the following
310 28         79 my @keys = qw/ Filter Wavelength Frequency Wavenumber /;
311 28         45 my $found = 0;
312 28         53 for my $key (@keys) {
313 112 100       270 $found++ if exists $args{$key};
314             }
315              
316 28 100       99 if ($found == 0) {
    100          
317 2 50       126 warnings::warn("Missing a mandatory key")
318             if warnings::enabled();
319 2         7 return undef;
320             } elsif ($found > 1) {
321 1 50       111 warnings::warn("More than one mandatory key")
322             if warnings::enabled();
323 1         5 return undef;
324             }
325              
326 25         120 my $w = bless { Cache => {} }, $class;
327              
328             # Now insert the information into the object
329             # Do Instrument first since we may need it to convert
330             # filter to wavelength
331 25 100       64 if (exists $args{Instrument}) {
332 18         45 $w->instrument( $args{Instrument});
333             }
334              
335 25         67 for my $key (keys %args) {
336 43         65 my $method = lc($key);
337 43 100       123 next if $method eq 'instrument';
338 25 50       109 if ($w->can($method)) {
339 25         64 $w->$method( $args{$key});
340             }
341             }
342              
343             # We are now done so just return the object
344 25         99 return $w;
345             }
346              
347             =back
348              
349             =head2 Accessor methods
350              
351             All the accessor methods associated with conversions will
352             automatically convert to the correct format on demand and will cache
353             it for later. If a new value is provided all caches will be cleared.
354              
355             All input values are converted to microns internally (since a
356             single base unit should be chosen to simplify internal conversions).
357              
358             =over 4
359              
360             =item B
361              
362             Wavelength in microns.
363              
364             $wav = $w->wavelength;
365             $w->wavelength(450.0);
366              
367             =cut
368              
369             sub wavelength {
370 54     54 1 13629 my $self = shift;
371 54 100       119 if (@_) {
372 8         12 my $value = shift;
373 8         20 $self->_store_in_cache('wavelength' => $value);
374             } else {
375 46         116 return $self->_fetch_from_cache( 'wavelength' );
376             }
377 8         16 return;
378             }
379              
380             =item B
381              
382             Frequency in Hertz.
383              
384             $frequency = $w->frequency;
385             $w->frequency(345E9);
386              
387             =cut
388              
389             sub frequency {
390 8     8 1 1933 my $self = shift;
391 8 100       22 if (@_) {
392 3         7 my $value = shift;
393              
394             # store value and wavelength in cache
395 3         9 $self->_cache_value_and_wav( 'frequency', $value);
396              
397             } else {
398             # Read value from the cache
399 5         17 return $self->_read_value_with_convert( "frequency" );
400              
401             }
402              
403 3         10 return;
404             }
405              
406             =item B
407              
408             Wavenumber (reciprocal of wavelength) in inverse centimetres.
409              
410             $value = $w->wavenumber;
411             $w->wavenumber(1500);
412              
413             =cut
414              
415             sub wavenumber {
416 11     11 1 6801 my $self = shift;
417 11 100       39 if (@_) {
418 1         2 my $value = shift;
419              
420             # store value and wavelength in cache
421 1         5 $self->_cache_value_and_wav( 'wavenumber', $value);
422              
423             } else {
424             # Read value from the cache
425 10         31 return $self->_read_value_with_convert( "wavenumber" );
426              
427             }
428              
429 1         3 return;
430             }
431              
432             =item B
433              
434             Set or retrieve filter name.
435              
436             Returns C if the filter can not be determined. If the filter
437             name can not be translated to a wavelength it will not be possible
438             to do any conversions to other forms.
439              
440             =cut
441              
442             sub filter {
443 55     55 1 22933 my $self = shift;
444 55 100       123 if (@_) {
445 13         21 my $value = shift;
446              
447             # store value and wavelength in cache
448 13         35 $self->_cache_value_and_wav( 'filter', $value);
449              
450             } else {
451             # Read value from the cache
452 42         104 return $self->_read_value_with_convert( "filter" );
453              
454             }
455              
456 13         32 return;
457              
458             }
459              
460              
461             =item B
462              
463             Name of associated instrument.
464              
465             $inst = $w->instrument;
466             $w->instrument( 'SCUBA' );
467              
468             Used to aid in the choice of natural unit.
469              
470             =cut
471              
472             sub instrument {
473 81     81 1 94 my $self = shift;
474 81 100       158 if (@_) { $self->{Instrument} = uc(shift); }
  18         98  
475 81         206 return $self->{Instrument};
476             }
477              
478             =item B
479              
480             Override the natural unit to be used for stringification. If this
481             value is not set the class will determine the unit of choice by
482             looking at the instrument name and then by taking an informed guess.
483              
484             $w->natural_unit('filter');
485              
486             =cut
487              
488             sub natural_unit {
489 26     26 1 29 my $self = shift;
490 26 50       77 if (@_) { $self->{NaturalUnit} = shift; }
  0         0  
491 26         59 return $self->{NaturalUnit};
492             }
493              
494              
495             =back
496              
497             =head2 General Methods
498              
499             =over 4
500              
501             =item B
502              
503             Return the name of the waveband associated with the object.
504              
505             Returns C if none can be determined.
506              
507             $band = $w->waveband;
508              
509             =cut
510              
511             sub waveband {
512 19     19 1 12556 my $self = shift;
513              
514 19         58 my $lambda = $self->wavelength;
515 19 50       47 return undef unless defined $lambda;
516              
517 19         23 my $band;
518 19 100 33     254 if ($lambda >= 10000 ) { # > 1cm
    50 66        
    100 66        
    100 33        
    50 0        
    0 0        
    0          
    0          
519 1         2 $band = 'radio';
520             } elsif ($lambda < 10000 and $lambda >= 1000) {
521 0         0 $band = 'mm';
522             } elsif ($lambda < 1000 and $lambda >= 100) {
523 3         5 $band = 'submm';
524             } elsif ($lambda < 100 and $lambda >= 1) {
525 11         18 $band = 'infrared';
526             } elsif ($lambda < 1 and $lambda >= 0.3) {
527 4         9 $band = 'optical';
528             } elsif ($lambda < 0.3 and $lambda >= 0.01) {
529 0         0 $band = 'ultraviolet';
530             } elsif ($lambda < 0.01 and $lambda >= 0.00001) {
531 0         0 $band = 'x-ray';
532             } elsif ($lambda < 0.00001) {
533 0         0 $band = 'gamma-ray';
534             }
535              
536 19         51 return $band;
537             }
538              
539             =item B
540              
541             Return the contents of the object in its most natural form. For
542             example, with UFTI the filter name will be returned whereas with ACSIS
543             the frequency will be returned. The choice of unit is chosen using
544             the supplied default unit (see C) or the instrument name.
545             If none of these is specified filter will be used and if no match is
546             present wavelength in microns.
547              
548             $value = $w->natural;
549              
550             Returns C if the value can not be determined.
551              
552             This method is called automatically when the object is stringified.
553             Note that you will not know the unit that was chosen a priori.
554              
555             =cut
556              
557             sub natural {
558 26     26 1 12146 my $self = shift;
559              
560             # First see if the default unit is set
561 26         62 my $unit = $self->natural_unit;
562              
563 26 50       57 unless (defined $unit) {
564             # Check the instrument
565 26         60 my $inst = $self->instrument;
566 26 100 66     130 if ($inst and exists $NATURAL{$inst}) {
567 18         36 $unit = $NATURAL{$inst};
568             }
569             }
570              
571             # Guess at filter if we have no choice
572 26 100       61 $unit = 'filter' unless defined $unit;
573              
574             # retrieve the value
575 26         31 my $value;
576 26 50       102 if ($self->can($unit)) {
577 26         53 $value = $self->$unit();
578             }
579              
580             # All else fails... try wavelength
581 26 100       67 $value = $self->wavelength() unless defined $value;
582              
583 26         73 return $value;
584             }
585              
586             =item B
587              
588             Compares two C objects.
589              
590             if( $wb1->compare( $wb2 ) ) { ... }
591              
592             This method will return -1 if, in the above example, $wb1 is of
593             a shorter wavelength than $wb2, 0 if the wavelengths are equal,
594             and +1 if $wb1 is of a longer wavelength than $wb2. Please note
595             that for strict waveband equality the C method should be
596             used, as that method uses the C method to check if two
597             wavebands are identical.
598              
599             This method is overloaded with the standard numerical comparison
600             operators, so to check if one waveband is shorter than another
601             you would do
602              
603             if( $wb1 < $wb2 ) { ... }
604              
605             and it will work as you expect. This method does not overload
606             the == operator; see the C method for that.
607              
608             =cut
609              
610             sub compare {
611 2     2 1 6 my ( $object1, $object2, $was_reversed ) = @_;
612 2 50       9 ( $object1, $object2 ) = ( $object2, $object1 ) if $was_reversed;
613              
614 2         8 return $object1->wavelength <=> $object2->wavelength;
615             }
616              
617             =item B
618              
619             Compares two C objects for equality.
620              
621             if( $wb1->equals( $wb2 ) ) { ... }
622              
623             This method will return 1 if, in the above example, both
624             C objects return the same value from the
625             C method AND for the C method (if it
626             is defined for both objects) , and 0 of they return different values.
627              
628             This method is overloaded using the == operator, so
629              
630             if( $wb1 == $wb2 ) { ... }
631              
632             is functionally the same as the first example.
633              
634             =cut
635              
636             sub equals {
637 3     3 1 9 my $self = shift;
638 3         5 my $comp = shift;
639              
640 3 100 66     7 if( defined( $self->instrument ) && defined( $comp->instrument ) ) {
641 1   33     4 return ( ( $self->natural eq $comp->natural ) &&
642             ( $self->instrument eq $comp->instrument ) );
643             } else {
644 2         6 return ( $self->natural eq $comp->natural );
645             }
646             }
647              
648             =item B
649              
650             Compares two C objects for inequality.
651              
652             if( $wb1->not_equals( $wb2 ) ) { ... }
653              
654             This method will return 1 if, in the above example, either the
655             C method or the C method return different
656             values. If the instrument is undefined for either object, then
657             the C method will be used.
658              
659             This method is overloaded using the != operator, so
660              
661             if( $wb1 != $wb2 ) { ... }
662              
663             is functionally the same as the first example.
664              
665             =cut
666              
667             sub not_equals {
668 1     1 1 7 my $self = shift;
669 1         2 my $comp = shift;
670              
671 1 50 33     3 if( ! defined( $self->instrument ) || ! defined( $comp->instrument ) ) {
672 0         0 return ( $self->natural ne $comp->natural );
673             } else {
674 1   33     4 return ( ( $self->natural ne $comp->natural ) ||
675             ( $self->instrument ne $comp->instrument ) );
676             }
677             }
678              
679             =back
680              
681             =begin __PRIVATE_METHODS__
682              
683             =head2 Private Methods
684              
685             =over 4
686              
687             =item B<_cache>
688              
689             Retrieve the hash reference associated with the cache (in a scalar
690             context) or the contents of the hash (in a list context).
691              
692             $ref = $w->cache;
693             %cache = $w->cache;
694              
695             =cut
696              
697             sub _cache {
698 169     169   199 my $self = shift;
699 169 50       294 if (wantarray) {
700 0         0 return %{ $self->{Cache} };
  0         0  
701             } else {
702 169         363 return $self->{Cache};
703             }
704             }
705              
706             =item B<_store_in_cache>
707              
708             Store values in the cache associated with particular types.
709              
710             $w->_store_in_cache( "filter" => "K",
711             "frequency" => 1.4E14,
712             );
713              
714             If the cache already contains a value for this entry the cache
715             is cleared prior to storing it (unless it contains the same value)
716             on the assumption that the cache is no longer consistent.
717              
718             More than one key can be supplied. All keys are tested for prior
719             existence before inserting the new ones.
720              
721             =cut
722              
723             sub _store_in_cache {
724 41     41   51 my $self = shift;
725 41         104 my %entries = @_;
726              
727             # Get the cache
728 41         71 my $cache = $self->_cache;
729              
730             # First check to see whether we have any entries in the
731             # cache that clash
732 41         145 for my $key (keys %entries) {
733              
734             # No worries if it is not there
735 58 50       192 next unless exists $cache->{$key};
736              
737             # Check to see if the value is the same as is already present
738             # Use a string comparison for filter
739 0 0       0 if ($key eq 'filter') {
740 0 0       0 next if $cache->{$key} eq $entries{$key};
741             } else {
742             # Number
743 0 0       0 next if $cache->{$key} == $entries{$key};
744             }
745              
746             # Now we have a key that exists but its value is
747             # different. Clear the cache and exit the loop.
748             # This means the loop never really reaches the end
749             # of the block...
750 0         0 $self->_clear_cache;
751              
752 0         0 last;
753             }
754              
755             # Now insert the values
756 41         94 for my $key (keys %entries) {
757 58         145 $cache->{$key} = $entries{$key};
758             }
759              
760             # finished
761 41         96 return;
762             }
763              
764             =item B<_clear_cache>
765              
766             Empty the cache.
767              
768             =cut
769              
770             sub _clear_cache {
771 0     0   0 my $self = shift;
772 0         0 %{ $self->_cache } = ();
  0         0  
773 0         0 return;
774             }
775              
776             =item B<_fetch_from_cache>
777              
778             Retrieve an item from the cache. Returns C if the item is
779             not stored in the cache.
780              
781             $filter = $w->_fetch_from_cache( "filter" );
782              
783             Could be combined into a single method with C<_store_in_cache> but
784             separated for simplicity.
785              
786             =cut
787              
788             sub _fetch_from_cache {
789 128     128   153 my $self = shift;
790 128 50       275 return undef unless @_;
791              
792 128         148 my $key = shift;
793 128 50       242 return undef unless $key;
794 128         154 $key = lc($key); # level playing field
795              
796             # Return the value from the cache if it exists
797 128         230 my $cache = $self->_cache;
798 128 100       492 return $cache->{$key} if exists $cache->{$key};
799              
800 25         40 return undef;
801             }
802              
803             =item B<_cache_value_and_wav>
804              
805             Cache the supplied value, converting it to the internal format
806             if necessary.
807              
808             $w->_cache_value_and_wav( 'frequency', $frequency );
809              
810             If the wavelength can not be determind the cache is cleared
811             and the supplied value is inserted (but without wavelength
812             information)..
813              
814             =cut
815              
816             sub _cache_value_and_wav {
817 17     17   22 my $self = shift;
818              
819 17         29 my $category = shift;
820 17         21 my $value = shift;
821 17 50       38 return unless defined $value;
822              
823             # Convert to the internal format (wavelength)
824 17         54 my $internal = $self->_convert_from( $category, $value );
825              
826             # Store all defined values into cache
827 17         22 my %store;
828 17         33 $store{$category} = $value;
829 17 50       50 $store{wavelength} = $internal if defined $internal;
830              
831             # Clear cache if wavelength is not to be supplied
832 17 50       33 $self->_clear_cache() unless defined $internal;
833              
834 17         55 $self->_store_in_cache( %store );
835              
836 17         40 return;
837             }
838              
839             =item B<_read_value_with_convert>
840              
841             Read a value from the cache, converting it to the required units
842             as necessary.
843              
844             $value = $w->_read_value_with_convert( 'frequency' );
845              
846             Returns C if no value has been stored in the object.
847              
848             =cut
849              
850             sub _read_value_with_convert {
851 57     57   92 my $self = shift;
852 57         92 my $category = lc(shift);
853              
854 57         137 my $value = $self->_fetch_from_cache( $category );
855              
856             # Convert it if necessary
857 57 100       111 unless ($value) {
858              
859             # Convert it from the default value (if set)
860 25         58 $value = $self->_convert_to( $category );
861              
862             # Cache it if necessary
863 25 100       76 $self->_store_in_cache( $category => $value )
864             if $value;
865             }
866              
867 57         147 return $value;
868             }
869              
870             =item B<_convert_to>
871              
872             Convert the value stored internally as the default format to the
873             required format. This simplifies the conversion routines since
874             there is only a single format to convert from and to.
875              
876             $value = $w->_convert_to( 'frequency' );
877              
878             Returns the converted value or undef on error. The internal format
879             (wavelength) is read directly from the cache.
880              
881             =cut
882              
883             sub _convert_to {
884 25     25   29 my $self = shift;
885 25         34 my $category = shift;
886              
887 25         39 my $lambda = $self->_fetch_from_cache( 'wavelength' );
888 25 50       91 return undef unless defined $lambda;
889              
890             # Check all types
891 25         24 my $output;
892 25 50       112 if ($category eq 'wavelength') {
    100          
    100          
    50          
893 0         0 $output = $lambda;
894             } elsif ($category eq 'frequency') {
895             # Microns
896 2         7 $output = CLIGHT / ( $lambda * 1.0E-6);
897             } elsif ($category eq 'wavenumber') {
898             # Inverse cm
899 9         24 $output = 1.0 / ( $lambda / 10_000);
900             } elsif ($category eq 'filter') {
901              
902             # This is slightly harder since we know the value but
903             # not the key. Go through each hash looking for a matching
904             # key. If we know the instrument we start looking there
905             # Else we have to look through GENERIC followed by all the
906             # remaining instruments
907              
908 14         29 my $instrument = $self->instrument;
909 14         69 my @search = ('GENERIC', keys %FILTERS);
910 14 100       44 unshift(@search, $instrument) if defined $instrument;
911              
912             # There will be a precision issue here so we convert
913             # the base wavelegnth to use 8 significant figures
914 14         116 $lambda = sprintf("%8e", $lambda);
915              
916 14         29 OUTER: foreach my $inst (@search) {
917 91 100       199 next unless exists $FILTERS{$inst};
918 88         116 my $hash = $FILTERS{$inst};
919 88         88 for my $key (keys %{ $hash }) {
  88         409  
920             # Make sure we use the same rounding scheme on the values
921             # returned from the hash, so we don't have to worry about
922             # rounding issues fouling things up (like saying 8.3e-1 !=
923             # 0.83).
924 1508 100       5754 if (sprintf("%8e", $hash->{$key} ) eq $lambda) {
925 5         10 $output = $key;
926 5         35 last OUTER;
927             }
928             }
929             }
930             }
931              
932 25         58 return $output;
933             }
934              
935             =item B<_convert_from>
936              
937             Convert from the supplied values to the internal format (wavelength).
938              
939             $value = $w->_convert_from( 'frequency', $frequency );
940              
941             Returns the converted value. Returns C if the conversion
942             is not possible.
943              
944             =cut
945              
946             sub _convert_from {
947 17     17   26 my $self = shift;
948              
949 17         27 my $category = lc(shift);
950 17         24 my $value = shift;
951 17 50       37 return undef unless defined $value;
952              
953             # Go through each type
954 17         19 my $output;
955 17 50       110 if ($category eq 'wavelength') {
    100          
    100          
    50          
956 0         0 $output = $value;
957             } elsif ($category eq 'frequency') {
958              
959             # Convert frequency to wavelength
960             # converting from metres to microns
961 3         8 $output = CLIGHT / ($value * 1.0E-6);
962              
963             } elsif ($category eq 'wavenumber') {
964             # 1 / cm then convert cm to microns
965 1         6 $output = (1.0 / $value) * 10_000;
966              
967             } elsif ($category eq 'filter') {
968             # Convert filter to wavelength
969             # Need to walk through %FILTERS first for a
970             # instrument match and then for a generic match
971 13         21 my $instrument = $self->instrument;
972 13         26 my @search = ('GENERIC');
973 13 100       36 unshift(@search, $instrument) if defined $instrument;
974              
975 13         18 foreach my $name (@search) {
976              
977             # First look for a match in %FILTERS
978 13 50       34 if (exists $FILTERS{$name}) {
979             # Now look for the filter itself
980 13 50       47 if (exists $FILTERS{$name}{$value}) {
981 13         26 $output = $FILTERS{$name}{$value};
982 13         26 last;
983             }
984             }
985             }
986             }
987              
988 17         38 return $output;
989             }
990              
991             =back
992              
993             =end __PRIVATE_METHODS__
994              
995             =head2 Static functions
996              
997             These functions enable the user to obtain an overview of
998             the supported filter, instrument and telescope combinations.
999              
1000             =over 4
1001              
1002             =item B
1003              
1004             Returns true if the a particular instrument has a particular filter,
1005             otherwise returns C, e.g.
1006              
1007             if( Astro::WaveBand::has_filter( UIST => "Kprime" ) {
1008             ...
1009             }
1010              
1011             if you pass a hash containing multiple instrument combinations,
1012             all must be valid or the method will return undef.
1013              
1014             =cut
1015              
1016             sub has_filter {
1017 4 50   4 1 796 return undef unless @_;
1018              
1019             # grab instrument and filter list
1020 4         12 my %list = @_;
1021              
1022 4         6 my $counter = 0;
1023 4         15 foreach my $key ( sort keys %list ) {
1024             # if the filter exists in the filter list for that instrument,
1025             # increment the counter
1026 6 100       26 $counter++ if exists $FILTERS{$key}{$list{$key}};
1027             }
1028              
1029             # if the counter is the same size as the input list then all conditons
1030             # have been proved to be true...
1031 4 100       17 return undef unless scalar(keys %list) == $counter;
1032 2         8 return 1;
1033             }
1034              
1035             =item B
1036              
1037             Returns true if the a particular instrument exists for a particular
1038             telescope, otherwise returns C, e.g.
1039              
1040             if( Astro::WaveBand::has_instrument( UKIRT => "UIST" ) {
1041             ...
1042             }
1043              
1044             if you pass a hash containing multiple instrument combinations,
1045             all must be valid or the method will return undef.
1046              
1047             =cut
1048              
1049             sub has_instrument {
1050 2 50   2 1 6 return undef unless @_;
1051              
1052             # grab instrument and filter list
1053 2         6 my %list = @_;
1054              
1055 2         3 my $counter = 0;
1056 2         5 foreach my $key ( sort keys %list ) {
1057             # if the filter exists in the filter list for that instrument,
1058             # increment the counter
1059 2         3 for my $i ( 0 ... $#{$TELESCOPE{$key}} ) {
  2         7  
1060 8 100       22 if ( $TELESCOPE{$key}->[$i] eq $list{$key} ) {
1061 1         2 $counter++;
1062 1         3 last;
1063             }
1064             }
1065             }
1066              
1067             # if the counter is the same size as the input list then all conditons
1068             # have been proved to be true...
1069 2 100       9 return undef unless scalar(keys %list) == $counter;
1070 1         4 return 1;
1071             }
1072              
1073              
1074             =item B
1075              
1076             Returns true if the a particular telescope and filter combination is
1077             avaialable, otherwise returns C, e.g.
1078              
1079             if( Astro::WaveBand::is_observable( UKIRT => 'Kprime' ) {
1080             ...
1081             }
1082              
1083             =cut
1084              
1085             sub is_observable {
1086             #my $self = shift;
1087 3 50   3 1 10 return undef unless @_;
1088              
1089             # grab instrument and filter list
1090 3         8 my %list = @_;
1091              
1092 3         4 my $counter = 0;
1093 3         9 foreach my $key ( sort keys %list ) {
1094             # if the filter exists in the filter list for that instrument,
1095             # increment the counter
1096             #print "TELESCOPE $key\n";
1097 3         3 for my $i ( 0 ... $#{$TELESCOPE{$key}} ) {
  3         14  
1098              
1099             #print " INSTRUMENT ${$TELESCOPE{$key}}[$i]\n";
1100             #print " \$list{\$key} = $list{$key}\n";
1101 8         8 my $instrument = ${$TELESCOPE{$key}}[$i];
  8         13  
1102              
1103 8 100       8 if ( ${$FILTERS{$instrument}}{$list{$key}} ) {
  8         27  
1104 2         2 $counter++;
1105             #print "$counter: $key\n";
1106             #print " $list{$key}, $instrument, $list{$key}, ".
1107             # "${$FILTERS{${$TELESCOPE{$key}}[$i]}}{$list{$key}}\n";
1108 2         6 last;
1109             }
1110             }
1111             }
1112              
1113             # if the counter is the same size as the input list then all conditons
1114             # have been proved to be true...
1115 3 100       12 return undef unless scalar(keys %list) == $counter;
1116 2         7 return 1;
1117             }
1118              
1119             =back
1120              
1121             =head1 BUGS
1122              
1123             Does not automatically convert metres to microns and GHz to Hz etc.
1124              
1125             Can not handle filters that correspond to multiple wavelengths.
1126             Currently SCUBA is the main issue. With a 450:850 filter this class
1127             always returns the shortest wavelength (since that is the wavelength
1128             that affects scheduling the most).
1129              
1130             Should handle velocities and redshifts in order to disambiguate rest
1131             frequencies and observed frequencies. Would also be nice if the class
1132             could accept a molecule and transition, allowing the natural unit
1133             to appear as something like: "CO 3-2 @ 30km/s LSR radio".
1134              
1135             =head1 AUTHORS
1136              
1137             Tim Jenness Et.jenness@jach.hawaii.eduE
1138             Alasdair Allan Eaa@astro.ex.ac.ukE
1139             Tim Lister Etlister@lcogt.netE
1140              
1141             =head1 COPYRIGHT
1142              
1143             Copyright (C) 2001-2003 Particle Physics and Astronomy Research Council.
1144             All Rights Reserved.
1145              
1146             This program is free software; you can redistribute it and/or modify
1147             it under the same terms as Perl itself.
1148              
1149             =cut
1150              
1151             1;