1#!/usr/local/bin/perl
2
3# (C) Copyright 2010-2020 MET Norway
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 2 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful, but
11# WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13# General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, write to the Free Software
17# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18# 02110-1301, USA.
19
20# pod included at end of file
21
22use strict;
23use warnings;
24use Getopt::Long;
25use Pod::Usage qw(pod2usage);
26use Geo::BUFR;
27
28# This is actually default in BUFR.pm, but provided here to make it
29# easier for users to change to 'ECCODES' if preferred
30use constant DEFAULT_TABLE_FORMAT => 'BUFRDC';
31
32# Will be used if neither --tablepath nor $ENV{BUFR_TABLES} is set
33use constant DEFAULT_TABLE_PATH_BUFRDC => '/usr/local/lib/bufrtables';
34use constant DEFAULT_TABLE_PATH_ECCODES => '/usr/local/share/eccodes/definitions/bufr/tables';
35
36# Parse command line options
37my %option = ();
38
39GetOptions(
40           \%option,
41           'data=s',
42           'help',
43           'metadata=s',
44           'outfile=s',
45           'strict_checking=i',
46           'tableformat=s',
47           'tablepath=s',
48           'verbose=i',
49       ) or pod2usage(-verbose => 0);
50
51# User asked for help
52pod2usage(-verbose => 1) if $option{help};
53
54# Data or metadata file not provided
55pod2usage(-verbose => 0) if not $option{data} or not $option{metadata};
56
57my $data_file     =  $option{data};
58my $metadata_file =  $option{metadata};
59
60# Default is croak if (recoverable) error found in encoded BUFR format
61my $strict_checking = defined $option{strict_checking}
62    ? $option{strict_checking} : 2;
63Geo::BUFR->set_strict_checking($strict_checking);
64
65# Set verbosity level
66Geo::BUFR->set_verbose($option{verbose}) if $option{verbose};
67
68# Set BUFR table format
69my $tableformat = (defined $option{tableformat}) ? uc $option{tableformat} : DEFAULT_TABLE_FORMAT;
70Geo::BUFR->set_tableformat($tableformat);
71
72# Set BUFR table path
73if ($option{tablepath}) {
74    # Command line option --tablepath overrides all
75    Geo::BUFR->set_tablepath($option{tablepath});
76} elsif ($ENV{BUFR_TABLES}) {
77    # If no --tablepath option, use the BUFR_TABLES environment variable
78    Geo::BUFR->set_tablepath($ENV{BUFR_TABLES});
79} else {
80    # If all else fails, use the default tablepath in BUFRDC/ECCODES
81    if ($tableformat eq 'BUFRDC') {
82        Geo::BUFR->set_tablepath(DEFAULT_TABLE_PATH_BUFRDC);
83    } elsif ($tableformat eq 'ECCODES')  {
84        Geo::BUFR->set_tablepath(DEFAULT_TABLE_PATH_ECCODES);
85    }
86}
87
88my $bufr = Geo::BUFR->new();
89
90# Read metadata into $bufr
91read_metadata($metadata_file, $bufr);
92
93# Load B and D tables (table version inferred from metadata)
94$bufr->load_BDtables();
95
96# Get the data
97my ($data_refs, $desc_refs, $num_subsets) = readdata($data_file);
98
99$bufr->set_number_of_subsets($num_subsets);
100
101# Print the encoded BUFR message
102my $buffer = $bufr->encode_message($data_refs, $desc_refs);
103if ($option{outfile}) {
104    my $outfile = $option{outfile};
105    open my $fh, '>', $outfile or die "Can't open $outfile: $!";
106    binmode($fh);
107    print $fh $buffer;
108} else {
109    binmode(STDOUT);
110    print $buffer;
111}
112
113# See OPTIONS section in pod for format of metadata file
114sub read_metadata {
115    my ($file, $bufr) = @_;
116
117    # Read metadata from file into a hash
118    my %metadata;
119    open (my $fh, '<', $file) or die "Cannot open $file: $!";
120    while ( <$fh> ) {
121        chomp;
122        next if /^\s*$/;
123        s/^\s+//;
124        my ($key, $value) = split /\s+/, $_, 2;
125        $metadata{$key} = $value;
126    }
127    close $fh or die "Cannot close $file: $!";
128
129    # Load the metadata into the BUFR object
130    my $m = \%metadata;
131
132    my $bufr_edition = $m->{BUFR_EDITION};
133
134    $bufr->set_bufr_edition($bufr_edition);
135    $bufr->set_master_table($m->{MASTER_TABLE});
136    $bufr->set_centre($m->{CENTRE});
137    $bufr->set_subcentre($m->{SUBCENTRE});
138    $bufr->set_update_sequence_number($m->{UPDATE_SEQUENCE_NUMBER});
139    $bufr->set_optional_section($m->{OPTIONAL_SECTION});
140    $bufr->set_data_category($m->{DATA_CATEGORY});
141    if ( $bufr_edition < 4 ) {
142        $bufr->set_data_subcategory($m->{DATA_SUBCATEGORY});
143    } else {
144        $bufr->set_int_data_subcategory($m->{INT_DATA_SUBCATEGORY});
145        $bufr->set_loc_data_subcategory($m->{LOC_DATA_SUBCATEGORY});
146    }
147    $bufr->set_master_table_version($m->{MASTER_TABLE_VERSION});
148    $bufr->set_local_table_version($m->{LOCAL_TABLE_VERSION});
149    if ( $bufr_edition < 4 ) {
150        $bufr->set_year_of_century($m->{YEAR_OF_CENTURY});
151    } else {
152        $bufr->set_year($m->{YEAR});
153    }
154    $bufr->set_month($m->{MONTH});
155    $bufr->set_day($m->{DAY});
156    $bufr->set_hour($m->{HOUR});
157    $bufr->set_minute($m->{MINUTE});
158    $bufr->set_second($m->{SECOND}) if $bufr_edition >= 4;
159    $bufr->set_observed_data($m->{OBSERVED_DATA});
160    $bufr->set_compressed_data($m->{COMPRESSED_DATA});
161    $bufr->set_descriptors_unexpanded($m->{DESCRIPTORS_UNEXPANDED});
162    $bufr->set_local_use($m->{LOCAL_USE}) if exists $m->{LOCAL_USE};
163
164    return;
165}
166
167# See OPTIONS section in pod for format of data file
168sub readdata {
169    my $file = shift;
170    open (my $fh, '<', $file) or die "Cannot open $file: $!";
171
172    my ($data_refs, $desc_refs);
173    my $subset = 0;
174    while ( <$fh> ) {
175        s/^\s+//;
176        # Lines not starting with a number are ignored
177        next if not /^\d/;
178        my ($n, $desc, $value) = split /\s+/, $_, 3;
179        $subset++ if $n == 1;
180        # Some operator descriptors are written on unnumbered lines
181        # without a value
182        if (!defined $desc || $desc !~ /^\d/) {
183            next unless $n >= 200000 && $n < 300000; # Better to die here?
184            $desc = $n;
185            $value = undef;
186        } else {
187            $value =~ s/\s+$//;
188            $value = undef if $value eq '' or $value eq 'missing';
189        }
190        push @{$data_refs->[$subset]}, $value;
191        push @{$desc_refs->[$subset]}, $desc;
192    }
193    close $fh or die "Cannot close $file: $!";
194
195    return ($data_refs, $desc_refs, $subset);
196}
197
198=pod
199
200=encoding utf8
201
202=head1 SYNOPSIS
203
204  bufrencode.pl --data <data file> --metadata <metadata file>
205      [--outfile <file to print encoded BUFR message to>]
206      [--strict_checking n]
207      [--tableformat <BUFRDC|ECCODES>]
208      [--tablepath <path to BUFR tables>]
209      [--verbose n]
210      [--help]
211
212=head1 DESCRIPTION
213
214Encode a BUFR message, reading data and metadata from files. The
215resulting BUFR message will be printed to STDOUT unless option
216C<--outfile> is set.
217
218Execute without arguments for Usage, with option --help for some
219additional info. See also L<https://wiki.met.no/bufr.pm/start> for
220examples of use.
221
222=head1 OPTIONS
223
224   --help               Display Usage and explain the options. Almost
225                        the same as consulting perldoc bufrencode.pl
226   --outfile <filename> Will print the encoded BUFR message to <filename>
227                        instead of STDOUT
228   --strict_checking n  n=0 Disable strict checking of BUFR format
229                        n=1 Issue warning if (recoverable) error in
230                            BUFR format
231                        n=2 (default) Croak if (recoverable) error in BUFR format.
232                            Nothing more in this message will be encoded.
233   --tableformat        Currently supported are BUFRDC and ECCODES (default is BUFRDC)
234   --tablepath <path to BUFR tables>
235                        If used, will set path to BUFR tables. If not
236                        set, will fetch tables from the environment
237                        variable BUFR_TABLES, or if this is not set:
238                        will use DEFAULT_TABLE_PATH_<tableformat>
239                        hard coded in source code.
240   --verbose n          Set verbose level to n, 0<=n<=6 (default 0).
241                        Verbose output is sent to STDOUT, so ought to
242                        be combined with option --outfile
243
244=head2 Required options
245
246=head4 --metadata <metadata file>
247
248For the metadata file, use this as a prototype and change the values
249as desired:
250
251  BUFR_EDITION  4
252  MASTER_TABLE  0
253  CENTRE  88
254  SUBCENTRE  0
255  UPDATE_SEQUENCE_NUMBER  0
256  OPTIONAL_SECTION  0
257  DATA_CATEGORY  0
258  INT_DATA_SUBCATEGORY  2
259  LOC_DATA_SUBCATEGORY  255
260  MASTER_TABLE_VERSION  14
261  LOCAL_TABLE_VERSION  0
262  YEAR  2008
263  MONTH  9
264  DAY  1
265  HOUR  6
266  MINUTE  0
267  SECOND  0
268  OBSERVED_DATA  1
269  COMPRESSED_DATA  0
270  DESCRIPTORS_UNEXPANDED  308004 012005 002002
271
272For BUFR edition < 4, replace the lines INT_DATA_SUBCATEGORY,
273LOC_DATA_SUBCATEGORY, YEAR and SECOND with new lines DATA_SUBCATEGORY
274and YEAR_OF_CENTURY (the order of lines doesn't matter).
275
276=head4 --data <data file>
277
278For the data file, use the same format as would result if you did run
279on the generated BUFR message
280
281    bufrread.pl <bufr file> --data_only | cut -c -31
282
283or if you use bufrread.pl with C<--width n>, replace 31 with n+16.
284For example, the file might begin with
285
286     1  001195          Newport
287     2  005002            51.55
288     3  006002            -2.99
289     4  004001             2008
290...
291
292Every time a new line starting with the number 1 is met, a new subset
293will be generated in the BUFR message. Lines not starting with a
294number are ignored.
295
296For missing values, use 'missing' or stop the line after the BUFR
297descriptor.
298
299Associated values should use BUFR descriptor 999999, and operator
300descriptors 22[2345]000 and 23[2567]000 should not have a value,
301neither should this line be numbered, e.g.
302
303   160  011002          missing
304        222000
305   161  031002              160
306   162  031031                0
307...
308
309To encode a NIL subset, all delayed replication factors should be
310nonzero, and all other values set to missing except for the
311descriptors defining the station.
312
313Options may be abbreviated, e.g. C<--h> or C<-h> for C<--help>
314
315=head1 AUTHOR
316
317Pål Sannes E<lt>pal.sannes@met.noE<gt>
318
319=head1 COPYRIGHT
320
321Copyright (C) 2010-2020 MET Norway
322
323=cut
324