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