|  line  | 
 stmt  | 
 bran  | 
 cond  | 
 sub  | 
 pod  | 
 time  | 
 code  | 
| 
1
 | 
  
 
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 package Weather::NHC::TropicalCyclone::ForecastAdvisory;  | 
| 
2
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
3
 | 
1
 | 
 
 | 
 
 | 
  
1
  
 | 
 
 | 
1185
 | 
 use strict;  | 
| 
 
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
2
 | 
    | 
| 
 
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
31
 | 
    | 
| 
4
 | 
1
 | 
 
 | 
 
 | 
  
1
  
 | 
 
 | 
4
 | 
 use warnings;  | 
| 
 
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
1
 | 
    | 
| 
 
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
24
 | 
    | 
| 
5
 | 
1
 | 
 
 | 
 
 | 
  
1
  
 | 
 
 | 
725
 | 
 use Date::Calc;  | 
| 
 
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
6742
 | 
    | 
| 
 
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
41
 | 
    | 
| 
6
 | 
1
 | 
 
 | 
 
 | 
  
1
  
 | 
 
 | 
658
 | 
 use Getopt::Long;  | 
| 
 
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
8046
 | 
    | 
| 
 
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
5
 | 
    | 
| 
7
 | 
1
 | 
 
 | 
 
 | 
  
1
  
 | 
 
 | 
1310
 | 
 use Util::H2O::More qw/baptise/;  | 
| 
 
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
8112
 | 
    | 
| 
 
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
2261
 | 
    | 
| 
8
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
9
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 sub new {  | 
| 
10
 | 
5
 | 
 
 | 
 
 | 
  
5
  
 | 
  
0
  
 | 
4138
 | 
     my ( $pkg, %self ) = @_;  | 
| 
11
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
12
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     # input_file and input_text are mutually exclusive via the contructor  | 
| 
13
 | 
5
 | 
  
100
  
 | 
  
100
  
 | 
 
 | 
 
 | 
42
 | 
     if ( not( exists $self{input_file} or exists $self{input_text} ) or ( exists $self{input_file} and exists $self{input_text} ) or not exists $self{output_file} ) {  | 
| 
 
 | 
 
 | 
 
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
 
 | 
 
 | 
 
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
 
 | 
 
 | 
 
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
14
 | 
3
 | 
 
 | 
 
 | 
 
 | 
 
 | 
17
 | 
         die qq{Constructor requires specifying the 'input_file' xor 'input_text', and 'output_file' parameters.\n};  | 
| 
15
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     }  | 
| 
16
 | 
2
 | 
 
 | 
 
 | 
 
 | 
 
 | 
8
 | 
     my @fields = (qw/input_file input_text output_file as_atcf/);  | 
| 
17
 | 
2
 | 
 
 | 
 
 | 
 
 | 
 
 | 
11
 | 
     my $self   = baptise -recurse, \%self, $pkg, @fields;  | 
| 
18
 | 
2
 | 
 
 | 
 
 | 
 
 | 
 
 | 
399
 | 
     return $self;  | 
| 
19
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 }  | 
| 
20
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
21
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 sub extract_and_save_atcf {  | 
| 
22
 | 
2
 | 
 
 | 
 
 | 
  
2
  
 | 
  
0
  
 | 
577
 | 
     my $self = shift;  | 
| 
23
 | 
2
 | 
 
 | 
 
 | 
 
 | 
 
 | 
7
 | 
     $self->extract_atcf;  | 
| 
24
 | 
2
 | 
 
 | 
 
 | 
 
 | 
 
 | 
31
 | 
     return $self->save_atcf;  | 
| 
25
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 }  | 
| 
26
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
27
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 sub save_atcf {  | 
| 
28
 | 
4
 | 
 
 | 
 
 | 
  
4
  
 | 
  
0
  
 | 
2029
 | 
     my $self = shift;  | 
| 
29
 | 
4
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
37
 | 
     open( my $fh, q{>}, $self->output_file ) || die qq{Failed to open output ATCF file} . $self->output_file . qq{ : $!.\n};  | 
| 
30
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
424
 | 
     my $output_ref = $self->as_atcf;  | 
| 
31
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
90
 | 
     print $fh join( qq{\n}, @$output_ref );  | 
| 
32
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
365
 | 
     close $fh;  | 
| 
33
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
22
 | 
     return $self->output_file;  | 
| 
34
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 }  | 
| 
35
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
36
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 sub extract_atcf {  | 
| 
37
 | 
4
 | 
 
 | 
 
 | 
  
4
  
 | 
  
0
  
 | 
2148
 | 
     my $self = shift;  | 
| 
38
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
39
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
9
 | 
     my @lines = ();  | 
| 
40
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
41
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   ADVISORY_SOURCE:  | 
| 
42
 | 
4
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
13
 | 
     if ( $self->input_file ) {  | 
| 
 
 | 
 
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
43
 | 
2
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
19
 | 
         open( my $INPUT, q{<}, $self->input_file ) || die q{Failed to open forecast advisory file} . $self->input_file . qq{ for conversion to ATCF format: $!.\n};  | 
| 
44
 | 
2
 | 
 
 | 
 
 | 
 
 | 
 
 | 
563
 | 
         @lines = (<$INPUT>);  | 
| 
45
 | 
2
 | 
 
 | 
 
 | 
 
 | 
 
 | 
30
 | 
         close $INPUT;  | 
| 
46
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     }  | 
| 
47
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     elsif ( $self->input_text ) {  | 
| 
48
 | 
2
 | 
 
 | 
 
 | 
 
 | 
 
 | 
26
 | 
         @lines = split /\n/, $self->input_text;  | 
| 
49
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     }  | 
| 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
51
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
53
 | 
     my @output = ();    # accumulate content for $output, do write at the very end  | 
| 
52
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
53
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     # parse rss reports from Nat'l Hurricane Center Atlantic Marine Forecast  | 
| 
54
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     # Advisory.  NHC Mail (Atlantic Marine)  | 
| 
55
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     #  See:  | 
| 
56
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     # http://www.nhc.noaa.gov/signup.shtml and select the following list: Atlantic  | 
| 
57
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     # Marine (Forecast/Advisories and updates ONLY)  | 
| 
58
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
59
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     # BASIN,CY,YYYYMMDDHH,TECHNUM,TECH,TAU,LatN/S,LonE/W,VMAX,MSLP,TY,RAD,WINDCODE,RAD1,RAD2,RAD3,RAD4,RADP,RRP,MRD,GUSTS,EYE,SUBREGION,MAXSEAS,INITIALS,DIR,SPEED,STORMNAME,DEPTH,SEAS,SEASCODE,SEAS1,SEAS2,SEAS3,SEAS4  | 
| 
60
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     #  | 
| 
61
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     #                                                                                                    1         1         1         1         1         1         1         1         1         1  | 
| 
62
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     #          1         2         3         4         5         6         7         8         9         0         1         2         3         4         5         6         7         8         9  | 
| 
63
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     #01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890  | 
| 
64
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     #AL, 01, 2009052912, 03, OFCL,   0, 393N,  649W,  30, 1006, TD,  34, NEQ,    0,    0,    0,    0,    0,    0,  40,  40,   0,    ,   0, TBK,  65,  17,           ,  , 12, NEQ,  60,  60,   0,   0  | 
| 
65
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
9
 | 
     my $template     = "_BASIN_, 01, 2009010100,   , OFCL,   0, 000N,  000W,  30,    0,   ,  34, NEQ,    0,    0,    0,    0,    0,    0,   0,  40,   0,    ,   0, TBK,  65,  17,           ,  , 12, NEQ,  60,  60,   0,   0";  | 
| 
66
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
38
 | 
     my %month_lookup = ( dummy => '00', JAN => '01', FEB => '02', MAR => '03', APR => '04', MAY => '05', JUN => '06', JUL => '07', AUG => '08', SEP => '09', OCT => '10', NOV => '11', DEC => '12' );  | 
| 
67
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
68
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     # These are the data we are extracting  | 
| 
69
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
5
 | 
     my $pressure;  | 
| 
70
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
8
 | 
     my $storm_class = "";  | 
| 
71
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
64
 | 
     my $storm_name;  | 
| 
72
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     my $storm_basin;  | 
| 
73
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
     my $storm_number;  | 
| 
74
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
     my $adv_num;  | 
| 
75
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
     my $storm_year;  | 
| 
76
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
     my $nowcast_year;  | 
| 
77
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
     my $forecast_year;  | 
| 
78
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
     my $nowcast_month;  | 
| 
79
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
     my $forecast_month;  | 
| 
80
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
     my $nowcast_day;  | 
| 
81
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
     my $forecast_day;  | 
| 
82
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
     my $nowcast_hour;  | 
| 
83
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
     my $forecast_hour;  | 
| 
84
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
     my $date_time;  | 
| 
85
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
     my $forecast_date_time;  | 
| 
86
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
     my $nowcast_date_time;  | 
| 
87
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
     my $nowcast_max_wind;  | 
| 
88
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
     my $forecast_max_wind;  | 
| 
89
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
5
 | 
     my $atcf_line = $template;  | 
| 
90
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
9
 | 
     my $lat;  | 
| 
91
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     my $lon;  | 
| 
92
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
     my $vmax;  | 
| 
93
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
     my $gusts;    # in kt  | 
| 
94
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
6
 | 
     my $center_direction = 65;  | 
| 
95
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
9
 | 
     my $center_speed     = 17;  | 
| 
96
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
97
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
7
 | 
     my $body_ref = \@lines;  | 
| 
98
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
6
 | 
     my $cnt      = @{$body_ref};  | 
| 
 
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
7
 | 
    | 
| 
99
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     #  | 
| 
100
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
7
 | 
     my @match = ();  | 
| 
101
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
102
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     # Get the NHC Number  | 
| 
103
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     # NWS TPC/NATIONAL HURRICANE CENTER MIAMI FL   AL172005  | 
| 
104
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
4
 | 
     @match = grep /(?:AL|EP|CP|WP|IO|SH|LS)\d{2}\d{4}$/, @{$body_ref};  | 
| 
 
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
78
 | 
    | 
| 
105
 | 
4
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
14
 | 
     if (@match) {  | 
| 
106
 | 
4
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
19
 | 
         if ( $match[0] =~ /(AL|EP|CP|WP|IO|SH|LS)(\d{2})(\d{4})$/ ) {  | 
| 
107
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
12
 | 
             $storm_basin  = $1;  | 
| 
108
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
17
 | 
             $storm_number = $2;  | 
| 
109
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
8
 | 
             $storm_year   = $3;  | 
| 
110
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         }  | 
| 
111
 | 
4
 | 
  
 50
  
 | 
  
 33
  
 | 
 
 | 
 
 | 
22
 | 
         die qq{NO NHC NUMBER/YEAR\n} if not $storm_number or not $storm_year or not $storm_basin;  | 
| 
 
 | 
 
 | 
 
 | 
  
 33
  
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
112
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     }  | 
| 
113
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
15
 | 
     $atcf_line =~ s/_BASIN_/$storm_basin/;  | 
| 
114
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
17
 | 
     my $storm_number_str = sprintf( "%02d", $storm_number );  | 
| 
115
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
9
 | 
     substr( $atcf_line, 4, 2 ) = $storm_number_str;  | 
| 
116
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
117
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     # Date format  | 
| 
118
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     # 1500Z THU SEP 02 2004  | 
| 
119
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     # July 18th TD 2. HAS CHANGED  | 
| 
120
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     # NOTE NOTE NOW! 1500 UTC TUE JUL 18 2006  | 
| 
121
 | 
4
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
12
 | 
     if ( $storm_year > 2005 ) {  | 
| 
122
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
6
 | 
         @match = grep /^\d{4} .+ \d{4}$/, @{$body_ref};  | 
| 
 
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
66
 | 
    | 
| 
123
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     }  | 
| 
124
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     else {  | 
| 
125
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
         @match = grep /^\d{4}Z .+ \d{4}$/, @{$body_ref};  | 
| 
 
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
    | 
| 
126
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     }  | 
| 
127
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     #  | 
| 
128
 | 
4
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
17
 | 
     if (@match) {  | 
| 
129
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
8
 | 
         $date_time = $match[0];  | 
| 
130
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
7
 | 
         chomp $date_time;  | 
| 
131
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
14
 | 
         my @vals = split( ' ', $date_time );  | 
| 
132
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
9
 | 
         $nowcast_hour = substr( $vals[0], 0, 2 );  | 
| 
133
 | 
4
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
8
 | 
         if ( $storm_year > 2005 ) {  | 
| 
134
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
6
 | 
             $nowcast_year  = $vals[5];  | 
| 
135
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
7
 | 
             $nowcast_month = $month_lookup{ $vals[3] };  | 
| 
136
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
6
 | 
             $nowcast_day   = $vals[4];  | 
| 
137
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         }  | 
| 
138
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         else {  | 
| 
139
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
             $nowcast_year  = $vals[4];  | 
| 
140
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
             $nowcast_month = $month_lookup{ $vals[2] };  | 
| 
141
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
             $nowcast_day   = $vals[3];  | 
| 
142
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         }  | 
| 
143
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
7
 | 
         $nowcast_date_time = $nowcast_year . $nowcast_month . $nowcast_day . $nowcast_hour;  | 
| 
144
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
16
 | 
         substr( $atcf_line, 8, 10 ) = sprintf( "%10d", $nowcast_date_time );  | 
| 
145
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     }  | 
| 
146
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
147
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     # advisory number does not appear in the ATCF format  | 
| 
148
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
8
 | 
     @match = grep /FORECAST.+ADVISORY NUMBER/, @{$body_ref};  | 
| 
 
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
47
 | 
    | 
| 
149
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
150
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     # HURRICANE FRANCES ADVISORY NUMBER  40  | 
| 
151
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     # HURRICANE FRANCES FORECAST/ADVISORY NUMBER  37  | 
| 
152
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     # HURRICANE FRANCES FORECAST/ADVISORY NUMBER  37...CORRECTED  | 
| 
153
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     # HURRICANE FRANCES SPECIAL FORECAST/ADVISORY NUMBER  37  | 
| 
154
 | 
4
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
9
 | 
     if (@match) {  | 
| 
155
 | 
4
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
41
 | 
         if ( $match[0] =~ /^(.+)\s+FORECAST.+ADVISORY NUMBER\s+(\d{1,3})/ ) {  | 
| 
156
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
10
 | 
             $storm_name = $1;  | 
| 
157
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
8
 | 
             $adv_num    = $2;  | 
| 
158
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
8
 | 
             $storm_name =~ s/SPECIAL//;  | 
| 
159
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         }  | 
| 
160
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     }  | 
| 
161
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     else {  | 
| 
162
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
         @match = grep /ADVISORY NUMBER/, @{$body_ref};  | 
| 
 
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
    | 
| 
163
 | 
  
0
  
 | 
  
  0
  
 | 
 
 | 
 
 | 
 
 | 
0
 | 
         if ( $match[0] =~ /^(.+)\s+ADVISORY NUMBER\s+(\d{1,3})/ ) {  | 
| 
164
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
             $storm_name = $1;  | 
| 
165
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
             $adv_num    = $2;  | 
| 
166
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         }  | 
| 
167
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     }  | 
| 
168
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
10
 | 
     my @tmp = split( ' ', $storm_name );  | 
| 
169
 | 
4
 | 
  
 50
  
 | 
  
  0
  
 | 
 
 | 
 
 | 
9
 | 
     if ( $tmp[0] eq 'HURRICANE' ) {  | 
| 
 
 | 
 
 | 
  
  0
  
 | 
  
  0
  
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
 
 | 
 
 | 
  
  0
  
 | 
  
  0
  
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
170
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
7
 | 
         $storm_class = $tmp[0];  | 
| 
171
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
6
 | 
         $storm_name  = $tmp[1];  | 
| 
172
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     }  | 
| 
173
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     elsif ( $tmp[0] eq 'POTENTIAL' ) {  | 
| 
174
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
         $storm_class = "$tmp[0] $tmp[1] $tmp[2]";  | 
| 
175
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
         $storm_name  = $tmp[3];  | 
| 
176
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     }  | 
| 
177
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     elsif ( $tmp[0] eq 'TROPICAL' or $tmp[0] eq 'SUBTROPICAL' or $tmp[0] eq 'REMNANTS' or $tmp[0] eq 'POST-TROPICAL' ) {  | 
| 
178
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
179
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         # SUBTROPICAL is rare. see 2007 01  | 
| 
180
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
         $storm_class = "$tmp[0] $tmp[1]";  | 
| 
181
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
         $storm_name  = $tmp[2];  | 
| 
182
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     }  | 
| 
183
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
12
 | 
     substr( $atcf_line, 148, 10 ) = sprintf( "%10s", $storm_name );  | 
| 
184
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
14
 | 
     my $adv_num_str     = sprintf( "%02d", $adv_num );  | 
| 
185
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
9
 | 
     my $adv_num_url_str = sprintf( "%03d", $adv_num );  | 
| 
186
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
187
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     # HURRICANE CENTER LOCATED NEAR 23.4N  73.9W AT 02/1500Z  | 
| 
188
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     # or  | 
| 
189
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     #TROPICAL DEPRESSION DISSIPATING NEAR 29.0N  70.0W AT 24/2100Z  | 
| 
190
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     # or  | 
| 
191
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     # TROPICAL DEPRESSION CENTER LOCATED NEAR 11.2N  36.0W AT 25/0300Z  | 
| 
192
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
6
 | 
     @match = grep /(CENTER LOCATED|DISSIPATING) NEAR/, @{$body_ref};  | 
| 
 
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
55
 | 
    | 
| 
193
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
9
 | 
     my $ns_hem = "N";  | 
| 
194
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
9
 | 
     my $ew_hem = "W";  | 
| 
195
 | 
4
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
32
 | 
     if (@match) {  | 
| 
196
 | 
4
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
21
 | 
         if ( $match[0] =~ /CENTER LOCATED NEAR\s+(\d{1,3}\.\d{1,2})([N|S])\s+(\d{1,3}\.\d{1,2})([E|W])\s+AT/ ) {  | 
| 
197
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
8
 | 
             $lat    = $1;  | 
| 
198
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
8
 | 
             $ns_hem = $2;  | 
| 
199
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
6
 | 
             $lon    = $3;  | 
| 
200
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
8
 | 
             $ew_hem = $4;  | 
| 
201
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         }  | 
| 
202
 | 
4
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
12
 | 
         if ( $match[0] =~ /DISSIPATING NEAR\s+(\d{1,3}\.\d{1,2})([N|S])\s+(\d{1,3}\.\d{1,2})([E|W])\s+AT/ ) {  | 
| 
203
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
             $lat    = $1;  | 
| 
204
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
             $ns_hem = $2;  | 
| 
205
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
             $lon    = $3;  | 
| 
206
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
             $ew_hem = $4;  | 
| 
207
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         }  | 
| 
208
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     }  | 
| 
209
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
210
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
19
 | 
     my $nowcast_lat = sprintf( "%4d$ns_hem", $lat * 10 );  | 
| 
211
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
12
 | 
     my $nowcast_lon = sprintf( "%4d$ew_hem", $lon * 10 );  | 
| 
212
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
9
 | 
     substr( $atcf_line, 34, 5 ) = sprintf( "%5s", $nowcast_lat );  | 
| 
213
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
10
 | 
     substr( $atcf_line, 41, 5 ) = sprintf( "%5s", $nowcast_lon );  | 
| 
214
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
215
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     #PRESENT MOVEMENT TOWARD THE NORTH-NORTHWEST OR 330 DEGREES AT   9 KT  | 
| 
216
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
5
 | 
     @match = grep /^PRESENT MOVEMENT TOWARD THE/, @{$body_ref};  | 
| 
 
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
46
 | 
    | 
| 
217
 | 
4
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
9
 | 
     if (@match) {  | 
| 
218
 | 
4
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
21
 | 
         if ( $match[0] =~ /PRESENT MOVEMENT TOWARD THE.+OR\s+(\d{1,3})\s+DEGREES AT\s+(\d{1,2})\s+KT/ ) {  | 
| 
219
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
7
 | 
             $center_direction = $1;  | 
| 
220
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
5
 | 
             $center_speed     = $2;  | 
| 
221
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         }  | 
| 
222
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
12
 | 
         substr( $atcf_line, 138, 4 ) = sprintf( "%4d", $center_direction );  | 
| 
223
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
7
 | 
         substr( $atcf_line, 143, 4 ) = sprintf( "%4d", $center_speed );  | 
| 
224
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     }  | 
| 
225
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
226
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
7
 | 
     @match = grep /^ESTIMATED MINIMUM CENTRAL PRESSURE/, @{$body_ref};  | 
| 
 
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
29
 | 
    | 
| 
227
 | 
4
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
9
 | 
     if (@match) {  | 
| 
228
 | 
4
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
15
 | 
         if ( $match[0] =~ /^ESTIMATED MINIMUM CENTRAL PRESSURE\s+(.+)\s+MB/ ) {  | 
| 
229
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
21
 | 
             $pressure = $1;  | 
| 
230
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         }  | 
| 
231
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     }  | 
| 
232
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
11
 | 
     substr( $atcf_line, 53, 4 ) = sprintf( "%4d", $pressure );  | 
| 
233
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
234
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     #MAX SUSTAINED WINDS  25 KT WITH GUSTS TO  35 KT.  | 
| 
235
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     #MAX SUSTAINED WINDS 125 KT WITH GUSTS TO 155 KT.  | 
| 
236
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
237
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
8
 | 
     @match = grep /^MAX SUSTAINED WINDS/, @{$body_ref};  | 
| 
 
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
32
 | 
    | 
| 
238
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
239
 | 
4
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
12
 | 
     if (@match) {  | 
| 
240
 | 
4
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
17
 | 
         if ( $match[0] =~ /^MAX SUSTAINED WINDS\s+(\d{1,4}) KT WITH GUSTS TO\s+(\d{1,4})/ ) {  | 
| 
241
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
5
 | 
             $vmax  = $1;  | 
| 
242
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
8
 | 
             $gusts = $2;  | 
| 
243
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         }  | 
| 
244
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     }  | 
| 
245
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
246
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
11
 | 
     substr( $atcf_line, 47,  4 ) = sprintf( "%4d", $vmax );  | 
| 
247
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
8
 | 
     substr( $atcf_line, 113, 4 ) = sprintf( "%4d", $gusts );  | 
| 
248
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
13
 | 
     my $forecast_atcf_filename = lc($storm_name) . "_advisory_" . $adv_num_str . ".fst";  | 
| 
249
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     #  | 
| 
250
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     # collect nowcast wind radii, if any  | 
| 
251
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
6
 | 
     my $isotachs_found = 0;  | 
| 
252
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
4
 | 
     my @isotachs;  | 
| 
253
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
7
 | 
     for my $i ( 0 ... $#{$body_ref} ) {  | 
| 
 
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
10
 | 
    | 
| 
254
 | 
76
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
107
 | 
         if ( @{$body_ref}[$i] =~ /^MAX SUSTAINED WINDS/ ) {  | 
| 
 
 | 
76
 | 
 
 | 
 
 | 
 
 | 
 
 | 
126
 | 
    | 
| 
255
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
256
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
             #64 KT....... 45NE  30SE  20SW  30NW.  | 
| 
257
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
             #50 KT.......120NE  75SE  60SW  75NW.  | 
| 
258
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
             #34 KT.......175NE 120SE 120SW 120NW.  | 
| 
259
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
7
 | 
             $i++;  | 
| 
260
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
7
 | 
             while (1) {  | 
| 
261
 | 
16
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
16
 | 
                 if ( @{$body_ref}[$i] =~ /^(\d{1,2}) KT\.{7}\s{0,}(\d{1,3})[N|S][E|W]\s+(\d{1,3})[N|S][E|W]\s+(\d{1,3})[N|S][E|W]\s+(\d{1,3})[N|S][E|W]/ ) {  | 
| 
 
 | 
16
 | 
 
 | 
 
 | 
 
 | 
 
 | 
50
 | 
    | 
| 
262
 | 
12
 | 
 
 | 
 
 | 
 
 | 
 
 | 
14
 | 
                     $isotachs_found++;  | 
| 
263
 | 
12
 | 
 
 | 
 
 | 
 
 | 
 
 | 
35
 | 
                     my @wind_radii = ( $1, $2, $3, $4, $5 );  | 
| 
264
 | 
12
 | 
 
 | 
 
 | 
 
 | 
 
 | 
36
 | 
                     push @isotachs, @wind_radii;  | 
| 
265
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
                 }  | 
| 
266
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
                 else {  | 
| 
267
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
8
 | 
                     last;  | 
| 
268
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
                 }  | 
| 
269
 | 
12
 | 
 
 | 
 
 | 
 
 | 
 
 | 
18
 | 
                 $i++;  | 
| 
270
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
             }  | 
| 
271
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
11
 | 
             for ( my $j = $isotachs_found; $j > 0; $j-- ) {  | 
| 
272
 | 
12
 | 
 
 | 
 
 | 
 
 | 
 
 | 
21
 | 
                 for ( my $k = 0; $k < 4; $k++ ) {  | 
| 
273
 | 
48
 | 
 
 | 
 
 | 
 
 | 
 
 | 
55
 | 
                     my $starting_pos = 72 + ( $k * 6 );  | 
| 
274
 | 
48
 | 
 
 | 
 
 | 
 
 | 
 
 | 
52
 | 
                     my $list_pos     = 1 + $k + ( 5 * ( $j - 1 ) );  | 
| 
275
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
276
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
                     # fill in wind radii  | 
| 
277
 | 
48
 | 
 
 | 
 
 | 
 
 | 
 
 | 
81
 | 
                     substr( $atcf_line, $starting_pos, 5 ) = sprintf( "%5d", $isotachs[$list_pos] );  | 
| 
278
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
279
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
                     # fill in isotach  | 
| 
280
 | 
48
 | 
 
 | 
 
 | 
 
 | 
 
 | 
96
 | 
                     substr( $atcf_line, 63, 3 ) = sprintf( "%3d", $isotachs[ 5 * ( $j - 1 ) ] );  | 
| 
281
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
                 }  | 
| 
282
 | 
12
 | 
 
 | 
 
 | 
 
 | 
 
 | 
22
 | 
                 push @output, $atcf_line;  | 
| 
283
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
             }  | 
| 
284
 | 
4
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
9
 | 
             unless ($isotachs_found) {  | 
| 
285
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
                 push @output, $atcf_line;  | 
| 
286
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
             }  | 
| 
287
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
8
 | 
             last;  | 
| 
288
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         }  | 
| 
289
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     }  | 
| 
290
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
291
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     # FORECAST tracks and points  | 
| 
292
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     # FORECAST VALID 10/0000Z 21.5N  84.5W FORECASTs have a bizzare date format:  | 
| 
293
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     # dd/hhmm  BUT near end of the month say 31/0000 the forecast dates switch to  | 
| 
294
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     # 01/0000, 01/12000 so we need to check for this and increment the month.  | 
| 
295
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
6
 | 
     my $forecast_period = 0;  | 
| 
296
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
6
 | 
     $forecast_year  = $nowcast_year;  | 
| 
297
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
18
 | 
     $forecast_month = $nowcast_month;  | 
| 
298
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
7
 | 
     $forecast_day   = $nowcast_day;  | 
| 
299
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
5
 | 
     $forecast_hour  = $nowcast_hour;  | 
| 
300
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
5
 | 
     my $i = 0;  | 
| 
301
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
6
 | 
     while ( $i < $#{$body_ref} ) {  | 
| 
 
 | 
188
 | 
 
 | 
 
 | 
 
 | 
 
 | 
260
 | 
    | 
| 
302
 | 
184
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
209
 | 
         if ( @{$body_ref}[$i] =~ /^(FORECAST|OUTLOOK) VALID/ ) {  | 
| 
 
 | 
184
 | 
 
 | 
 
 | 
 
 | 
 
 | 
331
 | 
    | 
| 
303
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
44
 | 
             my $atcf_line = $template;  | 
| 
304
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
77
 | 
             $atcf_line =~ s/_BASIN_/$storm_basin/;  | 
| 
305
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
306
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
             # jgf20160105: fill in the storm number  | 
| 
307
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
71
 | 
             substr( $atcf_line, 4, 2 ) = $storm_number_str;  | 
| 
308
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
309
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
             # fill in the nowcast time  | 
| 
310
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
60
 | 
             substr( $atcf_line, 8, 10 ) = sprintf( "%10d", $nowcast_date_time );  | 
| 
311
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
312
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
             # fill in the storm name  | 
| 
313
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
57
 | 
             substr( $atcf_line, 148, 10 ) = sprintf( "%10s", $storm_name );  | 
| 
314
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
36
 | 
             my $line = @{$body_ref}[$i];  | 
| 
 
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
47
 | 
    | 
| 
315
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
46
 | 
             chomp $line;  | 
| 
316
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
317
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
             # if the storm will dissipate, there is no more data to process  | 
| 
318
 | 
32
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
58
 | 
             if ( $line =~ /DISSIPATED/ ) {  | 
| 
319
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
                 last;  | 
| 
320
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
             }  | 
| 
321
 | 
32
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
87
 | 
             if ( $line =~ /^(FORECAST|OUTLOOK) VALID\s+(\d{2})\/(\d{4})Z/ ) {  | 
| 
322
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
52
 | 
                 $forecast_day  = $2;  | 
| 
323
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
56
 | 
                 $forecast_hour = substr( $3, 0, 2 );  | 
| 
324
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
             }  | 
| 
325
 | 
32
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
122
 | 
             if ( $line =~ /Z\s+(\d{1,2}\.\d{1,2})([N|S])\s+(\d{1,2}\.\d{1,2})([E|W])/ ) {  | 
| 
326
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
74
 | 
                 $lat    = $1;  | 
| 
327
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
38
 | 
                 $ns_hem = $2;  | 
| 
328
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
36
 | 
                 $lon    = $3;  | 
| 
329
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
38
 | 
                 $ew_hem = $4;  | 
| 
330
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
             }  | 
| 
331
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
92
 | 
             my $forecast_lat = sprintf( "%4d$ns_hem", $lat * 10 );  | 
| 
332
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
58
 | 
             my $forecast_lon = sprintf( "%4d$ew_hem", $lon * 10 );  | 
| 
333
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
53
 | 
             substr( $atcf_line, 34, 5 ) = sprintf( "%5s", $forecast_lat );  | 
| 
334
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
38
 | 
             substr( $atcf_line, 41, 5 ) = sprintf( "%5s", $forecast_lon );  | 
| 
335
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
336
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
             # Get the next line  | 
| 
337
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
             # MAX WIND  30 KT...GUSTS  40 KT.  | 
| 
338
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
51
 | 
             $i++;  | 
| 
339
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
34
 | 
             $line = @{$body_ref}[$i];  | 
| 
 
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
43
 | 
    | 
| 
340
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
42
 | 
             chomp $line;  | 
| 
341
 | 
32
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
90
 | 
             if ( $line =~ /^MAX WIND\s+(\d{1,4}) KT\.\.\.GUSTS\s+(\d{1,4}) KT\./ ) {  | 
| 
342
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
42
 | 
                 $vmax  = $1;  | 
| 
343
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
39
 | 
                 $gusts = $2;  | 
| 
344
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
             }  | 
| 
345
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
71
 | 
             substr( $atcf_line, 47,  4 ) = sprintf( "%4d", $vmax );  | 
| 
346
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
46
 | 
             substr( $atcf_line, 113, 4 ) = sprintf( "%4d", $gusts );  | 
| 
347
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
74
 | 
             $forecast_date_time = sprintf( "%04d%02d%02d%02d", $forecast_year, $forecast_month, $forecast_day, $forecast_hour );  | 
| 
348
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
349
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
             # check to see if we have crossed into the next month  | 
| 
350
 | 
32
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
62
 | 
             if ( $forecast_date_time < $nowcast_date_time ) {  | 
| 
351
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
                 $forecast_month++;  | 
| 
352
 | 
  
0
  
 | 
  
  0
  
 | 
 
 | 
 
 | 
 
 | 
0
 | 
                 if ( $forecast_month > 12 ) {  | 
| 
353
 | 
0
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
                     $forecast_month = 1;  | 
| 
354
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
                 }  | 
| 
355
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
             }  | 
| 
356
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
357
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
             # Determine the time in hours (forecast period) between the current  | 
| 
358
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
             # forecast and the nowcast time  | 
| 
359
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
101
 | 
             ( my $ddays, my $dhrs, my $dsec ) = Date::Calc::Delta_DHMS( $nowcast_year, $nowcast_month, $nowcast_day, $nowcast_hour, 0, 0, $forecast_year, $forecast_month, $forecast_day, $forecast_hour, 0, 0 );  | 
| 
360
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
47
 | 
             my $forecast_period = $ddays * 24 + $dhrs;  | 
| 
361
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
51
 | 
             substr( $atcf_line, 29, 4 ) = sprintf( "%4d", $forecast_period );  | 
| 
362
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
363
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
             # Get the next line and parse the isotachs  | 
| 
364
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
45
 | 
             $i++;  | 
| 
365
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
48
 | 
             ( $i, my $output_ref ) = _parseIotachs( $body_ref, $i, $atcf_line );  | 
| 
366
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
69
 | 
             push @output, @$output_ref;  | 
| 
367
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         }  | 
| 
368
 | 
184
 | 
 
 | 
 
 | 
 
 | 
 
 | 
214
 | 
         $i++;  | 
| 
369
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     }  | 
| 
370
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
371
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     # save in instance's internal field  | 
| 
372
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
13
 | 
     $self->{as_atcf} = \@output;  | 
| 
373
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
374
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     # return array reference for convenience  | 
| 
375
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
17
 | 
     return $self->as_atcf;  | 
| 
376
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 }  | 
| 
377
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
378
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 sub _parseIotachs {  | 
| 
379
 | 
32
 | 
 
 | 
 
 | 
  
32
  
 | 
 
 | 
43
 | 
     my ( $body_ref, $i, $atcf_line ) = @_;  | 
| 
380
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
33
 | 
     my $isotachs_found = 0;  | 
| 
381
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
38
 | 
     my @isotachs       = ();  | 
| 
382
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
33
 | 
     my @output         = ();  | 
| 
383
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
40
 | 
     while (1) {  | 
| 
384
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
385
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         #64 KT... 45NE  30SE  20SW  30NW.  | 
| 
386
 | 
96
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
96
 | 
         if ( @{$body_ref}[$i] =~ /^(\d{1,2}) KT\.{3}\s{0,}(\d{1,3})[N|S][E|W]\s+(\d{1,3})[N|S][E|W]\s+(\d{1,3})[N|S][E|W]\s+(\d{1,3})[N|S][E|W]/ ) {  | 
| 
 
 | 
96
 | 
 
 | 
 
 | 
 
 | 
 
 | 
278
 | 
    | 
| 
387
 | 
64
 | 
 
 | 
 
 | 
 
 | 
 
 | 
73
 | 
             $isotachs_found++;  | 
| 
388
 | 
64
 | 
 
 | 
 
 | 
 
 | 
 
 | 
149
 | 
             my @wind_radii = ( $1, $2, $3, $4, $5 );  | 
| 
389
 | 
64
 | 
 
 | 
 
 | 
 
 | 
 
 | 
162
 | 
             push @isotachs, @wind_radii;  | 
| 
390
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         }  | 
| 
391
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         else {  | 
| 
392
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
45
 | 
             last;  | 
| 
393
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         }  | 
| 
394
 | 
64
 | 
 
 | 
 
 | 
 
 | 
 
 | 
86
 | 
         $i++;  | 
| 
395
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     }  | 
| 
396
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
57
 | 
     for ( my $j = $isotachs_found; $j > 0; $j-- ) {  | 
| 
397
 | 
64
 | 
 
 | 
 
 | 
 
 | 
 
 | 
92
 | 
         for ( my $k = 0; $k < 4; $k++ ) {  | 
| 
398
 | 
256
 | 
 
 | 
 
 | 
 
 | 
 
 | 
319
 | 
             my $starting_pos = 72 + ( $k * 6 );  | 
| 
399
 | 
256
 | 
 
 | 
 
 | 
 
 | 
 
 | 
290
 | 
             my $list_pos     = 1 + $k + ( 5 * ( $j - 1 ) );  | 
| 
400
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
401
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
             # fill in wind radii  | 
| 
402
 | 
256
 | 
 
 | 
 
 | 
 
 | 
 
 | 
414
 | 
             substr( $atcf_line, $starting_pos, 5 ) = sprintf( "%5d", $isotachs[$list_pos] );  | 
| 
403
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
404
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
             # fill in isotach  | 
| 
405
 | 
256
 | 
 
 | 
 
 | 
 
 | 
 
 | 
482
 | 
             substr( $atcf_line, 63, 3 ) = sprintf( "%3d", $isotachs[ 5 * ( $j - 1 ) ] );  | 
| 
406
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         }  | 
| 
407
 | 
64
 | 
 
 | 
 
 | 
 
 | 
 
 | 
111
 | 
         push @output, $atcf_line;  | 
| 
408
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     }  | 
| 
409
 | 
32
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
45
 | 
     unless ($isotachs_found) {  | 
| 
410
 | 
8
 | 
 
 | 
 
 | 
 
 | 
 
 | 
11
 | 
         push @output, $atcf_line;  | 
| 
411
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     }  | 
| 
412
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
92
 | 
     return ( $i, \@output );  | 
| 
413
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 }  | 
| 
414
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
415
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 1;  | 
| 
416
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
417
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 __END__  |