File Coverage

bufrencode.pl
Criterion Covered Total %
statement 109 113 96.4
branch 32 52 61.5
condition 7 12 58.3
subroutine 10 10 100.0
pod n/a
total 158 187 84.4


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             # (C) Copyright 2010-2025 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              
22 4     4   20171 use strict;
  4         8  
  4         146  
23 4     4   15 use warnings;
  4         10  
  4         220  
24 4     4   3024 use Getopt::Long;
  4         67443  
  4         26  
25 4     4   2774 use Pod::Usage qw(pod2usage);
  4         304010  
  4         352  
26 4     4   5776 use Geo::BUFR;
  4         21  
  4         237  
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
30 4     4   30 use constant DEFAULT_TABLE_FORMAT => 'BUFRDC';
  4         5  
  4         361  
31              
32             # Will be used if neither --tablepath nor $ENV{BUFR_TABLES} is set
33 4     4   21 use constant DEFAULT_TABLE_PATH_BUFRDC => '/usr/local/lib/bufrtables';
  4         5  
  4         210  
34 4     4   17 use constant DEFAULT_TABLE_PATH_ECCODES => '/usr/local/share/eccodes/definitions/bufr/tables';
  4         6  
  4         11820  
35              
36             # Parse command line options
37 4         667032 my %option = ();
38              
39 4 50       45 GetOptions(
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
52 4 50       7312 pod2usage(-verbose => 1) if $option{help};
53              
54             # Data or metadata file not provided
55 4 50 33     41 pod2usage(-verbose => 0) if not $option{data} or not $option{metadata};
56              
57 4         16 my $data_file = $option{data};
58 4         12 my $metadata_file = $option{metadata};
59              
60             # Default is croak if (recoverable) error found in encoded BUFR format
61             my $strict_checking = defined $option{strict_checking}
62 4 50       17 ? $option{strict_checking} : 2;
63 4         55 Geo::BUFR->set_strict_checking($strict_checking);
64              
65             # Set verbosity level
66 4 50       18 Geo::BUFR->set_verbose($option{verbose}) if $option{verbose};
67              
68             # Set BUFR table format
69 4 100       21 my $tableformat = (defined $option{tableformat}) ? uc $option{tableformat} : DEFAULT_TABLE_FORMAT;
70 4         27 Geo::BUFR->set_tableformat($tableformat);
71              
72             # Set BUFR table path
73 4 50       15 if ($option{tablepath}) {
    0          
74             # Command line option --tablepath overrides all
75 4         25 Geo::BUFR->set_tablepath($option{tablepath});
76             } elsif ($ENV{BUFR_TABLES}) {
77             # If no --tablepath option, use the BUFR_TABLES environment variable
78 0         0 Geo::BUFR->set_tablepath($ENV{BUFR_TABLES});
79             } else {
80             # If all else fails, use the default tablepath in BUFRDC/ECCODES
81 0 0       0 if ($tableformat eq 'BUFRDC') {
    0          
82 0         0 Geo::BUFR->set_tablepath(DEFAULT_TABLE_PATH_BUFRDC);
83             } elsif ($tableformat eq 'ECCODES') {
84 0         0 Geo::BUFR->set_tablepath(DEFAULT_TABLE_PATH_ECCODES);
85             }
86             }
87              
88 4         23 my $bufr = Geo::BUFR->new();
89              
90             # Read metadata into $bufr
91 4         27 read_metadata($metadata_file, $bufr);
92              
93             # Load B and D tables (table version inferred from metadata)
94 4         26 $bufr->load_BDtables();
95              
96             # Get the data
97 4         28 my ($data_refs, $desc_refs, $num_subsets) = readdata($data_file);
98              
99 4         43 $bufr->set_number_of_subsets($num_subsets);
100              
101             # Print the encoded BUFR message
102 4         30 my $buffer = $bufr->encode_message($data_refs, $desc_refs);
103 4 100       42 if ($option{outfile}) {
104 1         3 my $outfile = $option{outfile};
105 1 50       344 open my $fh, '>', $outfile or die "Can't open $outfile: $!";
106 1         5 binmode($fh);
107 1         0 print $fh $buffer;
108             } else {
109 3         16 binmode(STDOUT);
110 3         0 print $buffer;
111             }
112              
113             # See OPTIONS section in pod for format of metadata file
114             sub read_metadata {
115 4     4   14 my ($file, $bufr) = @_;
116              
117             # Read metadata from file into a hash
118 4         9 my %metadata;
119 4 50       430 open (my $fh, '<', $file) or die "Cannot open $file: $!";
120 4         159 while ( <$fh> ) {
121 77         108 chomp;
122 77 50       234 next if /^\s*$/;
123 77         120 s/^\s+//;
124 77         236 my ($key, $value) = split /\s+/, $_, 2;
125 77         358 $metadata{$key} = $value;
126             }
127 4 50       69 close $fh or die "Cannot close $file: $!";
128              
129             # Load the metadata into the BUFR object
130 4         12 my $m = \%metadata;
131              
132 4         15 my $bufr_edition = $m->{BUFR_EDITION};
133              
134 4         29 $bufr->set_bufr_edition($bufr_edition);
135 4         22 $bufr->set_master_table($m->{MASTER_TABLE});
136 4         25 $bufr->set_centre($m->{CENTRE});
137 4         23 $bufr->set_subcentre($m->{SUBCENTRE});
138 4         21 $bufr->set_update_sequence_number($m->{UPDATE_SEQUENCE_NUMBER});
139 4         21 $bufr->set_optional_section($m->{OPTIONAL_SECTION});
140 4         51 $bufr->set_data_category($m->{DATA_CATEGORY});
141 4 100       15 if ( $bufr_edition < 4 ) {
142 2         11 $bufr->set_data_subcategory($m->{DATA_SUBCATEGORY});
143             } else {
144 2         12 $bufr->set_int_data_subcategory($m->{INT_DATA_SUBCATEGORY});
145 2         10 $bufr->set_loc_data_subcategory($m->{LOC_DATA_SUBCATEGORY});
146             }
147 4         40 $bufr->set_master_table_version($m->{MASTER_TABLE_VERSION});
148 4         22 $bufr->set_local_table_version($m->{LOCAL_TABLE_VERSION});
149 4 100       21 if ( $bufr_edition < 4 ) {
150 2         11 $bufr->set_year_of_century($m->{YEAR_OF_CENTURY});
151             } else {
152 2         11 $bufr->set_year($m->{YEAR});
153             }
154 4         25 $bufr->set_month($m->{MONTH});
155 4         23 $bufr->set_day($m->{DAY});
156 4         27 $bufr->set_hour($m->{HOUR});
157 4         31 $bufr->set_minute($m->{MINUTE});
158 4 100       37 $bufr->set_second($m->{SECOND}) if $bufr_edition >= 4;
159 4         28 $bufr->set_observed_data($m->{OBSERVED_DATA});
160 4         28 $bufr->set_compressed_data($m->{COMPRESSED_DATA});
161 4         32 $bufr->set_descriptors_unexpanded($m->{DESCRIPTORS_UNEXPANDED});
162 4 50       23 $bufr->set_local_use($m->{LOCAL_USE}) if exists $m->{LOCAL_USE};
163              
164 4         47 return;
165             }
166              
167             # See OPTIONS section in pod for format of data file
168             sub readdata {
169 4     4   12 my $file = shift;
170 4 50       287 open (my $fh, '<', $file) or die "Cannot open $file: $!";
171              
172 4         16 my ($data_refs, $desc_refs);
173 4         10 my $subset = 0;
174 4         181 while ( <$fh> ) {
175 1328         2938 s/^\s+//;
176             # Lines not starting with a number are ignored
177 1328 100       2794 next if not /^\d/;
178 1302         2770 my ($n, $desc, $value) = split /\s+/, $_, 3;
179 1302 100       2147 $subset++ if $n == 1;
180             # Some operator descriptors are written on unnumbered lines
181             # without a value
182 1302 100 66     3456 if (!defined $desc || $desc !~ /^\d/) {
183 2 50 33     8 next unless $n >= 200000 && $n < 300000; # Better to die here?
184 2         3 $desc = $n;
185 2         3 $value = undef;
186             } else {
187 1300         2770 $value =~ s/\s+$//;
188 1300 100 100     3362 $value = undef if $value eq '' or $value eq 'missing';
189             }
190 1302         1264 push @{$data_refs->[$subset]}, $value;
  1302         1952  
191 1302         1354 push @{$desc_refs->[$subset]}, $desc;
  1302         3894  
192             }
193 4 50       73 close $fh or die "Cannot close $file: $!";
194              
195 4         47 return ($data_refs, $desc_refs, $subset);
196             }
197              
198             =pod
199              
200             =encoding utf8
201              
202             =head1 SYNOPSIS
203              
204             bufrencode.pl --data --metadata
205             [--outfile ]
206             [--strict_checking n]
207             [--tableformat ]
208             [--tablepath ]
209             [--verbose n]
210             [--help]
211              
212             =head1 DESCRIPTION
213              
214             Encode a BUFR message, reading data and metadata from files. The
215             resulting BUFR message will be printed to STDOUT unless option
216             C<--outfile> is set.
217              
218             Execute without arguments for Usage, with option --help for some
219             additional info. See also L for
220             examples 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 Will print the encoded BUFR message to
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
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_
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
247              
248             For the metadata file, use this as a prototype and change the values
249             as 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              
272             For BUFR edition < 4, replace the lines INT_DATA_SUBCATEGORY,
273             LOC_DATA_SUBCATEGORY, YEAR and SECOND with new lines DATA_SUBCATEGORY
274             and YEAR_OF_CENTURY (the order of lines doesn't matter).
275              
276             =head4 --data
277              
278             For the data file, use the same format as would result if you did run
279             on the generated BUFR message
280              
281             bufrread.pl --data_only | cut -c -31
282              
283             or if you use bufrread.pl with C<--width n>, replace 31 with n+16.
284             For 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              
292             Every time a new line starting with the number 1 is met, a new subset
293             will be generated in the BUFR message. Lines not starting with a
294             number are ignored.
295              
296             For missing values, use 'missing' or stop the line after the BUFR
297             descriptor.
298              
299             Associated values should use BUFR descriptor 999999, and operator
300             descriptors 22[2345]000 and 23[2567]000 should not have a value,
301             neither should this line be numbered, e.g.
302              
303             160 011002 missing
304             222000
305             161 031002 160
306             162 031031 0
307             ...
308              
309             To encode a NIL subset, all delayed replication factors should be
310             nonzero, and all other values set to missing except for the
311             descriptors defining the station.
312              
313             Options may be abbreviated, e.g. C<--h> or C<-h> for C<--help>
314              
315             =head1 AUTHOR
316              
317             Pål Sannes Epal.sannes@met.noE
318              
319             =head1 COPYRIGHT
320              
321             Copyright (C) 2010-2025 MET Norway
322              
323             =cut