File Coverage

blib/lib/Geo/WeatherNWS.pm
Criterion Covered Total %
statement 280 404 69.3
branch 118 182 64.8
condition 70 122 57.3
subroutine 17 25 68.0
pod 20 20 100.0
total 505 753 67.0


line stmt bran cond sub pod time code
1             package Geo::WeatherNWS;
2              
3             #------------------------------------------------------------------------------
4             #
5             # Package Name: Get Observations from NWS (Geo::WeatherNWS)
6             #
7             # Last Modified: 18 December 2001 - Prepared for CPAN - Marc Slagle
8             # 24 February 2002 - Adding server/error code - Marc
9             # 10 August 2011 - changed FTP server name, added tests,
10             # docs, some restructuring. - Bob Ernst
11             # 14 November 2012 - removed unneeded /d after tr,
12             # make network tests optional,
13             # check status of opens - Bob
14             # 26 November 2012 - Address bug 14632 (METAR Decoding) from dstroma
15             # Address bug 27513 (Geo-WeatherNWS returns wrong station code)
16             # from Guenter Knauf
17             # Fix issues with undefined values,
18             # Change some conversion constants,
19             # Round instead of truncate results,
20             # Only calculate windchill for proper range,
21             # "ptemerature" is now spelled "ptemperature"
22             # Fixed handling of condition text
23             # Relax ICAO naming rules
24             # Change ICAO website
25             # Change http web site from weather.noaa.gov
26             # to www.aviationweather.gov, and change parsing to match.
27             # Add report_date and report_time items.
28             # - Bob
29             # 27 November 2012 - Add POD documentation for new functions.
30             # 20 December 2012 - Handle undefined dewpoint
31             # Minor performance improvement
32             # Add test for getreporthttp
33             # 22 December 2012 - Clean up and avoid division by zero
34             #
35             #------------------------------------------------------------------------------
36              
37             #------------------------------------------------------------------------------
38             # We need these
39             #------------------------------------------------------------------------------
40              
41             require 5.005_62;
42 3     3   213884 use strict;
  3         6  
  3         131  
43 3     3   17 use warnings;
  3         111  
  3         94  
44 3     3   3981 use Net::FTP ();
  3         313091  
  3         90  
45 3     3   5557 use POSIX ();
  3         80321  
  3         95  
46 3     3   38 use Carp ();
  3         7  
  3         48780  
47              
48             #------------------------------------------------------------------------------
49             # Version
50             #------------------------------------------------------------------------------
51              
52             our $VERSION = '1.053';
53              
54             #------------------------------------------------------------------------------
55             # Round function
56             # Using Math::Round would add another dependency
57             #------------------------------------------------------------------------------
58              
59             sub round {
60 157     157 1 209 my $float = shift;
61 157         183 my $rounded;
62              
63 157 100 66     1053 if ( defined $float && $float ne "" ) {
64 136         305 $rounded = sprintf "%.0f", $float;
65             }
66 157         433 return $rounded;
67             }
68              
69             #------------------------------------------------------------------------------
70             # Temperature conversion
71             # If the temperature we are converting from is undefined,
72             # then the temperature we are converting to is also undefined.
73             #------------------------------------------------------------------------------
74              
75             sub convert_f_to_c {
76 18     18 1 30 my $fahrenheit = shift;
77 18         25 my $celsius;
78              
79 18 100 66     91 if ( defined $fahrenheit && $fahrenheit ne "" ) {
80 8         17 $celsius = (5.0/9.0) * ($fahrenheit - 32.0);
81             }
82 18         39 return $celsius;
83             }
84              
85             sub convert_c_to_f {
86 18     18 1 29 my $celsius = shift;
87 18         23 my $fahrenheit;
88              
89 18 100 66     88 if ( defined $celsius && $celsius ne "" ) {
90 17         42 $fahrenheit = ((9.0/5.0) * $celsius) + 32.0;
91             }
92 18         44 return $fahrenheit;
93             }
94              
95             #------------------------------------------------------------------------------
96             # Windchill
97             #------------------------------------------------------------------------------
98              
99             sub windchill {
100 9     9 1 13 my $F = shift;
101 9         17 my $wind_speed_mph = shift;
102 9         15 my $windchill;
103              
104             # This is the North American wind chill index.
105             # Windchill temperature is only defined for:
106             # * temperatures at or below 50 F
107             # * wind speed above 3 mph
108             # Bright sunshine may increase the wind chill temperature by
109             # 10 to 18 degress F.
110              
111 9 50 33     117 if ( defined $F && $F ne "" && defined $wind_speed_mph && $wind_speed_mph ne "" ) {
      33        
      33        
112             # Old Formula
113             # my $Windc=int(
114             # 0.0817*
115             # (3.71*$Self->{windspeedmph}**0.5 + 5.81 - 0.25*$Self->{windspeedmph})*
116             # ($F - 91.4) + 91.4);
117              
118             # New Formula
119 9 50 66     47 if ($F <= 50 && $wind_speed_mph > 3) {
120 0         0 $windchill =
121             35.74 +
122             ( 0.6215 * $F ) -
123             ( 35.75 * ( $wind_speed_mph**0.16 ) ) +
124             ( ( 0.4275 * $F ) * ( $wind_speed_mph**0.16 ) );
125             }
126             }
127 9         19 return $windchill;
128             }
129              
130             #------------------------------------------------------------------------------
131             # Heat Index
132             #------------------------------------------------------------------------------
133              
134             sub heat_index {
135 9     9 1 18 my $F = shift;
136 9         16 my $rh = shift;
137 9         19 my $heat_index;
138              
139 9 100 33     115 if ( defined $F && $F ne "" && defined $rh && $rh ne "" ) {
      66        
      66        
140 8         60 $heat_index =
141             -42.379 +
142             2.04901523 * $F +
143             10.14333127 * $rh -
144             0.22475541 * $F * $rh -
145             6.83783e-03 * $F**2 -
146             5.481717e-02 * $rh**2 +
147             1.22874e-03 * $F**2 * $rh +
148             8.5282e-04 * $F * $rh**2 -
149             1.99e-06 * $F**2 * $rh**2;
150             }
151 9         21 return $heat_index;
152             }
153              
154             #------------------------------------------------------------------------------
155             # Convert wind speed from nautical miles per hour to miles per hour
156             #------------------------------------------------------------------------------
157              
158             sub convert_kts_to_mph {
159 20     20 1 32 my $knots = shift;
160 20         28 my $mph;
161              
162 20 50 33     101 if ( defined $knots && $knots ne "" ) {
163 20         51 $mph = $knots * 1.150779;
164             }
165 20         61 return $mph;
166             }
167              
168             #------------------------------------------------------------------------------
169             # Convert wind speed from nautical miles per hour to kilometers per hour
170             #------------------------------------------------------------------------------
171              
172             sub convert_kts_to_kmh {
173 20     20 1 33 my $knots = shift;
174 20         24 my $kmh;
175              
176 20 50 33     92 if ( defined $knots && $knots ne "" ) {
177 20         30 $kmh = $knots * 1.852;
178             }
179 20         51 return $kmh;
180             }
181              
182             #------------------------------------------------------------------------------
183             # Convert miles to kilometers
184             #------------------------------------------------------------------------------
185              
186             sub convert_miles_to_km {
187 9     9 1 18 my $miles = shift;
188 9         13 my $km;
189              
190 9 50 33     77 if ( defined $miles && $miles ne "" ) {
191 9         62 $km = $miles * 1.609344;
192             }
193 9         21 return $km;
194             }
195              
196             #------------------------------------------------------------------------------
197             # Translate Weather into readable Conditions Text
198             #
199             # Reference is WMO Code Table 4678
200             #------------------------------------------------------------------------------
201              
202             sub translate_weather {
203 7     7 1 16 my $coded = shift;
204 7         21 my $old_conditionstext = shift;
205 7         14 my $old_conditions1 = shift;
206 7         19 my $old_conditions2 = shift;
207 7         11 my ($conditionstext, $conditions1, $conditions2, $intensity);
208              
209             # We use %Converter to translate 2-letter codes into text
210              
211 7         208 my %Converter = (
212             BR => 'Mist',
213             TS => 'Thunderstorm',
214             MI => 'Shallow',
215             PR => 'Partial',
216             BC => 'Patches',
217             DR => 'Low Drifting',
218             BL => 'Blowing',
219             SH => 'Shower',
220             FZ => 'Freezing',
221             DZ => 'Drizzle',
222             RA => 'Rain',
223             SN => 'Snow',
224             SG => 'Snow Grains',
225             IC => 'Ice Crystals',
226             PE => 'Ice Pellets',
227             PL => 'Ice Pellets',
228             GR => 'Hail',
229             GS => 'Small Hail/Snow',
230             UP => 'Unknown Precipitation',
231             FG => 'Fog',
232             FU => 'Smoke',
233             VA => 'Volcanic Ash',
234             DU => 'Widespread Dust',
235             SA => 'Sand',
236             HZ => 'Haze',
237             PY => 'Spray',
238             PO => 'Dust Devils',
239             SQ => 'Squalls',
240             FC => 'Tornado',
241             SS => 'Sandstorm'
242             );
243              
244 7 100       31 if ( $coded =~ /^[-+]/ ) {
245             # Heavy(+) or Light(-) condition
246              
247 4 50       15 if ( !$old_conditions1 ) {
248 4         6 my ( $Block1, $Block2 );
249 4         13 my $Modifier = substr( $coded, 0, 1 ); # +/-
250 4         9 my $Block1t = substr( $coded, 1, 2 ); # e.g. TS
251 4         9 my $Block2t = substr( $coded, 3, 4 ); # e.g. RA
252              
253 4         9 $Block1 = $Converter{$Block1t}; # e.g. Thunderstorm
254 4         8 $conditions1 = $Block1; # e.g. Thunderstorm
255              
256 4 50       13 if ($Block2t) {
257 4         6 $Block2 = $Converter{$Block2t}; # e.g. Rain
258 4         8 $conditions2 = $Block2; # e.g. Rain
259             }
260              
261 4 100       26 if ( $Modifier =~ /^\-/ ) {
    50          
262 2         5 $Block1 = "Light $Block1"; # e.g. Light Thunderstorm
263 2         5 $intensity = "Light";
264             }
265             elsif ( $Modifier =~ /^\+/ ) {
266 2         22 $Block1 = "Heavy $Block1"; # e.g. Heavy Thunderstorm
267 2         7 $intensity = "Heavy";
268             }
269              
270 4 50       13 if ($Block2) {
271 4         11 $Block1 = "$Block1 $Block2"; # e.g. Light Thunderstorm Rain
272             }
273              
274 4 50       14 if ($old_conditionstext) {
275 0 0       0 if ( $Block1 eq "SH" ) {
276 0         0 $conditionstext = "$Block2 of $Block1";
277 0         0 $conditions1 = "Showers of";
278             }
279             else {
280 0         0 $conditionstext = "$old_conditionstext and $Block1";
281             }
282             }
283             else {
284 4         11 $conditionstext = $Block1;
285             }
286             }
287             }
288             else {
289             # Moderate condition
290              
291 3 50       11 if ( !$old_conditions1 ) {
292 3         7 my ( $Block1, $Block2 );
293 3         9 my $Block1t = substr( $coded, 0, 2 ); # e.g. TS
294 3         23 my $Block2t = substr( $coded, 2, 4 ); # e.g. RA
295              
296 3         6 $Block1 = $Converter{$Block1t}; # e.g. Thunderstorm
297 3         6 $conditions1 = $Block1; # e.g. Thunderstorm
298              
299 3 100       11 if ($Block2t) {
300 2         3 $Block2 = $Converter{$Block2t};
301 2         4 $conditions2 = $Block2;
302 2         8 $Block1 = "$Block1 $Block2";
303             }
304              
305 3 50       8 if ($old_conditionstext) {
306 0 0       0 if ( $Block1 eq "SH" ) {
307 0         0 $conditionstext = "$Block2 of $Block1";
308 0         0 $conditions1 = "Showers of";
309             }
310             else {
311 0         0 $conditionstext = "$old_conditionstext and $Block1";
312             }
313             }
314             else {
315 3         9 $conditionstext = $Block1;
316             }
317             }
318             }
319 7         62 return ($conditionstext, $conditions1, $conditions2, $intensity);
320             }
321              
322             #------------------------------------------------------------------------------
323             # Lets create a new self
324             #------------------------------------------------------------------------------
325              
326             sub new {
327 10     10 1 73362 my $Proto = shift;
328 10   100     111 my $Class = ref($Proto) || $Proto || __PACKAGE__;
329 10         28 my $Self = {};
330              
331 10         31 $Self->{servername} = "tgftp.nws.noaa.gov";
332 10         25 $Self->{username} = "anonymous";
333 10         23 $Self->{password} = 'weather@cpan.org';
334 10         29 $Self->{directory} = "/data/observations/metar/stations";
335 10         36 $Self->{timeout} = 120;
336              
337 10         30 bless $Self, $Class;
338 10         33 return $Self;
339             }
340              
341             #------------------------------------------------------------------------------
342             # Adding ability to edit server/user/directory at runtime...
343             #------------------------------------------------------------------------------
344              
345             sub setservername {
346 0     0 1 0 my $Self = shift;
347 0         0 my $Servername = shift;
348 0         0 $Self->{servername} = $Servername;
349 0         0 return $Self;
350             }
351              
352             sub setusername {
353 0     0 1 0 my $Self = shift;
354 0         0 my $User = shift;
355 0         0 $Self->{username} = $User;
356 0         0 return $Self;
357             }
358              
359             sub setpassword {
360 0     0 1 0 my $Self = shift;
361 0         0 my $Pass = shift;
362 0         0 $Self->{password} = $Pass;
363 0         0 return $Self;
364             }
365              
366             sub setdirectory {
367 0     0 1 0 my $Self = shift;
368 0         0 my $Dir = shift;
369 0         0 $Self->{directory} = $Dir;
370 0         0 return $Self;
371             }
372              
373             sub settemplatefile {
374 0     0 1 0 my $Self = shift;
375 0         0 my $Tfile = shift;
376 0         0 $Self->{tfile} = $Tfile;
377 0         0 return $Self;
378             }
379              
380             sub settimeout {
381 0     0 1 0 my $Self = shift;
382 0         0 my $Seconds = shift;
383 0         0 $Self->{timeout} = $Seconds;
384 0         0 return $Self;
385             }
386              
387             #------------------------------------------------------------------------------
388             # Here we get to FTP to the NWS and get the data
389             #------------------------------------------------------------------------------
390              
391             sub getreporthttp {
392 0     0 1 0 my $Self = shift;
393 0         0 my $Code = shift;
394             # The old site was: http://weather.noaa.gov/cgi-bin/mgetmetar.pl?cccc=$Code
395 0         0 $Self->{http} =
396             'http://www.aviationweather.gov/adds/metars/?station_ids=' . $Code . '&chk_metars=on&hoursStr=most+recent+only';
397 0         0 my $Ret = &getreport( $Self, $Code );
398 0         0 return $Ret;
399             }
400              
401             sub getreport {
402 0     0 1 0 my $Self = shift;
403 0         0 my $Station = shift;
404              
405 0         0 $Self->{error} = "0";
406 0         0 my $Tmpfile = POSIX::tmpnam();
407 0         0 my $Code = uc($Station);
408              
409 0 0       0 if ( !$Code ) {
410 0         0 $Self->{error} = "1";
411 0         0 $Self->{errortext} = "No Station Code Entered\n";
412 0         0 return $Self;
413             }
414              
415 0 0       0 if ( $Self->{http} ) {
416 0         0 require LWP::UserAgent;
417 0         0 my $Ua = LWP::UserAgent->new();
418 0         0 $Ua->agent("Geo::WeatherNWS $VERSION");
419              
420 0         0 my $Req = HTTP::Request->new( GET => $Self->{http} );
421 0         0 $Req->content_type('application/x-www-form-urlencoded');
422 0         0 my $Res = $Ua->request($Req);
423              
424 0 0       0 if ( $Res->is_success ) {
425 0         0 my @Lines = split( /\n/, $Res->content );
426 0         0 foreach my $Line (@Lines) {
427 0 0       0 if ( $Line =~ /<(TITLE|H1|H2)>/ ) {
428             # ignore
429             }
430             else {
431             # Remove HTML elements.
432             # (This isn't very robust, but it gets the job done for now.)
433 0         0 $Line =~ s/<[^>]*>//g;
434              
435             # If the line starts with an ICAO, then the line is an observation (we hope)
436 0 0       0 if ( $Line =~ /^[A-Z][A-Z0-9]{3}\s/ ) {
437 0         0 $Self->{obs} = $Line;
438 0         0 last;
439             }
440             }
441             }
442             }
443             }
444             else {
445              
446             # Some users needed this for firewalls...
447 0         0 my $Ftp = Net::FTP->new(
448             $Self->{servername},
449             Debug => 0,
450             Passive => 1,
451             Timeout => $Self->{timeout}
452             );
453              
454             # my $Ftp=Net::FTP->new($Self->{servername}, Debug => 0);
455 0 0       0 if ( !defined $Ftp ) {
456 0         0 $Self->{error} = 1;
457 0         0 $Self->{errortext} = "Cannot connect to $Self->{servername}: $@";
458 0         0 return $Self;
459             }
460 0         0 $Ftp->login( $Self->{username}, $Self->{password} );
461 0         0 my $Rcode = $Ftp->code();
462 0         0 my $Message = $Ftp->message();
463              
464 0 0       0 if ( $Rcode =~ /^[45]/ ) {
465 0         0 $Self->{error} = $Rcode;
466 0         0 $Self->{errortext} = $Message;
467 0         0 return $Self;
468             }
469              
470 0         0 $Ftp->cwd( $Self->{directory} );
471 0         0 $Rcode = $Ftp->code();
472 0         0 $Message = $Ftp->message();
473              
474 0 0       0 if ( $Rcode =~ /^[45]/ ) {
475 0         0 $Self->{error} = $Rcode;
476 0         0 $Self->{errortext} = $Message;
477 0         0 return $Self;
478             }
479              
480 0         0 $Rcode = $Ftp->get( "$Code.TXT", $Tmpfile );
481 0         0 $Rcode = $Ftp->code();
482 0         0 $Message = $Ftp->message();
483 0         0 $Ftp->quit;
484              
485 0 0       0 if ( $Rcode =~ /^[45]/ ) {
486 0         0 $Self->{error} = $Rcode;
487 0         0 $Self->{errortext} = $Message;
488 0         0 return $Self;
489             }
490              
491 0         0 local $/; # enable slurp mode
492 0 0       0 open my $F, '<', $Tmpfile or
493             Carp::croak "error opening temp input $Tmpfile: $!";
494 0         0 my $Data = <$F>;
495 0         0 close($F);
496 0         0 unlink($Tmpfile);
497              
498 0         0 $Data =~ tr/\n/ /;
499 0         0 $Self->{obs} = $Data;
500             }
501              
502 0         0 $Self->decode();
503 0         0 return $Self;
504             }
505              
506             #------------------------------------------------------------------------------
507             # Decodeobs takes the obs in a string format and decodes them
508             #------------------------------------------------------------------------------
509              
510             sub decodeobs {
511 10     10 1 900 my $Self = shift;
512 10         19 my $Obs = shift;
513 10         29 $Self->{obs} = $Obs;
514 10         37 $Self->decode();
515 10         36 return $Self;
516             }
517              
518             #------------------------------------------------------------------------------
519             # Decode does the work, and is only called internally
520             #------------------------------------------------------------------------------
521              
522             sub decode {
523 10     10 1 20 my $Self = shift;
524 10         15 my @Cloudlevels;
525              
526 10         92 my @Splitter = split( /\s+/, $Self->{obs} );
527              
528             #------------------------------------------------------------------------------
529             # Break the METAR observations down and decode
530             #------------------------------------------------------------------------------
531              
532 10         29 my $have_icao_code = 0;
533 10         20 my $column = 0;
534              
535 10         31 foreach my $Line (@Splitter) {
536 123         164 $column++;
537              
538              
539             #------------------------------------------------------------------------------
540             # Report date and time
541             # These aren't always present (for example, from the http interface)
542             #------------------------------------------------------------------------------
543              
544 123 100 66     392 if ( $column == 1 && $Line =~ /^\d{4}\/\d{2}\/\d{2}$/) {
545 10         29 $Self->{report_date} = $Line;
546             }
547              
548 123 100 66     353 if ( $column == 2 && $Line =~ /^\d{2}:\d{2}$/) {
549 10         31 $Self->{report_time} = $Line;
550             }
551              
552             #------------------------------------------------------------------------------
553             # ICAO station code
554             #------------------------------------------------------------------------------
555              
556 123 100 100     3214 if ( ( $Line =~ /^([A-Z][A-Z0-9]{3})/ ) &&
    100 66        
    100 66        
    100 33        
    100 33        
    100 66        
    100 100        
    100 100        
    100 100        
    100 100        
      66        
      66        
557             ( !$have_icao_code ) ) {
558             # Use the first value that looks like the ICAO code.
559             # This should either be the first item, or
560             # the third item if there is a leading date and time.
561             # (Before we checked have_icao_code, we'd get values
562             # like TSRA or FZFG later in the observation being treated
563             # as the ICAO code.)
564             # We also allow the last three characters to be digits.
565              
566             # There was a check for "AUTO" above before, for now
567             # we'll add an extra check for that value. (AUTO should
568             # show up in the fifth column.)
569 10 50       34 Carp::croak "Unexpected value AUTO for ICAO code" if $Line eq "AUTO";
570              
571 10         27 $Self->{code} = $Line;
572 10         29 $have_icao_code = 1;
573             }
574              
575             #------------------------------------------------------------------------------
576             # Report Time
577             #------------------------------------------------------------------------------
578              
579             elsif ( $Line =~ /([0-9]Z)$/ ) {
580 10         27 my $Timez = substr( $Line, 2, 4 );
581 10         26 $Self->{time} = $Timez;
582 10         38 $Self->{day} = substr( $Line, 0, 2 );
583             }
584              
585             #------------------------------------------------------------------------------
586             # Wind speed and direction
587             #------------------------------------------------------------------------------
588              
589             elsif ( $Line =~ /([0-9]KT)$/ ) {
590 10         12 my $Newline;
591             my $Variable;
592              
593 10 50       29 if ( $Line =~ /VRB/ ) {
594 0         0 $Newline = substr( $Line, 3 );
595 0         0 $Variable = "1";
596             }
597             else {
598 10         21 $Newline = $Line;
599             }
600              
601 10         28 my $Winddir = substr( $Newline, 0, 3 );
602 10         28 $Winddir =~ tr/[A-Z]/ /d;
603 10         24 $Winddir = $Winddir - 0;
604 10         13 my $Winddirtxt;
605              
606 10 50 66     166 if ($Variable) {
    100 33        
    50 33        
    50 33        
    50 33        
    50 66        
    100 33        
    50 33        
    50          
607 0         0 $Winddirtxt = "Variable";
608             }
609             elsif ( ( $Winddir <= 22.5 ) || ( $Winddir >= 337.5 ) ) {
610 3         25 $Winddirtxt = "North";
611             }
612             elsif ( ( $Winddir <= 67.5 ) && ( $Winddir >= 22.5 ) ) {
613 0         0 $Winddirtxt = "Northeast";
614             }
615             elsif ( ( $Winddir <= 112.5 ) && ( $Winddir >= 67.5 ) ) {
616 0         0 $Winddirtxt = "East";
617             }
618             elsif ( ( $Winddir <= 157.5 ) && ( $Winddir >= 112.5 ) ) {
619 0         0 $Winddirtxt = "Southeast";
620             }
621             elsif ( ( $Winddir <= 202.5 ) && ( $Winddir >= 157.5 ) ) {
622 0         0 $Winddirtxt = "South";
623             }
624             elsif ( ( $Winddir <= 247.5 ) && ( $Winddir >= 202.5 ) ) {
625 6         14 $Winddirtxt = "Southwest";
626             }
627             elsif ( ( $Winddir <= 292.5 ) && ( $Winddir >= 247.5 ) ) {
628 0         0 $Winddirtxt = "West";
629             }
630             elsif ( ( $Winddir <= 337.5 ) && ( $Winddir >= 292.5 ) ) {
631 1         3 $Winddirtxt = "Northwest";
632             }
633              
634 10         25 my $Windspeedkts = substr( $Line, 3 );
635 10         14 my $Windgustkts = 0;
636              
637 10 100       44 if ( $Windspeedkts =~ /G/ ) {
638 6         25 ($Windspeedkts, $Windgustkts) = split( /G/, $Windspeedkts );
639 6 50 33     42 if ( ! defined $Windgustkts || $Windgustkts eq "" ) {
640 0         0 $Windgustkts = 0;
641             }
642             }
643              
644 10         17 $Windspeedkts =~ tr/[A-Z]//d;
645 10         18 $Windgustkts =~ tr/[A-Z]//d;
646              
647 10 100       44 if ( $Windspeedkts == 0 ) {
648 2         6 $Winddirtxt = "Calm";
649             }
650              
651 10         34 my $MPH = round( convert_kts_to_mph($Windspeedkts) );
652 10         25 my $GMPH = round( convert_kts_to_mph($Windgustkts) );
653 10         30 my $KMH = round( convert_kts_to_kmh($Windspeedkts) );
654 10         22 my $GKMH = round( convert_kts_to_kmh($Windgustkts) );
655              
656 10         29 $Self->{windspeedkts} = $Windspeedkts;
657 10         24 $Self->{windgustkts} = $Windgustkts;
658 10         27 $Self->{windspeedkts} = $Self->{windspeedkts} - 0;
659 10         22 $Self->{windspeedmph} = $MPH;
660 10         23 $Self->{windspeedkmh} = $KMH;
661 10         39 $Self->{windgustmph} = $GMPH;
662 10         24 $Self->{windgustkmh} = $GKMH;
663 10         44 $Self->{winddirtext} = $Winddirtxt;
664 10         22 $Self->{winddir} = $Winddir;
665 10         41 $Self->{winddir} = $Self->{winddir} - 0;
666             }
667              
668             #------------------------------------------------------------------------------
669             # Current Visibility
670             #------------------------------------------------------------------------------
671              
672             elsif ( $Line =~ /([0-9]SM)$/ ) {
673 9         22 $DB::single = 1;
674 9         20 $Line =~ tr/[A-Z]//d;
675              
676             #------------------------------------------------------------------------------
677             # Some stations were reporting fractions for this value
678             #------------------------------------------------------------------------------
679              
680 9 100       38 if ( $Line =~ /\// ) {
681 7         33 my ($Numerator,$Denominator) = split( /\//, $Line );
682 7 50 33     56 if ( defined $Denominator && $Denominator ne "" ) {
683 7 50       25 if ( $Denominator != 0 ) {
684 7         21 $Line = $Numerator / $Denominator;
685             }
686             else {
687 0         0 $Line = undef;
688             }
689             }
690             else {
691 0         0 $Line = $Numerator;
692             }
693             }
694              
695 9         24 my $Viskm = convert_miles_to_km( $Line );
696 9         23 $Self->{visibility_mi} = round($Line);
697 9         20 $Self->{visibility_km} = round($Viskm);
698             }
699              
700             #------------------------------------------------------------------------------
701             # Current Conditions
702             #------------------------------------------------------------------------------
703              
704             elsif (
705             ( $Line =~ /
706             (BR|TS|MI|PR|BC|DR|BL|SH|FZ|DZ|RA|SN|SG|IC|PE|PL|GR|GS|UP|FG|FU|VA|DU|SA|HZ|PY|PO|SQ|FC|SS)
707             ([A-Z])*
708             /x)
709             || ( $Line =~ /^VC([A-Z])*/ )
710             || ( $Line =~ /[\+\-]VC([A-Z])*/ ) )
711             {
712 7         60 my ($conditionstext, $conditions1, $conditions2, $intensity) =
713             translate_weather($Line, $Self->{conditionstext}, $Self->{conditions1},$Self->{conditions2});
714 7 50       41 $Self->{conditionstext} = $conditionstext if defined $conditionstext;
715 7 50       26 $Self->{conditions1} = $conditions1 if defined $conditions1;
716 7 100       24 $Self->{conditions2} = $conditions2 if defined $conditions2;
717 7 100       31 $Self->{intensity} = $intensity if defined $intensity;
718             }
719              
720             #------------------------------------------------------------------------------
721             # Cloud Cover
722             #------------------------------------------------------------------------------
723              
724             elsif (( $Line =~ /^(VV[0-9])/ )
725             || ( $Line =~ /^(SKC[0-9])/ )
726             || ( $Line =~ /^(CLR)/ )
727             || ( $Line =~ /^(FEW)/ )
728             || ( $Line =~ /^(SCT[0-9])/ )
729             || ( $Line =~ /^(BKN[0-9])/ )
730             || ( $Line =~ /^(OVC[0-9])/ ) )
731             {
732              
733 13         31 push( @Cloudlevels, $Line );
734              
735 13 50       120 if ( $Line =~ /^(CLR)/ ) {
    100          
    100          
    100          
    50          
736 0         0 $Self->{cloudcover} = "Clear";
737             }
738             elsif ( $Line =~ /^(FEW)/ ) {
739 3         8 $Self->{cloudcover} = "Fair";
740             }
741             elsif ( $Line =~ /^(SCT[0-9])/ ) {
742 2         5 $Self->{cloudcover} = "Partly Cloudy";
743             }
744             elsif ( $Line =~ /^(BKN[0-9])/ ) {
745 2         7 $Self->{cloudcover} = "Mostly Cloudy";
746             }
747             elsif ( $Line =~ /^(OVC[0-9])/ ) {
748 6         15 $Self->{cloudcover} = "Cloudy";
749             }
750              
751 13 100       49 if ( !$Self->{conditionstext} ) {
752 3         11 $Self->{conditionstext} = $Self->{cloudcover};
753             }
754             }
755              
756             #------------------------------------------------------------------------------
757             # Get the temperature/dewpoint and calculate windchill/heat index
758             #------------------------------------------------------------------------------
759              
760             elsif (( $Line =~ /^([0-9][0-9]\/[0-9][0-9])/ )
761             || ( $Line =~ /^(M[0-9][0-9]\/)/ )
762             || ( $Line =~ /^(M[0-9][0-9]\/M[0-9][0-9])/ )
763             || ( $Line =~ /^([0-9][0-9]\/M[0-9][0-9])/ ) )
764             {
765 9         41 my ($Temperature, $Dewpoint) = split( /\//, $Line );
766 9 100       34 if ( $Dewpoint eq "" ) {
767 1         3 $Dewpoint = undef;
768             }
769              
770 9 100       33 if ( $Temperature =~ /M/ ) {
771 1         4 $Temperature =~ tr/[A-Z]//d;
772 1         3 $Temperature = ( $Temperature - ( $Temperature * 2 ) );
773             }
774              
775 9 50 66     48 if ( defined $Dewpoint && $Dewpoint =~ /M/ ) {
776 0         0 $Dewpoint =~ tr/[A-Z]//d;
777 0         0 $Dewpoint = ( $Dewpoint - ( $Dewpoint * 2 ) );
778             }
779              
780 9         28 my $Tempf = convert_c_to_f( $Temperature );
781 9         22 my $Dewf = convert_c_to_f( $Dewpoint );
782              
783 9         70 my $Es =
784             6.11 * 10.0**( 7.5 * $Temperature / ( 237.7 + $Temperature ) );
785 9         18 my $E;
786             my $rh;
787 9 100       28 if ( defined $Dewpoint ) {
788 8         27 $E = 6.11 * 10.0**( 7.5 * $Dewpoint / ( 237.7 + $Dewpoint ) );
789 8 50       26 if ($Es != 0) {
790 8         25 $rh = round( ( $E / $Es ) * 100 );
791             }
792             }
793              
794 9         16 my $F = $Tempf;
795              
796 9         27 my $Heati = heat_index( $F, $rh );
797 9         25 my $Heatic = convert_f_to_c( $Heati );
798              
799 9         20 $Tempf = round($Tempf);
800 9         21 $Dewf = round($Dewf);
801 9         27 $Heati = round($Heati);
802 9         21 $Heatic = round($Heatic);
803              
804 9         34 my $Windc = windchill( $F, $Self->{windspeedmph} );
805 9         16 my $Windcc = convert_f_to_c( $Windc );
806 9         18 $Windc = round($Windc);
807 9         21 $Windcc = round($Windcc);
808              
809 9         26 $Self->{temperature_c} = $Temperature;
810 9         22 $Self->{temperature_f} = $Tempf;
811 9         21 $Self->{dewpoint_c} = $Dewpoint;
812 9         20 $Self->{dewpoint_f} = $Dewf;
813 9         36 $Self->{relative_humidity} = $rh;
814 9         32 $Self->{heat_index_c} = $Heatic;
815 9         25 $Self->{heat_index_f} = $Heati;
816 9         20 $Self->{windchill_c} = $Windcc;
817 9         37 $Self->{windchill_f} = $Windc;
818             }
819              
820             #------------------------------------------------------------------------------
821             # Calculate the atmospheric pressure in different formats.
822             # Based on report (inches of mercury)
823             #------------------------------------------------------------------------------
824              
825             elsif ( $Line =~ /^(A[0-9]{4})/ ) {
826 9         20 $Line =~ tr/[A-Z]//d;
827 9         23 my $Part1 = substr( $Line, 0, 2 );
828 9         19 my $Part2 = substr( $Line, 2, 4 );
829 9         26 $Self->{pressure_inhg} = "$Part1.$Part2";
830              
831 9         35 my $mb = $Self->{pressure_inhg} * 33.8639;
832 9         17 my $mmHg = $Self->{pressure_inhg} * 25.4;
833 9         29 my $lbin = ( $Self->{pressure_inhg} * 0.491154 );
834 9         16 my $kgcm = ( $Self->{pressure_inhg} * 0.0345316 );
835 9         20 $mb = round($mb);
836 9         19 $mmHg = round($mmHg);
837              
838 9         24 $Self->{pressure_mb} = $mb;
839 9         20 $Self->{pressure_mmhg} = $mmHg;
840 9         20 $Self->{pressure_lbin} = $lbin;
841 9         26 $Self->{pressure_kgcm} = $kgcm;
842             }
843              
844             #------------------------------------------------------------------------------
845             # Calculate the atmospheric pressure in different formats.
846             # Based on report (millibars)
847             #------------------------------------------------------------------------------
848              
849             elsif ( $Line =~ /^(Q[0-9]{4})/ ) {
850 1         10 $Line =~ tr/[A-Z]//d;
851 1         4 $Self->{pressure_mb} = $Line;
852              
853 1         4 my $inhg = ( $Self->{pressure_mb} * 0.02953 );
854 1         15 $Self->{pressure_inhg} = sprintf( "%.2f", $inhg );
855 1         4 my $mmHg = $Self->{pressure_inhg} * 25.4;
856 1         2 my $lbin = ( $Self->{pressure_inhg} * 0.491154 );
857 1         3 my $kgcm = ( $Self->{pressure_inhg} * 0.0345316 );
858 1         2 $mmHg = round($mmHg);
859              
860 1         4 $Self->{pressure_mmhg} = $mmHg;
861 1         1 $Self->{pressure_lbin} = $lbin;
862 1         10 $Self->{pressure_kgcm} = $kgcm;
863             }
864              
865             #------------------------------------------------------------------------------
866             # If the remarks section is starting, we are done
867             #------------------------------------------------------------------------------
868              
869             elsif ( $Line =~ /^(RMK)/ ) {
870 9         23 last;
871             }
872             }
873              
874             #------------------------------------------------------------------------------
875             # Read the remarks into an array for later processing
876             #------------------------------------------------------------------------------
877              
878 10         20 my $Remarks = 0;
879 10         15 my @Remarkarray;
880              
881 10         26 foreach my $Line (@Splitter) {
882 145 100       351 if ( $Line =~ /^(RMK)/ ) {
883 9         14 $Remarks = 1;
884             }
885              
886 145 100       338 if ($Remarks) {
887 31         88 push( @Remarkarray, $Line );
888             }
889             }
890              
891             #------------------------------------------------------------------------------
892             # Delete the temp file
893             #------------------------------------------------------------------------------
894              
895 10         30 $Self->{cloudlevel_arrayref} = \@Cloudlevels;
896 10         26 $Self->{station_type} = "Manual";
897              
898             #------------------------------------------------------------------------------
899             # Now we process remarks. These aren't all going to be in the report,
900             # and usually aren't. This has made it hard to develop. This section
901             # is basically incomplete, but you can get some of the data out
902             #------------------------------------------------------------------------------
903              
904 10         22 foreach my $Remark (@Remarkarray) {
905 31 50       77 if ($Remark) {
906 31         53 my $Line = $Remark;
907              
908 31 100       261 if ( $Remark =~ /^AO[1-2]/ ) {
    100          
    50          
    100          
    100          
    50          
    50          
    100          
909 3         9 $Self->{station_type} = "Automated";
910             }
911             elsif ( $Remark =~ /^SLP/ ) {
912 9         21 $Remark =~ tr/[A-Z]//d;
913              
914 9 100 66     63 if ( !defined $Remark || $Remark eq "") {
915 1         3 $Remark = 0;
916             }
917              
918 9 50 66     52 if ( ($Remark) && ( $Remark >= 800 ) ) {
919 0         0 $Remark = $Remark * .1;
920 0         0 $Remark = $Remark + 900;
921             }
922             else {
923 9         15 $Remark = $Remark * .1;
924 9         20 $Remark = $Remark + 1000;
925             }
926              
927 9         23 $Self->{slp_inhg} = ( $Remark * 0.0295300 );
928 9         63 $Self->{slp_inhg} = substr( $Self->{slp_inhg}, 0, 5 );
929 9         27 $Self->{slp_mmhg} = round( $Remark * 0.750062 );
930 9         31 $Self->{slp_lbin} = ( $Remark * 0.0145038 );
931 9         26 $Self->{slp_kgcm} = ( $Remark * 0.00101972 );
932 9         20 $Self->{slp_mb} = round($Remark);
933             }
934              
935             #------------------------------------------------------------------------------
936             # Thunderstorm info
937             #------------------------------------------------------------------------------
938              
939             elsif ( $Remark =~ /^TS/ ) {
940 0         0 $Self->{storm} = $Remark;
941             }
942              
943             #------------------------------------------------------------------------------
944             # Three hour pressure tendency
945             #------------------------------------------------------------------------------
946              
947             elsif ( $Remark =~ /^5[0-9]/ ) {
948 1         5 $Self->{thpressure} = $Remark;
949             }
950              
951             #------------------------------------------------------------------------------
952             # Automated station needs maintenance
953             #------------------------------------------------------------------------------
954              
955             elsif ( $Remark =~ /\$/ ) {
956 1         5 $Self->{maintenance} = $Remark;
957             }
958              
959             #------------------------------------------------------------------------------
960             # Precipitation since last report (100ths of an inch)
961             #------------------------------------------------------------------------------
962              
963             elsif ( $Remark =~ /^P[0-9]/ ) {
964 0         0 $Self->{precipslr} = $Remark;
965             }
966              
967             #------------------------------------------------------------------------------
968             # Event beginning or ending
969             #------------------------------------------------------------------------------
970              
971             elsif ( $Line =~ /^(BRB|TSB|MIB|PRB|BCB|DRB|BLB|SHB|FZB|DZB|RAB|SNB|SGB|ICB|PEB|GRB|GSB|UPB|FGB|FUB|VAB|DUB|SAB|HZB|PYB|POB|SQB|FCB|SSB)/ )
972             {
973 0         0 $Self->{eventbe} = $Remark;
974             }
975              
976             #------------------------------------------------------------------------------
977             # Precise temperature reading
978             #------------------------------------------------------------------------------
979              
980             elsif ( $Remark =~ /^T[0-9]/ ) {
981 8         34 $Self->{ptemperature} = $Remark;
982             }
983             }
984             }
985              
986 10         28 my $Templatefile = $Self->{tfile};
987              
988 10 50       28 if ($Templatefile) {
989 0         0 local $/; # enable slurp mode
990 0 0       0 open my $F, '<', $Templatefile or
991             Carp::croak "error opening template file $Templatefile: $!";
992 0         0 my $tout = <$F>;
993 0         0 close($F);
994              
995 0         0 $tout =~ s{ %% ( .*? ) %% }
996 0 0       0 { exists( $Self->{$1} )
997             ? $Self->{$1}
998             : ""
999             }gsex;
1000              
1001 0         0 $Self->{templateout} = $tout;
1002             }
1003              
1004 10         27 $Self->{remark_arrayref} = \@Remarkarray;
1005 10         53 return $Self;
1006             }
1007              
1008             1;
1009             __END__