Differences
This shows you the differences between two versions of the page.
| Both sides previous revision Previous revision Next revision | Previous revision | ||
|
bufr.pm:bufrdump.pl_source [2010-10-06 07:25:13] pals |
bufr.pm:bufrdump.pl_source [2022-05-31 09:29:31] (current) |
||
|---|---|---|---|
| Line 1: | Line 1: | ||
| - | < | + | < |
| # | # | ||
| Line 26: | Line 26: | ||
| use constant DEFAULT_TABLE_PATH => '/ | use constant DEFAULT_TABLE_PATH => '/ | ||
| - | my $BUFRDUMP = '/ | + | my $BUFRDUMP = ' |
| + | # installed in a non-standard place | ||
| # Parse command line options | # Parse command line options | ||
| Line 34: | Line 35: | ||
| ' | ' | ||
| ' | ' | ||
| - | ' | + | ' |
| - | ' | + | ' |
| ' | ' | ||
| - | ' | + | ' |
| + | ' | ||
| ' | ' | ||
| + | ' | ||
| ' | ' | ||
| ' | ' | ||
| Line 44: | Line 47: | ||
| ' | ' | ||
| ' | ' | ||
| + | ' | ||
| ) or pod2usage(-verbose => 0); | ) or pod2usage(-verbose => 0); | ||
| Line 54: | Line 58: | ||
| # --csv can only be used together with --param | # --csv can only be used together with --param | ||
| pod2usage(-verbose => 0) if $option{csv} && !$option{param}; | pod2usage(-verbose => 0) if $option{csv} && !$option{param}; | ||
| - | |||
| - | # --sort and --sort_on are exclusive | ||
| - | pod2usage(-verbose => 0) if $option{sort} && $option{sort_on}; | ||
| # Prevent ECMWF software from printing table info | # Prevent ECMWF software from printing table info | ||
| Line 71: | Line 72: | ||
| $ENV{BUFR_TABLES} .= '/' | $ENV{BUFR_TABLES} .= '/' | ||
| - | my $filter | + | die " |
| + | if ! -d $ENV{BUFR_TABLES}; | ||
| + | |||
| + | my $obstype | ||
| + | my $filt = $option{filter} ? "$option{filter}" | ||
| my $lon1 = $option{lon1} ? " | my $lon1 = $option{lon1} ? " | ||
| my $lat1 = $option{lat1} ? " | my $lat1 = $option{lat1} ? " | ||
| my $lon2 = $option{lon2} ? " | my $lon2 = $option{lon2} ? " | ||
| my $lat2 = $option{lat2} ? " | my $lat2 = $option{lat2} ? " | ||
| + | my $del = $option{delimiter} ? " | ||
| + | my $delimiter = $option{delimiter} ? " | ||
| - | my $criteria_ref; | + | # Any filter criteria provided? |
| - | $criteria_ref = read_filter_file($option{filter}) if $filter; | + | my ($filter, |
| - | my $param_file | + | # Any specific stations requested? |
| - | my ($forced_params_ref, $params_ref); | + | my ($req_id, $req_stn_ref) |
| - | ($forced_params_ref, $params_ref) | + | |
| - | | + | # Any specific parameters specified? |
| + | my ($params_ref, $forced_params_ref) = get_params($option{param}); | ||
| my $csv = $option{csv} ? 1 : 0; | my $csv = $option{csv} ? 1 : 0; | ||
| # First line in CSV should be the parameters | # First line in CSV should be the parameters | ||
| - | print join(',' | + | print join($del, @$params_ref) . " |
| + | # Any transformations of units specified? | ||
| my $transform_file = $option{transform} ? $option{transform} : 0; | my $transform_file = $option{transform} ? $option{transform} : 0; | ||
| - | my $transform_ref; | + | my $transform_ref = read_transformation_file($transform_file); |
| - | $transform_ref = read_transformation_file($transform_file) | + | |
| + | # Any sorting requested? | ||
| my $sort = $option{sort} ? 1 : 0; | my $sort = $option{sort} ? 1 : 0; | ||
| my $sort_on = $option{sort_on} ? $option{sort_on} : ''; | my $sort_on = $option{sort_on} ? $option{sort_on} : ''; | ||
| - | # What kind of sorting is required? | + | # What kind of sorting is required (if any)? |
| - | my $sort_by; | + | ($sort_on, my $by) = get_sort_method($sort_on, $sort); |
| - | if ($sort_on) { | + | |
| - | my @ascii_params = qw(call_sign icao_id obstime name type); | + | |
| - | my $ascending_sort = 1; | + | |
| - | # A minus sign appended to the sort parameter means descending sort | + | |
| - | | + | |
| - | | + | |
| - | chop $sort_on; | + | |
| - | } | + | |
| - | # Just in case someone adds a ' | + | |
| - | if ($sort_on | + | |
| - | $ascending_sort | + | |
| - | chop $sort_on; | + | |
| - | } | + | |
| - | if (grep {$_ eq $sort_on} @ascii_params) { | + | |
| - | if ($ascending_sort) { | + | |
| - | $sort_by = sub {$a cmp $b}; | + | |
| - | } else { | + | |
| - | $sort_by = sub {$b cmp $a}; | + | |
| - | } | + | |
| - | } else { | + | |
| - | if ($ascending_sort) { | + | |
| - | $sort_by = sub {$a <=> $b}; | + | |
| - | } else { | + | |
| - | $sort_by = sub {$b <=> $a}; | + | |
| - | } | + | |
| - | } | + | |
| - | } | + | |
| # Loop for processing of BUFR input files | # Loop for processing of BUFR input files | ||
| Line 131: | Line 113: | ||
| # Dump the content of the BUFR file using the Fortran program $BUFRDUMP | # Dump the content of the BUFR file using the Fortran program $BUFRDUMP | ||
| - | my $dump = `$BUFRDUMP | + | my $fortran_options |
| + | my $dump = `$BUFRDUMP $fortran_options | ||
| die if $?; # Reason for bufrdump failing should have been printed to STDERR | die if $?; # Reason for bufrdump failing should have been printed to STDERR | ||
| Line 138: | Line 121: | ||
| # Add an empty line to simplify processing | # Add an empty line to simplify processing | ||
| push @lines, ''; | push @lines, ''; | ||
| - | my $station | + | my $stnid = ''; |
| - | # Skip first(blank) line | + | # Skip first (blank) line |
| shift @lines; | shift @lines; | ||
| Line 146: | Line 129: | ||
| my %msg; # Hash with parameter name as key, parameter value as value | my %msg; # Hash with parameter name as key, parameter value as value | ||
| - | LINE:while (defined(my $line = shift @lines)) { | + | LINE: |
| + | | ||
| # Each new message starts with a blank line | # Each new message starts with a blank line | ||
| if ($line !~ /^\s*$/) { | if ($line !~ /^\s*$/) { | ||
| + | # Skip error messages from libbufr, which should start with space(s) | ||
| + | next LINE if $line =~ /^\s+/; | ||
| # Build up the message to be (possibly) printed | # Build up the message to be (possibly) printed | ||
| - | my ($param, $value) = ($line =~ / | + | my ($param, $value) = ($line =~ / |
| + | # Know only of one case where next check is necessary: if | ||
| + | # a CCITT IA5 value contains new line (\n) | ||
| + | next LINE if !defined $value; | ||
| if ($transform_file && $transform_ref-> | if ($transform_file && $transform_ref-> | ||
| # Replace value with transformed value | # Replace value with transformed value | ||
| Line 166: | Line 155: | ||
| if ($line =~ /^\s*$/ or @lines == 0) { | if ($line =~ /^\s*$/ or @lines == 0) { | ||
| # A full message has been completed. Should it be printed? | # A full message has been completed. Should it be printed? | ||
| - | if ($filter | + | if ($filt && filter_obs(\%msg, |
| # Skip this message | # Skip this message | ||
| + | } elsif ($req_id && filter_station(\%msg, | ||
| + | # Skip this station | ||
| } else { | } else { | ||
| # Print the message (or if --sort or --sort_on: save the message) | # Print the message (or if --sort or --sort_on: save the message) | ||
| my $txt = ''; | my $txt = ''; | ||
| - | if ($param_file) { | + | if ($params_ref) { |
| # Print the params in @$params_ref if exists in | # Print the params in @$params_ref if exists in | ||
| # message, in same order as in @$params_ref | # message, in same order as in @$params_ref | ||
| foreach my $name (@$params_ref) { | foreach my $name (@$params_ref) { | ||
| if (exists $msg{$name}) { | if (exists $msg{$name}) { | ||
| - | $txt .= $csv ? $msg{$name} . ',' | + | $txt .= $csv ? $msg{$name} . $del : " |
| } elsif ($forced_params_ref-> | } elsif ($forced_params_ref-> | ||
| - | $txt .= $csv ? ' | + | $txt .= $csv ? ' |
| } elsif ($csv) { | } elsif ($csv) { | ||
| - | $txt .= ',' | + | $txt .= $del; |
| } | } | ||
| } | } | ||
| Line 189: | Line 180: | ||
| } | } | ||
| } | } | ||
| - | | + | if ($csv) { |
| + | | ||
| + | for (1 .. length($del)) { | ||
| + | chop $txt; | ||
| + | } | ||
| + | } | ||
| if ($txt) { | if ($txt) { | ||
| if ($sort) { | if ($sort) { | ||
| - | # Sort wmonr before call signs before buoy_id | + | # Sort wmonr before nationalnr |
| + | # buoy_id | ||
| if ($msg{wmonr}) { | if ($msg{wmonr}) { | ||
| - | $station | + | $stnid = ' |
| + | } elsif ($msg{nationalnr}) { | ||
| + | $stnid = ' | ||
| } elsif ($msg{call_sign}) { | } elsif ($msg{call_sign}) { | ||
| - | $station | + | $stnid = '20_' . $msg{call_sign}; |
| } elsif ($msg{buoy_id}) { | } elsif ($msg{buoy_id}) { | ||
| - | $station | + | $stnid = '30_' . $msg{buoy_id}; |
| + | } elsif ($msg{aircraft}) { | ||
| + | $stnid = ' | ||
| + | } elsif ($msg{icao_id}) { | ||
| + | $stnid = ' | ||
| + | } elsif ($msg{wigosid}) { | ||
| + | $stnid = ' | ||
| } else { | } else { | ||
| # Skip observation if no station identification found | # Skip observation if no station identification found | ||
| next LINE; | next LINE; | ||
| } | } | ||
| - | $data_of{$station} = exists $data_of{$station} | + | |
| - | ? $data_of{$station} . "$txt \n" : "$txt \n"; | + | my $val = exists $msg{$sort_on} ? $msg{$sort_on} : ''; |
| + | my $key = $stnid . ' | ||
| + | | ||
| + | ? $data_of{$key} . " | ||
| + | } else { | ||
| + | $data_of{$stnid} = exists $data_of{$stnid} | ||
| + | ? $data_of{$stnid} . " | ||
| + | } | ||
| } elsif ($sort_on) { | } elsif ($sort_on) { | ||
| my $val = exists $msg{$sort_on} ? $msg{$sort_on} : ''; | my $val = exists $msg{$sort_on} ? $msg{$sort_on} : ''; | ||
| $data_of{$val} = exists $data_of{$val} | $data_of{$val} = exists $data_of{$val} | ||
| - | | + | |
| } else { | } else { | ||
| # No sorting. We can print the line immediately | # No sorting. We can print the line immediately | ||
| Line 223: | Line 235: | ||
| # If sorting requested, we cannot print before now | # If sorting requested, we cannot print before now | ||
| - | if ($sort) { | + | if ($sort && $sort_on) { |
| + | for (sort $by keys %data_of) { | ||
| + | print $data_of{$_}; | ||
| + | } | ||
| + | } elsif ($sort) { | ||
| for (sort keys %data_of) { | for (sort keys %data_of) { | ||
| print $data_of{$_}; | print $data_of{$_}; | ||
| Line 231: | Line 247: | ||
| my $data_of_missing_value = $data_of{'' | my $data_of_missing_value = $data_of{'' | ||
| delete $data_of{'' | delete $data_of{'' | ||
| - | for (sort $sort_by | + | for (sort $by keys %data_of) { |
| print $data_of{$_}; | print $data_of{$_}; | ||
| } | } | ||
| Line 238: | Line 254: | ||
| - | sub read_param_file | + | # Read the filter conditions (if any). Return the filter option to be |
| - | my $parameter_file | + | # used by bufrdump, the found criteria (if any) as well as the number |
| + | # of succeeding alternatives for each criterium | ||
| + | sub get_filter_conditions | ||
| + | my $filt = shift; | ||
| + | return ('' | ||
| - | open my $PARAM, '<', | + | |
| - | or die " | + | my @f; |
| + | if ($filt =~ /,/) { | ||
| + | # Argument to --filter is a comma separated list | ||
| + | @f = split /,/, $filt; | ||
| + | } else { | ||
| + | # Argument to --filter is a file | ||
| + | $fortran_filter = " | ||
| + | | ||
| + | or die " | ||
| + | # Skip the criteria meant for Fortran parsing, i.e. proceed to | ||
| + | # first line following a blank line | ||
| + | while (< | ||
| + | last if $_ =~ /^\s*$/; | ||
| + | } | ||
| + | @f = < | ||
| + | close $FILTER or die " | ||
| + | } | ||
| + | return ($fortran_filter) if !@f; # BUFR descriptor criteria only | ||
| - | my %forced_params; | + | my @allowed_operators = |
| - | my @params; | + | (' |
| - | # Read the parameters into @params, those preceded by an | + | '<', |
| - | # exclamation mark also into %forced_params, skipping blank lines | + | '< |
| - | # and comment lines | + | '>', |
| - | | + | '> |
| - | $name =~ s/^\s+//; | + | ' |
| - | $name =~ s/\s+$//; | + | ' |
| - | next if !$name || $name =~ /^#/; | + | ' |
| - | | + | ); |
| - | $name = substr | + | my @criteria; |
| - | $forced_params{$name} = 1; | + | |
| + | # i.e. if line is '< | ||
| + | # corresponding values in @num_alt will be 2,1,0 | ||
| + | |||
| + | # Read the filter criteria meant for Perl parsing, skipping blank | ||
| + | # lines and comment lines | ||
| + | FILTERLINE: | ||
| + | | ||
| + | $line =~ s/^\s+//; | ||
| + | $line =~ s/\s+$//; | ||
| + | next FILTERLINE | ||
| + | | ||
| + | my $num = scalar @crit; | ||
| + | foreach my $criterium | ||
| + | | ||
| + | $criterium | ||
| + | | ||
| + | push @criteria, | ||
| + | push @num_alt, --$num; | ||
| + | } | ||
| } | } | ||
| - | push @params, $name; | ||
| } | } | ||
| - | | + | |
| - | return \%forced_params, \@params; | + | |
| + | foreach my $criterium (@criteria) { | ||
| + | # Naked parameter possibly preceded by ' | ||
| + | next if $criterium =~ / | ||
| + | |||
| + | my $op = (split / +/, $criterium)[1]; | ||
| + | if (!defined($op) or grep(/ | ||
| + | or !grep(/ | ||
| + | print "Error in $filt: | ||
| + | . " or operator not supported: | ||
| + | exit 1; | ||
| + | } | ||
| + | } | ||
| + | | ||
| } | } | ||
| + | |||
| + | |||
| + | # Read the parameters into @params, those preceded by an exclamation | ||
| + | # mark also into %forced_params, | ||
| + | sub get_params { | ||
| + | my $params = shift; | ||
| + | return if ! $params; | ||
| + | |||
| + | my @params; | ||
| + | my %forced_params; | ||
| + | |||
| + | if ($params =~ /,/) { | ||
| + | # Argument to --params is a comma separated list | ||
| + | my @p = split /,/, $params; | ||
| + | foreach my $name (@p) { | ||
| + | $name =~ s/^\s+//; | ||
| + | $name =~ s/\s+$//; | ||
| + | if ($name =~ /^!/) { | ||
| + | $name = substr $name, 1; | ||
| + | $forced_params{$name} = 1; | ||
| + | } | ||
| + | push @params, $name; | ||
| + | } | ||
| + | } else { | ||
| + | # Argument to --params is a file | ||
| + | open my $PARAM, '<', | ||
| + | or die " | ||
| + | while (my $name = < | ||
| + | # Skip blank lines and comment lines | ||
| + | $name =~ s/^\s+//; | ||
| + | $name =~ s/\s+$//; | ||
| + | next if !$name || $name =~ /^#/; | ||
| + | if ($name =~ /^!/) { | ||
| + | $name = substr $name, 1; | ||
| + | $forced_params{$name} = 1; | ||
| + | } | ||
| + | push @params, $name; | ||
| + | } | ||
| + | close $PARAM or die " | ||
| + | } | ||
| + | return \@params, \%forced_params; | ||
| + | } | ||
| + | |||
| sub read_transformation_file { | sub read_transformation_file { | ||
| my $transform_file = shift; | my $transform_file = shift; | ||
| + | return if !$transform_file; | ||
| open my $TRANSFORM, '<', | open my $TRANSFORM, '<', | ||
| Line 276: | Line 388: | ||
| $line =~ s/^\s+//; | $line =~ s/^\s+//; | ||
| $line =~ s/\s+$//; | $line =~ s/\s+$//; | ||
| - | next if !$line || $line =~ /^#/; | + | |
| my ($param, $transform) = split /=/, $line, 2; | my ($param, $transform) = split /=/, $line, 2; | ||
| die " | die " | ||
| Line 284: | Line 396: | ||
| $transform_of{$param} = $transform; | $transform_of{$param} = $transform; | ||
| } | } | ||
| + | close $TRANSFORM or die " | ||
| return \%transform_of; | return \%transform_of; | ||
| } | } | ||
| - | sub read_filter_file | + | # Return true (1) if observation is to be filtered, i.e. does not |
| - | my $filter_file | + | # comply with at least one line in filter file, where each line is one |
| - | my @allowed_operators = | + | # or more alternatives < |
| - | ('=', | + | sub filter_obs |
| - | '<', | + | my $msg_ref |
| - | '< | + | my $criteria_ref |
| - | '>', | + | my $num_alt_ref = shift; # gives the number of alternative |
| - | '> | + | # |
| - | ' | + | return unless $criteria_ref; |
| - | ); | + | |
| - | my @criteria; | + | |
| - | | + | my @ascii_params = qw(aircraft call_sign icao_id name obstime type wigosid); |
| - | or die " | + | |
| - | # Skip the criteria meant for Fortran parsing, i.e. proceed to | + | |
| - | # first line following a blank line | + | |
| - | while (< | + | |
| - | last if $_ =~ /^\s*$/; | + | |
| - | } | + | |
| - | # Read the filter criteria meant for Perl parsing, skipping blank | + | # Note that the loop counter $i might be changed in the loop |
| - | | + | |
| - | while (my $line = <$FILTER>) { | + | |
| - | $line =~ s/^\s+//; | + | |
| - | $line =~ s/\s+$//; | + | |
| - | | + | |
| - | push @criteria, $line; | + | |
| - | } | + | |
| - | | + | |
| - | | + | |
| - | # Naked parameter is ok | + | |
| - | | + | |
| + | if (exists | ||
| + | next if $num_alt; # More alternatives to check | ||
| - | my $op = (split / +/, $criterium)[1]; | + | # Criterium not fulfilled and no more alternatives to |
| - | if (!defined($op) or grep(/ | + | # check. This observation should be filtered away |
| - | print "Error in $filter_file, | + | return |
| - | | + | } else { |
| - | exit 1; | + | # Criterium fulfilled. No need to check alternative criteria |
| + | $i += $num_alt if $num_alt; | ||
| + | next; | ||
| + | } | ||
| } | } | ||
| - | } | ||
| - | return \@criteria; | ||
| - | } | ||
| - | # Return true (1) if observation | + | |
| - | # comply with at least one of the <param> or < | + | if (not exists $msg_ref->{$f_param}) |
| - | # < | + | next if $num_alt; |
| - | sub filter_obs | + | |
| - | | + | } |
| - | my $criteria_ref = shift; | + | |
| - | | + | |
| + | # If a naked parameter criterium, we already know parameter is | ||
| + | # present | ||
| + | if (not defined $f_operator) { | ||
| + | if ($num_alt) { | ||
| + | # No need to check the alternative criteria | ||
| + | $i += $num_alt; | ||
| + | } | ||
| + | next; | ||
| + | } | ||
| - | foreach my $criterium (@$criteria_ref) { | ||
| - | my ($f_param, $f_operator, | ||
| - | return 1 unless exists $msg_ref-> | ||
| - | next if not defined $f_operator; | ||
| - | # present, so criterium fulfilled | ||
| chomp $f_value; | chomp $f_value; | ||
| - | if ($f_operator eq ' | + | |
| + | | ||
| + | || $f_operator eq '< | ||
| + | || $f_operator eq '>' | ||
| + | || $f_operator eq '> | ||
| + | || $f_operator eq ' | ||
| + | || $f_operator eq ' | ||
| + | $op = $f_operator; | ||
| + | } elsif ($f_operator eq ' | ||
| + | || $f_operator eq '!=') { | ||
| if (grep {$_ eq $f_param} @ascii_params) { | if (grep {$_ eq $f_param} @ascii_params) { | ||
| - | $msg_ref-> | + | $msg_value |
| - | | + | $op = ($f_operator |
| } else { | } else { | ||
| - | | + | $op = ($f_operator eq '=') ? '==' : ' |
| } | } | ||
| - | } elsif ($f_operator eq '<' | + | } else { |
| - | | + | |
| - | } elsif ($f_operator eq '<=') { | + | } |
| - | | + | |
| - | } elsif ($f_operator | + | # Some parameters might need special massaging |
| - | | + | if ($f_operator |
| - | } elsif ($f_operator | + | if ($f_param |
| - | | + | # Make non octal by removing leading 0 |
| - | } elsif ($f_operator | + | |
| - | if (grep {$_ eq $f_param} @ascii_params) { | + | |
| - | | + | } elsif ($f_param |
| - | | + | # Convert to a pure numerical value (float). For 001101 |
| - | } else { | + | # State id only numbers between 100 and 699 are operational |
| - | | + | |
| + | | ||
| + | } elsif ($f_param | ||
| + | # Convert to a pure numerical value (float) | ||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | $f_value =~ s/ +$//; | ||
| + | $f_value =~ s/ /./; | ||
| + | | ||
| + | # Add or correct quoting to "" | ||
| + | $msg_value = '"' | ||
| + | $f_value = lc $f_value; | ||
| + | | ||
| + | $f_value | ||
| + | $f_value | ||
| + | } | ||
| + | | ||
| + | | ||
| + | } | ||
| } | } | ||
| } | } | ||
| + | |||
| + | my $condition = " | ||
| + | # Some values should be string values | ||
| + | if ($f_operator =~ /~/) { | ||
| + | $condition = " | ||
| + | } elsif (grep {$_ eq $f_param} @ascii_params) { | ||
| + | $condition = " | ||
| + | } | ||
| + | |||
| + | # Finally, do the criterium check | ||
| + | if (eval $condition) { | ||
| + | # No need to check the remaining alternative criteria | ||
| + | $i += $num_alt if $num_alt; | ||
| + | next; | ||
| + | } else { | ||
| + | next if $num_alt; | ||
| + | return 1; | ||
| + | } | ||
| } | } | ||
| # All filter conditions have been fullfilled | # All filter conditions have been fullfilled | ||
| return 0; | return 0; | ||
| + | } | ||
| + | |||
| + | # Return the type of station requested, and the station | ||
| + | # identifications. Leave some leeway for how to list wmonr and | ||
| + | # nationalnr (leading 0's might be omitted - added here) | ||
| + | sub get_requested_stations { | ||
| + | my $req_stations = shift; | ||
| + | return if !$req_stations; | ||
| + | die " | ||
| + | . "' | ||
| + | unless $req_stations | ||
| + | =~ / | ||
| + | |||
| + | my ($id, $rest) = split /=/, $req_stations; | ||
| + | my @stations = split /,/, $rest; | ||
| + | if ($id eq ' | ||
| + | my @req_stn; | ||
| + | foreach my $station (@stations) { | ||
| + | # Turn $station into a 5 digit wmonr | ||
| + | $station =~ s/^0+//; | ||
| + | $station += 1000 if $station < 1000; | ||
| + | $station = sprintf(" | ||
| + | push @req_stn, $station; | ||
| + | } | ||
| + | return ($id, \@req_stn); | ||
| + | } elsif ($id eq ' | ||
| + | my @req_stn; | ||
| + | foreach my $station (@stations) { | ||
| + | # Turn national station number into 10 digits | ||
| + | my ($state_id, $national_id) = split /_/, $station; | ||
| + | die " | ||
| + | if !defined $national_id or $national_id eq ''; | ||
| + | $station = $state_id . ' | ||
| + | push @req_stn, $station; | ||
| + | } | ||
| + | return ($id, \@req_stn); | ||
| + | } else { | ||
| + | return ($id, \@stations); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | # Return true (1) if observation does not contain one of the stations | ||
| + | # listed in @$req_stn_ref (of type $req_id), i.e. if this observation | ||
| + | # should be filtered away | ||
| + | sub filter_station { | ||
| + | my ($msg_ref, $req_id, $req_stn_ref) = @_; | ||
| + | |||
| + | return 1 unless $msg_ref-> | ||
| + | my $stn = $msg_ref-> | ||
| + | |||
| + | return !grep { $_ eq $stn } @$req_stn_ref; | ||
| + | } | ||
| + | |||
| + | |||
| + | # When --sort_on is used, need to supply the sort method. Also returns | ||
| + | # input parameter $sort_on with possible trailing ' | ||
| + | # stripped off | ||
| + | sub get_sort_method { | ||
| + | my ($sort_on, $sort) = @_; | ||
| + | return if !$sort_on; | ||
| + | |||
| + | my $ascending_sort = 1; | ||
| + | # A minus sign appended to the sort parameter means descending sort | ||
| + | if ($sort_on =~ /-$/) { | ||
| + | $ascending_sort = 0; | ||
| + | chop $sort_on; | ||
| + | } | ||
| + | # Just in case someone adds a ' | ||
| + | if ($sort_on =~ /[+]$/) { | ||
| + | chop $sort_on; | ||
| + | } | ||
| + | |||
| + | my @ascii_params = qw(aircraft call_sign icao_id obstime name type wigosid); | ||
| + | my $lexical_sort = grep {$_ eq $sort_on} @ascii_params; | ||
| + | |||
| + | my $sort_sub; | ||
| + | if ($sort) { | ||
| + | $sort_sub = sub { | ||
| + | my ($stn_a, $val_a) = split /\|/, $a; | ||
| + | my ($stn_b, $val_b) = split /\|/, $b; | ||
| + | # Sort on stationid is always lexical and ascending | ||
| + | my $cmp = $stn_a cmp $stn_b; | ||
| + | if ($cmp) { | ||
| + | return $cmp; | ||
| + | } else { | ||
| + | # Stationids are equal. Sort on value of sort_on parameter | ||
| + | if ($lexical_sort) { | ||
| + | if ($ascending_sort) { | ||
| + | return $val_a cmp $val_b; | ||
| + | } else { | ||
| + | return $val_b cmp $val_a; | ||
| + | } | ||
| + | } else { | ||
| + | # Numerical sort | ||
| + | if ($ascending_sort) { | ||
| + | return $val_a <=> $val_b; | ||
| + | } else { | ||
| + | return $val_b <=> $val_a; | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | } else { | ||
| + | # Sort restricted to sort_on parameter | ||
| + | $sort_sub = sub { | ||
| + | if ($lexical_sort) { | ||
| + | if ($ascending_sort) { | ||
| + | return $a cmp $b; | ||
| + | } else { | ||
| + | return $b cmp $a; | ||
| + | } | ||
| + | } else { | ||
| + | # Numerical sort | ||
| + | if ($ascending_sort) { | ||
| + | return $a <=> $b; | ||
| + | } else { | ||
| + | return $b <=> $a; | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | |||
| + | return ($sort_on, $sort_sub); | ||
| } | } | ||
| Line 380: | Line 657: | ||
| bufrdump.pl <bufr file(s)> | bufrdump.pl <bufr file(s)> | ||
| - | [--filter <filter file>] | + | [--filter <filter file | filter list>] |
| - | [--param < | + | [--param < |
| - | [--sort | + | [--sort] |
| + | [--sort_on < | ||
| + | [--station <station list>] | ||
| [--transform < | [--transform < | ||
| - | [--lon1 x1] | + | [--lon1 |
| - | [--lat1 y1] | + | [--lat1 |
| - | [--lon2 x2] | + | [--lon2 |
| - | [--lat2 | + | [--lat2 |
| + | [--obstype < | ||
| [--tablepath <path to BUFR tables>] | [--tablepath <path to BUFR tables>] | ||
| [--help] | [--help] | ||
| Line 406: | Line 686: | ||
| - | --filter <filter file> | + | --filter <filter file | filter list> |
| - | Decode observations meeting criteria in <filter file> only | + | Decode observations meeting criteria in filter file or |
| - | --param < | + | filter list only |
| - | Print parameters in <parameter file> only, in same order | + | --param < |
| - | as they occur in < | + | Print parameters in parameter file or comma |
| - | | + | separated list (e.g. wmonr, |
| + | as they occur there. If using --csv possibly | ||
| + | followed by --delimiter <del>, the parameters | ||
| + | be printed using the CSV (comma-separated values) | ||
| + | | ||
| --sort | --sort | ||
| - | first stations with wmonr, then stations with call sign, | + | first stations with wmonr, then stations with nationalnr, |
| - | | + | |
| + | | ||
| --sort_on < | --sort_on < | ||
| - | values of <parameter>, or decreasing values if a ' | + | values of parameter, or decreasing values if a ' |
| - | follows the parameter name. Observations not containing | + | follows the parameter name. E.g. --sort_on TA- will |
| - | the parameter at all will be printed lastly. E.g. | + | sort on decreasing temperatures. Observations not |
| - | --sort_on | + | |
| + | | ||
| + | case sorting is done firstly | ||
| + | secondly on parameter with missing values printed first) | ||
| + | --station <station list> | ||
| + | Print observations for stations in station list only, | ||
| + | e.g. wmonr=01384, | ||
| --transform < | --transform < | ||
| Do the transformations of parameter values listed in | Do the transformations of parameter values listed in | ||
| - | | + | transformation file |
| - | --lon1 x1 | + | --lon1 |
| - | --lat1 y1 | + | --lat1 |
| - | --lon2 x2 | + | --lon2 |
| - | --lat2 y2 | + | --lat2 |
| x1,y1,x2,y2 should be decimal degrees | x1,y1,x2,y2 should be decimal degrees | ||
| + | --obstype < | ||
| + | Force observation type. If this option is not set, | ||
| + | will make an educated guess of observation type | ||
| + | based on metadata in section 1 of each BUFR message | ||
| --tablepath <path to BUFR tables> | --tablepath <path to BUFR tables> | ||
| Set path to BUFR tables (overrides ENV{BUFR_TABLES}) | Set path to BUFR tables (overrides ENV{BUFR_TABLES}) | ||
| - | --help | + | --help |
| + | perldoc bufrdump.pl) | ||
| Options may be abbreviated, | Options may be abbreviated, | ||
| To avoid having to use the C< | To avoid having to use the C< | ||
| - | set the invironment | + | set the environment |
| BUFR tables are located (unless the default path provided by | BUFR tables are located (unless the default path provided by | ||
| bufrdump.pl works for you). | bufrdump.pl works for you). | ||
| - | The lines in < | + | The lines in < |
| - | want to be printed. For example, if you want only station | + | < |
| - | identification and temperature to be printed for a BUFR SYNOP file, | + | printed. For example, if you want only station identification and |
| - | the < | + | temperature to be printed for a BUFR SYNOP file, either supply |
| + | |||
| + | wmonr, | ||
| + | |||
| + | as argument to --params, or supply a < | ||
| + | look like this: | ||
| wmonr | wmonr | ||
| + | nationalnr | ||
| call_sign | call_sign | ||
| TA | TA | ||
| Line 450: | Line 752: | ||
| If you want " | If you want " | ||
| in BUFR message, precede the parameter name with an exclamation mark | in BUFR message, precede the parameter name with an exclamation mark | ||
| - | (e.g. ' | + | (e.g. ' |
| + | argument to --param is a parameter list, you must prevent the shell | ||
| + | from attaching special meaning to the exclamation mark by enclosing the | ||
| + | list in single quotes. | ||
| + | |||
| + | If the parameter list consists of one parameter only, a comma must be | ||
| + | appended (e.g. ' | ||
| + | comma to signal that this is not a filename but parameter name(s). | ||
| If --csv is used in conjunction with --param, all values will be | If --csv is used in conjunction with --param, all values will be | ||
| printed using the CSV format, with first line listing the parameters, | printed using the CSV format, with first line listing the parameters, | ||
| and with missing fields printed as -32767 if the parameter is marked | and with missing fields printed as -32767 if the parameter is marked | ||
| - | with ' | + | with ' |
| - | may for example start like | + | listing |
| + | |||
| + | wmonr; | ||
| + | 01001;;; | ||
| + | ;;LF5U;9.0 | ||
| - | wmonr, | + | You can choose another delimiter than semicolon by use of option |
| - | | + | --delimiter <del>, e.g. --csv --delimiter ',' |
| - | | + | |
| Using --filter will decode only those observations that meet at least | Using --filter will decode only those observations that meet at least | ||
| Line 466: | Line 778: | ||
| in <filter file>, where the BUFR descriptor criteria should come first | in <filter file>, where the BUFR descriptor criteria should come first | ||
| in filter file followed by a blank line, then comes the parameter | in filter file followed by a blank line, then comes the parameter | ||
| - | criteria which should match < | + | criteria which should match < |
| - | where operator is one of =, !=, <, <=, > and >=. An example filter | + | < |
| - | file is | + | follows =~ and !~ should be a Perl match regular expression. The parameter |
| + | criteria may be phrased as alternatives by separating them with ' | ||
| + | a single line. An example filter file is | ||
| D: 001001 I2.2 | D: 001001 I2.2 | ||
| Line 485: | Line 799: | ||
| which decodes all observations with block number 01, two other | which decodes all observations with block number 01, two other | ||
| - | specific wmo stations and one specific ship, being manned | + | specific wmo stations and one specific ship, where stations |
| - | having | + | manned and have cloud cover with a value different from 8, and have |
| - | message) | + | temperature between 5 and 9.5 degrees Celsius, and contain |
| - | containing | + | precipitation for last 24 hours. Comment lines starting with # will be |
| - | with # will be ignored. | + | ignored. |
| - | Another example: the simple | + | Another example: the filter file (starting with a blank line!) |
| - | | + | |
| + | obstime >= ' | ||
| + | HW | HWA | PW | PWA | ||
| + | FF > 10 | FG_010 > 10 | ||
| - | will print only those observations containing a wmonr (skipping | + | will print only those ship observations |
| - | ships). | + | call_sign starts with 2 letters in the interval LA-LN, and having |
| + | obstime larger or equal to the datetime given, and containing | ||
| + | data (specifically: | ||
| + | automatically measured), and with wind or 10 minutes gust more than 10 | ||
| + | m/s. | ||
| + | |||
| + | For convenience, | ||
| + | provide the filter criteria on the command line. Example: | ||
| + | |||
| + | --filter ' | ||
| + | |||
| + | will decode only observations with wmonr, having positive temperature | ||
| + | and containing precipitation for 12 or 24 hours and not reporting | ||
| + | wind. If (like for --param) the filter list consists of one criterium | ||
| + | only, a comma must be appended. | ||
| + | |||
| + | To avoid the need of creating a filter file when observations for some | ||
| + | few stations are requested, you can provide the stations in a comma | ||
| + | separated list after option --station. Some examples: | ||
| + | |||
| + | --station | ||
| + | --station nationalnr=614_0050410003, | ||
| + | --station call_sign=LF5U | ||
| + | --station buoyid=64607, | ||
| + | --station aircraft=EU3421, | ||
| + | --station icao_id=ENGM, | ||
| + | --station wigosid=0-376-0-511, | ||
| + | |||
| + | You cannot mix different kinds of stations this way (before ' | ||
| + | must choose either wmonr, nationalnr, call_sign, buoy_id, aircraft, | ||
| + | icao_id or wigosid). Note also that providing the stations in the BUFR | ||
| + | descriptor part (first part) of the filter file will speed up | ||
| + | execution time considerably, | ||
| + | possible to combine --filter with --station if done with some care, | ||
| + | e.g. specifying WMO block 01 and the required parameters in filter | ||
| + | file, then the requested stations in station list. | ||
| The --transform option is provided mainly to be able to use other | The --transform option is provided mainly to be able to use other | ||
| Line 520: | Line 872: | ||
| is to be applied for sky not all covered by clouds, you should use NN | is to be applied for sky not all covered by clouds, you should use NN | ||
| != 100 instead of NN != 8 in filter file. | != 100 instead of NN != 8 in filter file. | ||
| + | |||
| + | The --obstype option might be handy in some special cases, like when | ||
| + | you are interested only in the surface part of oceanographic data | ||
| + | (then use ' | ||
| + | with vss>0 in high resolution radiosonde data (then use ' | ||
| + | sounding-' | ||
| + | BUFR messages have unusual values. | ||
| =head1 AUTHOR | =head1 AUTHOR | ||