Astro-Catalog

 view release on metacpan or  search on metacpan

lib/Astro/Catalog/IO/JCMT.pm  view on Meta::CPAN

Supported options (with defaults) are:

=over 4

=item incheader

Add a comment header to the start of the catalog.  [default: true]

=item removeduplicates

Check for duplicates.  Remove if the coordinates match.  Add suffix
to disambiguate otherwise.  [default: true]

=back

=cut

sub _write_catalog {
    my $class = shift;
    my $cat = shift;

    # Default options
    my %defaults = (
        incheader => 1,
        removeduplicates => 1,
    );

    my %options = (%defaults, @_);

    # Would make more sense to use the array ref here
    my @sources = $cat->stars;

    # Counter for unknown targets
    my $unk = 1;

    # Hash for storing target information
    # so that we can search for duplicates
    my %targets;

    # Create hash of all unique target names present
    # after cleaning. We need this so that we can make sure
    # a generated name derived from a duplication (with target mismatch)
    # does not generate a name that already existed explicitly.
    my %allnames = map {$class->clean_target_name($_->coords->name), undef}
        @sources;

    # Loop over each source and extract catalog information
    # Make sure that we remove unique entries
    # BUT THAT WE RETAIN THE ORDER OF THE SOURCES IN THE CATALOG
    # Hence an array for the information
    my @processed;
    for my $star (@sources) {
        # Extract the coordinate object
        my $src = $star->coords;

        # Get the name but do not deal with undef yet
        # in case the type is not valid
        my $name = $src->name;

        # Somewhere to store the extracted information
        my %srcdata;

        # Store the name (stripped of spaces) and
        # treat srcdata{name} as the primary name from here on
        $srcdata{name} = $class->clean_target_name($name);

        # Store a comment
        $srcdata{comment} = $star->comment;

        # prepopulate the default velocity settings
        $srcdata{rv}    = 'n/a';
        $srcdata{vdefn}  = 'RADIO';
        $srcdata{vframe} = 'LSR';

        # Default proper motion and parallax.
        $srcdata{'pm1'} = 'n/a';
        $srcdata{'pm2'} = 'n/a';
        $srcdata{'parallax'} = 'n/a';

        # Get the miscellaneous data.
        my $misc = $star->misc;
        if (defined $misc) {
            $srcdata{vrange} = ((defined $misc->{'velocity_range'})
                ? sprintf("%s", $misc->{'velocity_range'})
                : "n/a");
            $srcdata{flux850} = ((defined $misc->{'flux850'})
                ?  sprintf("%s", $misc->{'flux850'})
                : "n/a" );
        }
        else {
            $srcdata{vrange} = "n/a";
            $srcdata{flux850} = "n/a";
        }

        foreach (qw/_jcmt_com_before _jcmt_com_after/) {
            $srcdata{$_} = $misc->{$_} if exists $misc->{$_};
        }

        # Get the type of source
        my $type = $src->type;
        if ($type eq 'RADEC') {
            $srcdata{system} = "RJ";

            # Need to get the space separated RA/Dec and the sign
            $srcdata{long} = $src->ra2000(format => 'array');
            $srcdata{lat} = $src->dec2000(format => 'array');

            # Get the velocity information
            my $rv = $src->rv;
            if ($rv) {
                $srcdata{rv}    = $rv;
                $srcdata{vdefn}  = $src->vdefn;
                $srcdata{vframe} = $src->vframe;

                # JCMT compatibility
                $srcdata{vframe} = "LSR" if $srcdata{vframe} eq 'LSRK';

            }

            my $parallax = $src->parallax;
            my @pm = $src->pm;
            if (scalar @pm) {
                if (not $parallax) {
                    my $errname = (defined $srcdata{name} ? $srcdata{name} : "<undefined>");
                    warnings::warnif "Proper motion for target $errname specified without parallax";
                }
                $srcdata{'pm1'} = $pm[0] * 1000.0;
                $srcdata{'pm2'} = $pm[1] * 1000.0;
            }
            if ($parallax) {
                $srcdata{'parallax'} = $parallax * 1000.0;
            }

        }
        elsif ($type eq 'PLANET') {
            # Planets are not supported in catalog form. Skip them
            next;

        }
        elsif ($type eq 'FIXED') {
            $srcdata{system} = "AZ";

            $srcdata{long} = $src->az(format => 'array');
            $srcdata{lat} = $src->el(format => 'array');

            # Need to remove + sign from long/AZ since we are not expecting
            # it in RA/DEC. This is probably a bug in Astro::Coords
            shift(@{$srcdata{long}}) if $srcdata{long}->[0] eq '+';

        }
        else {
            my $errname = (defined $srcdata{name} ? $srcdata{name} : "<undefined>");
            warnings::warnif "Coordinate of type $type for target $errname not supported in JCMT catalog files\n";
            next;
        }

        # Generate a name if not defined
        if (!defined $srcdata{name}) {
            $srcdata{name} = "UNKNOWN$unk";
            $unk++;
        }

        # See if we already have this source and that it is really the
        # same source Note that we do not see whether this name is the
        # same as one of the derived names. Eg if CRL618 is in the
        # pointing catalog 3 times with identical coords and we add a
        # new CRL618 with different coords then we trigger 3 warning
        # messages rather than 1 because we do not check that CRL618_2 is
        # the same as CRL618_1

        # Note that velocity specification is included in this comparison

        if ($options{'removeduplicates'} and exists $targets{$srcdata{name}}) {
            my $previous = $targets{$srcdata{name}};

            # Create stringified form of previous coordinate with same name
            # and current coordinate
            my $prevcoords = join(" ",@{$previous->{long}},@{$previous->{lat}},
                    $previous->{rv}, $previous->{vdefn}, $previous->{vframe});
            my $curcoords = join(" ",@{$srcdata{long}},@{$srcdata{lat}},
                    $srcdata{rv}, $srcdata{vdefn}, $srcdata{vframe});

            if ($prevcoords eq $curcoords) {
                # This is the same target so we can ignore it
            }
            else {
                # Make up a new name. Use a counter for this.
                my $oldname = $srcdata{name};

                # loop for 100 times
                my $count;
                while (1) {
                    # protection loop
                    $count++;

                    # Try to construct a new name based on the counter.
                    my $suffix = "_$count";

                    # Abort if we have gone round too many times
                    if ($count > 100) {
                        $srcdata{name} = substr($oldname, 0, int(MAX_SRC_LENGTH / 2)) .
                            int(rand(10000) + 1000);
                        warn "Uncontrollable looping (or unfeasibly large number of duplicate sources with different coordinates). Panicked and generated random source name of $srcdata{name}\n";
                        last;
                    }

                    # Assume the old name will do fine
                    my $root = $oldname;

                    # Do not want to truncate the _XX off the end later on
                    if (length($oldname) > MAX_SRC_LENGTH - length($suffix)) {
                        # This may well be confusing but we have no choice. Since
                        # _XX is unique the only time we will get a name clash by
                        # simply chopping the string is if we have a duplicate
                        # that is too long along with a target name that includes
                        # _XX amd matches the truncated source name!
                        $root = substr($oldname, 0, (MAX_SRC_LENGTH - length($suffix)) );
                    }

                    # Form the new name
                    my $newname = $root . $suffix;

                    # check to see if this name is in the existing target list
                    unless ((exists $allnames{$newname}) or (exists $targets{$newname})) {
                        # Store it in the targets array and exit loop
                        $srcdata{name} = $newname;
                        last;
                    }
                }

                # different target
                warn "Found target with the same name [$oldname] but with different coordinates, renaming it to $srcdata{name}\n";

                $targets{$srcdata{name}} = \%srcdata;

                # Store it in the array
                push @processed, \%srcdata;
            }
        }
        else {
            # Store in hash for easy lookup for duplicates
            $targets{$srcdata{name}} = \%srcdata;

            # Store it in the array
            push @processed, \%srcdata;
        }
    }

    # Output array for new catalog lines
    my @lines;

    if ($options{'incheader'}) {
        # Write a header
        push @lines, "*\n";
        push @lines, "* Catalog written automatically by class ". __PACKAGE__ ."\n";
        push @lines, "* on date " . gmtime . "UT\n";
        push @lines, "* Origin of catalog: ". $cat->origin ."\n";
        push @lines, "*\n";
    }

    # Now need to go through the targets and write them to disk
    for my $src (@processed) {
        if (exists $src->{'_jcmt_com_before'}) {
            push @lines, '*' . $_ foreach @{$src->{'_jcmt_com_before'}};
        }

        my $name    = $src->{name};
        my $long    = $src->{long};
        my $lat     = $src->{lat};
        my $system  = $src->{system};
        my $comment = $src->{comment};
        my $rv      = $src->{rv};
        my $vdefn   = $src->{vdefn};
        my $vframe  = $src->{vframe};
        my $vrange  = $src->{vrange};
        my $flux850 = $src->{flux850};
        my $pm1     = $src->{'pm1'};
        my $pm2     = $src->{'pm2'};
        my $px      = $src->{'parallax'};

        $comment = '' unless defined $comment;

        # Velocity can not easily be done with a sprintf since it can be either
        # a string or a 2 column number
        $rv  = _format_value($rv, '%6.1f', '  n/a   ', 1);

        # Similarly format proper motion and parallax.
        $pm1 = _format_value($pm1, '%8.3f', '   n/a    ', 1);
        $pm2 = _format_value($pm2, '%8.3f', '   n/a    ', 1);
        $px  = _format_value($px,  '%8.4f', '  n/a   ', 0);

        # Name must be limited to MAX_SRC_LENGTH characters
        # [this should be taken care of by clean_target_name but
        # if we have appended _X....
        $name = substr($name,0,MAX_SRC_LENGTH);

        # Maybe shift flux by 1 space to align decimal point in
        # 1dp values with that in 2dp values and also middle of n/a.
        $flux850 .= ' ' if $flux850 =~ /(?:\.\d|n\/a)$/ and 5 > length $flux850;

        push @lines, sprintf(
            "%-" . MAX_SRC_LENGTH .  "s %02d %02d %06.3f %1s %02d %02d %05.2f %2s  %s %5s  %5s  %-4s %s %s %s %s %s\n",
            $name, @$long, @$lat, $system,
            $rv, $flux850, $vrange, $vframe, $vdefn,
            $pm1, $pm2, $px,



( run in 1.899 second using v1.01-cache-2.11-cpan-39bf76dae61 )