Weather-GHCN-Fetch
view release on metacpan or search on metacpan
lib/Weather/GHCN/App/Extremes.pm view on Meta::CPAN
my $ndays = $Opt->ndays // $DEFAULT_NDAYS;
my $cmp_op = $Opt->cold ? '<=' : '>=';
my $years_set = Set::IntSpan::Fast->new;
## no critic [RequireBriefOpen]
my ( $output, $new_fh, $old_fh );
if ( $Opt->outclip and $USE_WINCLIP ) {
open $new_fh, '>', \$output
or die 'Unable to open buffer for write';
$old_fh = select $new_fh; ## no critic (ProhibitOneArgSelect)
}
@files = $argv_aref->@*;
@files = ($DASH) unless @files;
foreach my $file (@files) {
my $fh;
if ($file eq $DASH) {
$fh = *STDIN;
} else {
open $fh, q(<), $file or die;
}
@ExtremeWaves = ();
%Location = ();
# controlling on bool is_extreme and alpha stnid, minor to major
my $cb = ControlBreak->new( '+XT', 'STNID', 'EOF' );
read_data( $fh, $cb, $limit );
}
my $years_href;
if ( $Opt->peryear ) {
$years_href = report_extremes_per_year($limit, $ndays, $cmp_op);
} else {
$years_href = report_extremes_daycounts($limit, $ndays, $cmp_op)
}
# generate lines for each year that was missing
if ($Opt->nogaps) {
foreach my $stnid (keys $years_href->%*) {
my @years = sort keys $years_href->{$stnid}->%*;
my $s = Set::IntSpan::Fast->new( min(@years) .. max(@years) );
my $t = Set::IntSpan::Fast->new( @years );
my $gaps = $s->diff($t);
my $iter = $gaps->iterate_runs();
while ( my ( $from, $to ) = $iter->() ) {
foreach my $yr ($from .. $to) {
say join $TAB, $stnid, $Location{$stnid}, $yr;
}
}
}
}
WRAP_UP:
# send output to the Windows clipboard
if ( $Opt->outclip and $USE_WINCLIP ) {
Win32::Clipboard->new()->Set( $output );
select $old_fh; ## no critic [ProhibitOneArgSelect]
}
return;
}
########################################################################
# Script-specific Subroutines
########################################################################
=head2 read_data ( $fh, $cb, $limit )
Read weather data from the filehandle and collect extreme waves
according to $limit and $Opt->cold (true for cold waves, false for
heat waves).
=cut
sub read_data ( $fh, $cb, $limit ) {
my $extremes_begins;
my @extreme_days;
my $lineno;
while ( my $data = <$fh> ) {
chomp $data;
next if $data eq $EMPTY;
last if $data =~ m{ \A Notes: }xms;
my ($year,$month,$day,$decade,$s_decade,$s_year,$s_qtr,$tmax,$tmin,$tavg,$qflags,$stnid,$loc ) = split $TAB, $data;
$lineno++;
if ($lineno == 1) {
die '*E* invalid input data: ' . $data
unless $year eq 'Year' and $tmax =~ m{ \A TMAX }xms and $tmin =~ m{ \A TMIN }xms;
next;
}
last unless $year =~ m{ \A \d{4} \Z }xms;
my $ymd = sprintf '%04d-%02d-%02d', $year, $month, $day;
my $value = $Opt->cold ? $tmin : $tmax;
next if not defined $value or $value eq $EMPTY;
$Location{$stnid} = $loc;
my $is_extreme = $Opt->cold
? $value <= $limit
: $value >= $limit
;
my $on_break = sub {
if ( $is_extreme ) {
$extremes_begins = $ymd if $cb->break('XT');
push @extreme_days, [$ymd, $value, $stnid, $loc];
} elsif ($cb->break('XT')) {
lib/Weather/GHCN/App/Extremes.pm view on Meta::CPAN
If the option -cold is given, then cold waves are examined.
Returns a reference to a hash keyed on year, and which contains a
tab_separated line of text that includes the station id, location,
year, and a count of the number of waves detected during that year.
=cut
sub report_extremes_per_year ($limit, $ndays, $cmp_op) {
my $type = $Opt->cold ? 'Coldwaves' : 'Heatwaves';
my $title = sprintf '%d-day waves %s %dC', $ndays, $cmp_op, $limit;
say join $TAB, 'StnId', 'Location', 'Year', $title;
my %years;
foreach my $xw_aref (@ExtremeWaves) {
my ($xw_begin, $xdays_aref, $stnid, $loc) = $xw_aref->@*;
$stnid //= $EMPTY;
$loc //= $EMPTY;
my ($year) = split $DASH, $xw_begin;
my $count = scalar $xdays_aref->@*;
next if $count < $ndays;
$years{$stnid}{$year}++;
}
foreach my $stnid ( sort keys %years ) {
foreach my $yr ( sort keys $years{$stnid}->%* ) {
say join $TAB, $stnid, $Location{$stnid}, $yr, $years{$stnid}{$yr};
}
}
return \%years;
}
########################################################################
# Script-standard Subroutines
########################################################################
=head2 get_options ( \@ARGV )
B<get_options> encapsulates everything we need to process command line
options, or to set options when invoking this script from a test script.
Normally it's called by passing a reference to @ARGV; from a test script
you'd set up a local array variable to specify the options.
By convention, you should set up a file-scoped lexical variable named
$Opt and set it in the mainline using the return value from this function.
Then all options can be accessed used $Opt->option notation.
=cut
sub get_options ($argv_aref) {
my @options = (
'limit=i', # lower bound of extremes daily temperature
'ndays=i', # number of consecutive days needed to be a extremes
'peryear', # report number of heatwaves per year
'cold', # report coldwaves instead of heatwaves
'nogaps', # generate a line for missing years (for charting)
'outclip', # output data to the Windows clipboard
'help','usage|?', # help
);
my %opt;
# create a list of option key names by stripping the various adornments
my @keys = map { (split m{ [!+=:|] }xms)[0] } grep { !ref } @options;
# initialize all possible options to undef
@opt{ @keys } = ( undef ) x @keys;
GetOptionsFromArray($argv_aref, \%opt, @options)
or pod2usage(2);
# Make %opt into an object and name it the same as what we usually
# call the global options object. Note that this doesn't set the
# global -- the script will have to do that using the return value
# from this function. But, what this does is allow us to call
# $Opt->help and other option within this function using the same
# syntax as what we use in the script. This is handy if you need
# to rename option '-foo' to '-bar' because you can do a find/replace
# on '$Opt->foo' and you'll get any instances of it here as well as
# in the script.
## no critic [Capitalization]
## no critic [ProhibitReusedNames]
my $Opt = _wrap_hash \%opt;
pod2usage(1) if $Opt->usage;
pod2usage(-verbose => 2) if $Opt->help;
return $Opt;
}
1; # needed in case we import this as a module (e.g. for testing)
=head1 AUTHOR
Gary Puckering (jgpuckering@rogers.com)
=head1 LICENSE AND COPYRIGHT
Copyright 2022, Gary Puckering
=cut
( run in 0.811 second using v1.01-cache-2.11-cpan-84e82930d8c )