line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# Weather::GHCN::Extremes.pm - analyze of extremes from Weather::GHCN::Fetch.pm output
|
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
## no critic (Documentation::RequirePodAtEnd)
|
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
=head1 NAME
|
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
Weather::GHCN::App::Extremes - Report temperature extremes from Weather::GHCN::Fetch output
|
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
=head1 VERSION |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
version v0.0.010 |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
=head1 SYNOPSIS
|
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
use Weather::GHCN::App::Extremes;
|
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
Weather::GHCN::App::Extremes->run( \@ARGV );
|
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
See ghcn_extremes -help for details.
|
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
=cut
|
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
########################################################################
|
24
|
|
|
|
|
|
|
# Pragmas
|
25
|
|
|
|
|
|
|
########################################################################
|
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
# these are needed because perlcritic fails to detect that Object::Pad handles these things
|
28
|
|
|
|
|
|
|
## no critic [ValuesAndExpressions::ProhibitVersionStrings]
|
29
|
|
|
|
|
|
|
|
30
|
1
|
|
|
1
|
|
105217
|
use v5.18;
|
|
1
|
|
|
|
|
14
|
|
31
|
1
|
|
|
1
|
|
7
|
use warnings;
|
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
84
|
|
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
package Weather::GHCN::App::Extremes;
|
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
our $VERSION = 'v0.0.010'; |
36
|
|
|
|
|
|
|
|
37
|
1
|
|
|
1
|
|
6
|
use feature 'signatures';
|
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
167
|
|
38
|
1
|
|
|
1
|
|
7
|
no warnings 'experimental::signatures';
|
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
47
|
|
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
########################################################################
|
41
|
|
|
|
|
|
|
# perlcritic rules
|
42
|
|
|
|
|
|
|
########################################################################
|
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
## no critic [Subroutines::ProhibitSubroutinePrototypes]
|
45
|
|
|
|
|
|
|
## no critic [ErrorHandling::RequireCarping]
|
46
|
|
|
|
|
|
|
## no critic [Modules::ProhibitAutomaticExportation]
|
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
# due to use of postfix dereferencing, we have to disable these warnings
|
49
|
|
|
|
|
|
|
## no critic [References::ProhibitDoubleSigils]
|
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
########################################################################
|
52
|
|
|
|
|
|
|
# Export
|
53
|
|
|
|
|
|
|
########################################################################
|
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
require Exporter;
|
56
|
|
|
|
|
|
|
|
57
|
1
|
|
|
1
|
|
5
|
use base 'Exporter';
|
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
160
|
|
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
our @EXPORT = ( 'run' );
|
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
########################################################################
|
62
|
|
|
|
|
|
|
# Libraries
|
63
|
|
|
|
|
|
|
########################################################################
|
64
|
1
|
|
|
1
|
|
1006
|
use English qw( -no_match_vars ) ;
|
|
1
|
|
|
|
|
3554
|
|
|
1
|
|
|
|
|
5
|
|
65
|
1
|
|
|
1
|
|
1090
|
use Getopt::Long qw( GetOptionsFromArray );
|
|
1
|
|
|
|
|
12716
|
|
|
1
|
|
|
|
|
6
|
|
66
|
1
|
|
|
1
|
|
680
|
use Pod::Usage;
|
|
1
|
|
|
|
|
54350
|
|
|
1
|
|
|
|
|
129
|
|
67
|
1
|
|
|
1
|
|
503
|
use Const::Fast;
|
|
1
|
|
|
|
|
2468
|
|
|
1
|
|
|
|
|
7
|
|
68
|
1
|
|
|
1
|
|
597
|
use Hash::Wrap {-lvalue => 1, -defined => 1, -as => '_wrap_hash'};
|
|
1
|
|
|
|
|
3974
|
|
|
1
|
|
|
|
|
7
|
|
69
|
|
|
|
|
|
|
|
70
|
1
|
|
|
1
|
|
1993
|
use ControlBreak;
|
|
1
|
|
|
|
|
16178
|
|
|
1
|
|
|
|
|
56
|
|
71
|
1
|
|
|
1
|
|
11
|
use List::Util qw(max min sum);
|
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
86
|
|
72
|
1
|
|
|
1
|
|
482
|
use Set::IntSpan::Fast;
|
|
1
|
|
|
|
|
6096
|
|
|
1
|
|
|
|
|
45
|
|
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
# modules for Windows only
|
75
|
1
|
|
|
1
|
|
580
|
use if $OSNAME eq 'MSWin32', 'Win32::Clipboard';
|
|
1
|
|
|
|
|
15
|
|
|
1
|
|
|
|
|
19
|
|
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
########################################################################
|
78
|
|
|
|
|
|
|
# Global delarations
|
79
|
|
|
|
|
|
|
########################################################################
|
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
# is it ok to use Win32::Clipboard?
|
82
|
|
|
|
|
|
|
our $USE_WINCLIP = $OSNAME eq 'MSWin32';
|
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
my $Opt;
|
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
my @ExtremeWaves;
|
87
|
|
|
|
|
|
|
my %Location;
|
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
########################################################################
|
90
|
|
|
|
|
|
|
# Constants
|
91
|
|
|
|
|
|
|
########################################################################
|
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
const my $EMPTY => q(); # empty string
|
94
|
|
|
|
|
|
|
const my $SPACE => q( ); # space character
|
95
|
|
|
|
|
|
|
const my $TAB => qq(\t); # tab character
|
96
|
|
|
|
|
|
|
const my $DASH => q(-); # dash character
|
97
|
|
|
|
|
|
|
const my $TRUE => 1; # perl's usual TRUE
|
98
|
|
|
|
|
|
|
const my $FALSE => not $TRUE; # a dual-var consisting of '' and 0
|
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
const my $DEFAULT_HOT_LIMIT => 30;
|
101
|
|
|
|
|
|
|
const my $DEFAULT_COLD_LIMIT => -20;
|
102
|
|
|
|
|
|
|
const my $DEFAULT_NDAYS => 5;
|
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
########################################################################
|
105
|
|
|
|
|
|
|
# Script Mainline
|
106
|
|
|
|
|
|
|
########################################################################
|
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
__PACKAGE__->run( \@ARGV ) unless caller;
|
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
#-----------------------------------------------------------------------
|
111
|
|
|
|
|
|
|
=head1 SUBROUTINES
|
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
=head2 run ( \@ARGV )
|
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
Invoke this subroutine, passing in a reference to @ARGV, in order to
|
116
|
|
|
|
|
|
|
perform an analysis of the heat or cold waves in the input data.
|
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
Input is from stdin, or from the files listed in @ARGV. Data should
|
119
|
|
|
|
|
|
|
contain tab-separated output if the format generated by:
|
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
ghcn_fetch -report detail
|
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
The following columns are expected:
|
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
Year, Month, Day, Decade, S_Decade, S_Year, S_Qtr,
|
126
|
|
|
|
|
|
|
TMAX, TMIN, Tavg, Qflags, StationId, Location
|
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
Any other columns are ignored.
|
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
See ghnc_extremes.pl -help for details.
|
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
=cut
|
133
|
|
|
|
|
|
|
|
134
|
3
|
|
|
3
|
1
|
20992
|
sub run ($progname, $argv_aref) {
|
|
3
|
|
|
|
|
7
|
|
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
5
|
|
135
|
|
|
|
|
|
|
|
136
|
3
|
|
|
|
|
16
|
$Opt = get_options($argv_aref);
|
137
|
|
|
|
|
|
|
|
138
|
3
|
|
|
|
|
15
|
my @files = $argv_aref->@*;
|
139
|
|
|
|
|
|
|
|
140
|
3
|
50
|
66
|
|
|
65
|
my $limit = $Opt->limit //
|
141
|
|
|
|
|
|
|
( $Opt->cold ? $DEFAULT_COLD_LIMIT
|
142
|
|
|
|
|
|
|
: $DEFAULT_HOT_LIMIT
|
143
|
|
|
|
|
|
|
);
|
144
|
|
|
|
|
|
|
|
145
|
3
|
|
66
|
|
|
1025
|
my $ndays = $Opt->ndays // $DEFAULT_NDAYS;
|
146
|
|
|
|
|
|
|
|
147
|
3
|
100
|
|
|
|
567
|
my $cmp_op = $Opt->cold ? '<=' : '>=';
|
148
|
|
|
|
|
|
|
|
149
|
3
|
|
|
|
|
74
|
my $years_set = Set::IntSpan::Fast->new;
|
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
## no critic [RequireBriefOpen]
|
152
|
3
|
|
|
|
|
34
|
my ( $output, $new_fh, $old_fh );
|
153
|
3
|
0
|
33
|
|
|
55
|
if ( $Opt->outclip and $USE_WINCLIP ) {
|
154
|
0
|
0
|
|
|
|
0
|
open $new_fh, '>', \$output
|
155
|
|
|
|
|
|
|
or die 'Unable to open buffer for write';
|
156
|
0
|
|
|
|
|
0
|
$old_fh = select $new_fh; ## no critic (ProhibitOneArgSelect)
|
157
|
|
|
|
|
|
|
}
|
158
|
|
|
|
|
|
|
|
159
|
3
|
|
|
|
|
482
|
@files = $argv_aref->@*;
|
160
|
3
|
50
|
|
|
|
13
|
@files = ($DASH) unless @files;
|
161
|
|
|
|
|
|
|
|
162
|
3
|
|
|
|
|
8
|
foreach my $file (@files) {
|
163
|
3
|
|
|
|
|
7
|
my $fh;
|
164
|
3
|
50
|
|
|
|
10
|
if ($file eq $DASH) {
|
165
|
0
|
|
|
|
|
0
|
$fh = *STDIN;
|
166
|
|
|
|
|
|
|
} else {
|
167
|
3
|
50
|
|
|
|
156
|
open $fh, q(<), $file or die;
|
168
|
|
|
|
|
|
|
}
|
169
|
|
|
|
|
|
|
|
170
|
3
|
|
|
|
|
385
|
@ExtremeWaves = ();
|
171
|
3
|
|
|
|
|
12
|
%Location = ();
|
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
# controlling on bool is_extreme and alpha stnid, minor to major
|
174
|
3
|
|
|
|
|
50
|
my $cb = ControlBreak->new( '+XT', 'STNID', 'EOF' );
|
175
|
|
|
|
|
|
|
|
176
|
3
|
|
|
|
|
579
|
read_data( $fh, $cb, $limit );
|
177
|
|
|
|
|
|
|
}
|
178
|
|
|
|
|
|
|
|
179
|
3
|
|
|
|
|
11
|
my $years_href;
|
180
|
3
|
100
|
|
|
|
92
|
if ( $Opt->peryear ) {
|
181
|
1
|
|
|
|
|
30
|
$years_href = report_extremes_per_year($limit, $ndays, $cmp_op);
|
182
|
|
|
|
|
|
|
} else {
|
183
|
2
|
|
|
|
|
642
|
$years_href = report_extremes_daycounts($limit, $ndays, $cmp_op)
|
184
|
|
|
|
|
|
|
}
|
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
# generate lines for each year that was missing
|
187
|
3
|
50
|
|
|
|
86
|
if ($Opt->nogaps) {
|
188
|
0
|
|
|
|
|
0
|
foreach my $stnid (keys $years_href->%*) {
|
189
|
0
|
|
|
|
|
0
|
my @years = sort keys $years_href->{$stnid}->%*;
|
190
|
|
|
|
|
|
|
|
191
|
0
|
|
|
|
|
0
|
my $s = Set::IntSpan::Fast->new( min(@years) .. max(@years) );
|
192
|
0
|
|
|
|
|
0
|
my $t = Set::IntSpan::Fast->new( @years );
|
193
|
0
|
|
|
|
|
0
|
my $gaps = $s->diff($t);
|
194
|
0
|
|
|
|
|
0
|
my $iter = $gaps->iterate_runs();
|
195
|
|
|
|
|
|
|
|
196
|
0
|
|
|
|
|
0
|
while ( my ( $from, $to ) = $iter->() ) {
|
197
|
0
|
|
|
|
|
0
|
foreach my $yr ($from .. $to) {
|
198
|
0
|
|
|
|
|
0
|
say join $TAB, $stnid, $Location{$stnid}, $yr;
|
199
|
|
|
|
|
|
|
}
|
200
|
|
|
|
|
|
|
}
|
201
|
|
|
|
|
|
|
}
|
202
|
|
|
|
|
|
|
}
|
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
WRAP_UP:
|
205
|
|
|
|
|
|
|
# send output to the Windows clipboard
|
206
|
3
|
0
|
33
|
|
|
608
|
if ( $Opt->outclip and $USE_WINCLIP ) {
|
207
|
0
|
|
|
|
|
0
|
Win32::Clipboard->new()->Set( $output );
|
208
|
0
|
|
|
|
|
0
|
select $old_fh; ## no critic [ProhibitOneArgSelect]
|
209
|
|
|
|
|
|
|
}
|
210
|
|
|
|
|
|
|
|
211
|
3
|
|
|
|
|
93
|
return;
|
212
|
|
|
|
|
|
|
}
|
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
########################################################################
|
215
|
|
|
|
|
|
|
# Script-specific Subroutines
|
216
|
|
|
|
|
|
|
########################################################################
|
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
=head2 read_data ( $fh, $cb, $limit )
|
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
Read weather data from the filehandle and collect extreme waves
|
221
|
|
|
|
|
|
|
according to $limit and $Opt->cold (true for cold waves, false for
|
222
|
|
|
|
|
|
|
heat waves).
|
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
=cut
|
225
|
|
|
|
|
|
|
|
226
|
3
|
|
|
3
|
1
|
7
|
sub read_data ( $fh, $cb, $limit ) {
|
|
3
|
|
|
|
|
7
|
|
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
6
|
|
227
|
|
|
|
|
|
|
|
228
|
3
|
|
|
|
|
10
|
my $extremes_begins;
|
229
|
|
|
|
|
|
|
my @extreme_days;
|
230
|
3
|
|
|
|
|
0
|
my $lineno;
|
231
|
|
|
|
|
|
|
|
232
|
3
|
|
|
|
|
243
|
while ( my $data = <$fh> ) {
|
233
|
14076
|
|
|
|
|
451175
|
chomp $data;
|
234
|
14076
|
50
|
|
|
|
27552
|
next if $data eq $EMPTY;
|
235
|
14076
|
50
|
|
|
|
31418
|
last if $data =~ m{ \A Notes: }xms;
|
236
|
|
|
|
|
|
|
|
237
|
14076
|
|
|
|
|
166814
|
my ($year,$month,$day,$decade,$s_decade,$s_year,$s_qtr,$tmax,$tmin,$tavg,$qflags,$stnid,$loc ) = split $TAB, $data;
|
238
|
|
|
|
|
|
|
|
239
|
14076
|
|
|
|
|
31715
|
$lineno++;
|
240
|
14076
|
100
|
|
|
|
27485
|
if ($lineno == 1) {
|
241
|
3
|
50
|
33
|
|
|
47
|
die '*E* invalid input data: ' . $data
|
|
|
|
33
|
|
|
|
|
242
|
|
|
|
|
|
|
unless $year eq 'Year' and $tmax =~ m{ \A TMAX }xms and $tmin =~ m{ \A TMIN }xms;
|
243
|
3
|
|
|
|
|
12
|
next;
|
244
|
|
|
|
|
|
|
}
|
245
|
|
|
|
|
|
|
|
246
|
14073
|
100
|
|
|
|
51079
|
last unless $year =~ m{ \A \d{4} \Z }xms;
|
247
|
|
|
|
|
|
|
|
248
|
14070
|
|
|
|
|
57843
|
my $ymd = sprintf '%04d-%02d-%02d', $year, $month, $day;
|
249
|
|
|
|
|
|
|
|
250
|
14070
|
100
|
|
|
|
272291
|
my $value = $Opt->cold ? $tmin : $tmax;
|
251
|
|
|
|
|
|
|
|
252
|
14070
|
100
|
66
|
|
|
184323
|
next if not defined $value or $value eq $EMPTY;
|
253
|
|
|
|
|
|
|
|
254
|
13827
|
|
|
|
|
26394
|
$Location{$stnid} = $loc;
|
255
|
|
|
|
|
|
|
|
256
|
13827
|
100
|
|
|
|
216332
|
my $is_extreme = $Opt->cold
|
257
|
|
|
|
|
|
|
? $value <= $limit
|
258
|
|
|
|
|
|
|
: $value >= $limit
|
259
|
|
|
|
|
|
|
;
|
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
my $on_break = sub {
|
262
|
13827
|
100
|
|
13827
|
|
858667
|
if ( $is_extreme ) {
|
|
|
100
|
|
|
|
|
|
263
|
1185
|
100
|
|
|
|
2568
|
$extremes_begins = $ymd if $cb->break('XT');
|
264
|
1185
|
|
|
|
|
16443
|
push @extreme_days, [$ymd, $value, $stnid, $loc];
|
265
|
|
|
|
|
|
|
} elsif ($cb->break('XT')) {
|
266
|
545
|
|
|
|
|
7830
|
push @ExtremeWaves, [$extremes_begins, [@extreme_days], $stnid, $loc ];
|
267
|
545
|
|
|
|
|
963
|
$extremes_begins = undef;
|
268
|
545
|
|
|
|
|
934
|
@extreme_days = ();
|
269
|
|
|
|
|
|
|
}
|
270
|
13827
|
100
|
|
|
|
159803
|
if ($cb->break('STNID')) {
|
271
|
3
|
|
|
|
|
46
|
$extremes_begins = undef;
|
272
|
3
|
|
|
|
|
8
|
@extreme_days = ();
|
273
|
|
|
|
|
|
|
}
|
274
|
13827
|
|
|
|
|
184396
|
};
|
275
|
|
|
|
|
|
|
|
276
|
13827
|
|
|
|
|
42878
|
$cb->test_and_do($is_extreme, $stnid, eof, $on_break);
|
277
|
|
|
|
|
|
|
}
|
278
|
|
|
|
|
|
|
|
279
|
3
|
|
|
|
|
166
|
return;
|
280
|
|
|
|
|
|
|
}
|
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
=head2 report_extremes_daycounts ($limit, $ndays, $cmp_op)
|
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
Analyzes the input data lookin for $ndays consecutive days when
|
285
|
|
|
|
|
|
|
the temperature is beyond $limit. By default, heatwaves are examined.
|
286
|
|
|
|
|
|
|
If the option -cold is given, then cold waves are examined.
|
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
Returns a reference to a hash keyed on year, and which contains a
|
289
|
|
|
|
|
|
|
tab_separated line of text that includes the station id, location,
|
290
|
|
|
|
|
|
|
year, ymd the wave began, the number of days the wave lasted,
|
291
|
|
|
|
|
|
|
the average temperature during the wave, and the most extreme (hot
|
292
|
|
|
|
|
|
|
or cold) temperature during the wave.
|
293
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
=cut
|
295
|
|
|
|
|
|
|
|
296
|
2
|
|
|
2
|
1
|
4
|
sub report_extremes_daycounts ($limit, $ndays, $cmp_op) {
|
|
2
|
|
|
|
|
6
|
|
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
5
|
|
297
|
2
|
|
|
|
|
4
|
my %years;
|
298
|
|
|
|
|
|
|
|
299
|
2
|
|
|
|
|
15
|
my $daycount_col_head = sprintf '%d-day waves %s %dC', $ndays, $cmp_op, $limit;
|
300
|
2
|
|
|
|
|
161
|
say join $TAB, 'StnId', 'Location', 'Year', 'YMD', $daycount_col_head, 'Avg C', 'Max C';
|
301
|
|
|
|
|
|
|
|
302
|
2
|
|
|
|
|
12
|
foreach my $xw_aref (@ExtremeWaves) {
|
303
|
338
|
|
|
|
|
1127
|
my ($xw_begin, $xdays_aref, $stnid, $loc) = $xw_aref->@*;
|
304
|
|
|
|
|
|
|
|
305
|
338
|
|
33
|
|
|
601
|
$stnid //= $EMPTY;
|
306
|
338
|
|
33
|
|
|
538
|
$loc //= $EMPTY;
|
307
|
|
|
|
|
|
|
|
308
|
338
|
|
|
|
|
494
|
my $count = scalar $xdays_aref->@*;
|
309
|
|
|
|
|
|
|
|
310
|
338
|
100
|
|
|
|
1058
|
next if $count < $ndays;
|
311
|
|
|
|
|
|
|
|
312
|
58
|
|
|
|
|
117
|
my $year = substr $xw_begin, 0, 4; ## no critic [ProhibitMagicNumbers]
|
313
|
|
|
|
|
|
|
|
314
|
58
|
|
|
|
|
141
|
my @temps = map { $_->[1] } $xdays_aref->@*;
|
|
264
|
|
|
|
|
684
|
|
315
|
58
|
|
|
|
|
176
|
my $sum = sum(@temps);
|
316
|
58
|
100
|
|
|
|
1279
|
my $extreme = $Opt->cold ? min(@temps) : max(@temps);
|
317
|
58
|
|
|
|
|
976
|
my $avg = sprintf '%0.1f', $sum / $count;
|
318
|
|
|
|
|
|
|
say join $TAB,
|
319
|
58
|
|
|
|
|
976
|
$stnid, $Location{$stnid}, $year, $xw_begin, $count, $avg, $extreme;
|
320
|
58
|
|
|
|
|
462
|
$years{$stnid}{$year}++;
|
321
|
|
|
|
|
|
|
}
|
322
|
|
|
|
|
|
|
|
323
|
2
|
|
|
|
|
11
|
return \%years;
|
324
|
|
|
|
|
|
|
}
|
325
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
=head2 report_extremes_per_year ($limit, $ndays, $cmp_op)
|
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
Analyzes the input data lookin for $ndays consecutive days when
|
329
|
|
|
|
|
|
|
the temperature is beyond $limit. By default, heatwaves are examined.
|
330
|
|
|
|
|
|
|
If the option -cold is given, then cold waves are examined.
|
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
Returns a reference to a hash keyed on year, and which contains a
|
333
|
|
|
|
|
|
|
tab_separated line of text that includes the station id, location,
|
334
|
|
|
|
|
|
|
year, and a count of the number of waves detected during that year.
|
335
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
=cut
|
337
|
|
|
|
|
|
|
|
338
|
1
|
|
|
1
|
1
|
34
|
sub report_extremes_per_year ($limit, $ndays, $cmp_op) {
|
|
1
|
|
|
|
|
4
|
|
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
4
|
|
|
1
|
|
|
|
|
2
|
|
339
|
1
|
50
|
|
|
|
22
|
my $type = $Opt->cold ? 'Coldwaves' : 'Heatwaves';
|
340
|
1
|
|
|
|
|
28
|
my $title = sprintf '%d-day waves %s %dC', $ndays, $cmp_op, $limit;
|
341
|
1
|
|
|
|
|
102
|
say join $TAB, 'StnId', 'Location', 'Year', $title;
|
342
|
|
|
|
|
|
|
|
343
|
1
|
|
|
|
|
29
|
my %years;
|
344
|
|
|
|
|
|
|
|
345
|
1
|
|
|
|
|
6
|
foreach my $xw_aref (@ExtremeWaves) {
|
346
|
207
|
|
|
|
|
592
|
my ($xw_begin, $xdays_aref, $stnid, $loc) = $xw_aref->@*;
|
347
|
207
|
|
33
|
|
|
339
|
$stnid //= $EMPTY;
|
348
|
207
|
|
33
|
|
|
347
|
$loc //= $EMPTY;
|
349
|
207
|
|
|
|
|
1266
|
my ($year) = split $DASH, $xw_begin;
|
350
|
207
|
|
|
|
|
392
|
my $count = scalar $xdays_aref->@*;
|
351
|
207
|
100
|
|
|
|
516
|
next if $count < $ndays;
|
352
|
18
|
|
|
|
|
53
|
$years{$stnid}{$year}++;
|
353
|
|
|
|
|
|
|
}
|
354
|
|
|
|
|
|
|
|
355
|
1
|
|
|
|
|
15
|
foreach my $stnid ( sort keys %years ) {
|
356
|
2
|
|
|
|
|
15
|
foreach my $yr ( sort keys $years{$stnid}->%* ) {
|
357
|
12
|
|
|
|
|
157
|
say join $TAB, $stnid, $Location{$stnid}, $yr, $years{$stnid}{$yr};
|
358
|
|
|
|
|
|
|
}
|
359
|
|
|
|
|
|
|
}
|
360
|
|
|
|
|
|
|
|
361
|
1
|
|
|
|
|
6
|
return \%years;
|
362
|
|
|
|
|
|
|
}
|
363
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
########################################################################
|
365
|
|
|
|
|
|
|
# Script-standard Subroutines
|
366
|
|
|
|
|
|
|
########################################################################
|
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
=head2 get_options ( \@ARGV )
|
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
B encapsulates everything we need to process command line
|
371
|
|
|
|
|
|
|
options, or to set options when invoking this script from a test script.
|
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
Normally it's called by passing a reference to @ARGV; from a test script
|
374
|
|
|
|
|
|
|
you'd set up a local array variable to specify the options.
|
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
By convention, you should set up a file-scoped lexical variable named
|
377
|
|
|
|
|
|
|
$Opt and set it in the mainline using the return value from this function.
|
378
|
|
|
|
|
|
|
Then all options can be accessed used $Opt->option notation.
|
379
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
=cut
|
381
|
|
|
|
|
|
|
|
382
|
3
|
|
|
3
|
1
|
7
|
sub get_options ($argv_aref) {
|
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
4
|
|
383
|
|
|
|
|
|
|
|
384
|
3
|
|
|
|
|
21
|
my @options = (
|
385
|
|
|
|
|
|
|
'limit=i', # lower bound of extremes daily temperature
|
386
|
|
|
|
|
|
|
'ndays=i', # number of consecutive days needed to be a extremes
|
387
|
|
|
|
|
|
|
'peryear', # report number of heatwaves per year
|
388
|
|
|
|
|
|
|
'cold', # report coldwaves instead of heatwaves
|
389
|
|
|
|
|
|
|
'nogaps', # generate a line for missing years (for charting)
|
390
|
|
|
|
|
|
|
'outclip', # output data to the Windows clipboard
|
391
|
|
|
|
|
|
|
'help','usage|?', # help
|
392
|
|
|
|
|
|
|
);
|
393
|
|
|
|
|
|
|
|
394
|
3
|
|
|
|
|
7
|
my %opt;
|
395
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
# create a list of option key names by stripping the various adornments
|
397
|
3
|
|
|
|
|
12
|
my @keys = map { (split m{ [!+=:|] }xms)[0] } grep { !ref } @options;
|
|
24
|
|
|
|
|
86
|
|
|
24
|
|
|
|
|
53
|
|
398
|
|
|
|
|
|
|
# initialize all possible options to undef
|
399
|
3
|
|
|
|
|
27
|
@opt{ @keys } = ( undef ) x @keys;
|
400
|
|
|
|
|
|
|
|
401
|
3
|
50
|
|
|
|
27
|
GetOptionsFromArray($argv_aref, \%opt, @options)
|
402
|
|
|
|
|
|
|
or pod2usage(2);
|
403
|
|
|
|
|
|
|
|
404
|
|
|
|
|
|
|
# Make %opt into an object and name it the same as what we usually
|
405
|
|
|
|
|
|
|
# call the global options object. Note that this doesn't set the
|
406
|
|
|
|
|
|
|
# global -- the script will have to do that using the return value
|
407
|
|
|
|
|
|
|
# from this function. But, what this does is allow us to call
|
408
|
|
|
|
|
|
|
# $Opt->help and other option within this function using the same
|
409
|
|
|
|
|
|
|
# syntax as what we use in the script. This is handy if you need
|
410
|
|
|
|
|
|
|
# to rename option '-foo' to '-bar' because you can do a find/replace
|
411
|
|
|
|
|
|
|
# on '$Opt->foo' and you'll get any instances of it here as well as
|
412
|
|
|
|
|
|
|
# in the script.
|
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
## no critic [Capitalization]
|
415
|
|
|
|
|
|
|
## no critic [ProhibitReusedNames]
|
416
|
3
|
|
|
|
|
2450
|
my $Opt = _wrap_hash \%opt;
|
417
|
|
|
|
|
|
|
|
418
|
3
|
50
|
|
|
|
150
|
pod2usage(1) if $Opt->usage;
|
419
|
3
|
50
|
|
|
|
641
|
pod2usage(-verbose => 2) if $Opt->help;
|
420
|
|
|
|
|
|
|
|
421
|
3
|
|
|
|
|
490
|
return $Opt;
|
422
|
|
|
|
|
|
|
}
|
423
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
1; # needed in case we import this as a module (e.g. for testing)
|
425
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
=head1 AUTHOR
|
427
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
Gary Puckering (jgpuckering@rogers.com)
|
429
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
=head1 LICENSE AND COPYRIGHT
|
431
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
Copyright 2022, Gary Puckering
|
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
=cut
|