File Coverage

blib/lib/Astro/Coords/Angle.pm
Criterion Covered Total %
statement 175 192 91.1
branch 81 104 77.8
condition 8 15 53.3
subroutine 30 31 96.7
pod 16 18 88.8
total 310 360 86.1


line stmt bran cond sub pod time code
1             package Astro::Coords::Angle;
2              
3             =head1 NAME
4              
5             Astro::Coords::Angle - Representation of an angle
6              
7             =head1 SYNOPSIS
8              
9             use Astro::Coords::Angle;
10              
11             $ang = new Astro::Coords::Angle( 45.5, units => 'deg' );
12             $ang = new Astro::Coords::Angle( "45:30:00", units => 'sexagesimal' );
13              
14             $rad = $ang->radians;
15             $deg = $ang->degrees;
16             $asec = $ang->arcsec;
17             $amin = $ang->arcmin;
18             $string = $ang->string;
19              
20             =head1 DESCRIPTION
21              
22             Helper class for C<Astro::Coords> to represent an angle. Methods are
23             provided for parsing angles in sexagesimal format and for returning
24             angles in any desired format.
25              
26             =cut
27              
28 23     23   8298103 use 5.006;
  23         91  
29 23     23   142 use strict;
  23         52  
  23         573  
30 23     23   146 use warnings;
  23         77  
  23         811  
31 23     23   198 use warnings::register;
  23         50  
  23         3615  
32 23     23   363 use Carp;
  23         52  
  23         1764  
33              
34 23     23   172 use Scalar::Util qw/ looks_like_number /;
  23         54  
  23         2432  
35 23     23   1195 use Astro::PAL;
  23         7510  
  23         9334  
36              
37             # Overloading
38             use overload
39 23         433 '""' => "stringify",
40             '0+' => "numify",
41 23     23   218 fallback => 1;
  23         54  
42              
43             # Package Global variables
44 23     23   2782 use vars qw/ $VERSION /;
  23         69  
  23         51534  
45              
46             $VERSION = '0.20';
47              
48             =head1 METHODS
49              
50             =head2 Constructor
51              
52             =over 4
53              
54             =item B<new>
55              
56             Construct a new C<Angle> object. Must be called with an angle as first
57             argument. Optional hash arguments can be supplied to specify, for example,
58             the units of the supplied angle.
59              
60             $ang = new Astro::Coords::Angle( $angle,
61             units => "degrees" );
62              
63             Supported options are:
64              
65             units - units of the supplied string or number
66             range - restricted range of the angle
67              
68             Supported units are:
69              
70             sexagesimal - A string of format either dd:mm:ss or "dd mm ss"
71             "dms" separators are also supported.
72             degrees - decimal degrees
73             radians - radians
74             arcsec - arc seconds (abbreviated form is 'as')
75             arcmin - arc minutes (abbreviated form is 'am')
76              
77             The units can be abbreviated to the first 3 characters.
78              
79             If the units are not supplied the default is to assume "sexagesimal"
80             if the supplied string contains spaces or colons or the characters
81             "d", "m" or "s", "degrees" if the supplied number is greater than 2*PI
82             (6.28), and "radians" for all other values. Negative angles are supported.
83              
84             The options for range are documented in the C<range> method.
85              
86             If the angle can not be decoded (if a string), the constructor will fail.
87              
88             =cut
89              
90             sub new {
91 70302     70302 1 118401 my $proto = shift;
92 70302   66     201397 my $class = ref($proto) || $proto;
93              
94 70302 50       136616 croak "Constructor for object of class $class must be called with an argument" unless @_;
95              
96             # first argument is the angle
97 70302         101179 my $input_ang = shift;
98              
99             # optional hash, only read it if we have an even number of
100             # remaining arguments
101 70302         97547 my %args;
102 70302 100       124388 if (@_) {
103 70295 50       143820 if (scalar(@_) % 2 == 0) {
104 70295         191673 %args = @_;
105             } else {
106 0         0 warnings::warnif("An odd number of Optional arguments were supplied to constructor");
107             }
108             }
109              
110             # Now need to convert this to radians (the internal representation)
111             # Allow for inheritance
112 70302         176813 my $rad = $class->_cvt_torad($input_ang, $args{units});
113              
114 70302 0       139228 croak "Unable to decode supplied angle (".
    50          
115             (defined $input_ang ? "'$input_ang'" : "<undef>").")"
116             unless defined $rad;
117              
118             # Create the object
119 70302         247672 my $ang = bless {
120             ANGLE => $rad,
121             RANGE => 'NONE',
122             NDP => undef, # number of decimal places
123             DELIM => undef, # string delimiter
124             }, $class;
125              
126             # If a range was specified, normalise the angle
127 70302 100       196632 $ang->range( $args{range} ) if exists $args{range};
128              
129             # And return the object
130 70302         211910 return $ang;
131             }
132              
133             =back
134              
135             =head2 Accessor Methods
136              
137             =over 4
138              
139             =item B<radians>
140              
141             Return the angle in radians.
142              
143             $rad = $ang->radians;
144              
145             =cut
146              
147             sub radians {
148 106634     106634 1 143016 my $self = shift;
149 106634         1317899 return $self->{ANGLE};
150             }
151              
152             # undocumented since we do not want a public way of changing the
153             # angle
154             sub _setRadians {
155 38388     38388   58390 my $self = shift;
156 38388         52867 my $rad = shift;
157 38388 50       75400 croak "Angle must be defined" unless defined $rad;
158 38388         66666 $self->{ANGLE} = $rad;
159             }
160              
161             =item B<degrees>
162              
163             Return the angle in decimal degrees.
164              
165             $deg = $ang->degrees;
166              
167             =cut
168              
169             sub degrees {
170 227     227 1 402 my $self = shift;
171 227         417 my $rad = $self->radians;
172 227         2218 return $rad * Astro::PAL::DR2D;
173             }
174              
175             =item B<str_ndp>
176              
177             Number of decimal places to use when stringifying the object.
178             Default is to use the global class value (see the C<NDP> class method).
179             Set to C<undef> to revert to the class setting.
180              
181             $ang->str_ndp( 4 );
182             $ndp = $ang->str_ndp;
183              
184             =cut
185              
186             sub str_ndp {
187 93     93 1 616 my $self = shift;
188 93 100       217 if (@_) {
189 12         22 $self->{NDP} = shift;
190             }
191             # A value has been requested. Do we have a local value
192             # or should we return the default.
193 93 100       270 if (defined $self->{NDP} ) {
194 26         62 return $self->{NDP};
195             } else {
196 67         189 return $self->NDP;
197             }
198             }
199              
200             =item B<str_delim>
201              
202             Delimiter to use between components when stringifying.
203             Default is to use the global class value (see the C<DELIM> class method).
204             Set to C<undef> to revert to the class setting.
205              
206             $ang->str_delim( ":" );
207             $delim = $ang->str_delim;
208              
209             =cut
210              
211             sub str_delim {
212 87     87 1 174 my $self = shift;
213 87 100       180 if (@_) {
214 6         11 $self->{DELIM} = shift;
215             }
216             # A value has been requested. Do we have a local value
217             # or should we return the default.
218 87 100       186 if (defined $self->{DELIM} ) {
219 13         23 return $self->{DELIM};
220             } else {
221 74         198 return $self->DELIM;
222             }
223             }
224              
225             =item B<components>
226              
227             Return an array of components that correspond to the sign, degrees,
228             arcminutes and arcseconds of the angle. The sign will be either a '+'
229             or '-' and is required to distinguish '+0' from '-0'.
230              
231             @comp = $ang->components;
232              
233             The number of decimal places in the seconds will not be constrained by the
234             setting of C<str_ndp>, but is constrained by an optional argument:
235              
236             @comp = $ang->components( $ndp );
237              
238             Default resolution is 5 decimal places.
239              
240             In scalar context, returns a reference to an array.
241              
242             =cut
243              
244             sub components {
245 84     84 1 131 my $self = shift;
246 84         130 my $res = shift; # internal api
247              
248             # Get the angle in radians
249 84         166 my $rad = $self->radians;
250              
251             # Convert to components using PAL. COCO uses 4 dp for high
252             # resolution.
253 84 50       217 $res = 5 unless defined $res;
254 84         231 my @dmsf = $self->_r2f( $res );
255              
256             # Combine the fraction with the seconds unless no decimal places
257 84         159 my $frac = pop(@dmsf);
258 84 100       498 $dmsf[-1] .= sprintf( ".%0$res"."d",$frac) unless $res == 0;
259              
260             #use Data::Dumper;
261             #print Dumper(\@dmsf);
262              
263 84 100       196 if (wantarray) {
264 81         251 return @dmsf;
265             } else {
266 3         14 return \@dmsf;
267             }
268              
269             }
270              
271             =item B<string>
272              
273             Return the angle as a string in sexagesimal format (e.g. 12:30:52.4).
274              
275             $string = $ang->string();
276              
277             The form of this string depends on the C<str_delim> and C<str_ndp>
278             settings and on whether the angular range allows negative values (the
279             sign will be dropped if the range is known to be positive).
280              
281             =cut
282              
283             sub string {
284 81     81 1 165 my $self = shift;
285              
286             # Get the components
287 81         188 my $ndp = $self->str_ndp;
288 81         219 my @dms = $self->components( $ndp );
289              
290             # Play it safe, and split the fractional part into two strings.
291             # if ndp > 0
292 81 100       218 if ( $ndp > 0 ) {
293 80         273 my ($sec, $frac) = split(/\./,$dms[-1]);
294 80         143 $dms[-1] = $sec;
295 80         180 push(@dms, $frac);
296             }
297              
298             # Now build the string.
299              
300             # Clear the + sign, setting it to empty string if the angle can never
301             # go negative.
302 81         153 my $sign = shift(@dms);
303 81 100       226 if ($sign eq '+') {
304 50 100       121 if ($self->range eq '2PI') {
305 41         79 $sign = '';
306             } else {
307 9         84 $sign = ' ';
308             }
309             }
310              
311             # Get the delimiter
312 81         208 my $delim = $self->str_delim;
313              
314             # Build the format
315              
316             # fractional part will not require a decimal place
317             # if ndp is 0. If ndp>0 the fraction is formatted
318 81 100       184 my $fracfmt = ( $ndp == 0 ? '' : '.%s' );
319              
320             # starting with the numeric part. Gal longitude will want %03d and no sign.
321             # RA will want no sign and %02d. Dec wants sign with %02d.
322              
323 81         229 my @fmts = ( '%02d', '%02d', '%02d'.$fracfmt);
324 81         118 my $fmt;
325 81 100       167 if (length($delim) == 1) {
326 78         180 $fmt = join($delim, @fmts );
327             } else {
328 3         8 my @chars = split (//, $delim );
329 3         7 for my $f (@fmts) {
330 9         17 $fmt .= $f . shift(@chars);
331             }
332             }
333              
334 81         983 return $sign . sprintf( $fmt, @dms);
335              
336             }
337              
338             =item B<arcsec>
339              
340             Return the angle in arcseconds.
341              
342             $asec = $ang->arcsec;
343              
344             =cut
345              
346             sub arcsec {
347 11     11 1 24 my $self = shift;
348 11         22 my $rad = $self->radians;
349 11         36 return $rad * Astro::PAL::DR2AS;
350             }
351              
352             =item B<arcmin>
353              
354             Return the angle in arcminutes.
355              
356             $amin = $ang->arcmin;
357              
358             =cut
359              
360             sub arcmin {
361 0     0 1 0 my $self = shift;
362 0         0 my $asec = $self->arcsec;
363 0         0 return $asec / 60.0;
364             }
365              
366             =item B<range>
367              
368             String describing the allowed range of the angle. Allowed values
369             are
370              
371             NONE - no pre-determined range
372             2PI - 0 to 2*PI radians (0 to 360 degrees)
373             PI - -PI to +PI radians (-180 to 180 degrees)
374              
375             Any other strings will be ignored (and a warning issued if appropriate).
376              
377             When a new value is provided, the angle is normalised to this range.
378             Note that this is not always reversible (especially if reverting to
379             "NONE"). The range can also be specified to the constructor.
380              
381             Default is not to normalize the angle.
382              
383             =cut
384              
385             sub range {
386 38447     38447 1 61437 my $self = shift;
387 38447 100       78244 if (@_) {
388 38391         61630 my $rng = shift;
389 38391 50       68640 if (defined $rng) {
390             # upper case
391 38391         67935 $rng = uc($rng);
392              
393             # get the current value for the angle
394 38391         71097 my $rad = $self->radians;
395              
396             # Now check validity of string and normalise
397 38391 100       95902 if ($rng eq 'NONE') {
    100          
    50          
398             # do nothing apart from store it
399             } elsif ($rng eq '2PI') {
400 29672         92212 $self->_setRadians( Astro::PAL::palDranrm( $rad ));
401             } elsif ($rng eq 'PI') {
402 8716         26376 $self->_setRadians( Astro::PAL::palDrange( $rad ));
403             } else {
404 0         0 warnings::warnif("Supplied range '$rng' not recognized");
405 0         0 return;
406             }
407             # store it
408 38391         77776 $self->{RANGE} = $rng;
409             } else {
410 0         0 warnings::warnif("Supplied range was not defined");
411             }
412             }
413 38447         58195 return $self->{RANGE};
414             }
415              
416             =item B<in_format>
417              
418             Simple wrapper method to support the backwards compatibility interface
419             in C<Astro::Coords> when requesting an angle by using a string format rather
420             than an explicit method.
421              
422             $angle = $ang->in_format( 'sexagesimal' );
423              
424             Supported formats are:
425              
426             radians calls 'radians' method
427             degrees calls 'degrees' method
428             sexagesimal calls 'string' method
429             array calls 'components' method (returns 2 dp resolution)
430             arcsec calls 'arcsec' method
431             arcmin calls 'arcmin' method
432              
433             The format can be abbreviated to the first 3 letters, or 'am' or 'as'
434             for arcmin and arcsec respectively. If no format is specified explicitly, the
435             object itself will be returned.
436              
437             =cut
438              
439             sub in_format {
440 41441     41441 1 62685 my $self = shift;
441 41441         69422 my $format = shift;
442              
443             # No format (including empty string), return the object
444 41441 100       136474 return $self unless $format;
445 2749         5237 $format = lc($format);
446              
447 2749 100 33     13072 if ($format =~ /^d/) {
    100 33        
    100          
    50          
    50          
    50          
448 184         413 return $self->degrees;
449             } elsif ($format =~ /^s/) {
450 16         47 return $self->string();
451             } elsif ($format =~ /^r/) {
452 2546         5321 return $self->radians();
453             } elsif ($format =~ /^arcm/ || $format eq 'am') {
454 0         0 return $self->arcmin;
455             } elsif ($format =~ /^arcs/ || $format eq 'as') {
456 0         0 return $self->arcsec;
457             } elsif ($format =~ /^a/) {
458 3         11 return $self->components( 2 );
459             } else {
460 0         0 warnings::warnif("Unsupported format '$format'. Returning radians.");
461 0         0 return $self->radians;
462             }
463             }
464              
465             =item B<clone>
466              
467             Create new cloned copy of this object.
468              
469             $clone = $ang->clone;
470              
471             =cut
472              
473             sub clone {
474 1     1 1 2 my $self = shift;
475 1         7 return bless { %$self }, ref $self;
476             }
477              
478             =item B<negate>
479              
480             Negate the sense of the angle, returning a new angle object.
481              
482             $neg = $ang->negate;
483              
484             Not allowed if the range is defined as 0 to 2PI.
485              
486             =cut
487              
488             sub negate {
489 3     3 1 8 my $self = shift;
490 3 50       62 croak "Angle can not be negated since its range is 0 to 2PI"
491             if $self->range eq '2PI';
492 3         10 my $rad = $self->radians;
493 3         43 return $self->new( $rad * -1.0, units => 'radians', range => $self->range );
494             }
495              
496             =back
497              
498             =head2 Overloading
499              
500             The object is overloaded such that it stringifies via the C<string>
501             method, and returns the angle in radians in numify context.
502              
503             =cut
504              
505             sub stringify {
506 22     22 0 461 my $self = shift;
507 22         77 return $self->string();
508             }
509              
510             sub numify {
511 64943     64943 0 128499 my $self = shift;
512 64943         109323 return $self->radians();
513             }
514              
515             =head2 Class Methods
516              
517             The following methods control the default behaviour of the class.
518              
519             =over 4
520              
521             =item B<NDP>
522              
523             The number of decimal places to use in the fractional part of
524             the number when stringifying (from either the C<string> method
525             or the C<components> method).
526              
527             Astro::Coords::Angle->NDP( 4 );
528              
529             Default value is 2. If this is changed then
530             all instances will be affected on stringification unless the
531             C<str_ndp> attribute has been set explicitly for an instance.
532              
533             If an undefined argument is supplied, the class will revert to its
534             initial state.
535              
536             Astro::Coords::Angle->NDP( undef );
537              
538             =cut
539              
540             {
541             my $DEFAULT_NDP = 2;
542             my $NDP = $DEFAULT_NDP;
543             sub NDP {
544 39     39 1 79 my $class = shift;
545 39 100       95 if (@_) {
546 1         2 my $arg = shift;
547 1 50       4 if (defined $arg) {
548 1         2 $NDP = $arg;
549             } else {
550 0         0 $NDP = $DEFAULT_NDP;
551             }
552             }
553 39         84 return $NDP;
554             }
555             }
556              
557             =item B<DELIM>
558              
559             Delimiter to use to separate components of a sexagesimal triplet when
560             the object is stringified. If this is changed then all instances will
561             be affected on stringification unless the C<str_delim> attribute has
562             been set explicitly for an instance.
563              
564             Common values are a colon (12:52:45.4) or a space (12 52 45.4). If
565             more than one character is present in the string, each character will
566             be used in turn as a delimiter in the string until either no more gaps
567             are present (or characters have been exhausted. In the former, if
568             there are more characters than gaps, the first character remaining in
569             the string will be appended, in the latter case, no more characters
570             will be printed. For example, "dms" would result in '12d52m45.4s',
571             whereas 'dm' would result in '12d52m45.4'
572              
573             Astro::Coords::Angle->DELIM( ':' );
574              
575             Default is ":". An undefined argument will result in the class reverting
576             to the default state.
577              
578             =cut
579              
580             {
581             my $DEFAULT_DELIM = ":";
582             my $DELIM = $DEFAULT_DELIM;
583             sub DELIM {
584 43     43 1 12713 my $class = shift;
585 43 100       123 if (@_) {
586 2         6 my $arg = shift;
587 2 50       8 if (defined $arg) {
588 2         5 $DELIM = $arg;
589             } else {
590 0         0 $DELIM = $DEFAULT_DELIM;
591             }
592             }
593 43         107 return $DELIM;
594             }
595             }
596              
597             =item B<to_radians>
598              
599             Low level utility routine to convert an input value in specified format
600             to radians. This method uses the same code as the object constructor to parse
601             the supplied input argument but does not require the overhead of object
602             construction if the result is only to be used transiently.
603              
604             $rad = Astro::Coords::Angle->to_radians( $string, $format );
605              
606             See the constructor documentation for the supported format strings.
607              
608             =cut
609              
610             sub to_radians {
611 2064     2064 1 3464 my $class = shift;
612             # simply delegate to the internal routine. Could use it directly but it feels
613             # better to leave options open for the moment
614 2064         5437 $class->_cvt_torad( @_ );
615             }
616              
617             =back
618              
619             =begin __PRIVATE_METHODS__
620              
621             =head2 Private Methods
622              
623             These methods are not part of the API and should not be called directly.
624             They are documented for completeness.
625              
626             =over 4
627              
628             =item B<_cvt_torad>
629              
630             Internal class method to convert an input string to the equivalent value in
631             radians. The following units are supported:
632              
633             sexagesimal - A string of format "dd:mm:ss.ss", "dd mm ss.ss"
634             or even "-ddxmmyss.ss" (ie -5x53y28.5z)
635             degrees - decimal degrees
636             radians - radians
637             arcsec - arc seconds (abbreviated form is 'as')
638             arcmin - arc minutes (abbreviated form is 'am')
639              
640             If units are not supplied, default is to call the C<_guess_units>
641             method.
642              
643             $radians = $angle->_cvt_torad( $angle, $units );
644              
645             Warnings are issued if the string can not be parsed or the values are
646             out of range.
647              
648             If the supplied angle is an Angle object itself, units are ignored and
649             the value is extracted directly from the object.
650              
651             Returns C<undef> on error. Does not modify the internal state of the object.
652              
653             =cut
654              
655             sub _cvt_torad {
656 72366     72366   104512 my $self = shift;
657 72366         97435 my $input = shift;
658 72366         96752 my $units = shift;
659              
660 72366 50       136007 return undef unless defined $input;
661              
662             # do we have an object?
663             # and can it implement the radians() method?
664 72366 100       589858 if (UNIVERSAL::can( $input, 'radians')) {
665 214         392 return $input->radians;
666             }
667              
668             # Clean up the string
669 72152         265369 $input =~ s/^\s+//g;
670 72152         201983 $input =~ s/\s+$//g;
671              
672             # guess the units
673 72152 100       145261 unless (defined $units) {
674 28         85 $units = $self->_guess_units( $input );
675 28 50       89 croak "No units supplied, and unable to guess any units either"
676             unless defined $units;
677             }
678              
679             # Now process the input - starting with strings
680 72152         105096 my $output = 0;
681 72152 100 66     381654 if ($units =~ /^s/) {
    100 66        
    100          
    100          
682              
683             # Since we can support aritrary delimiters on write,
684             # we should be flexible on read. Slalib is very flexible
685             # once the numbers are space separated, so remove all
686             # non-numeric characters except + and - and replace with space
687             # For now, remove all alphabetic characters and colon only
688              
689             # Need to clean up the string for PAL
690 2047         9879 $input =~ s/[:[:alpha:]]/ /g;
691              
692 2047         3627 my $nstrt = 1;
693 2047         11481 ($nstrt, $output, my $j) = Astro::PAL::palDafin( $input, $nstrt );
694 2047 50       4973 $output = undef unless $j == 0;
695              
696 2047 50       6986 if ($j == -1) {
    50          
    50          
    50          
697 0         0 warnings::warnif "In coordinate '$input' the degrees do not look right";
698             } elsif ($j == -2) {
699 0         0 warnings::warnif "In coordinate '$input' the minutes field is out of range";
700             } elsif ($j == -3) {
701 0         0 warnings::warnif "In coordinate '$input' the seconds field is out of range (0-59.9)";
702             } elsif ($j == 1) {
703 0         0 warnings::warnif "Unable to find plausible coordinate in string '$input'";
704             }
705              
706             } elsif ($units =~ /^d/) {
707             # Degrees decimal
708 32         69 $output = $input * Astro::PAL::DD2R;
709              
710             } elsif ($units =~ /^arcs/ || $units eq 'as') {
711             # Arcsec
712 24         47 $output = $input * Astro::PAL::DAS2R;
713              
714             } elsif ($units =~ /^arcm/ || $units eq 'am') {
715             # Arcmin
716 2         7 $output = $input * Astro::PAL::DAS2R * 60 ;
717              
718             } else {
719             # Already in radians
720 70047         108165 $output = $input;
721             }
722              
723 72152         158841 return $output;
724             }
725              
726             =item B<_guess_units>
727              
728             Given a string or number, tries to guess the units. Default is to
729             assume "sexagesimal" if the supplied string does not look like a
730             number to perl, "degrees" if the supplied number is greater than 2*PI
731             (6.28), and "radians" for all other values.
732              
733             $units = $class->_guess_units( $input );
734              
735             Returns undef if the input does not look at all plausible or is undef
736             itself.
737              
738             Arcsec or arcmin can not be determined with this routine.
739              
740             =cut
741              
742             sub _guess_units {
743 47     47   86 my $self = shift;
744 47         80 my $input = shift;
745 47 50       136 return undef if !defined $input;
746              
747             # Now if we have a space, colon or alphabetic character
748             # then we have a real string and assume sexagesimal.
749             # Use pre-defined character classes
750 47         70 my $units;
751             # if it does not look like a number choose sexagesimal
752 47 100       201 if (!looks_like_number($input)) {
    100          
753 34         93 $units = "sexagesimal";
754             } elsif ($input > Astro::PAL::D2PI) {
755 4         8 $units = "degrees";
756             } else {
757 9         18 $units = "radians";
758             }
759              
760 47         120 return $units;
761             }
762              
763             =item B<_r2f>
764              
765             Routine to convert angle in radians to a formatted array
766             of numbers in order of sign, deg, min, sec, frac.
767              
768             @retval = $ang->_r2f( $ndp );
769              
770             Note that the number of decimal places is an argument.
771              
772             =cut
773              
774             sub _r2f {
775 48     48   76 my $self = shift;
776 48         78 my $res = shift;
777 48         113 my ($sign, @dmsf) = Astro::PAL::palDr2af($res, $self->radians);
778 48         166 return ($sign, @dmsf);
779             }
780              
781             =back
782              
783             =end __PRIVATE_METHODS__
784              
785             =head1 AUTHOR
786              
787             Tim Jenness E<lt>t.jenness@cpan.orgE<gt>
788              
789             =head1 COPYRIGHT
790              
791             Copyright (C) 2004-2005 Tim Jenness. All Rights Reserved.
792              
793             This program is free software; you can redistribute it and/or modify it under
794             the terms of the GNU General Public License as published by the Free Software
795             Foundation; either version 3 of the License, or (at your option) any later
796             version.
797              
798             This program is distributed in the hope that it will be useful,but WITHOUT ANY
799             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
800             PARTICULAR PURPOSE. See the GNU General Public License for more details.
801              
802             You should have received a copy of the GNU General Public License along with
803             this program; if not, write to the Free Software Foundation, Inc., 59 Temple
804             Place,Suite 330, Boston, MA 02111-1307, USA
805              
806             =cut
807              
808             1;
809