1package Astro::Catalog::Item;
2
3=head1 NAME
4
5Astro::Catalog::Item - A generic star object in a stellar catalogue.
6
7=head1 SYNOPSIS
8
9    $star = new Astro::Catalog::Item(
10        ID           => $id,
11        Coords       => new Astro::Coords(),
12        Morphology   => new Astro::Catalog::Item::Morphology(),
13        Fluxes       => new Astro::Fluxes(),
14        Quality      => $quality_flag,
15        Field        => $field,
16        GSC          => $in_gsc,
17        Distance     => $distance_to_centre,
18        PosAngle     => $position_angle,
19        X            => $x_pixel_coord,
20        Y            => $y_pixel_coord,
21        WCS          => new Starlink::AST(),
22        Comment      => $comment_string
23        SpecType     => $spectral_type,
24        StarType     => $star_type,
25        LongStarType => $long_star_type,
26        MoreInfo     => $url,
27        InsertDate   => new Time::Piece(),
28        Misc         => $hash_ref,
29    );
30
31=head1 DESCRIPTION
32
33Stores generic meta-data about an individual stellar object from a catalogue.
34
35If the catalogue has a field center the Distance and Position Angle properties
36should be used to store the direction to the field center, e.g. a star from the
37USNO-A2 catalogue retrieived from the ESO/ST-ECF Archive will have these
38properties.
39
40=cut
41
42
43use strict;
44use warnings;
45use Carp;
46use Astro::Coords 0.12;
47use Astro::Catalog::Item::Morphology;
48use Astro::Fluxes;
49use Astro::Flux;
50use Astro::FluxColor;
51
52# Register an Astro::Catalog::Item warning category
53use warnings::register;
54
55our $VERSION = '4.36';
56
57# Internal lookup table for Simbad star types
58my %STAR_TYPE_LOOKUP = (
59    'vid' => 'Underdense region of the Universe',
60    'Er*' => 'Eruptive variable Star',
61    'Rad' => 'Radio-source',
62    'Q?' => 'Possible Quasar',
63    'IR' => 'Infra-Red source',
64    'SB*' => 'Spectrocopic binary',
65    'C*' => 'Carbon Star',
66    'Gl?' => 'Possible Globular Cluster',
67    'DNe' => 'Dark Nebula',
68    'GlC' => 'Globular Cluster',
69    'No*' => 'Nova',
70    'V*?' => 'Star suspected of Variability',
71    'LeG' => 'Gravitationnaly Lensed Image of a Galaxy',
72    'mAL' => 'metallic Absorption Line system',
73    'LeI' => 'Gravitationnaly Lensed Image',
74    'WU*' => 'Eclipsing binary of W UMa type',
75    'Be*' => 'Be Star',
76    'PaG' => 'Pair of Galaxies',
77    'Mas' => 'Maser',
78    'LeQ' => 'Gravitationnaly Lensed Image of a Quasar',
79    'mul' => 'Composite object',
80    'SBG' => 'Starburst Galaxy',
81    '*' => 'Star',
82    'gam' => 'gamma-ray source',
83    'bL*' => 'Eclipsing binary of beta Lyr type',
84    'S*' => 'S Star',
85    'El*' => 'Elliptical variable Star',
86    'GNe' => 'Galactic Nebula',
87    'DQ*' => 'Cataclysmic Var. DQ Her type',
88    '?' => 'Object of unknown nature',
89    'WV*' => 'Variable Star of W Vir type',
90    'SR?' => 'SuperNova Remnant Candidate',
91    'Bla' => 'Blazar',
92    'G' => 'Galaxy',
93    'SCG' => 'Supercluster of Galaxies',
94    'OH*' => 'Star with envelope of OH/IR type',
95    'Lev' => '(Micro)Lensing Event',
96    'BNe' => 'Bright Nebula',
97    'RV*' => 'Variable Star of RV Tau type',
98    'IR0' => 'IR source at lambda < 10 microns',
99    'OVV' => 'Optically Violently Variable object',
100    'a2*' => 'Variable Star of alpha2 CVn type',
101    'IR1' => 'IR source at lambda > 10 microns',
102    'Em*' => 'Emission-line Star',
103    'PM*' => 'High proper-motion Star',
104    'X' => 'X-ray source',
105    'HzG' => 'Galaxy with high redshift',
106    'Sy*' => 'Symbiotic Star',
107    'LXB' => 'Low Mass X-ray Binary',
108    '*i*' => 'Star in double system',
109    'Sy1' => 'Seyfert 1 Galaxy',
110    'Sy2' => 'Seyfert 2 Galaxy',
111    'LIN' => 'LINER-type Active Galaxy Nucleus',
112    'rG' => 'Radio Galaxy',
113    'Cl*' => 'Cluster of Stars',
114    'NL*' => 'Nova-like Star',
115    'HV*' => 'High-velocity Star',
116    'EmG' => 'Emission-line galaxy',
117    '*iA' => 'Star in Association',
118    'grv' => 'Gravitational Source',
119    '*iC' => 'Star in Cluster',
120    'SyG' => 'Seyfert Galaxy',
121    'RNe' => 'Reflection Nebula',
122    'EmO' => 'Emission Object',
123    'Ce*' => 'Classical Cepheid variable Star',
124    'CV*' => 'Cataclysmic Variable Star',
125    '*iN' => 'Star in Nebula',
126    'BY*' => 'Variable of BY Dra type',
127    'Pe*' => 'Peculiar Star',
128    'AM*' => 'Cataclysmic Var. AM Her type',
129    'FU*' => 'Variable Star of FU Ori type',
130    'HVC' => 'High-velocity Cloud',
131    'ClG' => 'Cluster of Galaxies',
132    'Ir*' => 'Variable Star of irregular type',
133    'PN?' => 'Possible Planetary Nebula',
134    'ALS' => 'Absorption Line system',
135    'cm' => 'centimetric Radio-source',
136    'As*' => 'Association of Stars',
137    'V*' => 'Variable Star',
138    'Fl*' => 'Flare Star',
139    'EB*' => 'Eclipsing binary',
140    'CGG' => 'Compact Group of Galaxies',
141    'UV' => 'UV-emission source',
142    'Ro*' => 'Rotationally variable Star',
143    'SN*' => 'SuperNova',
144    'pr*' => 'Pre-main sequence Star',
145    'CH*' => 'Star with envelope of CH type',
146    'Al*' => 'Eclipsing binary of Algol type',
147    'Pu*' => 'Pulsating variable Star',
148    'Cld' => 'Cloud of unknown nature',
149    'QSO' => 'Quasar',
150    'Psr' => 'Pulsars',
151    'GiC' => 'Galaxy in Cluster of Galaxies',
152    'V* RI*' => 'Variable Star with rapid variations',
153    'sh' => 'HI shell',
154    'GiG' => 'Galaxy in Group of Galaxies',
155    'OpC' => 'Open (galactic) Cluster',
156    'WR*' => 'Wolf-Rayet Star',
157    'BCG' => 'Blue compact Galaxy',
158    'blu' => 'Blue object',
159    'GiP' => 'Galaxy in Pair of Galaxies',
160    'LyA' => 'Ly alpha Absorption Line system',
161    'CGb' => 'Cometary Globule',
162    '**' => 'Double or multiple star',
163    'H2G' => 'HII Galaxy',
164    'RR*' => 'Variable Star of RR Lyr type',
165    'HB*' => 'Horizontal Branch Star',
166    'RC*' => 'Variable Star of R CrB type',
167    'SNR' => 'SuperNova Remnant',
168    'MoC' => 'Molecular Cloud',
169    'HXB' => 'High Mass X-ray Binary',
170    'mR' => 'metric Radio-source',
171    'TT*' => 'T Tau-type Star',
172    'DN*' => 'Dwarf Nova',
173    'eg sr*' => 'Semi-regular pulsating Star',
174    'HII' => 'HII (ionized) region',
175    'HH' => 'Herbig-Haro Object',
176    'HI' => 'HI (neutral) region',
177    'WD*' => 'White Dwarf',
178    'Or*' => 'Variable Star in Orion Nebula',
179    'dS*' => 'Variable Star of delta Sct type',
180    'DLy' => 'Dumped Ly alpha Absorption Line system',
181    'AGN' => 'Active Galaxy Nucleus',
182    'GrG' => 'Group of Galaxies',
183    'Mi*' => 'Variable Star of Mira Cet type',
184    'RS*' => 'Variable of RS CVn type',
185    'mm' => 'millimetric Radio-source',
186    'red' => 'Very red source',
187    'BLL' => 'BL Lac - type object',
188    'reg' => 'Region defined in the sky',
189    'PN' => 'Planetary Nebula',
190    'ZZ*' => 'Variable White Dwarf of ZZ Cet type',
191    'gB' => 'gamma-ray Burster',
192    'PoC' => 'Part of Cloud',
193    'XB*' => 'X-ray Binary',
194    'PoG' => 'Part of a Galaxy',
195    'Neb' => 'Nebula of unknown nature'
196);
197
198=head1 METHODS
199
200=head2 Constructor
201
202=over 4
203
204=item B<new>
205
206Create a new instance from a hash of options
207
208    $star = new Astro::Catalog::Item(
209        ID           => $id,
210        Coords       => new Astro::Coords(),
211        Morphology   => new Astro::Catalog::Item::Morphology(),
212        Fluxes       => new Astro::Fluxes(),
213        Quality      => $quality_flag,
214        Field        => $field,
215        GSC          => $in_gsc,
216        Distance     => $distance_to_centre,
217        PosAngle     => $position_angle,
218        X            => $x_pixel_coord,
219        Y            => $y_pixel_coord,
220        Comment      => $comment_string
221        SpecType     => $spectral_type,
222        StarType     => $star_type,
223        LongStarType => $long_star_type,
224        MoreInfo     => $url,
225        InsertDate   => new Time::Piece(),
226        Misc         => $misc,
227    );
228
229returns a reference to an Astro::Catalog::Item object.
230
231The coordinates can also be specified as individual RA and Dec values
232(sexagesimal format) if they are known to be J2000.
233
234=cut
235
236sub new {
237    my $proto = shift;
238    my $class = ref($proto) || $proto;
239
240    # bless the query hash into the class
241    my $block = bless {
242        ID         => undef,
243        FLUXES     => undef,
244        MORPHOLOGY => undef,
245        QUALITY    => undef,
246        FIELD      => undef,
247        GSC        => undef,
248        DISTANCE   => undef,
249        POSANGLE   => undef,
250        COORDS     => undef,
251        X          => undef,
252        Y          => undef,
253        WCS        => undef,
254        COMMENT    => undef,
255        SPECTYPE   => undef,
256        STARTYPE   => undef,
257        LONGTYPE   => undef,
258        MOREINFO   => undef,
259        INSERTDATE => undef,
260        PREFERRED_MAG_TYPE => undef,
261        MISC => undef,
262    }, $class;
263
264    # If we have arguments configure the object
265    $block->configure( @_ ) if @_;
266
267    return $block;
268}
269
270=back
271
272=head2 Accessor Methods
273
274=over 4
275
276=item B<id>
277
278Return (or set) the ID of the star
279
280    $id = $star->id();
281    $star->id( $id );
282
283If an Astro::Coords object is associated with the Star, the name
284field is set in the underlying Astro::Coords object as well as in
285the current Star object.
286
287=cut
288
289sub id {
290    my $self = shift;
291    if (@_) {
292        $self->{ID} = shift;
293
294        my $c = $self->coords;
295        $c->name($self->{ID}) if defined $c;
296    }
297    return $self->{ID};
298}
299
300=item B<coords>
301
302Return or set the coordinates of the star as an C<Astro::Coords>
303object.
304
305    $c = $star->coords();
306    $star->coords($c);
307
308The object returned by this method is the actual object stored
309inside this Star object and not a clone. If the coordinates
310are changed through this object the coordinate of the star is
311also changed.
312
313Currently, if you modify the RA or Dec through the ra()
314or dec() methods of Star, the internal object associated with
315the Star will change.
316
317Returns undef if the coordinates have never been specified.
318
319If the name() field is defined in the Astro::Coords object
320the id() field is set in the current Star object. Similarly for
321the comment field.
322
323=cut
324
325sub coords {
326    my $self = shift;
327    if (@_) {
328        my $c = shift;
329        croak "Coordinates must be an Astro::Coords object"
330            unless UNIVERSAL::isa($c, "Astro::Coords");
331
332        # force the ID and comment to match
333        $self->id($c->name) if defined $c->name;
334        $self->comment($c->comment) if $c->comment;
335
336        # Store the new coordinate object
337        # Storing it late stops looping from the id and comment methods
338        $self->{COORDS} = $c;
339    }
340    return $self->{COORDS};
341}
342
343=item B<ra>
344
345Return (or set) the current object R.A. (J2000).
346
347    $ra = $star->ra();
348
349If the Star is associated with a moving object such as a planet,
350comet or asteroid this method will return the J2000 RA associated
351with the time and observer position associated with the coordinate
352object itself (by default current time, longitude of 0 degrees).
353Returns undef if no coordinate has been associated with this star.
354
355    $star->ra($ra);
356
357The RA can be changed using this method but only if the coordinate
358object is associated with a fixed position. Attempting to change the
359J2000 RA of a moving object will fail. If an attempt is made to
360change the RA when no coordinate is associated with this object then
361a new Astro::Coords object will be created (with a
362Dec of 0.0).
363
364RA accepted by this method must be in sexagesimal format, space or
365colon-separated. Returns a space-separated sexagesimal number.
366
367
368=cut
369
370sub ra {
371    my $self = shift;
372    if (@_) {
373        my $ra = shift;
374
375        # Issue a warning specifically for this call
376        my @info = caller();
377        warnings::warnif("deprecated","Use of ra() method for setting RA now deprecated. Please use the coords() method instead, at $info[1] line $info[2]");
378
379
380        # Get the coordinate object
381        my $c = $self->coords;
382        if (defined $c) {
383            # Need to tweak RA?
384            croak "Can only adjust RA with Astro::Coords::Equatorial coordinates"
385                unless $c->isa("Astro::Coords::Equatorial");
386
387            # For now need to kluge since Astro::Coords does not allow
388            # you to change the position (it is an immutable object)
389            $c = $c->new(
390                type => 'J2000',
391                dec => $c->dec(format => 's'),
392                ra => $ra,
393            );
394
395        }
396        else {
397            $c = new Astro::Coords(
398                type => 'J2000',
399                ra => $ra,
400                dec => '0',
401            );
402        }
403
404        # Update the object
405        $self->coords($c);
406    }
407
408    my $outc = $self->coords;
409    return unless defined $outc;
410
411    # Astro::Coords inserts colons by default. Grab the old delimiter
412    # and number of decimal places if we're using a recent enough
413    # version of Astro::Coords.
414    my $ra = $outc->ra;
415    if (UNIVERSAL::isa($ra, "Astro::Coords::Angle")) {
416        $ra->str_delim(' ');
417        $ra->str_ndp(2);
418        return "$ra";
419    }
420    else {
421        my $outra = $outc->ra(format => 's');
422        $outra =~ s/:/ /g;
423        $outra =~ s/^\s*//;
424
425        return $outra;
426    }
427}
428
429=item B<dec>
430
431Return (or set) the current object Dec (J2000).
432
433    $dec = $star->dec();
434
435If the Star is associated with a moving object such as a planet,
436comet or asteroid this method will return the J2000 Dec associated
437with the time and observer position associated with the coordinate
438object itself (by default current time, longitude of 0 degrees).
439Returns undef if no coordinate has been associated with this star.
440
441    $star->dec( $dec );
442
443The Dec can be changed using this method but only if the coordinate
444object is associated with a fixed position. Attempting to change the
445J2000 Dec of a moving object will fail. If an attempt is made to
446change the Dec when no coordinate is associated with this object then
447a new Astro::Coords object will be created (with a
448Dec of 0.0).
449
450Dec accepted by this method must be in sexagesimal format, space or
451colon-separated. Returns a space-separated sexagesimal number
452with a leading sign.
453
454=cut
455
456sub dec {
457    my $self = shift;
458    if (@_) {
459        my $dec = shift;
460
461        # Issue a warning specifically for this call
462        my @info = caller();
463        warnings::warnif("deprecated","Use of ra() method for setting RA now deprecated. Please use the coords() method instead, at $info[1] line $info[2]");
464
465        # Get the coordinate object
466        my $c = $self->coords;
467        if (defined $c) {
468            # Need to tweak RA?
469            croak "Can only adjust Dec with Astro::Coords::Equatorial coordinates"
470                unless $c->isa("Astro::Coords::Equatorial");
471
472            # For now need to kluge since Astro::Coords does not allow
473            # you to change the position (it is an immutable object)
474            $c = $c->new(
475                type => 'J2000',
476                ra => $c->ra(format => 's'),
477                dec => $dec,
478            );
479
480        }
481        else {
482            $c = new Astro::Coords(
483                type => 'J2000',
484                dec => $dec,
485                ra => 0,
486            );
487        }
488
489        # Update the object
490        $self->coords($c);
491    }
492
493    my $outc = $self->coords;
494    return unless defined $outc;
495
496    # Astro::Coords inserts colons by default. Grab the old delimiter
497    # and number of decimal places if we're using a recent enough
498    # version of Astro::Coords.
499    my $dec = $outc->dec;
500    if (UNIVERSAL::isa($dec, "Astro::Catalog::Angle")) {
501        $dec->str_delim(' ');
502        $dec->str_ndp(2);
503        $dec = "$dec";
504        $dec = (substr($dec, 0, 1) eq '-' ? '' : '+') . $dec;
505        return $dec;
506    }
507    else {
508        my $outdec = $outc->dec(format => 's');
509        $outdec =~ s/:/ /g;
510        $outdec =~ s/^\s*//;
511
512        # require leading sign for backwards compatibility
513        # Sign will be there for negative
514        $outdec = (substr($outdec, 0, 1) eq '-' ? '' : '+') . $outdec;
515
516        return $outdec;
517    }
518}
519
520=item B<fluxes>
521
522Return or set the flux measurements of the star as an C<Astro::Fluxes>
523object.
524
525    $f = $star->fluxes();
526    $star->fluxes($f);
527
528    $star->fluxes($f, 1);  # will replace instead of appending
529
530
531The object returned by this method is the actual object stored
532inside this Item object and not a clone. If the flux values
533are changed through this object the flu values of the star is
534also changed.
535
536If an optional flag is passed as set to the routine it will replace
537instead of appending (default action) to an existing fluxes object
538in the catalogue.
539
540Returns undef if the fluxes have never been specified.
541
542=cut
543
544sub fluxes {
545    my $self = shift;
546    if (@_) {
547        my $flux = shift;
548        my $flag = shift;
549        croak "Flux must be an Astro::Fluxes object"
550            unless UNIVERSAL::isa($flux, "Astro::Fluxes");
551
552        if (defined $self->{FLUXES}) {
553            if (defined $flag) {
554                $self->{FLUXES} = $flux;
555            }
556            else {
557                $self->{FLUXES}->merge( $flux );
558            }
559        }
560        else {
561            $self->{FLUXES} = $flux;
562        }
563    }
564    return $self->{FLUXES};
565}
566
567=item B<what_filters>
568
569Returns a list of the wavebands for which the object has defined values.
570
571    @filters = $star->what_filters();
572    $num = $star->what_filters();
573
574if called in a scalar context it will return the number of filters which
575have defined magnitudes in the object. It will included 'derived' values,
576see C<Astro::Flux> for details.
577
578=cut
579
580sub what_filters {
581    my $self = shift;
582
583    my $fluxes = $self->{FLUXES};
584
585    my @mags = $fluxes->original_wavebands('filters') if defined $fluxes;
586
587    # return array of filters or number if called in scalar context
588    return wantarray ? @mags : scalar(@mags);
589}
590
591=item B<what_colours>
592
593Returns a list of the colours for which the object has defined values.
594
595    @colours = $star->what_colours();
596    $num = $star->what_colours();
597
598if called in a scalar context it will return the number of colours which
599have defined values in the object.
600
601=cut
602
603sub what_colours {
604    my $self = shift;
605
606    my $fluxes = $self->{FLUXES};
607    my @cols = $fluxes->original_colors() if defined $fluxes;
608
609    # return array of colours or number if called in scalar context
610    return wantarray ? @cols : scalar(@cols);
611}
612
613=item B<get_magnitude>
614
615Returns the magnitude for the supplied filter if available
616
617    $magnitude = $star->get_magnitude('B');
618
619=cut
620
621sub get_magnitude {
622  my $self = shift;
623
624  my $magnitude;
625  if (@_) {
626     # grab passed filter
627     my $filter = shift;
628     my $fluxes = $self->{FLUXES};
629     $magnitude = $fluxes->flux(
630        waveband => $filter,
631        type => $self->preferred_magnitude_type);
632
633     if (defined($magnitude)) {
634       return $magnitude->quantity($self->preferred_magnitude_type);
635     }
636     else {
637       return undef;
638     }
639  }
640}
641
642=item B<get_flux_quantity>
643
644Returns the flux quantity for the given waveband and flux type.
645
646    my $flux = $star->get_flux_quantity(
647        waveband => 'B',
648        type => 'mag');
649
650The arguments are passed as a hash. The value for the waveband
651argument can be either a string describing a filter or an
652Astro::WaveBand object. The value for the flux type is
653case-insensitive.
654
655Returns a scalar.
656
657=cut
658
659sub get_flux_quantity {
660    my $self = shift;
661    my %args = @_;
662
663    unless (defined $args{'waveband'}) {
664        croak "Must supply waveband to Astro::Catalog::Item->get_flux_quantity()";
665    }
666    unless (defined $args{'type'}) {
667        croak "Must supply flux type to Astro::Catalog::Item->get_flux_quantity()";
668    }
669
670    my $waveband;
671    unless (UNIVERSAL::isa($args{'waveband'}, "Astro::WaveBand")) {
672        $waveband = new Astro::WaveBand(Filter => $args{'waveband'});
673    }
674    else {
675        $waveband = $args{'waveband'};
676    }
677
678    my $fluxes = $self->fluxes;
679    if (defined $fluxes) {
680        my $flux = $fluxes->flux(waveband => $waveband, type => $args{'type'});
681        if (defined $flux) {
682            return $flux->quantity($args{'type'} );
683        }
684    }
685    return undef;
686}
687
688=item B<get_errors>
689
690Returns the error in the magnitude value for the supplied filter if available
691
692    $mag_errors = $star->get_errors('B');
693
694=cut
695
696sub get_errors {
697    my $self = shift;
698
699    my $mag_error;
700    if (@_) {
701        # grab passed filter
702        my $filter = shift;
703        my $fluxes = $self->{FLUXES};
704        my $magnitude = $fluxes->flux(
705            waveband => $filter,
706            type => $self->preferred_magnitude_type);
707        if (defined $magnitude) {
708            return $magnitude->error( $self->preferred_magnitude_type );
709        }
710        else {
711            return undef;
712        }
713    }
714    return $mag_error;
715}
716
717=item B<get_flux_error>
718
719Returns the flux error for the given waveband and flux type.
720
721    my $flux = $star->get_flux_error(
722        waveband => 'B',
723        type => 'mag');
724
725The arguments are passed as a hash. The value for the waveband
726argument can be either a string describing a filter or an
727Astro::WaveBand object. The value for the flux type is
728case-insensitive.
729
730Returns a scalar.
731
732=cut
733
734sub get_flux_error {
735    my $self = shift;
736    my %args = @_;
737
738    unless (defined $args{'waveband'}) {
739        croak "Must supply waveband to Astro::Catalog::Item->get_flux_error()";
740    }
741    unless(defined $args{'type'}) {
742        croak "Must supply flux type to Astro::Catalog::Item->get_flux_error()";
743    }
744
745    my $waveband;
746    unless (UNIVERSAL::isa($args{'waveband'}, "Astro::WaveBand")) {
747        $waveband = new Astro::WaveBand(Filter => $args{'waveband'});
748    }
749    else {
750        $waveband = $args{'waveband'};
751    }
752    my $fluxes = $self->fluxes;
753    if (defined $fluxes) {
754        my $flux = $fluxes->flux(waveband => $waveband, type => $args{'type'});
755        if (defined $flux) {
756            return $flux->error($args{'type'});
757        }
758    }
759    return undef;
760}
761
762=item B<get_colour>
763
764Returns the value of the supplied colour if available
765
766    $colour = $star->get_colour('B-V');
767
768=cut
769
770sub get_colour {
771    my $self = shift;
772
773    my $value;
774    if (@_) {
775        # grab passed colour
776        my $colour = shift;
777        my @filters = split "-", $colour;
778        my $fluxes = $self->{FLUXES};
779        my $color = $fluxes->color(
780            upper => new Astro::WaveBand(Filter => $filters[0]),
781            lower => new Astro::WaveBand(Filter => $filters[1]));
782        $value = $color->quantity('mag');
783    }
784    return $value;
785}
786
787=item B<get_colourerror>
788
789Returns the error in the colour value for the supplied colour if available
790
791    $col_errors = $star->get_colourerr('B-V');
792
793=cut
794
795sub get_colourerr {
796    my $self = shift;
797
798    my $col_error;
799    if (@_) {
800        # grab passed colour
801        my $colour = shift;
802        my @filters = split "-", $colour;
803        my $fluxes = $self->{FLUXES};
804        my $color = $fluxes->color(
805            upper => new Astro::WaveBand(Filter => $filters[0]),
806            lower => new Astro::WaveBand(Filter => $filters[1]));
807
808        $col_error = $color->error('mag');
809    }
810    return $col_error;
811}
812
813=item B<preferred_magnitude_type>
814
815Get or set the preferred magnitude type to be returned from the get_magnitude method.
816
817    my $type = $item->preferred_magnitude_type;
818    $item->preferred_magnitude_type('MAG_ISO');
819
820Defaults to 'MAG'.
821
822=cut
823
824sub preferred_magnitude_type {
825    my $self = shift;
826    if (@_) {
827        my $type = shift;
828        $self->{PREFERRED_MAG_TYPE} = $type;
829    }
830
831    unless (defined $self->{PREFERRED_MAG_TYPE}) {
832        $self->{PREFERRED_MAG_TYPE} = 'MAG';
833    }
834
835    return $self->{PREFERRED_MAG_TYPE};
836}
837
838=item B<morphology>
839
840Get or set the morphology of the star as an C<Astro::Catalog::Item::Morphology>
841object.
842
843    $star->morphology($morphology);
844
845The object returned by this method is the actual object stored
846inside this Star object and not a clone. If the morphology
847is changed through this object the morphology of the star is
848also changed.
849
850=cut
851
852sub morphology {
853    my $self = shift;
854    if (@_) {
855        my $m = shift;
856        croak "Morphology must be an Astro::Catalog::Item::Morphology object"
857            unless UNIVERSAL::isa($m, "Astro::Catalog::Item::Morphology");
858
859        # Store the new coordinate object
860        # Storing it late stops looping from the id and comment methods
861        $self->{MORPHOLOGY} = $m;
862    }
863    return $self->{MORPHOLOGY};
864}
865
866=item B<quality>
867
868Return (or set) the quality flag of the star
869
870    $quality = $star->quailty();
871    $star->quality(0);
872
873for example for the USNO-A2 catalogue, 0 denotes good quality, and 1
874denotes a possible problem object. In the generic case any flag value,
875including a boolean, could be used.
876
877These quality flags are standardised sybolically across catalogues and
878have the following definitions:
879
880    STARGOOD
881    STARBAD
882
883TBD. Need to provide quality constants and mapping to and from these
884constants on catalog I/O.
885
886=cut
887
888sub quality {
889    my $self = shift;
890    if (@_) {
891
892        # 2MASS hack
893        # ----------
894        # quick, dirty and ultimately icky hack. The entire quality flag
895        # code is going to have to be rewritten so it works like mag errors,
896        # and gets assocaited with a magnitude. For now, if the JHK QFlag
897        # for 2MASS is A,B or C then the internal quality flag is 0 (good),
898        # otherwise it gets set to 1 (bad). This pretty much sucks.
899
900        # Yes Tim, I know I'm doing this in the wrong place. I'm panicing
901        # I'll fix it later. I've moved the Cluster specific hack about the
902        # star ID's out of Astro::Catalog::query::USNOA2 and into the Cluster
903        # IO module and used Scalar::Util to figure out whether I've got a
904        # number (neat solution) before blowing it away.
905
906        # Anyway...
907        my $quality = shift;
908
909        # Shouldn't happen?
910        unless (defined $quality) {
911            $self->{QUALITY} = undef;
912            return undef;
913        }
914
915        if ($quality =~ /^[A-Z][A-Z][A-Z]$/) {
916            $_ = $quality;
917            m/^([A-Z])([A-Z])([A-Z])$/;
918
919            my $j_quality = $1;
920            my $h_quality = $2;
921            my $k_quality = $3;
922
923            if (($j_quality eq 'A' || $j_quality eq 'B' || $j_quality eq 'C') &&
924                    ($h_quality eq 'A' || $h_quality eq 'B' || $h_quality eq 'C')) {
925                # good quality
926                $self->{QUALITY} = 0;
927            }
928            else {
929                # bad quality
930                $self->{QUALITY} = 1;
931            }
932        }
933        else {
934            $self->{QUALITY} = $quality;
935        }
936
937    }
938    return $self->{QUALITY};
939}
940
941=item B<field>
942
943Return (or set) the field parameter for the star
944
945    $field = $star->field();
946    $star->field('0080');
947
948=cut
949
950sub field {
951    my $self = shift;
952    if (@_) {
953        $self->{FIELD} = shift;
954    }
955    return $self->{FIELD};
956}
957
958=item B<gsc>
959
960Return (or set) the GSC flag for the object
961
962    $gsc = $star->gsc();
963    $star->gsc( 'TRUE' );
964
965the flag is TRUE if the object is known to be in the Guide Star Catalogue,
966and FALSE otherwise.
967
968=cut
969
970sub gsc {
971    my $self = shift;
972    if (@_) {
973        $self->{GSC} = shift;
974    }
975    return $self->{GSC};
976}
977
978=item B<distance>
979
980Return (or set) the distance from the field centre
981
982    $distance = $star->distance();
983    $star->distance('0.009');
984
985e.g. for the USNO-A2 catalogue.
986
987=cut
988
989sub distance {
990    my $self = shift;
991    if (@_) {
992        $self->{DISTANCE} = shift;
993    }
994    return $self->{DISTANCE};
995}
996
997=item B<posangle>
998
999Return (or set) the position angle from the field centre
1000
1001    $position_angle = $star->posangle();
1002    $star->posangle('50.761');
1003
1004e.g. for the USNO-A2 catalogue.
1005
1006=cut
1007
1008sub posangle {
1009    my $self = shift;
1010    if (@_) {
1011        $self->{POSANGLE} = shift;
1012    }
1013    return $self->{POSANGLE};
1014}
1015
1016=item B<x>
1017
1018Return (or set) the X pixel co-ordinate of the star
1019
1020    $x = $star->x();
1021    $star->id($x);
1022
1023=cut
1024
1025sub x {
1026    my $self = shift;
1027    if (@_) {
1028        $self->{X} = shift;
1029    }
1030
1031    if (! defined($self->{X}) &&
1032            defined($self->wcs) &&
1033            defined($self->coords)) {
1034
1035        # We need to get a template FK5 SkyFrame to be able to convert
1036        # properly between RA/Dec and X/Y, but we can only do this if
1037        # we load Starlink::AST. So that we don't have a major dependency
1038        # on that module, load it here at runtime.
1039        eval {require Starlink::AST;};
1040        if ($@) {
1041            croak "Attempted to convert from RA/Dec to X position and cannot load Starlink::AST. Error: $@";
1042        }
1043        my $template = new Starlink::AST::SkyFrame("System=FK5");
1044        my $wcs = $self->wcs;
1045        my $frameset = $wcs->FindFrame($template, "");
1046        unless (defined $frameset) {
1047            croak "Could not find FK5 SkyFrame to do RA/Dec to X position translation";
1048        }
1049        my ($ra, $dec) = $self->coords->radec();
1050        my ($x, $y) = $frameset->Tran2(
1051            [$ra->radians],
1052            [$dec->radians],
1053            0);
1054        $self->{X} = $x->[0];
1055    }
1056    return $self->{X};
1057}
1058
1059=item B<y>
1060
1061Return (or set) the Y pixel co-ordinate of the star
1062
1063    $y = $star->y();
1064    $star->id($y);
1065
1066=cut
1067
1068sub y {
1069    my $self = shift;
1070    if (@_) {
1071        $self->{Y} = shift;
1072    }
1073
1074    if (! defined($self->{Y}) &&
1075            defined($self->wcs) &&
1076            defined($self->coords)) {
1077
1078        # We need to get a template FK5 SkyFrame to be able to convert
1079        # properly between RA/Dec and X/Y, but we can only do this if
1080        # we load Starlink::AST. So that we don't have a major dependency
1081        # on that module, load it here at runtime.
1082        eval {require Starlink::AST;};
1083        if ($@) {
1084            croak "Attempted to convert from RA/Dec to Y position and cannot load Starlink::AST. Error: $@";
1085        }
1086        my $template = new Starlink::AST::SkyFrame("System=FK5");
1087        my $wcs = $self->wcs;
1088        my $frameset = $wcs->FindFrame($template, "");
1089        unless (defined $frameset) {
1090            croak "Could not find FK5 SkyFrame to do RA/Dec to Y position translation";
1091        }
1092        my ($ra, $dec) = $self->coords->radec();
1093        my ($x, $y) = $frameset->Tran2(
1094            [$ra->radians],
1095            [$dec->radians],
1096            0);
1097        $self->{Y} = $y->[0];
1098    }
1099
1100    return $self->{Y};
1101}
1102
1103=item B<wcs>
1104
1105Return (or set) the WCS associated with the star.
1106
1107    $wcs = $star->wcs;
1108    $star->wcs($wcs);
1109
1110The WCS is a C<Starlink::AST> object.
1111
1112=cut
1113
1114sub wcs {
1115    my $self = shift;
1116    if (@_) {
1117        my $wcs = shift;
1118        unless (defined $wcs) {
1119            $self->{WCS} = undef;
1120        }
1121        elsif (UNIVERSAL::isa($wcs, "Starlink::AST")) {
1122            $self->{WCS} = $wcs;
1123        }
1124    }
1125    return $self->{WCS};
1126}
1127
1128=item B<comment>
1129
1130Return (or set) a comment associated with the star
1131
1132    $comment = $star->comment();
1133    $star->comment($comment_string);
1134
1135The comment is propogated to the underlying coordinate
1136object (if one is present) if the comment is updated.
1137
1138=cut
1139
1140sub comment {
1141    my $self = shift;
1142    if (@_) {
1143        $self->{COMMENT} = shift;
1144
1145        my $c = $self->coords;
1146        $c->comment($self->{COMMENT}) if defined $c;
1147    }
1148    return $self->{COMMENT};
1149}
1150
1151=item B<spectype>
1152
1153The spectral type of the Star.
1154
1155    $spec = $star->spectype;
1156
1157=cut
1158
1159sub spectype {
1160    my $self = shift;
1161    if (@_) {
1162        $self->{SPECTYPE} = shift;
1163    }
1164    return $self->{SPECTYPE};
1165}
1166
1167=item B<startype>
1168
1169The type of star. Usually uses the Simbad abbreviation.
1170eg. '*' for a star, 'rG' for a Radio Galaxy.
1171
1172    $type = $star->startype;
1173
1174See also C<longstartype> for the expanded version of this type.
1175
1176=cut
1177
1178sub startype {
1179    my $self = shift;
1180    if (@_) {
1181        $self->{STARTYPE} = shift;
1182    }
1183    return $self->{STARTYPE};
1184}
1185
1186=item B<longstartype>
1187
1188The full description of the type of star. Usually uses the Simbad text.
1189If no text has been provided, a lookup will be performed using the
1190abbreviated C<startype>.
1191
1192    $long = $star->longstartype;
1193    $star->longstartype("A variable star");
1194
1195See also C<longstartype> for the expanded version of this type.
1196
1197=cut
1198
1199sub longstartype {
1200    my $self = shift;
1201    if (@_) {
1202        $self->{LONGTYPE} = shift;
1203    }
1204    # if we have nothing, attempt a look up
1205    if (! defined $self->{LONGTYPE} && defined $self->startype
1206            && exists $STAR_TYPE_LOOKUP{$self->startype}) {
1207        return $STAR_TYPE_LOOKUP{$self->startype};
1208    }
1209    else {
1210        return $self->{STARTYPE};
1211    }
1212}
1213
1214=item B<moreinfo>
1215
1216A link (URL) to more information on the star in question. For example
1217this might provide a direct link to the full Simbad description.
1218
1219    $url = $star->moreinfo;
1220
1221=cut
1222
1223sub moreinfo {
1224    my $self = shift;
1225    if (@_) {
1226        $self->{MOREINFO} = shift;
1227    }
1228    return $self->{MOREINFO};
1229}
1230
1231=item B<insertdate>
1232
1233The time the information for the star in question was gathered. This
1234is different from the time of observation of the star.
1235
1236    $insertdate = $star->insertdate;
1237
1238This is a C<Time::Piece> object.
1239
1240=cut
1241
1242sub insertdate {
1243    my $self = shift;
1244    if (@_) {
1245        $self->{INSERTDATE} = shift;
1246    }
1247    return $self->{INSERTDATE};
1248}
1249
1250
1251=item B<fluxdatestamp>
1252
1253Apply a datestamp to all the C<Astro::Flux> objects inside the
1254C<Astro::Fluxes> object contained within this object
1255
1256    $star->fluxdatestamp(new DateTime());
1257
1258this is different from the time for which the inormation about the
1259star was gathered, see the insertdate() method call, and is the
1260time of observation of the object.
1261
1262=cut
1263
1264sub fluxdatestamp {
1265    my $self = shift;
1266    if (@_) {
1267        my $datetime = shift;
1268        croak "Astro::Catalog::Item::fluxdatestamp()\n".
1269            "Error: Not a DateTime object\n"
1270            unless UNIVERSAL::isa($datetime, "DateTime");
1271        $self->{FLUXES}->datestamp($datetime);
1272    }
1273    return $self->{FLUXES};
1274}
1275
1276
1277=item B<distancetostar>
1278
1279The distance from another Item,
1280
1281    my $distance1 = $star->distancetostar($star2);
1282
1283returns a tangent plane separation value in arcsec. Returns undef if
1284the star is too far away.
1285
1286=cut
1287
1288sub distancetostar {
1289    my $self = shift;
1290    my $other = shift;
1291
1292    croak "Astro::Catalog::Item::distancetostar()\n".
1293        "Error: Not an Astro::Catalog::Item object\n"
1294        unless UNIVERSAL::isa($other, "Astro::Catalog::Item");
1295
1296    my $sep = $self->coords->distance($other->coords);
1297    return (defined $sep ? $sep->arcsec : $sep);
1298}
1299
1300
1301=item B<within>
1302
1303Check if the passed star is within $distance_in_arcsec of the object.
1304
1305    my $status = $star->within($star2, $distance_in_arcsec);
1306
1307returns true if this is the case.
1308
1309=cut
1310
1311sub within {
1312    my $self = shift;
1313    my $other = shift;
1314    my $max = shift;
1315
1316    croak "Astro::Catalog::Item::within()\n".
1317        "Error: Not an Astro::Catalog::Item object\n"
1318        unless UNIVERSAL::isa( $other, "Astro::Catalog::Item" );
1319
1320    my $distance = $self->distancetostar($other);
1321    return 1 if $distance < $max;
1322    return 0;
1323}
1324
1325
1326=item B<misc>
1327
1328A hold-all method to contain information not covered by other methods.
1329
1330    my $misc = $item->misc;
1331    $item->misc($misc);
1332
1333This accessor can hold any type of variable, although it is
1334recommended that a hash reference is used for easier lookups:
1335
1336    my $misc = $item->misc;
1337    my $vrad = $misc->{'vrad'};
1338    my $vopt = $misc->{'vopt'}
1339
1340=cut
1341
1342sub misc {
1343    my $self = shift;
1344    if (@_) {
1345        $self->{'MISC'} = shift;
1346    }
1347    return $self->{'MISC'};
1348}
1349
1350=back
1351
1352=head2 Obsolete Methods
1353
1354Several methods were made obsolete with the introduction of V4 of the
1355Astro::Catalog class. These were magnitudes(), magerr(), colours() and
1356colerr(). The functionality these supported is now part of the addfluxes()
1357method.
1358
1359=cut
1360
1361sub magnitudes {
1362    my $self = shift;
1363    croak "Astro::Catalog::Item::magnitudes()\n" .
1364        "This method is no longer supported, use fluxes() instead.\n";
1365}
1366
1367sub magerr {
1368    my $self = shift;
1369    croak "Astro::Catalog::Item::magerr()\n" .
1370        "This method is no longer supported, use fluxes() instead.\n";
1371}
1372
1373
1374sub colours {
1375    my $self = shift;
1376    croak "Astro::Catalog::Item::colours()\n" .
1377        "This method is no longer supported, use fluxes() instead.\n";
1378
1379}
1380
1381sub colerr {
1382    my $self = shift;
1383    croak "Astro::Catalog::Item::colerr()\n" .
1384        "This method is no longer supported, use fluxes() instead.\n";
1385
1386}
1387
1388=head2 General Methods
1389
1390=over 4
1391
1392=item B<configure>
1393
1394Configures the object from multiple pieces of information.
1395
1396    $star->configure(%options);
1397
1398Takes a hash as argument with the list of keywords.
1399The keys are not case-sensitive and map to accessor methods.
1400
1401Note that RA and Dec keys are allowed. The values can be supplied in either sexagesimal or decimal degrees.
1402
1403=cut
1404
1405sub configure {
1406    my $self = shift;
1407
1408    # return unless we have arguments
1409    return unless @_;
1410
1411    # grab the argument list
1412    my %args = @_;
1413
1414    # First check for duplicate keys (case insensitive) with different
1415    # values and store the unique lower-cased keys
1416    my %check;
1417    for my $key (keys %args) {
1418        my $lckey = lc($key);
1419        if (exists $check{$lckey} && $check{$lckey} ne $args{$key}) {
1420            warnings::warnif("Duplicated key in constructor [$lckey] with differing values ".
1421                    " '$check{$lckey}' and '$args{$key}'\n");
1422        }
1423        $check{$lckey} = $args{$key};
1424    }
1425
1426    # Now that we have lower cased keys we can look to see if we have
1427    # ra & dec as well as coords and also verify that they are actually
1428    # the same if we have them
1429    if (exists $check{coords} && (exists $check{ra} || exists $check{dec})) {
1430        # coords + one of ra or dec is a mistake
1431        if (exists $check{ra} && exists $check{dec}) {
1432            # Create a new coords object - assume J2000
1433            my $c = new Astro::Coords(
1434                type => 'J2000',
1435                ra => $check{ra},
1436                dec => $check{dec},
1437                # units => 'sex',
1438            );
1439
1440            # Make sure we have the same reference place and time
1441            $c->datetime($check{coords}->datetime)
1442                if $check{coords}->has_datetime;
1443            $c->telescope($check{coords}->telescope)
1444                if defined $check{coords}->telescope;
1445
1446
1447            # Check the distance
1448            my $d = $c->distance($check{coords});
1449
1450            # Raise warn if the error is more than 1 arcsecond
1451            warnings::warnif( "Coords and RA/Dec were specified and they differ by more than 1 arcsec [".
1452                    (defined $d ? $d->arcsec : "<undef>")
1453                    ." sec]. Ignoring RA/Dec keys.\n")
1454                if (!defined $d || $d->arcsec > 1.0);
1455
1456        }
1457        elsif (! exists $check{ra}) {
1458            warnings::warnif("Dec specified in addition to Coords but without RA. Ignoring it.");
1459        }
1460        elsif (! exists $check{dec}) {
1461            warnings::warnif("RA specified in addition to Coords but without Dec. Ignoring it.");
1462        }
1463
1464        # Whatever happens we do not want ra and dec here
1465        delete $check{dec};
1466        delete $check{ra};
1467    }
1468    elsif (exists $check{ra} || $check{dec}) {
1469        # Generate a Astro::Coords object here in one go rather than
1470        # relying on the old ra() dec() methods individually
1471        my $ra = $check{ra} || 0.0;
1472        my $dec = $check{dec} || 0.0;
1473        $check{coords} = new Astro::Coords(
1474            type => 'J2000',
1475            ra => $ra,
1476            dec => $dec);
1477        delete $check{ra};
1478        delete $check{dec};
1479    }
1480
1481    # Loop over the allowed keys storing the values
1482    # in the object if they exist. Case insensitive.
1483    for my $key (keys %check) {
1484        my $method = lc($key);
1485        $self->$method($check{$key}) if $self->can($method);
1486    }
1487    return;
1488}
1489
14901;
1491
1492__END__
1493
1494=back
1495
1496=head1 COPYRIGHT
1497
1498Copyright (C) 2001-2002 University of Exeter. All Rights Reserved.
1499Some modification are Copyright (C) 2003 Particle Physics and
1500Astronomy Research Council. All Rights Reserved.
1501
1502This program was written as part of the eSTAR project and is free software;
1503you can redistribute it and/or modify it under the terms of the GNU Public
1504License.
1505
1506=head1 AUTHORS
1507
1508Alasdair Allan E<lt>aa@astro.ex.ac.ukE<gt>,
1509Tim Jenness E<lt>tjenness@cpan.orgE<gt>,
1510
1511=cut
1512