File Coverage

blib/lib/Astro/WaveBand.pm
Criterion Covered Total %
statement 213 232 91.8
branch 96 138 69.5
condition 14 36 38.8
subroutine 29 30 96.6
pod 15 15 100.0
total 367 451 81.3


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