| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Astro::Catalog::IO::JCMT; |
|
2
|
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
=head1 NAME |
|
4
|
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
Astro::Catalog::IO::JCMT - JCMT catalogue I/O for Astro::Catalog |
|
6
|
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
8
|
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
$cat = Astro::Catalog::IO::JCMT->_read_catalog( \@lines ); |
|
10
|
|
|
|
|
|
|
$arrref = Astro::Catalog::IO::JCMT->_write_catalog( $cat, %options ); |
|
11
|
|
|
|
|
|
|
$filename = Astro::Catalog::IO::JCMT->_default_file(); |
|
12
|
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
14
|
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
This class provides read and write methods for catalogues in the JCMT |
|
16
|
|
|
|
|
|
|
pointing catalogue format. The methods are not public and should, in general, |
|
17
|
|
|
|
|
|
|
only be called from the C C and C |
|
18
|
|
|
|
|
|
|
methods. |
|
19
|
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
=cut |
|
21
|
|
|
|
|
|
|
|
|
22
|
1
|
|
|
1
|
|
6262771
|
use 5.006; |
|
|
1
|
|
|
|
|
10
|
|
|
|
1
|
|
|
|
|
61
|
|
|
23
|
1
|
|
|
1
|
|
7
|
use warnings; |
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
104
|
|
|
24
|
1
|
|
|
1
|
|
62
|
use warnings::register; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
478
|
|
|
25
|
1
|
|
|
1
|
|
8
|
use Carp; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
260
|
|
|
26
|
1
|
|
|
1
|
|
5
|
use strict; |
|
|
1
|
|
|
|
|
9
|
|
|
|
1
|
|
|
|
|
57
|
|
|
27
|
|
|
|
|
|
|
|
|
28
|
1
|
|
|
1
|
|
1113
|
use Astro::Telescope; |
|
|
1
|
|
|
|
|
42184
|
|
|
|
1
|
|
|
|
|
42
|
|
|
29
|
1
|
|
|
1
|
|
720
|
use Astro::Coords; |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
use Astro::Catalog; |
|
31
|
|
|
|
|
|
|
use Astro::Catalog::Star; |
|
32
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
use base qw/ Astro::Catalog::IO::ASCII /; |
|
34
|
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
use vars qw/$VERSION $DEBUG /; |
|
36
|
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
$VERSION = '4.31'; |
|
38
|
|
|
|
|
|
|
$DEBUG = 0; |
|
39
|
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
# Name must be limited to 15 characters on write |
|
41
|
|
|
|
|
|
|
use constant MAX_SRC_LENGTH => 15; |
|
42
|
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
# Default location for a JCMT catalog |
|
44
|
|
|
|
|
|
|
my $defaultCatalog = "/local/progs/etc/poi.dat"; |
|
45
|
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
# Planets appended to the catalogue |
|
47
|
|
|
|
|
|
|
my @PLANETS = qw/ mercury mars uranus saturn jupiter venus neptune /; |
|
48
|
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
=over 4 |
|
50
|
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
=item B |
|
52
|
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
Method to take a general target name and clean it up |
|
54
|
|
|
|
|
|
|
so that it is suitable for writing in a JCMT source catalog. |
|
55
|
|
|
|
|
|
|
This routine is used by the catalog writing code but can also |
|
56
|
|
|
|
|
|
|
be used publically in order to make sure that a target name |
|
57
|
|
|
|
|
|
|
to be written to the catalogue is guaranteed to match that used |
|
58
|
|
|
|
|
|
|
in another location (e.g. when writing an a document to accompany |
|
59
|
|
|
|
|
|
|
the catalogue which refers to targets within it). |
|
60
|
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
The source name can be truncated. |
|
62
|
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
$cleaned = Astro::Catalog::IO::JCMT->clean_target_name( $dirty ); |
|
64
|
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
Will return undef if the argument is not defined. |
|
66
|
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
Punctuation such as "," and ";" are replaced with underscores. |
|
68
|
|
|
|
|
|
|
".", "()" and "+-" are allowed. |
|
69
|
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
=cut |
|
71
|
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
sub clean_target_name { |
|
73
|
|
|
|
|
|
|
my $class = shift; |
|
74
|
|
|
|
|
|
|
my $dirty = shift; |
|
75
|
|
|
|
|
|
|
return unless defined $dirty; |
|
76
|
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
# Remove spaces [compress] |
|
78
|
|
|
|
|
|
|
$dirty =~ s/\s+//g; |
|
79
|
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
# Remove disallowed characters |
|
81
|
|
|
|
|
|
|
# and replace with dashes |
|
82
|
|
|
|
|
|
|
$dirty =~ s/[,;:'"`]/-/g; |
|
83
|
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
# Truncate it to the allowed length |
|
85
|
|
|
|
|
|
|
# Name must be limited to MAX_SRC_LENGTH characters |
|
86
|
|
|
|
|
|
|
$dirty = substr($dirty,0,MAX_SRC_LENGTH); |
|
87
|
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
# Return the cleaned name |
|
89
|
|
|
|
|
|
|
return $dirty; |
|
90
|
|
|
|
|
|
|
} |
|
91
|
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
=item B<_default_file> |
|
94
|
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
Returns the location of the default JCMT pointing catalogue at the |
|
96
|
|
|
|
|
|
|
JCMT itself. This is purely for convenience of the caller when they |
|
97
|
|
|
|
|
|
|
are at the JCMT and wish to use the default catalogue without having |
|
98
|
|
|
|
|
|
|
to know explicitly where it is. |
|
99
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
$filename = Astro::Catalog::IO::JCMT->_default_file(); |
|
101
|
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
Returns empty list/undef if the file is not available. |
|
103
|
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
If the environment variable ASTRO_CATALOG_JCMT is defined (and exists) |
|
105
|
|
|
|
|
|
|
this will be used as the default. |
|
106
|
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
=cut |
|
108
|
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
sub _default_file { |
|
110
|
|
|
|
|
|
|
my $class = shift; |
|
111
|
|
|
|
|
|
|
return $ENV{ASTRO_CATALOG_JCMT} |
|
112
|
|
|
|
|
|
|
if (exists $ENV{ASTRO_CATALOG_JCMT} && -e $ENV{ASTRO_CATALOG_JCMT}); |
|
113
|
|
|
|
|
|
|
return (-e $defaultCatalog ? $defaultCatalog : () ); |
|
114
|
|
|
|
|
|
|
} |
|
115
|
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
=item B<_read_catalog> |
|
117
|
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
Parses the catalogue lines and returns a new C |
|
119
|
|
|
|
|
|
|
object containing the catalog entries. |
|
120
|
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
$cat = Astro::Catalog::IO::JCMT->_read_catalog( \@lines, %options ); |
|
122
|
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
Supported options (with defaults) are: |
|
124
|
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
telescope => Name of telescope to associate with each coordinate entry |
|
126
|
|
|
|
|
|
|
(defaults to JCMT). If the telescope option is specified |
|
127
|
|
|
|
|
|
|
but is undef or empty string, no telescope is used. |
|
128
|
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
incplanets => Append planets to catalogue entries (default is true) |
|
130
|
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
=cut |
|
133
|
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
sub _read_catalog { |
|
135
|
|
|
|
|
|
|
my $class = shift; |
|
136
|
|
|
|
|
|
|
my $lines = shift; |
|
137
|
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
# Default options |
|
139
|
|
|
|
|
|
|
my %defaults = ( telescope => 'JCMT', |
|
140
|
|
|
|
|
|
|
incplanets => 1); |
|
141
|
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
my %options = (%defaults, @_); |
|
143
|
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
croak "Must supply catalogue contents as a reference to an array" |
|
145
|
|
|
|
|
|
|
unless ref($lines) eq 'ARRAY'; |
|
146
|
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
# Create a new telescope to associate with this |
|
148
|
|
|
|
|
|
|
my $tel; |
|
149
|
|
|
|
|
|
|
$tel = new Astro::Telescope( $options{telescope} ) |
|
150
|
|
|
|
|
|
|
if $options{telescope}; |
|
151
|
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
# Go through each line and parse it |
|
153
|
|
|
|
|
|
|
# and store in the array if we had a successful read |
|
154
|
|
|
|
|
|
|
my @stars = map { $class->_parse_line( $_, $tel); } @$lines; |
|
155
|
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
# Add planets if required |
|
157
|
|
|
|
|
|
|
if ($options{incplanets}) { |
|
158
|
|
|
|
|
|
|
# create coordinate objects for the planets |
|
159
|
|
|
|
|
|
|
my @planets = map { new Astro::Coords(planet => $_) } @PLANETS; |
|
160
|
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
# And associate a telescope |
|
162
|
|
|
|
|
|
|
if ($tel) { |
|
163
|
|
|
|
|
|
|
for (@planets) { |
|
164
|
|
|
|
|
|
|
$_->telescope($tel); |
|
165
|
|
|
|
|
|
|
} |
|
166
|
|
|
|
|
|
|
} |
|
167
|
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
# And create the star objects |
|
169
|
|
|
|
|
|
|
push(@stars, map { new Astro::Catalog::Star( |
|
170
|
|
|
|
|
|
|
field => 'JCMT', |
|
171
|
|
|
|
|
|
|
id => $_->name, |
|
172
|
|
|
|
|
|
|
coords => $_, |
|
173
|
|
|
|
|
|
|
comment => 'Added automatically', |
|
174
|
|
|
|
|
|
|
) } @planets); |
|
175
|
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
} |
|
177
|
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
# Create the catalog object |
|
179
|
|
|
|
|
|
|
return new Astro::Catalog( Stars => \@stars, |
|
180
|
|
|
|
|
|
|
Origin => 'JCMT', |
|
181
|
|
|
|
|
|
|
); |
|
182
|
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
} |
|
184
|
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
=item B<_write_catalog> |
|
186
|
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
Write the catalog to an array and return it. Returning a reference to |
|
188
|
|
|
|
|
|
|
an array provides more flexibility to the caller. |
|
189
|
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
$ref = Astro::Catalog::IO::JCMT->_write_catalog( $cat ); |
|
191
|
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
Spaces are removed from source names. The contents of the catalog |
|
193
|
|
|
|
|
|
|
are sanity checked. |
|
194
|
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
=cut |
|
196
|
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
sub _write_catalog { |
|
198
|
|
|
|
|
|
|
my $class = shift; |
|
199
|
|
|
|
|
|
|
my $cat = shift; |
|
200
|
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
# Would make more sense to use the array ref here |
|
202
|
|
|
|
|
|
|
my @sources = $cat->stars; |
|
203
|
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
# Counter for unknown targets |
|
205
|
|
|
|
|
|
|
my $unk = 1; |
|
206
|
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
# Hash for storing target information |
|
208
|
|
|
|
|
|
|
# so that we can search for duplicates |
|
209
|
|
|
|
|
|
|
my %targets; |
|
210
|
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
# Create hash of all unique target names present |
|
212
|
|
|
|
|
|
|
# after cleaning. We need this so that we can make sure |
|
213
|
|
|
|
|
|
|
# a generated name derived from a duplication (with target mismatch) |
|
214
|
|
|
|
|
|
|
# does not generate a name that already existed explicitly. |
|
215
|
|
|
|
|
|
|
my %allnames = map { $class->clean_target_name($_->coords->name), undef } |
|
216
|
|
|
|
|
|
|
@sources; |
|
217
|
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
# Loop over each source and extract catalog information |
|
219
|
|
|
|
|
|
|
# Make sure that we remove unique entries |
|
220
|
|
|
|
|
|
|
# BUT THAT WE RETAIN THE ORDER OF THE SOURCES IN THE CATALOG |
|
221
|
|
|
|
|
|
|
# Hence an array for the information |
|
222
|
|
|
|
|
|
|
my @processed; |
|
223
|
|
|
|
|
|
|
for my $star (@sources) { |
|
224
|
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
# Extract the coordinate object |
|
226
|
|
|
|
|
|
|
my $src = $star->coords; |
|
227
|
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
# Get the name but do not deal with undef yet |
|
229
|
|
|
|
|
|
|
# in case the type is not valid |
|
230
|
|
|
|
|
|
|
my $name = $src->name; |
|
231
|
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
# Somewhere to store the extracted information |
|
233
|
|
|
|
|
|
|
my %srcdata; |
|
234
|
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
# Store the name (stripped of spaces) and |
|
236
|
|
|
|
|
|
|
# treat srcdata{name} as the primary name from here on |
|
237
|
|
|
|
|
|
|
$srcdata{name} = $class->clean_target_name( $name ); |
|
238
|
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
# Store a comment |
|
240
|
|
|
|
|
|
|
$srcdata{comment} = $star->comment; |
|
241
|
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
# prepopulate the default velocity settings |
|
243
|
|
|
|
|
|
|
$srcdata{rv} = 'n/a'; |
|
244
|
|
|
|
|
|
|
$srcdata{vdefn} = 'RADIO'; |
|
245
|
|
|
|
|
|
|
$srcdata{vframe} = 'LSR'; |
|
246
|
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
# Get the miscellaneous data. |
|
248
|
|
|
|
|
|
|
my $misc = $star->misc; |
|
249
|
|
|
|
|
|
|
if( defined( $misc ) ) { |
|
250
|
|
|
|
|
|
|
$srcdata{vrange} = ( defined( $misc->{'velocity_range'} ) ? |
|
251
|
|
|
|
|
|
|
sprintf( "%s", $misc->{'velocity_range'} ) : |
|
252
|
|
|
|
|
|
|
"n/a" ); |
|
253
|
|
|
|
|
|
|
$srcdata{flux850} = ( defined( $misc->{'flux850'} ) ? |
|
254
|
|
|
|
|
|
|
sprintf( "%s", $misc->{'flux850'} ) : |
|
255
|
|
|
|
|
|
|
"n/a" ); |
|
256
|
|
|
|
|
|
|
} else { |
|
257
|
|
|
|
|
|
|
$srcdata{vrange} = "n/a"; |
|
258
|
|
|
|
|
|
|
$srcdata{flux850} = "n/a"; |
|
259
|
|
|
|
|
|
|
} |
|
260
|
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
# Get the type of source |
|
262
|
|
|
|
|
|
|
my $type = $src->type; |
|
263
|
|
|
|
|
|
|
if ($type eq 'RADEC') { |
|
264
|
|
|
|
|
|
|
$srcdata{system} = "RJ"; |
|
265
|
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
# Need to get the space separated RA/Dec and the sign |
|
267
|
|
|
|
|
|
|
$srcdata{long} = $src->ra(format => 'array'); |
|
268
|
|
|
|
|
|
|
$srcdata{lat} = $src->dec(format => 'array'); |
|
269
|
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
# Get the velocity information |
|
271
|
|
|
|
|
|
|
my $rv = $src->rv; |
|
272
|
|
|
|
|
|
|
if ($rv) { |
|
273
|
|
|
|
|
|
|
$srcdata{rv} = $rv; |
|
274
|
|
|
|
|
|
|
$srcdata{vdefn} = $src->vdefn; |
|
275
|
|
|
|
|
|
|
$srcdata{vframe} = $src->vframe; |
|
276
|
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
# JCMT compatibility |
|
278
|
|
|
|
|
|
|
$srcdata{vframe} = "LSR" if $srcdata{vframe} eq 'LSRK'; |
|
279
|
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
} |
|
281
|
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
} elsif ($type eq 'PLANET') { |
|
283
|
|
|
|
|
|
|
# Planets are not supported in catalog form. Skip them |
|
284
|
|
|
|
|
|
|
next; |
|
285
|
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
} elsif ($type eq 'FIXED') { |
|
287
|
|
|
|
|
|
|
$srcdata{system} = "AZ"; |
|
288
|
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
$srcdata{long} = $src->az(format => 'array'); |
|
290
|
|
|
|
|
|
|
$srcdata{lat} = $src->el(format => 'array'); |
|
291
|
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
# Need to remove + sign from long/AZ since we are not expecting |
|
293
|
|
|
|
|
|
|
# it in RA/DEC. This is probably a bug in Astro::Coords |
|
294
|
|
|
|
|
|
|
shift(@{ $srcdata{long} } ) if $srcdata{long}->[0] eq '+'; |
|
295
|
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
} else { |
|
297
|
|
|
|
|
|
|
my $errname = ( defined $srcdata{name} ? $srcdata{name} : ""); |
|
298
|
|
|
|
|
|
|
warnings::warnif "Coordinate of type $type for target $errname not supported in JCMT catalog files\n"; |
|
299
|
|
|
|
|
|
|
next; |
|
300
|
|
|
|
|
|
|
} |
|
301
|
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
# Generate a name if not defined |
|
303
|
|
|
|
|
|
|
if (!defined $srcdata{name}) { |
|
304
|
|
|
|
|
|
|
$srcdata{name} = "UNKNOWN$unk"; |
|
305
|
|
|
|
|
|
|
$unk++; |
|
306
|
|
|
|
|
|
|
} |
|
307
|
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
# See if we already have this source and that it is really the |
|
309
|
|
|
|
|
|
|
# same source Note that we do not see whether this name is the |
|
310
|
|
|
|
|
|
|
# same as one of the derived names. Eg if CRL618 is in the |
|
311
|
|
|
|
|
|
|
# pointing catalogue 3 times with identical coords and we add a |
|
312
|
|
|
|
|
|
|
# new CRL618 with different coords then we trigger 3 warning |
|
313
|
|
|
|
|
|
|
# messages rather than 1 because we do not check that CRL618_2 is |
|
314
|
|
|
|
|
|
|
# the same as CRL618_1 |
|
315
|
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
# Note that velocity specification is included in this comparison |
|
317
|
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
if (exists $targets{$srcdata{name}}) { |
|
319
|
|
|
|
|
|
|
my $previous = $targets{$srcdata{name}}; |
|
320
|
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
# Create stringified form of previous coordinate with same name |
|
322
|
|
|
|
|
|
|
# and current coordinate |
|
323
|
|
|
|
|
|
|
my $prevcoords = join(" ",@{$previous->{long}},@{$previous->{lat}}, |
|
324
|
|
|
|
|
|
|
$previous->{rv}, $previous->{vdefn}, $previous->{vframe}); |
|
325
|
|
|
|
|
|
|
my $curcoords = join(" ",@{$srcdata{long}},@{$srcdata{lat}}, |
|
326
|
|
|
|
|
|
|
$srcdata{rv}, $srcdata{vdefn}, $srcdata{vframe}); |
|
327
|
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
if ($prevcoords eq $curcoords) { |
|
329
|
|
|
|
|
|
|
# This is the same target so we can ignore it |
|
330
|
|
|
|
|
|
|
} else { |
|
331
|
|
|
|
|
|
|
# Make up a new name. Use the unknown counter for this since |
|
332
|
|
|
|
|
|
|
# we probably have not used it before. Probably not the best |
|
333
|
|
|
|
|
|
|
# approach and might have problems in edge cases but good |
|
334
|
|
|
|
|
|
|
# enough for now |
|
335
|
|
|
|
|
|
|
my $oldname = $srcdata{name}; |
|
336
|
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
# loop for 100 times |
|
338
|
|
|
|
|
|
|
my $count; |
|
339
|
|
|
|
|
|
|
while (1) { |
|
340
|
|
|
|
|
|
|
# protection loop |
|
341
|
|
|
|
|
|
|
$count++; |
|
342
|
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
# Try to construct a new name based on a global counter |
|
344
|
|
|
|
|
|
|
# rather than a counter that starts at 1 for each root |
|
345
|
|
|
|
|
|
|
my $suffix = "_$unk"; |
|
346
|
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
# increment $unk for next try |
|
348
|
|
|
|
|
|
|
$unk++; |
|
349
|
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
# Abort if we have gone round too many times |
|
351
|
|
|
|
|
|
|
# Making sure that $unk is incremented first |
|
352
|
|
|
|
|
|
|
if ($count > 100) { |
|
353
|
|
|
|
|
|
|
$srcdata{name} = substr($oldname,0,int(MAX_SRC_LENGTH/2)) . |
|
354
|
|
|
|
|
|
|
int(rand(10000)+1000); |
|
355
|
|
|
|
|
|
|
warn "Uncontrollable looping (or unfeasibly large number of duplicate sources with different coordinates). Panicked and generated random source name of $srcdata{name}.\n"; |
|
356
|
|
|
|
|
|
|
last; |
|
357
|
|
|
|
|
|
|
} |
|
358
|
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
# Assume the old name will do fine |
|
360
|
|
|
|
|
|
|
my $root = $oldname; |
|
361
|
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
# Do not want to truncate the _XX off the end later on |
|
363
|
|
|
|
|
|
|
if (length($oldname) > MAX_SRC_LENGTH - length($suffix)) { |
|
364
|
|
|
|
|
|
|
# This may well be confusing but we have no choice. Since |
|
365
|
|
|
|
|
|
|
# _XX is unique the only time we will get a name clash by |
|
366
|
|
|
|
|
|
|
# simply chopping the string is if we have a duplicate |
|
367
|
|
|
|
|
|
|
# that is too long along with a target name that includes |
|
368
|
|
|
|
|
|
|
# _XX amd matches the truncated source name! |
|
369
|
|
|
|
|
|
|
$root = substr($oldname, 0, (MAX_SRC_LENGTH-length($suffix)) ); |
|
370
|
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
} |
|
372
|
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
# Form the new name |
|
374
|
|
|
|
|
|
|
my $newname = $root . $suffix; |
|
375
|
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
# check to see if this name is in the existing target list |
|
377
|
|
|
|
|
|
|
next if exists $allnames{$newname}; |
|
378
|
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
# Store it in the targets array and exit loop |
|
380
|
|
|
|
|
|
|
$srcdata{name} = $newname; |
|
381
|
|
|
|
|
|
|
last; |
|
382
|
|
|
|
|
|
|
} |
|
383
|
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
# different target |
|
385
|
|
|
|
|
|
|
warn "Found target with the same name [$oldname] but with different coordinates, renaming it to $srcdata{name}!\n"; |
|
386
|
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
$targets{$srcdata{name}} = \%srcdata; |
|
388
|
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
# Store it in the array |
|
390
|
|
|
|
|
|
|
push(@processed, \%srcdata); |
|
391
|
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
} |
|
393
|
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
} else { |
|
395
|
|
|
|
|
|
|
# Store in hash for easy lookup for duplicates |
|
396
|
|
|
|
|
|
|
$targets{$srcdata{name}} = \%srcdata; |
|
397
|
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
# Store it in the array |
|
399
|
|
|
|
|
|
|
push(@processed, \%srcdata); |
|
400
|
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
} |
|
402
|
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
} |
|
404
|
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
# Output array for new catalog lines |
|
407
|
|
|
|
|
|
|
my @lines; |
|
408
|
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
# Write a header |
|
410
|
|
|
|
|
|
|
push @lines, "*\n"; |
|
411
|
|
|
|
|
|
|
push @lines, "* Catalog written automatically by class ". __PACKAGE__ ."\n"; |
|
412
|
|
|
|
|
|
|
push @lines, "* on date " . gmtime . "UT\n"; |
|
413
|
|
|
|
|
|
|
push @lines, "* Origin of catalogue: ". $cat->origin ."\n"; |
|
414
|
|
|
|
|
|
|
push @lines, "*\n"; |
|
415
|
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
# Now need to go through the targets and write them to disk |
|
417
|
|
|
|
|
|
|
for my $src (@processed) { |
|
418
|
|
|
|
|
|
|
my $name = $src->{name}; |
|
419
|
|
|
|
|
|
|
my $long = $src->{long}; |
|
420
|
|
|
|
|
|
|
my $lat = $src->{lat}; |
|
421
|
|
|
|
|
|
|
my $system = $src->{system}; |
|
422
|
|
|
|
|
|
|
my $comment = $src->{comment}; |
|
423
|
|
|
|
|
|
|
my $rv = $src->{rv}; |
|
424
|
|
|
|
|
|
|
my $vdefn = $src->{vdefn}; |
|
425
|
|
|
|
|
|
|
my $vframe = $src->{vframe}; |
|
426
|
|
|
|
|
|
|
my $vrange = $src->{vrange}; |
|
427
|
|
|
|
|
|
|
my $flux850 = $src->{flux850}; |
|
428
|
|
|
|
|
|
|
|
|
429
|
|
|
|
|
|
|
$comment = '' unless defined $comment; |
|
430
|
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
# Velocity can not easily be done with a sprintf since it can be either |
|
432
|
|
|
|
|
|
|
# a string or a 2 column number |
|
433
|
|
|
|
|
|
|
if (lc($rv) eq 'n/a') { |
|
434
|
|
|
|
|
|
|
$rv = ' n/a '; |
|
435
|
|
|
|
|
|
|
} else { |
|
436
|
|
|
|
|
|
|
my $sign = ( $rv >= 0 ? '+' : '-' ); |
|
437
|
|
|
|
|
|
|
my $val = $rv; |
|
438
|
|
|
|
|
|
|
$val =~ s/^\s*[+-]\s*//; |
|
439
|
|
|
|
|
|
|
$val =~ s/\s*$//; |
|
440
|
|
|
|
|
|
|
$rv = $sign . ' '. sprintf('%6.1f',$val); |
|
441
|
|
|
|
|
|
|
} |
|
442
|
|
|
|
|
|
|
|
|
443
|
|
|
|
|
|
|
# Name must be limited to MAX_SRC_LENGTH characters |
|
444
|
|
|
|
|
|
|
# [this should be taken care of by clean_target_name but |
|
445
|
|
|
|
|
|
|
# if we have appended _X.... |
|
446
|
|
|
|
|
|
|
$name = substr($name,0,MAX_SRC_LENGTH); |
|
447
|
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
push @lines, |
|
449
|
|
|
|
|
|
|
sprintf("%-". MAX_SRC_LENGTH. |
|
450
|
|
|
|
|
|
|
"s %02d %02d %06.3f %1s %02d %02d %04.1f %2s %s %5s %5s %-4s %s %s\n", |
|
451
|
|
|
|
|
|
|
$name, @$long, @$lat, $system, $rv, $flux850, $vrange, $vframe, $vdefn, $comment); |
|
452
|
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
} |
|
454
|
|
|
|
|
|
|
|
|
455
|
|
|
|
|
|
|
return \@lines; |
|
456
|
|
|
|
|
|
|
} |
|
457
|
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
=item B<_parse_line> |
|
459
|
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
Parse a line from a JCMT format catalogue and return a corresponding |
|
461
|
|
|
|
|
|
|
C object. Returns empty list if the line can not |
|
462
|
|
|
|
|
|
|
be parsed or refers to a comment line (so that map can be used in the |
|
463
|
|
|
|
|
|
|
caller). |
|
464
|
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
$star = Astro::Catalog::IO::JCMT->_parse_line( $line, $tel ); |
|
466
|
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
where C<$line> is the line to be parsed and (optional) C<$tel> |
|
468
|
|
|
|
|
|
|
is an C object to be associated with the |
|
469
|
|
|
|
|
|
|
coordinate objects. |
|
470
|
|
|
|
|
|
|
|
|
471
|
|
|
|
|
|
|
The line is parsed using a pattern match. |
|
472
|
|
|
|
|
|
|
|
|
473
|
|
|
|
|
|
|
=cut |
|
474
|
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
sub _parse_line { |
|
476
|
|
|
|
|
|
|
my $class = shift; |
|
477
|
|
|
|
|
|
|
my $line = shift; |
|
478
|
|
|
|
|
|
|
my $tel = shift; |
|
479
|
|
|
|
|
|
|
chomp $line; |
|
480
|
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
# Skip commented and blank lines |
|
482
|
|
|
|
|
|
|
return if ($line =~ /^\s*[\*\%]/); |
|
483
|
|
|
|
|
|
|
return if ($line =~ /^\s*$/); |
|
484
|
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
# Use a pattern match parser |
|
486
|
|
|
|
|
|
|
my @match = ( $line =~ m/^(.*?) # Target name (non greedy) |
|
487
|
|
|
|
|
|
|
\s* # optional trailing space |
|
488
|
|
|
|
|
|
|
(\d{1,2}) # 1 or 2 digits [RA:h] [greedy] |
|
489
|
|
|
|
|
|
|
\s+ # separator |
|
490
|
|
|
|
|
|
|
(\d{1,2}) # 1 or 2 digits [RA:m] |
|
491
|
|
|
|
|
|
|
\s+ # separator |
|
492
|
|
|
|
|
|
|
(\d{1,2}(?:\.\d*)?) # 1|2 digits opt .fraction [RA:s] |
|
493
|
|
|
|
|
|
|
# no capture on fraction |
|
494
|
|
|
|
|
|
|
\s+ |
|
495
|
|
|
|
|
|
|
([+-]?\s*\d{1,2}) # 1|2 digit [dec:d] inc sign |
|
496
|
|
|
|
|
|
|
\s+ |
|
497
|
|
|
|
|
|
|
(\d{1,2}) # 1|2 digit [dec:m] |
|
498
|
|
|
|
|
|
|
\s+ |
|
499
|
|
|
|
|
|
|
(\d{1,2}(?:\.\d*)?) # arcsecond (optional fraction) |
|
500
|
|
|
|
|
|
|
# no capture on fraction |
|
501
|
|
|
|
|
|
|
\s+ |
|
502
|
|
|
|
|
|
|
(RJ|RB|GA|AZ) # coordinate type |
|
503
|
|
|
|
|
|
|
# most everything else is optional |
|
504
|
|
|
|
|
|
|
# [sign]velocity, flux,vrange,vel_def,frame,comments |
|
505
|
|
|
|
|
|
|
\s* |
|
506
|
|
|
|
|
|
|
(n\/a|[+-]\s*\d+(?:\.\d*)?)? # velocity [8] |
|
507
|
|
|
|
|
|
|
\s* |
|
508
|
|
|
|
|
|
|
(n\/a|\d+(?:\.\d*)?)? # flux [9] |
|
509
|
|
|
|
|
|
|
\s* |
|
510
|
|
|
|
|
|
|
(n\/a|\d+(?:\.\d*)?)? # vel range [10] |
|
511
|
|
|
|
|
|
|
\s* |
|
512
|
|
|
|
|
|
|
([\w\/]+)? # vel frame [11] |
|
513
|
|
|
|
|
|
|
\s* |
|
514
|
|
|
|
|
|
|
([\w\/]+)? # vel defn [12] |
|
515
|
|
|
|
|
|
|
\s* |
|
516
|
|
|
|
|
|
|
(.*)$ # comment [13] |
|
517
|
|
|
|
|
|
|
/xi); |
|
518
|
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
# Abort if we do not have matches for the first 8 fields |
|
520
|
|
|
|
|
|
|
for (0..7) { |
|
521
|
|
|
|
|
|
|
return unless defined $match[$_]; |
|
522
|
|
|
|
|
|
|
} |
|
523
|
|
|
|
|
|
|
|
|
524
|
|
|
|
|
|
|
# Read the values |
|
525
|
|
|
|
|
|
|
my $target = $match[0]; |
|
526
|
|
|
|
|
|
|
my $ra = join(":",@match[1..3]); |
|
527
|
|
|
|
|
|
|
my $dec = join(":",@match[4..6]); |
|
528
|
|
|
|
|
|
|
$dec =~ s/\s//g; # remove space between the sign and number |
|
529
|
|
|
|
|
|
|
my $epoc = $match[7]; |
|
530
|
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
print "Creating a new source in _parse_line: $target\n" if $DEBUG; |
|
532
|
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
# need to translate JCMT epoch to normal epoch |
|
534
|
|
|
|
|
|
|
my %coords; |
|
535
|
|
|
|
|
|
|
$epoc = uc($epoc); |
|
536
|
|
|
|
|
|
|
$coords{name} = $target; |
|
537
|
|
|
|
|
|
|
if ($epoc eq 'RJ') { |
|
538
|
|
|
|
|
|
|
$coords{ra} = $ra; |
|
539
|
|
|
|
|
|
|
$coords{dec} = $dec; |
|
540
|
|
|
|
|
|
|
$coords{type} = "j2000" |
|
541
|
|
|
|
|
|
|
} elsif ($epoc eq 'RB') { |
|
542
|
|
|
|
|
|
|
$coords{ra} = $ra; |
|
543
|
|
|
|
|
|
|
$coords{dec} = $dec; |
|
544
|
|
|
|
|
|
|
$coords{type} = "b1950"; |
|
545
|
|
|
|
|
|
|
} elsif ($epoc eq 'GA') { |
|
546
|
|
|
|
|
|
|
$coords{long} = $ra; |
|
547
|
|
|
|
|
|
|
$coords{lat} = $dec; |
|
548
|
|
|
|
|
|
|
$coords{type} = "galactic"; |
|
549
|
|
|
|
|
|
|
} elsif ($epoc eq 'AZ') { |
|
550
|
|
|
|
|
|
|
$coords{az} = $ra; |
|
551
|
|
|
|
|
|
|
$coords{el} = $dec; |
|
552
|
|
|
|
|
|
|
$coords{units} = 'sexagesimal'; |
|
553
|
|
|
|
|
|
|
} else { |
|
554
|
|
|
|
|
|
|
warnings::warnif "Unknown coordinate type: '$epoc' for target $target. Ignoring line."; |
|
555
|
|
|
|
|
|
|
return; |
|
556
|
|
|
|
|
|
|
} |
|
557
|
|
|
|
|
|
|
|
|
558
|
|
|
|
|
|
|
# catalog comments are space delimited |
|
559
|
|
|
|
|
|
|
my $ccol = 13; |
|
560
|
|
|
|
|
|
|
my $cat_comm = (defined $match[$ccol] ? $match[$ccol] : ''); |
|
561
|
|
|
|
|
|
|
|
|
562
|
|
|
|
|
|
|
# Replace multiple spaces in comment with single space |
|
563
|
|
|
|
|
|
|
$cat_comm =~ s/\s+/ /g; |
|
564
|
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
# velocity |
|
566
|
|
|
|
|
|
|
$coords{vdefn} = "RADIO"; |
|
567
|
|
|
|
|
|
|
$coords{vframe} = "LSR"; |
|
568
|
|
|
|
|
|
|
if (defined $match[8] && $match[8] !~ /n/) { |
|
569
|
|
|
|
|
|
|
$match[8] =~ s/\s//g; # remove spaces |
|
570
|
|
|
|
|
|
|
$coords{rv} = $match[8]; |
|
571
|
|
|
|
|
|
|
$coords{vdefn} = $match[12]; |
|
572
|
|
|
|
|
|
|
$coords{vframe} = $match[11]; |
|
573
|
|
|
|
|
|
|
} |
|
574
|
|
|
|
|
|
|
|
|
575
|
|
|
|
|
|
|
# create the source object |
|
576
|
|
|
|
|
|
|
my $source = new Astro::Coords( %coords ); |
|
577
|
|
|
|
|
|
|
|
|
578
|
|
|
|
|
|
|
unless (defined $source ) { |
|
579
|
|
|
|
|
|
|
if ($DEBUG) { |
|
580
|
|
|
|
|
|
|
print "failed to create source for '$target' and $ra and $dec and $epoc\n"; |
|
581
|
|
|
|
|
|
|
return; |
|
582
|
|
|
|
|
|
|
} else { |
|
583
|
|
|
|
|
|
|
croak "Error parsing line. Unable to create source date for target '$target' at RA '$ra' Dec '$dec' and Epoch '$epoc'\n"; |
|
584
|
|
|
|
|
|
|
} |
|
585
|
|
|
|
|
|
|
} |
|
586
|
|
|
|
|
|
|
|
|
587
|
|
|
|
|
|
|
$source->telescope( $tel ) if $tel; |
|
588
|
|
|
|
|
|
|
$source->comment($cat_comm); |
|
589
|
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
# Field name should simply be linked to the telescope |
|
591
|
|
|
|
|
|
|
my $field = (defined $tel ? $tel->name : '' ); |
|
592
|
|
|
|
|
|
|
|
|
593
|
|
|
|
|
|
|
my %misc; |
|
594
|
|
|
|
|
|
|
# Grab the line's velocity range, if it isn't "n/a". |
|
595
|
|
|
|
|
|
|
if( defined $match[10] && $match[10] !~ /n\/a/ ) { |
|
596
|
|
|
|
|
|
|
$misc{'velocity_range'} = $match[10]; |
|
597
|
|
|
|
|
|
|
} |
|
598
|
|
|
|
|
|
|
|
|
599
|
|
|
|
|
|
|
# Grab the 850-micron flux, if it isn't "n/a". |
|
600
|
|
|
|
|
|
|
if( defined $match[9] && $match[9] !~ /n\/a/ ) { |
|
601
|
|
|
|
|
|
|
$misc{'flux850'} = $match[9]; |
|
602
|
|
|
|
|
|
|
} |
|
603
|
|
|
|
|
|
|
|
|
604
|
|
|
|
|
|
|
print "Created a new source in _parse_line: $target in field $field\n" if $DEBUG; |
|
605
|
|
|
|
|
|
|
|
|
606
|
|
|
|
|
|
|
# Now create the star object |
|
607
|
|
|
|
|
|
|
return new Astro::Catalog::Star( id => $target, |
|
608
|
|
|
|
|
|
|
coords => $source, |
|
609
|
|
|
|
|
|
|
field => $field, |
|
610
|
|
|
|
|
|
|
comment => $cat_comm, |
|
611
|
|
|
|
|
|
|
misc => \%misc, |
|
612
|
|
|
|
|
|
|
); |
|
613
|
|
|
|
|
|
|
|
|
614
|
|
|
|
|
|
|
} |
|
615
|
|
|
|
|
|
|
|
|
616
|
|
|
|
|
|
|
|
|
617
|
|
|
|
|
|
|
=back |
|
618
|
|
|
|
|
|
|
|
|
619
|
|
|
|
|
|
|
=head1 NOTES |
|
620
|
|
|
|
|
|
|
|
|
621
|
|
|
|
|
|
|
Coordinates are stored as C objects inside |
|
622
|
|
|
|
|
|
|
C objects. |
|
623
|
|
|
|
|
|
|
|
|
624
|
|
|
|
|
|
|
|
|
625
|
|
|
|
|
|
|
=head1 GLOBAL VARIABLES |
|
626
|
|
|
|
|
|
|
|
|
627
|
|
|
|
|
|
|
The following global variables can be modified to control the state of the |
|
628
|
|
|
|
|
|
|
module: |
|
629
|
|
|
|
|
|
|
|
|
630
|
|
|
|
|
|
|
=over 4 |
|
631
|
|
|
|
|
|
|
|
|
632
|
|
|
|
|
|
|
=item $DEBUG |
|
633
|
|
|
|
|
|
|
|
|
634
|
|
|
|
|
|
|
Controls debugging messages. Default state is false. |
|
635
|
|
|
|
|
|
|
|
|
636
|
|
|
|
|
|
|
=back |
|
637
|
|
|
|
|
|
|
|
|
638
|
|
|
|
|
|
|
=head1 CONSTANTS |
|
639
|
|
|
|
|
|
|
|
|
640
|
|
|
|
|
|
|
The following constants are available for querying: |
|
641
|
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
=over 4 |
|
643
|
|
|
|
|
|
|
|
|
644
|
|
|
|
|
|
|
=item MAX_SRC_LENGTH |
|
645
|
|
|
|
|
|
|
|
|
646
|
|
|
|
|
|
|
The maximum length of sourcenames writable to a JCMT source catalogue. |
|
647
|
|
|
|
|
|
|
|
|
648
|
|
|
|
|
|
|
=back |
|
649
|
|
|
|
|
|
|
|
|
650
|
|
|
|
|
|
|
=head1 COPYRIGHT |
|
651
|
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
Copyright (C) 1999-2003 Particle Physics and Astronomy Research Council. |
|
653
|
|
|
|
|
|
|
All Rights Reserved. |
|
654
|
|
|
|
|
|
|
|
|
655
|
|
|
|
|
|
|
=head1 AUTHORS |
|
656
|
|
|
|
|
|
|
|
|
657
|
|
|
|
|
|
|
Tim Jenness Etjenness@cpan.orgE |
|
658
|
|
|
|
|
|
|
|
|
659
|
|
|
|
|
|
|
=cut |
|
660
|
|
|
|
|
|
|
|
|
661
|
|
|
|
|
|
|
1; |