File Coverage

blib/lib/Geo/ReadGRIB.pm
Criterion Covered Total %
statement 354 431 82.1
branch 106 158 67.0
condition 21 48 43.7
subroutine 40 47 85.1
pod 36 36 100.0
total 557 720 77.3


line stmt bran cond sub pod time code
1             #--------------------------------------------------------------------------
2             # Geo::ReadGRIB
3             #
4             # - A Perl extension that gives read access to GRIB "GRIdded Binary"
5             # format Weather data files.
6             #
7             # - Copyright (C) 2006 by Frank Cox
8             #--------------------------------------------------------------------------
9              
10             package Geo::ReadGRIB;
11              
12 12     12   305026 use 5.006_001;
  12         112  
  12         659  
13 12     12   64 use strict;
  12         23  
  12         405  
14 12     12   71 use warnings;
  12         22  
  12         369  
15 12     12   17953 use IO::File;
  12         166532  
  12         2013  
16 12     12   110 use Carp;
  12         24  
  12         866  
17              
18             our $VERSION = 1.4;
19              
20 12     12   8875 use Geo::ReadGRIB::PlaceIterator;
  12         29  
  12         3597  
21              
22             my $LIB_DIR = "./";
23              
24             # try to find wgrib.exe
25             foreach my $inc (@INC) {
26             if ( -e "$inc/Geo/wgrib.exe" ) {
27             $LIB_DIR = "$inc/Geo";
28             last;
29             }
30             }
31              
32             unless ( -e "$LIB_DIR/wgrib.exe" ) {
33             die "CAN'T CONTINUE: Path to wgrib.exe not found";
34             }
35              
36             ## Set some signal handlers to clean up temp files in case of interruptions
37             # it does this by calling exit(0) which will run the END block
38              
39             $SIG{INT} = $SIG{QUIT} = $SIG{TERM} = sub {
40             print "ReadGRIB attempting cleanup...\n";
41             exit(0);
42             };
43              
44             END {
45 12     12   20076 unlink glob("wgrib.tmp.*");
46             }
47              
48             #--------------------------------------------------------------------------
49             # new()
50             #--------------------------------------------------------------------------
51             sub new {
52              
53 13     13 1 16478 my $class = shift;
54 13         43 my $gFile = shift;
55 13 50       93 unless ( defined $gFile ) {
56 0         0 croak "new(): Usage: Geo::ReadGRIB->new(GRIB_FILE)";
57             }
58 13         291 my $self = {};
59 13         40 bless $self, $class;
60              
61 13         89 $self->{fileName} = $gFile;
62 13         40 $self->{DEBUG} = 0;
63 13         89 $self->backflip( 0 );
64              
65 13         63 $self->openGrib();
66              
67 13         387 return $self;
68             }
69              
70             #--------------------------------------------------------------------------
71             # openGrib()
72             #
73             # Open grib file using wgrib.exe and extract header data
74             #
75             # Version 1.0 added a call to _getCatalog() here to get all critical
76             # header data
77             #--------------------------------------------------------------------------
78             sub openGrib {
79              
80 12     12   13236 use Time::Local;
  12         31136  
  12         31218  
81              
82 13     13 1 27 my $self = shift;
83              
84 13         84 my $tmp = $self->tempfile();
85 13         108 my $cmd = "\"$LIB_DIR\"/wgrib.exe \"$self->{fileName}\" -d 1 -4yr -PDS10 -GDS10 -text -nh -o $tmp";
86              
87 13         1294676 my $header = qx($cmd);
88 13         8982 unlink $tmp;
89              
90 13 50       1031 if ($?) {
91 0         0 croak "Error in $cmd: $?";
92             }
93              
94 13         1988 my @inv = split /:/, $header;
95              
96 13         113 my ( $arg, $val, %head );
97              
98 13         218 $head{recNum} = $inv[0];
99 13         95 $head{offset} = $inv[1];
100 13         116 $head{name} = $inv[3];
101 13         112 $head{level} = $inv[11];
102 13         97 $head{fcst} = $inv[12];
103              
104 13         567 foreach my $invel (@inv) {
105 208         286 chomp $invel;
106              
107             # print "$invel \n";
108 208 100       1550 if ( $invel =~ /=/ ) {
109 143         489 ( $arg, $val ) = split /=/, $invel;
110 143         489 $val =~ s/^\s+//;
111 143         836 $head{$arg} = $val;
112              
113             # print " ($arg,$val) \n";
114             }
115             }
116              
117 13         400 foreach ( sort keys %head ) {
118             # print " $_: $head{$_}\n";
119 208         2317 $self->{$_} = $head{$_};
120             }
121              
122              
123             # reduce date string to 'time' format
124 13         214 my ( $yr, $mo, $day, $hr ) = unpack 'A4A2A2A2', $self->{d};
125 13         1050 $self->{TIME} = timegm( 0, 0, $hr, $day, $mo - 1, $yr - 1900 );
126              
127 13         1223 $self->{LAST_TIME} = $self->{THIS_TIME} = $self->{TIME};
128              
129 13         444 $self->parseGDS( $head{GDS10} );
130              
131             # if this isn't a lat/long grid we can't go on
132 13 50       135 if ( $self->{GRID_TYPE} != 0 ) {
133 0         0 croak "GDS byte 6 not 0: Only latitude/longitude grids are currently supported";
134             }
135              
136 13         69 $self->_getCatalog;
137              
138 13         1419 return;
139             }
140              
141             #--------------------------------------------------------------------------
142             # getCatalogVerbose() DEPRECATED. Use getFullCatalog() instead
143             #
144             # This method is now redundent and just calls getFullCatalog() and sets
145             # an error.
146             #--------------------------------------------------------------------------
147             sub getCatalogVerbose {
148 0     0 1 0 my $self = shift;
149 0         0 $self->error( "Method getCatalogVerbose() DEPRECATED and is now redundant "
150             . "in Geo::ReadGRIB V0.98 and above. Use getFullCatalog() instead" );
151 0         0 $self->getFullCatalog();
152 0         0 return 1;
153             }
154              
155              
156             #--------------------------------------------------------------------------
157             # getCatalog( ) DEPRECATED. It now does nothing but set an error.
158             # Do not use in new code and please remove it from old code.
159             #
160             # The catalog is now fetched in openGRIB() and this method dose not need
161             # to be called.
162             #--------------------------------------------------------------------------
163             sub getCatalog {
164 0     0 1 0 my $self = shift;
165 0         0 $self->error( "Method getCatalog DEPRECATED and is no longer needed "
166             . "in Geo::ReadGRIB V1.0 and above" );
167 0         0 return 1;
168             }
169              
170             #--------------------------------------------------------------------------
171             # _getCatalog()
172             #--------------------------------------------------------------------------
173             sub _getCatalog {
174              
175 13     13   26 my $self = shift;
176              
177 13         265 my $tmp = $self->tempfile();
178 13         112 my $cmd = "\"$LIB_DIR\"/wgrib.exe \"$self->{fileName}\" -o $tmp";
179              
180 13         235698 my @cat = qx($cmd);
181 13         2194 unlink $tmp;
182              
183 13 50       582 if ($?) {
184 0         0 croak "Error in \$cmd: $?";
185             }
186              
187 13         394 my $timeRange = $self->TR;
188              
189 13         68 my ( @line, $offset );
190 13         130 foreach (@cat) {
191 433         4334 @line = split /:/;
192 433         1751 $line[8] =~ s/P1=//;
193 433         1158 $line[9] =~ s/P2=//;
194 433 100       1075 if ( $timeRange == 0 ) {
    50          
195 427         626 $offset = $line[8];
196             }
197             elsif ( $timeRange == 10 ) {
198 6         143 $offset = ($line[8] * 256) + $line[9];
199             }
200             else {
201 0         0 croak "Time Range flag $timeRange not yet supported. Please ask the Geo::ReadGRIB maintainer to add support for GRIB files like this";
202             }
203              
204 433         1504 $offset = $offset * 3600 + $self->{TIME};
205 433 100       1339 $self->{LAST_TIME} = $offset if $offset > $self->{LAST_TIME};
206 433         3961 $self->{catalog}->{ $offset }->{ $line[3] } = $line[0];
207             }
208              
209 13         169 return;
210             }
211              
212             #--------------------------------------------------------------------------
213             # getFullCatalog()
214             #
215             # recovers the verbose catalog which has text discriptions
216             # of data items.
217             #--------------------------------------------------------------------------
218             sub getFullCatalog{
219              
220 9     9 1 296 my $self = shift;
221              
222 9         326 my $tmp = $self->tempfile();
223 9         72 my $cmd = "\"$LIB_DIR\"/wgrib.exe -v \"$self->{fileName}\" -o $tmp";
224              
225 9         173605 my @cat = qx($cmd);
226 9         1571 unlink $tmp;
227              
228 9 50       523 if ($?) {
229 0         0 croak "Error in \$cmd: $?";
230             }
231              
232 9         66 my @line;
233 9         166 foreach (@cat) {
234 309         605 chomp;
235 309         2521 @line = split /:/;
236 309         1309 $line[7] =~ s/"//g;
237 309         1293 $self->{v_catalog}->{ $line[3] } = $line[7];
238             }
239 9         510 return;
240             }
241              
242             #--------------------------------------------------------------------------
243             # parseGDS()
244             #
245             # Assumes gds is dumped in "decimal" (-GDS10)
246             #--------------------------------------------------------------------------
247             sub parseGDS {
248              
249 13     13 1 50 my $self = shift;
250 13         30 my $gds = shift;
251              
252 13         100 $gds =~ s/^\s+//;
253              
254 13         238 my @GDS = split /\s+/, $gds;
255              
256 13         85 $self->{GRID_TYPE} = $GDS[5];
257              
258 13         50 my @slice = @GDS[ 6, 7 ];
259 13         158 $self->Ni( $self->toDecimal( \@slice ) );
260              
261 13         2144 @slice = @GDS[ 8, 9 ];
262 13         65 $self->Nj( $self->toDecimal( \@slice ) );
263              
264 13         53 @slice = @GDS[ 10, 11, 12 ];
265 13         50 $self->La1( $self->toDecimal( \@slice ) / 1000 );
266              
267 13         55 @slice = @GDS[ 13, 14, 15 ];
268 13         49 $self->Lo1( $self->toDecimal( \@slice ) / 1000 );
269              
270 13         124 my @rc_flags = split '', sprintf "%08b", $GDS[16];
271            
272             # if INCREMENTS_FLAG is true get increments from GDS else calculate
273 13         88 $self->{INCREMENTS_FLAG} = $rc_flags[0];
274              
275 13         59 @slice = @GDS[ 17, 18, 19 ];
276 13         87 $self->La2( $self->toDecimal( \@slice ) / 1000 );
277              
278 13         115 @slice = @GDS[ 20, 21, 22 ];
279 13         69 $self->Lo2( $self->toDecimal( \@slice ) / 1000 );
280              
281 13         47 @slice = @GDS[ 23, 24 ];
282 13 50 33     258 if ( ($slice[0] == $slice[1] and $slice[0] == 255) and not $self->{INCREMENTS_FLAG} ) {
      33        
283 0         0 $self->LoInc( $self->calInc( $self->Lo1, $self->Lo2, $self->Ni ) );
284             }
285             else {
286 13         65 $self->LoInc( $self->toDecimal( \@slice ) / 1000 );
287             }
288              
289 13         52 @slice = @GDS[ 25, 26 ];
290 13 50 33     220 if ( ($slice[0] == $slice[1] and $slice[0] == 255) and not $self->{INCREMENTS_FLAG} ) {
      33        
291 0         0 $self->LaInc = $self->calInc( $self->La1, $self->La2, $self->Nj );
292             }
293             else {
294 13         56 $self->LaInc( $self->toDecimal( \@slice ) / 1000 );
295             }
296              
297 13         110 my @scan_mode_flags = split '', sprintf "%08b", $GDS[27];
298              
299             # if SN_SCAN_FLAG true data runs in numeric order south-north
300             # elce data runs north-south
301 13         69 $self->sn_scan_flag( $scan_mode_flags[1] );
302              
303 13 50       56 if ( $scan_mode_flags[0] ) {
304 0         0 croak "Scan mode -i not yet supported. Please contact the Geo::ReadGRIB maintainer to add support for GRIB files that scan east to west";
305             }
306              
307 13 50       50 if ( $scan_mode_flags[2] ) {
308 0         0 croak "Scan mode (J,I) not yet supported. Please contact the Geo::ReadGRIB maintainer to add support for GRIB files where adjacent points are consecutive in the j direction (north/south)";
309             }
310              
311             # Calculate effective Lo where thay are shifted west to 0 degrees.
312             # This will be used for finding data offset and for checking for
313             # out of range args where ranges may cross long 0
314 13         43 $self->{eLo2} = $self->Lo2;
315 13         45 $self->{eLo1} = $self->Lo2 - ($self->Ni -1) * $self->LoInc;
316 13         50 $self->{Lo_SHIFT} = 0 - $self->{eLo1};
317              
318 13         87 return;
319             }
320              
321             #--------------------------------------------------------------------------
322             # sn_scan_flag( [flag] ) - getter/setter
323             #--------------------------------------------------------------------------
324             sub sn_scan_flag {
325 1335     1335 1 2485 my ( $self, $flag ) = @_;
326 1335 100       3291 $self->{SN_SCAN_FLAG} = $flag if defined $flag;
327 1335         4719 return $self->{SN_SCAN_FLAG};
328             }
329              
330             #--------------------------------------------------------------------------
331             # error( [error message] ) - getter/setter for errors
332             #--------------------------------------------------------------------------
333             sub error {
334 42     42 1 141 my ( $self, $error ) = @_;
335 42 100       156 $self->{ERROR} = $error if defined $error;
336 42         409 return $self->{ERROR};
337             }
338              
339             #--------------------------------------------------------------------------
340             # Lo1( [val] ) - Getter/setter for Lo1
341             #--------------------------------------------------------------------------
342             sub Lo1 {
343 103     103 1 216 my ( $self, $flag ) = @_;
344 103 100       529 $self->{Lo1} = $flag if defined $flag;
345 103         631 return $self->{Lo1};
346             }
347              
348             #--------------------------------------------------------------------------
349             # Lo2( [flag] ) - Getter/setter for Lo2
350             #--------------------------------------------------------------------------
351             sub Lo2 {
352 71     71 1 151 my ( $self, $flag ) = @_;
353 71 100       251 $self->{Lo2} = $flag if defined $flag;
354 71         275 return $self->{Lo2};
355             }
356              
357             #--------------------------------------------------------------------------
358             # LoInc( [flag] ) - Getter/setter for LoInc
359             #--------------------------------------------------------------------------
360             sub LoInc {
361 1315     1315 1 1774 my ( $self, $flag ) = @_;
362 1315 100       3517 $self->{LoInc} = $flag if defined $flag;
363 1315         3307 return $self->{LoInc};
364             }
365              
366             #--------------------------------------------------------------------------
367             # La1( [flag] ) - Getter/setter for La1
368             #--------------------------------------------------------------------------
369             sub La1 {
370 1334     1334 1 1901 my ( $self, $flag ) = @_;
371 1334 100       2785 $self->{La1} = $flag if defined $flag;
372 1334         4952 return $self->{La1};
373             }
374              
375             #--------------------------------------------------------------------------
376             # TR( [flag] ) - Getter/setter for TR
377             #--------------------------------------------------------------------------
378             sub TR {
379 13     13 1 94 my ( $self, $flag ) = @_;
380 13 50       209 $self->{TR} = $flag if defined $flag;
381 13         127 return $self->{TR};
382             }
383              
384             #--------------------------------------------------------------------------
385             # La2( [flag] ) - Getter/setter for La2
386             #--------------------------------------------------------------------------
387             sub La2 {
388 58     58 1 225 my ( $self, $flag ) = @_;
389 58 100       630 $self->{La2} = $flag if defined $flag;
390 58         396 return $self->{La2};
391             }
392              
393             #--------------------------------------------------------------------------
394             # LaInc( [flag] )- Getter/setter for LaInc
395             #--------------------------------------------------------------------------
396             sub LaInc {
397 1937     1937 1 2718 my ( $self, $flag ) = @_;
398 1937 100       3931 $self->{LaInc} = $flag if defined $flag;
399 1937         9536 return $self->{LaInc};
400             }
401              
402             #--------------------------------------------------------------------------
403             # Ni( [flag] ) - Getter/setter for Ni
404             #--------------------------------------------------------------------------
405             sub Ni {
406 1309     1309 1 1706 my ( $self, $flag ) = @_;
407 1309 100       4112 $self->{Ni} = $flag if defined $flag;
408 1309         5828 return $self->{Ni};
409             }
410              
411             #--------------------------------------------------------------------------
412             # Nj( [flag] ) - Getter/setter for Nj
413             #--------------------------------------------------------------------------
414             sub Nj {
415 13     13 1 31 my ( $self, $flag ) = @_;
416 13 50       127 $self->{Nj} = $flag if defined $flag;
417 13         38 return $self->{Nj};
418             }
419              
420             #--------------------------------------------------------------------------
421             # clearError()
422             # set errors undef
423             #--------------------------------------------------------------------------
424             sub clearError {
425 1273     1273 1 1466 my $self = shift;
426 1273         2911 undef $self->{ERROR};
427             }
428              
429              
430             #--------------------------------------------------------------------------
431             # validLa( lat )
432             #
433             # check if lats are in range and return true if they arr, else false
434             #--------------------------------------------------------------------------
435             sub validLa {
436              
437 27     27 1 60 my $self = shift;
438 27         56 my $lat = shift;
439              
440 27 100       210 if ( $self->sn_scan_flag ) {
441 13 100 100     103 if ( $lat < $self->La1 or $lat > $self->La2 ) {
442 3         21 return 0;
443             }
444             }
445             else {
446 14 100 66     137 if ( $lat > $self->La1 or $lat < $self->La2 ) {
447 2         9 return 0;
448             }
449             }
450              
451              
452 22         121 return 1;
453             }
454              
455             #--------------------------------------------------------------------------
456             # validLo( 1ong )
457             #
458             # check that longs are in range and return true if they are, else false.
459             #--------------------------------------------------------------------------
460             sub validLo {
461              
462 22     22 1 46 my $self = shift;
463 22         42 my $lo = shift;
464              
465 22 100       66 if ( $self->sn_scan_flag ) {
466 10         34 $lo += $self->{Lo_SHIFT};
467 10 50       37 if ( $lo > 360 ) {
468 0         0 $lo -= 360;
469             }
470            
471 10         37 $lo /= $self->LoInc;
472 10 50 33     101 if ( $lo < 0 or $lo > $self->Ni ) {
473 0         0 return 0;
474             }
475             }
476             else {
477 12 100 100     34 if ( $lo < $self->Lo1 or $lo > $self->Lo2 ) {
478 2         13 return 0;
479             }
480             }
481              
482 20         135 return 1;
483             }
484              
485             #--------------------------------------------------------------------------
486             # adjustLong( long )
487             #
488             # takes a long and returns a effective long adjusted to the shifted grid
489             #--------------------------------------------------------------------------
490             sub adjustLong {
491              
492 0     0 1 0 my $self = shift;
493 0         0 my $l1 = shift;
494             }
495              
496             #--------------------------------------------------------------------------
497             # toDecimal()
498             #
499             # helper method for parseGDS()
500             #--------------------------------------------------------------------------
501             sub toDecimal {
502              
503 104     104 1 151 my $self = shift;
504 104         252 my $inArray = shift;
505              
506             # if the most segnificant bit is one it's negative...
507 104         411 my $isNeg = 0;
508 104 100       335 if ( $$inArray[0] >= 128 ) {
509 6         36 $isNeg = 1;
510 6         34 $$inArray[0] -= 128;
511             }
512              
513             # print "===== " . $$inArray[0] . " -- " . 2**((@$inArray -1) *8) . "\n";
514              
515 104         111 my ( $result, $m );
516 104         320 for ( my $i = @$inArray - 1, my $j = 0 ; $i >= 0 ; $i--, $j++ ) {
517 260         446 $m = 2**( $j * 8 );
518 260         679 $result += $$inArray[$i] * $m;
519             }
520              
521 104 100       367 $result *= -1 if $isNeg;
522 104         910 return sprintf "%.2d", $result;
523             }
524              
525             #--------------------------------------------------------------------------
526             # dumpit()
527             #--------------------------------------------------------------------------
528             sub dumpit {
529              
530 0     0 1 0 my $self = shift;
531              
532 12     12   16930 use Data::Dumper;
  12         171589  
  12         32574  
533 0         0 print Dumper($self);
534 0         0 return;
535             }
536              
537             #--------------------------------------------------------------------------
538             # calInc( cord1, cord2, points)
539             #
540             # finds degrees between grid points given the start and end and
541             # number of points. If one cord is negative South or west long/lat assumed
542             #--------------------------------------------------------------------------#
543             sub calInc {
544              
545 0     0 1 0 my $self = shift;
546 0         0 my $c1 = shift;
547 0         0 my $c2 = shift;
548 0         0 my $pts = shift;
549              
550 0         0 my $size;
551 0 0 0     0 if ( $pts == 0 ) {
    0          
552 0         0 $size = 0;
553 0         0 $self->error( "calInc: \$pts = 0" );
554             }
555             elsif ( $c1 < 0 or $c2 < 0 ) {
556              
557             # print "$size = (abs($c1) + abs($c2) +1) / $pts;\n";
558 0         0 $size = ( abs($c1) + abs($c2) + 1 ) / $pts;
559             }
560             else {
561              
562             # print "$size = (abs($c1 - $c2)) / $pts\n";
563 0         0 $size = ( abs( $c1 - $c2 ) ) / $pts;
564             }
565 0         0 return sprintf "%.2f", $size;
566             }
567              
568             #--------------------------------------------------------------------------
569             # lalo2offset(lat, long)
570             #
571             # converts long/lat pairs in degrees to grib table offset
572             #--------------------------------------------------------------------------
573             sub lalo2offset {
574              
575 1273     1273 1 336909 my $self = shift;
576 1273         1730 my $lat = shift;
577 1273         1838 my $long = shift;
578              
579            
580 1273         1726 my $thislong = 0;
581              
582 1273         3486 $self->clearError;
583 1273         1332 my $out;
584            
585 1273 100       2722 if ( $self->sn_scan_flag ) {
586              
587             # shift long east until Lo1 = 0 and make sure any long
588             # > 360 degrees is moved back into the range of 0 - 360
589 1216         2339 $thislong = $long + $self->{Lo_SHIFT};
590 1216 50       2371 if ( $thislong > 360 ) {
591 0         0 $thislong -= 360;
592             }
593              
594 1216         2611 $out = ( ( ( $lat - $self->La1 ) / $self->LaInc ) * $self->Ni )
595             + ( ($thislong ) / $self->LoInc );
596             }
597             else {
598 57         149 $out = ( ( $self->La1 - $lat ) / $self->LaInc ) * $self->Ni
599             + ( ($long - $self->Lo1 ) / $self->LoInc );
600             }
601            
602 1273         30327 return sprintf "%.0f", sprintf "%.6f", $out;
603             }
604              
605             #--------------------------------------------------------------------------
606             # $plit = $w->extractLaLo( data_types, lat1, long1, lat2, long2, time )
607             #
608             #
609             # data_types is a scalar containing a single data type as a string or
610             # an array ref of data type strings.
611             #
612             # Extracts forecast data for a range of locations from (lat1, long1) to
613             # (lat2, long2) for the given data_type and time.
614             #
615             # This will be much faster than repeated calls to extract() because only one
616             # call to wgrib and just one file open are required.
617             #
618             # Returns a Geo::ReadGRIB::PlaceIterator object.
619             #
620             # Data is no longer stored in the current object by default as of version
621             # 1.4. To turn this behavior back on use the backflip() method;
622             #
623             #
624             # require: lat1 >= lat2 and long1 <= long2 - that is, lat1 is north or lat2
625             # and long1 is west of long2 (or is the same as)
626             #
627             #--------------------------------------------------------------------------
628             sub extractLaLo {
629              
630 6     6 1 67 my $self = shift;
631 6         31 my $type_s = shift;
632 6         19 my $lat1 = shift;
633 6         28 my $long1 = shift;
634 6         19 my $lat2 = shift;
635 6         23 my $long2 = shift;
636 6         109 my $time = shift;
637              
638 6 100       73 my $flipBack = $self->backflip() ? 1 : 0;
639 6 50       58 my $DEBUG = $self->{DEBUG} ? 1 : 0;
640 6         46 my $LoInc = $self->LoInc;
641              
642 6         13 my @types;
643 6 50       176 if ( ref $type_s eq 'ARRAY' ) {
    50          
644 0         0 push @types, @$type_s;
645             }
646             elsif ( $type_s =~ /\w+/ ) {
647 6         17 push @types, $type_s;
648             }
649             else {
650 0         0 $self->error( "ERROR extractLaLo() \$types required" );
651 0         0 return;
652             }
653              
654 6         16 my @times = sort keys %{ $self->{catalog} };
  6         173  
655              
656             # First see that requested values are in range...
657 6         52 my ($lo1, $lo2, $la1, $la2) = ($self->Lo1, $self->Lo2, $self->La1, $self->La2, );
658 6 50 33     189 if ( not $lat1 >= $lat2 or not $long1 <= $long2 ) {
659 0         0 $self->error( "ERROR extractLaLo() require: lat1 >= lat2 and long1 <= long2" );
660 0         0 return;
661             }
662              
663 6 50       44 if ( not defined $time ) {
664 0         0 $self->error( "ERROR extractLaLo() \$time is required " );
665 0         0 return;
666             }
667              
668 6 50 33     115 if ( $time < $self->{TIME} or $time > $self->{LAST_TIME} ) {
669 0         0 $self->error( "ERROR extractLaLo() \$time \"$time\" out of range "
670             . scalar gmtime( $times[0] ) . " ($times[0]) to "
671             . scalar gmtime( $times[-1] ) . " ($times[-1])" );
672 0         0 return;
673             }
674              
675 6 50 33     59 if ( not $self->validLa($lat1) or not $self->validLa($lat2) ) {
676 0         0 $self->error( "extractLaLo(): LAT >$lat1 or $lat2< out of range $la1 to $la2" );
677 0         0 return;
678             }
679              
680 6 50 33     42 if ( not $self->validLo( $long1 ) or not $self->validLo( $long2 ) ) {
681 0         0 $self->error( "extractLaLo(): LONG: >$long1 or $long2 < out of range $lo1 to $lo2" );
682 0         0 return;
683             }
684              
685 6         245 my $plit = Geo::ReadGRIB::PlaceIterator->new();
686              
687             # if time is given, use nearest time in catalog...
688 6 50       29 if ( defined $time ) {
689 6         216 $self->findNearestTime( $time );
690             }
691              
692 6         23 my ( $dtaLength, $fileDump, $offset, $dump, $record, $lo, $la );
693 6         20 my $tm = $self->{THIS_TIME};
694              
695              
696 6         30 for my $type ( @types ) {
697 6         24 $record = $self->{catalog}->{$tm}->{$type};
698              
699 6 50       323 if ( not defined $record ) {
700 0         0 $self->error( "extractLaLo(): Not a valid type: $type" );
701 0         0 return 1;
702             }
703              
704 6         82 my $tmp = $self->tempfile();
705 6         53 my $cmd = "\"$LIB_DIR\"/wgrib.exe \"$self->{fileName}\" -d $record -nh -o $tmp";
706 6         200726 my $res = qx($cmd);
707 6 50       651 my $F = IO::File->new( $tmp ) or croak "Can't open temp file";
708              
709             # Make sure first offset is smallest for any scanning order
710 6         1956 my ( $offsetFst, $offsetLst ) = sort {$a <=> $b} (
  6         57  
711             $self->lalo2offset($lat1, $long1), $self->lalo2offset($lat2, $long2) );
712 6         32 $dtaLength = ($offsetLst - $offsetFst +1) * 4;
713 6         51 seek $F, $offsetFst * 4, 0;
714 6         13041 read $F, $fileDump, $dtaLength;
715              
716 6         26 my ($x, $y);
717 6         113 $dump = "";
718 6         114 for ( $la = $lat1, $x = 0 ; $la >= $lat2 ; $la -= $self->LaInc, $x++ ) {
719 651         2709 my $num_lo = sprintf "%d", (($long2 - $long1) / $LoInc) +1;
720 651         1927 $offset = $self->lalo2offset( $la, $long1 ) - $offsetFst;
721 651         3938 my @luck = unpack "f*", substr $fileDump, $offset * 4, 4 * $num_lo;
722 651         1976 for ( $lo = $long1, $y = 0 ; $lo <= $long2 ; $lo += $LoInc, $y++ ) {
723 1083         1444 my $dump = shift @luck;
724 1083 100       5422 $dump = defined $dump ? sprintf "%.2f", $dump : 0.00;
725 1083 50       2474 $dump = 0 if $dump eq '';
726 1083 50       2718 $dump = "UNDEF" if $dump > 999900000000000000000;
727 1083 50       1855 print gmtime($tm) . ": $self->{v_catalog}->{$type} $dump\n" if $DEBUG;
728 1083 100       3190 $self->{data}->{$tm}->{$la}->{$lo}->{$type} = $dump if $flipBack;
729             # TODO look into the negative lats with CMS data
730             # $self->{matrix}->{$tm}->{$type}->[$x]->[$y] = $dump if $la >= 0 and $lo >= 0;
731 1083         4918 $plit->addData( $tm, $la, $lo, $type, $dump );
732             }
733             }
734 6         204 close $F;
735 6         2996 unlink $tmp;
736 6         499 undef $fileDump;
737             }
738              
739 6         505 return $plit;
740             }
741              
742             #--------------------------------------------------------------------------
743             # backflip( [flag] ) - Getter/setter for the backflip() state.
744             #
745             # When true, certain older behaviors return. In version 1.4 this is only the
746             # storage in the current object of data extracted by extractLaLo().
747             #
748             # default false
749             #--------------------------------------------------------------------------
750             sub backflip {
751 20     20 1 1782 my ( $self, $flag ) = @_;
752 20 100       128 $self->{backflip} = $flag if defined $flag;
753 20         186 return $self->{backflip};
754             }
755              
756             #---------------------------------------------------------------------------
757             # getMatrix({ time => time, type => type })
758             #
759             # return the array of the extracted region for type and time
760             # POSSIBLE FUTURE FUNCTION
761             #---------------------------------------------------------------------------
762             #sub getMatrix {
763             # my ( $self, $r ) = @_;
764             # return $self->{matrix}->{ $r->{time} }->{ $r->{type} };
765             #}
766              
767             #--------------------------------------------------------------------------
768             # $plit = extract(data_type, lat, long, [time])
769             #
770             # Extracts forecast data for given type and location. Ectracts data for all
771             # times in file unless a specific time is given in epoch seconds.
772             #
773             # Returns a Geo::ReadGRIB::PlaceIterator and extracted data is also
774             # retained in the ReadGRIB object.
775             #
776             # type will be one of the data types in the data
777             #--------------------------------------------------------------------------
778             sub extract {
779              
780 15     15 1 14964 my $self = shift;
781 15         54 my $type = shift;
782 15         26 my $lat = shift;
783 15         38 my $long = shift;
784 15         32 my $time = shift;
785              
786             # First see that requested values are in range...
787 15         246 my ($lo1, $lo2, $la1, $la2) = ($self->Lo1, $self->Lo2, $self->La1, $self->La2, );
788 15 100       110 if ( not $self->validLa($lat) ) {
789 5         146 $self->error( "extract(): LAT >$lat< out of range $la1 to $la2" );
790 5         38 return;
791             }
792              
793 10 100       45 if ( not $self->validLo($long) ) {
794 2         34 $self->error( "extract(): LONG: >$long< out of range $lo1 to $lo2" );
795 2         9 return;
796             }
797              
798 8         90 my $offset = $self->lalo2offset( $lat, $long );
799              
800 8 50       46 if ( $offset < 0 ) {
801 0         0 $self->error( "extract(): offset less than zero" );
802 0         0 return 1;
803             }
804              
805 8 50       29 $time = 0 unless defined $time;
806              
807 8 50 33     175 if ( $time != 0 and $time < $self->{TIME}
      33        
808             or $time > $self->{LAST_TIME} ) {
809              
810 0         0 my @times = sort keys %{ $self->{catalog} };
  0         0  
811 0         0 $self->error( "ERROR extract() \$time \"$time\" out of range "
812             . scalar gmtime( $times[0] ) . " ($times[0]) to "
813             . scalar gmtime( $times[-1] ) . " ($times[-1])" );
814 0         0 return 1;
815             }
816              
817             # If a time is given find nearest in catalog
818 8 50       41 if ( defined $time ) {
819 8         125 $self->findNearestTime( $time );
820             }
821              
822 8         17 my ( $record, $cmd, $res, $dump );
823              
824 8 50       189 unless ( defined $self->{v_catalog}->{$type} ) {
825 0         0 $self->error( "extract() Type not found: $type" );
826 0         0 return 1;
827             }
828              
829             # Give avaiable data for type and offset.
830             # All times returned unless $time is given.
831             #
832             # If record is alredy in $self->{data} use that
833             # else go to disk...
834              
835 8         357 my $plit = Geo::ReadGRIB::PlaceIterator->new();
836              
837 8         34 $dump = "";
838 8         24 foreach my $tm ( sort keys %{ $self->{catalog} } ) {
  8         160  
839 8 50       32 if ( $time != 0 ) {
840 8         29 $tm = $self->{THIS_TIME};
841             }
842            
843 8         32 $record = $self->{catalog}->{$tm}->{$type};
844              
845 8 50       27 if ( not defined $record ) {
846 0         0 $self->error( "extract(): Not a valid type: $type" );
847 0         0 return 1;
848             }
849              
850 8         124 my $tmp = $self->tempfile();
851 8         62 $cmd = "\"$LIB_DIR\"/wgrib.exe \"$self->{fileName}\" -d $record -nh -o $tmp";
852 8         195415 $res = qx($cmd);
853 8 50       281 print "$cmd - OFFSET: $offset " . $offset * 4 . " bytes\n"
854             if $self->{DEBUG};
855 8 50       779 my $F = IO::File->new( "$tmp" ) or croak "Can't open temp file";
856 8         3022 seek $F, $offset * 4, 0;
857 8         253 read $F, $dump, 4;
858 8         79 $dump = unpack "f", $dump;
859 8         225 $dump = sprintf "%.2f", $dump;
860 8 100       178 $dump = "UNDEF" if $dump > 999900000000000000000;
861 8 50       60 print gmtime($tm) . ": $self->{v_catalog}->{$type} $dump\n"
862             if $self->{DEBUG};
863 8         245 $self->{data}->{$tm}->{$lat}->{$long}->{$type} = $dump;
864 8         152 $plit->addData( $tm, $lat, $long, $type, $dump );
865 8         152 close $F;
866 8         5078 unlink $tmp;
867 8 50       183 last if $time != 0;
868             }
869 8         304 return $plit;
870             }
871              
872             #--------------------------------------------------------------------------
873             # findNearestTime( time )
874             #
875             # Find nearest time in catalog...
876             #--------------------------------------------------------------------------
877             sub findNearestTime {
878              
879 14     14 1 42 my $self = shift;
880 14         74 my $time = shift;
881              
882 14         34 my @times = sort keys %{ $self->{catalog} };
  14         565  
883              
884 14 100       214 if ( 1 == @times ) {
885 7         37 $self->{THIS_TIME} = $times[0];
886             }
887             else {
888 7         40 for ( my $i = 0, my $j = 1 ; $j <= @times ; $i++, $j++ ) {
889 283 100 66     1788 if ( $time >= $times[$i] and $time <= $times[$j] ) {
890 7 50       32 if ( ( $time - $times[$i] ) <= ( $times[$j] - $time ) ) {
891 0         0 $self->{THIS_TIME} = $times[$i];
892             }
893             else {
894 7         25 $self->{THIS_TIME} = $times[$j];
895             }
896 7         214 last;
897             }
898             }
899             }
900             }
901              
902             #--------------------------------------------------------------------------
903             # getDataHash()
904             #
905             # Returns a hash ref with all the data items in the object.
906             # This will be all the data extracted from the GRIB file for
907             # in the life of the object.
908             #
909             # The structure is
910             #
911             # $t->{time}->{lat}->{long}->{type}
912             #--------------------------------------------------------------------------
913             sub getDataHash {
914 4     4 1 12211 my $self = shift;
915 4         23 return $self->{data};
916             }
917              
918              
919             #--------------------------------------------------------------------------
920             # clearData( )
921             #--------------------------------------------------------------------------
922             sub clearData {
923 0     0 1 0 my $self = shift;
924 0         0 undef $self->{data};
925 0         0 return ;
926             }
927              
928             #--------------------------------------------------------------------------
929             # getError()
930             #
931             # returns error string from $self->error
932             #--------------------------------------------------------------------------
933             sub getError {
934 28     28 1 1564 my $self = shift;
935 28 100       225 return defined $self->error ? $self->error : undef;
936             }
937              
938             #--------------------------------------------------------------------------
939             # m2ft(meters)
940             #
941             # convert meters to feet
942             #--------------------------------------------------------------------------
943             sub m2ft {
944              
945 0     0 1 0 my $self = shift;
946 0         0 my $m = shift;
947 0         0 return $m * 3.28;
948             }
949              
950             #--------------------------------------------------------------------------
951             # tempfile()
952             #
953             # return a temp file name
954             #--------------------------------------------------------------------------
955             sub tempfile {
956              
957 49     49 1 117 my $self = shift;
958              
959 12     12   19811 use File::Temp qw(:mktemp);
  12         194573  
  12         9536  
960              
961 49         792 my ( $fh, $fn ) = mkstemp("wgrib.tmp.XXXXXXXXX");
962 49         33047 return $fn;
963             }
964              
965             #--------------------------------------------------------------------------
966             # $p = getParam(parm_name)
967             #
968             # getParam(param_name) returns a scalar with the value of param_name
969             # getParam("show") returns a scalar listing published parameter names.
970             #--------------------------------------------------------------------------
971             sub getParam {
972              
973 2     2 1 953 my $self = shift;
974 2         9 my $arg = shift;
975              
976 2         27 my @published = qw/TIME LAST_TIME La1 La2 LaInc Lo1 Lo2 LoInc fileName/;
977              
978 2         3 my $param;
979 2 50       18 if ( defined $arg ) {
980 2 100       50 if ( $arg =~ /show/i ) {
    50          
981 1         7 $param = "@published";
982             }
983             elsif ( grep /$arg/, @published ) {
984 1         5 $param = $self->{$arg};
985             }
986             else {
987 0         0 $self->error( "getParam(): $arg - Undefined or unpublished parameter" );
988 0         0 return 1;
989             }
990             }
991             else {
992 0         0 $self->error( "getParam(): Usage: getParam(param_name)" );
993 0         0 return 1;
994             }
995              
996 2         12 return $param;
997             }
998              
999             #--------------------------------------------------------------------------
1000             # show()
1001             #
1002             # Returns a scalar containing a string with some selected meta data
1003             # describing the GRIB file.
1004             #--------------------------------------------------------------------------
1005             sub show {
1006              
1007 3     3 1 8146 my $self = shift;
1008 3         14 my $arg = shift;
1009              
1010 3         22 my $param;
1011              
1012 3         66 my @published = qw/LAST_TIME La1 La2 LaInc Lo1 Lo2 LoInc TIME fileName/;
1013              
1014 3 50       40 if ( defined $arg ) {
1015 0 0       0 if ( $arg =~ /show/i ) {
    0          
1016 0         0 $param = "@published";
1017             }
1018             elsif ( grep /$arg/, @published ) {
1019 0         0 $param = $self->{$arg};
1020             }
1021             else {
1022 0         0 $self->error( "show(): $arg - Undefined or unpublished parameter" );
1023 0         0 return 1;
1024             }
1025             }
1026             else {
1027 3         13 my $types;
1028 3         13 foreach ( sort keys %{ $self->{v_catalog} } ) {
  3         29  
1029 3         36 $types .= sprintf "%8s: %s\n", $_, $self->{v_catalog}->{$_};
1030             }
1031              
1032 3         12 my @times = sort keys %{ $self->{catalog} };
  3         109  
1033 3         57 my $t = scalar gmtime( $times[0] ) . " ($times[0]) to ";
1034 3         28 $t .= scalar gmtime( $times[-1] ) . " ($times[-1])";
1035              
1036 3         96 $param = <<" PARAM";
1037            
1038             Locations:
1039            
1040             lat: $self->{La1} to $self->{La2}
1041             long: $self->{Lo1} to $self->{Lo2}
1042            
1043             Times:
1044            
1045             $t
1046            
1047             Types:
1048             \n$types
1049             PARAM
1050             }
1051 3         21 return $param;
1052             }
1053              
1054             1;
1055              
1056             __DATA__