File Coverage

lib/Geo/ShapeFile.pm
Criterion Covered Total %
statement 328 388 84.5
branch 82 130 63.0
condition 12 29 41.3
subroutine 59 74 79.7
pod 44 48 91.6
total 525 669 78.4


line stmt bran cond sub pod time code
1             package Geo::ShapeFile;
2              
3 2     2   155530 use strict;
  2         15  
  2         58  
4 2     2   12 use warnings;
  2         4  
  2         63  
5 2     2   11 use Carp;
  2         2  
  2         111  
6 2     2   980 use IO::File;
  2         17617  
  2         213  
7 2     2   1035 use Geo::ShapeFile::Shape;
  2         18  
  2         88  
8 2     2   19 use Config;
  2         4  
  2         107  
9 2     2   11 use List::Util qw /min max/;
  2         4  
  2         154  
10 2     2   14 use Scalar::Util qw/weaken/;
  2         4  
  2         91  
11 2     2   12 use Tree::R;
  2         4  
  2         113  
12              
13 2     2   15 use constant ON_WINDOWS => ($^O eq 'MSWin32');
  2         4  
  2         245  
14 2     2   1478 use if ON_WINDOWS, 'Win32::LongPath';
  2         28  
  2         22  
15              
16             our $VERSION = '3.03';
17              
18             my $little_endian_sys = unpack 'b', (pack 'S', 1 );
19              
20             # Preloaded methods go here.
21             sub new {
22 43     43 1 81994 my $proto = shift;
23 43   33     155 my $filebase = shift || croak "Must specify filename!";
24 43   100     195 my $args = shift || {}; # should check it's a haashref
25              
26 43   33     199 my $class = ref($proto) || $proto;
27 43         97 my $self = {};
28              
29 43         136 $self->{filebase} = $filebase;
30             # should use a proper file name handler
31             # so we can deal with fred.ext referring to fred.ext.shp
32 43         191 $self->{filebase} =~ s/\.\w{3}$//;
33              
34             $self->{_enable_caching} = {
35 43         228 shp => 1,
36             dbf => 1,
37             shx => 1,
38             shapes_in_area => 1,
39             };
40 43         103 $self->{has_shx} = 0;
41 43         103 $self->{has_shp} = 0;
42 43         86 $self->{has_dbf} = 0;
43              
44 43         129 bless $self, $class;
45              
46             # control overall caching
47 43 100       155 if ($args->{no_cache}) {
48 14         45 $self->{_no_cache} = 1;
49             }
50              
51             # not sure what this does - possible residual from early plans
52             $self->{_change_cache} = {
53 43         245 shape_type => undef,
54             records => undef,
55             shp => {},
56             dbf => {},
57             shx => {},
58             };
59             $self->{_object_cache} = {
60 43         213 shp => {},
61             dbf => {},
62             shx => {},
63             shapes_in_area => {},
64             };
65              
66 43 100       274 if ($self->file_exists ($self->{filebase} . '.shx')) {
67 40         232 $self->_read_shx_header();
68 40         134 $self->{has_shx} = 1;
69             }
70              
71 43 100       259 if ($self->file_exists ($self->{filebase} . '.shp')) {
72 40         228 $self->_read_shp_header();
73 40         282 $self->{has_shp} = 1;
74             }
75              
76 43 100       249 if ($self->file_exists ($self->{filebase} . '.dbf')) {
77 41         344 $self->_read_dbf_header();
78 41         127 $self->{has_dbf} = 1;
79             }
80              
81 43 100       169 if (!$self->{has_dbf}) {
82             croak "$self->{filebase}: shp and/or shx file do not exist or are invalid"
83 2 50 33     263 if !($self->{has_shp} && $self->{has_shx});
84              
85 0         0 croak "$self->{filebase}.dbf does not exist or is invalid";
86             }
87              
88 41         350 return $self;
89             }
90              
91             sub get_file_size {
92 41     41 0 151 my ($self, $file_name) = @_;
93              
94 41         74 my $file_size;
95              
96 41 50       548 if (-e $file_name) {
97 41         470 $file_size = -s $file_name;
98             }
99             elsif (ON_WINDOWS) {
100             my $stat = statL ($file_name)
101             or die ("unable to get stat for $file_name ($^E)");
102             $file_size = $stat->{size};
103             }
104             else {
105 0         0 croak "$file_name does not exist or cannot be read, cannot get file size\n";
106             }
107              
108 41         156 return $file_size;
109             }
110              
111             sub file_exists {
112 129     129 0 302 my ($self, $file_name) = @_;
113              
114 129 100       2385 return 1 if -e $file_name;
115            
116 8         18 if (ON_WINDOWS) {
117             return testL ('e', $file_name);
118             }
119              
120 8         28 return;
121             }
122              
123              
124             sub _disable_all_caching {
125 0     0   0 my $self = shift;
126             # a bit nuclear...
127 0         0 foreach my $type (qw/shp shx dbf shapes_in_area/) {
128 0         0 $self->{_enable_caching}{$type} = 0;
129 0         0 $self->{_object_cache} = {};
130             #$self->{_change_cache} = {}; # need to work out what this is for
131             }
132 0         0 return;
133             }
134              
135             sub caching {
136 27430     27430 0 36662 my $self = shift;
137 27430         37861 my $what = shift;
138 27430         34014 my $flag = shift;
139              
140 27430 50       47572 if (defined $flag) {
141 0         0 $self->{_enable_caching}->{$what} = $flag;
142             }
143 27430         65185 return $self->{_enable_caching}->{$what};
144             }
145              
146             sub cache {
147 64438     64438 0 123940 my ($self, $type, $obj, $cache) = @_;
148            
149 64438 100       167614 return if $self->{_no_cache};
150              
151             return $self->{_change_cache}->{$type}->{$obj}
152 27430 50 33     106000 if $self->{_change_cache}->{$type} && $self->{_change_cache}->{$type}->{$obj};
153              
154 27430 50       52221 return if !$self->caching($type);
155              
156 27430 100       53446 if ($cache) {
157 8371         30579 $self->{_object_cache}->{$type}->{$obj} = $cache;
158             }
159 27430         72272 return $self->{_object_cache}->{$type}->{$obj};
160             }
161              
162             # This will trigger the various caching
163             # so we end up with the file in memory.
164             # Not an issue for most files.
165             sub get_all_shapes {
166 8     8 1 22561 my $self = shift;
167              
168 8         22 my @shapes;
169              
170 8         39 foreach my $id (1 .. $self->shapes()) {
171 1427         2483 my $shape = $self->get_shp_record($id);
172 1427         2642 push @shapes, $shape;
173             }
174              
175 8 100       112 return wantarray ? @shapes : \@shapes;
176             }
177              
178             sub get_shapes_sorted {
179 2     2 1 752 my $self = shift;
180 2         4 my $shapes = shift;
181 2         5 my $sub = shift;
182              
183 2 50       6 if (!defined $sub) {
184             $sub = sub {
185 3593     3593   5033 my ($s1, $s2) = @_;
186 3593         6253 return $s1->{shp_record_number} <=> $s2->{shp_record_number};
187 2         14 };
188             }
189              
190 2 100       7 if (!defined $shapes) {
191 1         3 $shapes = $self->get_all_shapes;
192             }
193              
194 2         40 my @sorted = sort {$sub->($a, $b)} @$shapes;
  3593         4395  
195              
196 2 50       121 return wantarray ? @sorted : \@sorted;
197             }
198              
199             sub get_shapes_sorted_spatially {
200 0     0 1 0 my $self = shift;
201 0         0 my $shapes = shift;
202 0         0 my $sub = shift;
203              
204 0 0       0 if (!defined $sub) {
205             $sub = sub {
206 0     0   0 my ($s1, $s2) = @_;
207             return
208 0   0     0 $s1->x_min <=> $s2->x_min
209             || $s1->y_min <=> $s2->y_min
210             || $s1->x_max <=> $s2->x_max
211             || $s1->y_max <=> $s2->y_max
212             || $s1->shape_id <=> $s2->shape_id
213             ;
214 0         0 };
215             }
216              
217 0         0 return $self->get_shapes_sorted ($shapes, $sub);
218             }
219              
220             sub build_spatial_index {
221 1     1 1 14 my $self = shift;
222              
223 1         7 my $shapes = $self->get_all_shapes;
224              
225 1         16 my $rtree = Tree::R->new();
226 1         32 foreach my $shape (@$shapes) {
227 474         297018 my @bbox = ($shape->x_min, $shape->y_min, $shape->x_max, $shape->y_max);
228 474         1087 $rtree->insert($shape, @bbox);
229             }
230              
231 1         867 $self->{_spatial_index} = $rtree;
232              
233 1         84 return $rtree;
234             }
235              
236             sub get_spatial_index {
237 12     12 1 18 my $self = shift;
238 12         45 return $self->{_spatial_index};
239             }
240              
241              
242             sub _read_shx_header {
243 40     40   159 shift()->_read_shx_shp_header('shx', @_);
244             }
245              
246             sub _read_shp_header {
247 40     40   162 shift()->_read_shx_shp_header('shp', @_);
248             }
249              
250             sub _read_shx_shp_header {
251 80     80   154 my $self = shift;
252 80         152 my $which = shift;
253 80         117 my $doubles;
254              
255 80         314 $self->{$which . '_header'} = $self->_get_bytes($which, 0, 100);
256             (
257             $self->{$which . '_file_code'}, $self->{$which . '_file_length'},
258             $self->{$which . '_version'}, $self->{$which . '_shape_type'}, $doubles
259 80         884 ) = unpack 'N x20 N V2 a64', $self->{$which . '_header'};
260              
261             (
262             $self->{$which . '_x_min'}, $self->{$which . '_y_min'},
263             $self->{$which . '_x_max'}, $self->{$which . '_y_max'},
264             $self->{$which . '_z_min'}, $self->{$which . '_z_max'},
265 80 50       1078 $self->{$which . '_m_min'}, $self->{$which . '_m_max'},
266             ) = (
267             $little_endian_sys
268             ? (unpack 'd8', $doubles )
269             : (reverse unpack 'd8', scalar reverse $doubles)
270             );
271              
272 80         205 return 1;
273             }
274              
275             sub type_is {
276 5     5 1 18 my $self = shift;
277 5         10 my $type = shift;
278              
279             # numeric code
280 5 100       47 return $self->shape_type == $type
281             if ($type =~ /^[0-9]+$/);
282              
283 3         9 return (lc $self->type($self->shape_type)) eq (lc $type);
284             }
285              
286             sub get_dbf_field_names {
287 28     28 1 995 my $self = shift;
288              
289             croak 'dbf field names not loaded yet'
290 28 50       123 if !defined $self->{dbf_field_names};
291              
292             # make sure we return a copy
293 28         70 my @fld_names = @{$self->{dbf_field_names}};
  28         120  
294              
295 28 50       165 return wantarray ? @fld_names : \@fld_names;
296             }
297              
298             sub _read_dbf_header {
299 41     41   125 my $self = shift;
300              
301 41         148 $self->{dbf_header} = $self->_get_bytes('dbf', 0, 12);
302             (
303             $self->{dbf_version},
304             $self->{dbf_updated_year},
305             $self->{dbf_updated_month},
306             $self->{dbf_updated_day},
307             $self->{dbf_num_records},
308             $self->{dbf_header_length},
309             $self->{dbf_record_length},
310 41         441 ) = unpack 'C4 V v v', $self->{dbf_header};
311             # unpack changed from c4 l s s to fix endianess problem
312             # reported by Daniel Gildea
313              
314             my $ls = $self->{dbf_header_length}
315 41         169 + $self->{dbf_num_records} * $self->{dbf_record_length};
316 41         248 my $li = $self->get_file_size($self->{filebase} . '.dbf');
317              
318             # some shapefiles (such as are produced by the NOAA NESDIS) don't
319             # have a end-of-file marker in their dbf files, Aleksandar Jelenak
320             # says the ESRI tools don't have a problem with this, so we shouldn't
321             # either
322 41         213 my $last_byte = $self->_get_bytes('dbf', $li-1, 1);
323 41 50       284 $ls ++ if ord $last_byte == 0x1A;
324              
325 41 50       142 croak "dbf: file wrong size (should be $ls, but found $li)"
326             if $ls != $li;
327              
328 41         173 my $header = $self->_get_bytes('dbf', 32, $self->{dbf_header_length} - 32);
329 41         145 my $count = 0;
330 41         195 $self->{dbf_header_info} = [];
331              
332 41         134 while ($header) {
333 394         881 my $tmp = substr $header, 0, 32, '';
334 394         587 my $chr = substr $tmp, 0, 1;
335              
336 394 100       710 last if ord $chr == 0x0D;
337 357 100       731 last if length ($tmp) < 32;
338              
339 353         540 my %tmp = ();
340             (
341             $tmp{name},
342             $tmp{type},
343             $tmp{size},
344             $tmp{decimals}
345 353         1274 ) = unpack 'Z11 Z x4 C2', $tmp;
346              
347 353         1453 $self->{dbf_field_info}->[$count] = {%tmp};
348              
349 353         996 $count++;
350             }
351              
352 41         171 $self->{dbf_fields} = $count;
353 41 50       102 croak "dbf: Not enough fields ($count < 1)"
354             if $count < 1;
355              
356 41         101 my @template = ();
357 41         79 foreach (@{$self->{dbf_field_info}}) {
  41         143  
358             croak "dbf: Field $_->{name} too short ($_->{size} bytes)"
359 353 50       730 if $_->{size} < 1;
360              
361             croak "dbf: Field $_->{name} too long ($_->{size} bytes)"
362 353 50       702 if $_->{size} > 4000;
363              
364 353         865 push @template, 'A' . $_->{size};
365             }
366 41         264 $self->{dbf_record_template} = join ' ', @template;
367              
368 41         103 my @field_names = ();
369 41         91 foreach (@{$self->{dbf_field_info}}) {
  41         109  
370 353         643 push @field_names, $_->{name};
371             }
372 41         220 $self->{dbf_field_names} = [@field_names];
373              
374             # should return field names?
375 41         153 return 1;
376             }
377              
378             # needed now there is Geo::ShapeFile::Writer?
379             sub _generate_dbf_header {
380 0     0   0 my $self = shift;
381              
382             #$self->{dbf_header} = $self->_get_bytes('dbf',0,12);
383             (
384             $self->{dbf_version},
385             $self->{dbf_updated_year},
386             $self->{dbf_updated_month},
387             $self->{dbf_updated_day},
388             $self->{dbf_num_records},
389             $self->{dbf_header_length},
390             $self->{dbf_record_length},
391 0         0 ) = unpack 'C4 V v v', $self->{dbf_header};
392              
393             $self->{_change_cache}->{dbf_cache}->{header}
394 0         0 = pack
395             'C4 V v v',
396             3,
397             (localtime)[5],
398             (localtime)[4]+1,
399             (localtime)[3],
400             0, # TODO - num_records,
401             0, # TODO - header_length,
402             0, # TODO - record_length,
403             ;
404             }
405              
406             sub get_dbf_field_info {
407 0     0 1 0 my $self = shift;
408            
409 0         0 my $header = $self->{dbf_field_info};
410            
411 0 0       0 return if !$header;
412            
413             # Return a deep copy to avoid callers
414             # messing up the internals
415 0         0 my @hdr;
416 0         0 foreach my $field (@$header) {
417 0         0 my %h = %$field;
418 0         0 push @hdr, \%h;
419             }
420              
421 0 0       0 return wantarray ? @hdr : \@hdr;
422             }
423              
424             sub get_dbf_record {
425 9252     9252 1 4955692 my $self = shift;
426 9252         15785 my $entry = shift;
427              
428 9252         19805 my $dbf = $self->cache('dbf', $entry);
429              
430 9252 100       18937 if (!$dbf) {
431 6939         10698 $entry--; # make entry 0-indexed
432              
433             my $record = $self->_get_bytes(
434             'dbf',
435             $self->{dbf_header_length}+($self->{dbf_record_length} * $entry),
436 6939         21954 $self->{dbf_record_length}+1, # +1 for deleted flag
437             );
438 6939         54507 my ($del, @data) = unpack 'c' . $self->{dbf_record_template}, $record;
439              
440 6939         17660 map { s/^\s*//; s/\s*$//; } @data;
  79611         177938  
  79611         237664  
441              
442 6939         11209 my %record;
443 6939         10563 @record{@{$self->{dbf_field_names}}} = @data;
  6939         64755  
444 6939         17638 $record{_deleted} = (ord $del == 0x2A);
445 6939         80425 $dbf = {%record};
446 6939         22910 $self->cache('dbf', $entry + 1, $dbf);
447             }
448              
449 9252 100       33881 return wantarray ? %{$dbf} : $dbf;
  4626         56397  
450             }
451              
452             # needed? not called anywhere
453             sub _set_dbf_record {
454 0     0   0 my $self = shift;
455 0         0 my $entry = shift;
456 0         0 my %record = @_;
457              
458 0         0 $self->{_change_cache}->{dbf}->{$entry} = {%record};
459             }
460              
461             sub _get_shp_shx_header_value {
462 151     151   277 my $self = shift;
463 151         284 my $val = shift;
464              
465 151 0 33     523 if (!defined($self->{'shx_' . $val}) && !defined($self->{'shp_' . $val})) {
466 0         0 $self->_read_shx_header(); # ensure we load at least one of the headers
467             }
468              
469             return defined($self->{'shx_' . $val})
470             ? $self->{'shx_' . $val}
471 151 50       1015 : $self->{'shp_' . $val};
472             }
473              
474             # factory these
475 29     29 1 88 sub x_min { shift()->_get_shp_shx_header_value('x_min'); }
476 29     29 1 114 sub x_max { shift()->_get_shp_shx_header_value('x_max'); }
477 29     29 1 83 sub y_min { shift()->_get_shp_shx_header_value('y_min'); }
478 29     29 1 123 sub y_max { shift()->_get_shp_shx_header_value('y_max'); }
479 0     0 1 0 sub z_min { shift()->_get_shp_shx_header_value('z_min'); }
480 0     0 1 0 sub z_max { shift()->_get_shp_shx_header_value('z_max'); }
481 0     0 1 0 sub m_min { shift()->_get_shp_shx_header_value('m_min'); }
482 0     0 1 0 sub m_max { shift()->_get_shp_shx_header_value('m_max'); }
483              
484             sub upper_left_corner {
485 1     1 1 6 my $self = shift;
486              
487 1         6 return Geo::ShapeFile::Point->new(X => $self->x_min, Y => $self->y_max);
488             }
489              
490             sub upper_right_corner {
491 1     1 1 13 my $self = shift;
492              
493 1         7 return Geo::ShapeFile::Point->new(X => $self->x_max, Y => $self->y_max);
494             }
495              
496             sub lower_right_corner {
497 1     1 1 20 my $self = shift;
498              
499 1         12 return Geo::ShapeFile::Point->new(X => $self->x_max, Y => $self->y_min);
500             }
501              
502             sub lower_left_corner {
503 1     1 1 5 my $self = shift;
504              
505 1         5 return Geo::ShapeFile::Point->new(X => $self->x_min, Y => $self->y_min);
506             }
507              
508             sub height {
509 28     28 1 70993 my $self = shift;
510              
511 28 100       139 return if !$self->records;
512              
513 26         143 return $self->y_max - $self->y_min;
514             }
515              
516             sub width {
517 28     28 1 92 my $self = shift;
518              
519 28 100       84 return if !$self->records;
520              
521 26         179 return $self->x_max - $self->x_min;
522             }
523              
524             sub corners {
525 0     0 1 0 my $self = shift;
526              
527             return (
528 0         0 $self->upper_left_corner,
529             $self->upper_right_corner,
530             $self->lower_right_corner,
531             $self->lower_left_corner,
532             );
533             }
534              
535             sub area_contains_point {
536 59     59 1 103 my $self = shift;
537 59         82 my $point = shift;
538              
539 59         126 my ($x_min, $y_min, $x_max, $y_max) = @_;
540              
541 59         157 my $x = $point->get_x;
542 59         141 my $y = $point->get_y;
543              
544 59   100     495 my $result =
545             ($x >= $x_min) &&
546             ($x <= $x_max) &&
547             ($y >= $y_min) &&
548             ($y <= $y_max);
549              
550 59         257 return $result;
551             }
552              
553             sub bounds_contains_point {
554 59     59 1 106 my $self = shift;
555 59         104 my $point = shift;
556              
557 59         180 return $self->area_contains_point (
558             $point,
559             $self->x_min, $self->y_min,
560             $self->x_max, $self->y_max,
561             );
562             }
563              
564             sub file_version {
565 2     2 1 14 shift()->_get_shp_shx_header_value('version');
566             }
567              
568             sub shape_type {
569 33     33 1 72 my $self = shift;
570              
571             return $self->{_change_cache}->{shape_type}
572 33 50       147 if defined $self->{_change_cache}->{shape_type};
573              
574 33         123 return $self->_get_shp_shx_header_value('shape_type');
575             }
576              
577             sub shapes {
578 214     214 1 25316 my $self = shift;
579              
580             return $self->{_change_cache}->{records}
581 214 50       744 if defined $self->{_change_cache}->{records};
582              
583 214 50       611 if (!$self->{shx_file_length}) {
584 0         0 $self->_read_shx_header();
585             }
586              
587 214         359 my $filelength = $self->{shx_file_length};
588 214         326 $filelength -= 50; # don't count the header
589              
590 214         1104 return $filelength / 4;
591             }
592              
593             sub records {
594 197     197 1 74679 my $self = shift;
595              
596             return $self->{_change_cache}->{records}
597 197 50       711 if defined $self->{_change_cache}->{records};
598              
599 197 100       516 if ($self->{shx_file_length}) {
    50          
600 196         412 my $filelength = $self->{shx_file_length};
601 196         348 $filelength -= 50; # don't count the header
602 196         848 return $filelength / 4;
603             }
604             # should perhaps just return dbf_num_records if we get this far?
605             elsif ($self->{dbf_num_records}) {
606 0         0 return $self->{dbf_num_records};
607             }
608              
609 1         5 return 0;
610             }
611              
612             sub shape_type_text {
613 28     28 1 18804 my $self = shift;
614              
615 28         126 return $self->type($self->shape_type());
616             }
617              
618             sub get_shx_record_header {
619 0     0 1 0 shift()->get_shx_record(@_);
620             }
621              
622             sub get_shx_record {
623 17630     17630 1 4311455 my $self = shift;
624 17630         25016 my $entry = shift;
625              
626 17630 50       40592 croak 'must specify entry index'
627             if !$entry;
628              
629 17630         36323 my $shx = $self->cache('shx', $entry);
630              
631 17630 100       33705 if (!$shx) {
632 12520         33757 my $record = $self->_get_bytes('shx', (($entry - 1) * 8) + 100, 8);
633 12520         46832 $shx = [unpack 'N N', $record];
634 12520         32123 $self->cache('shx', $entry, $shx);
635             }
636              
637 17630         23013 return @{$shx};
  17630         44825  
638             }
639              
640             sub get_shp_record_header {
641 4626     4626 1 14979 my $self = shift;
642 4626         6348 my $entry = shift;
643              
644 4626         8750 my($offset) = $self->get_shx_record($entry);
645              
646 4626         10964 my $record = $self->_get_bytes('shp', $offset * 2, 8);
647 4626         14376 my ($number, $content_length) = unpack 'N N', $record;
648              
649 4626         13030 return ($number, $content_length);
650             }
651              
652              
653             # returns indexes, not objects - need to change that or add method for shape_objects_in_area
654             sub shapes_in_area {
655 12     12 1 72413 my $self = shift;
656 12         38 my @area = @_; # x_min, y_min, x_max, y_max,
657              
658 12 100       33 if (my $sp_index = $self->get_spatial_index) {
659 2         9 my $shapes = [];
660 2         16 $sp_index->query_partly_within_rect (@area, $shapes);
661 2         17362 my @indexes;
662 2         8 foreach my $shape (@$shapes) {
663 604         1130 push @indexes, $shape->shape_id;
664             }
665 2 50       40 return wantarray ? @indexes : \@indexes;
666             }
667              
668 10         21 my @results = ();
669             SHAPE:
670 10         27 foreach my $shp_id (1 .. $self->shapes) {
671 962         2643 my ($offset, $content_length) = $self->get_shx_record($shp_id);
672 962         2334 my $type = unpack 'V', $self->_get_bytes ('shp', $offset * 2 + 8, 4);
673              
674 962 50       2410 next SHAPE if $self->type($type) eq 'Null';
675              
676 962 50       1718 if ($self->type($type) =~ /^Point/) {
    50          
677 0         0 my $bytes = $self->_get_bytes('shp', $offset * 2 + 12, 16);
678 0 0       0 my ($x, $y) = (
679             $little_endian_sys
680             ? (unpack 'dd', $bytes )
681             : (reverse unpack 'dd', scalar reverse $bytes)
682             );
683 0         0 my $pt = Geo::ShapeFile::Point->new(X => $x, Y => $y);
684 0 0       0 if ($self->area_contains_point($pt, @area)) {
685 0         0 push @results, $shp_id;
686             }
687             }
688             elsif ($self->type($type) =~ /^(PolyLine|Polygon|MultiPoint|MultiPatch)/) {
689 962         2415 my $bytes = $self->_get_bytes('shp', ($offset * 2) + 12, 32);
690 962 50       3652 my @p = (
691             $little_endian_sys
692             ? (unpack 'd4', $bytes )
693             : (reverse unpack 'd4', scalar reverse $bytes )
694             );
695 962 100       2312 if ($self->check_in_area(@p, @area)) {
696 615         2219 push @results, $shp_id;
697             }
698             }
699             else {
700 0         0 print 'type=' . $self->type($type) . "\n";
701             }
702             }
703              
704 10 100       53 return wantarray ? @results : \@results;
705             }
706              
707             sub check_in_area {
708 962     962 1 1459 my $self = shift;
709             my (
710 962         2074 $x1_min, $y1_min, $x1_max, $y1_max,
711             $x2_min, $y2_min, $x2_max, $y2_max,
712             ) = @_;
713              
714 962   66     4804 my $result = !(
715             $x1_min > $x2_max
716             or $x1_max < $x2_min
717             or $y1_min > $y2_max
718             or $y1_max < $y2_min
719             );
720              
721 962         2679 return $result;
722             }
723              
724             # SWL: not used anymore - remove?
725             sub _between {
726 0     0   0 my $self = shift;
727 0         0 my $check = shift;
728              
729             # ensure min then max
730 0 0       0 if ($_[0] > $_[1]) {
731 0         0 @_ = reverse @_;
732             }
733              
734 0   0     0 return ($check >= $_[0]) && ($check <= $_[1]);
735             }
736              
737             sub bounds {
738 1     1 1 614 my $self = shift;
739              
740             return (
741 1         6 $self->x_min, $self->y_min,
742             $self->x_max, $self->y_max,
743             );
744             }
745              
746             # is this ever called?
747             sub _extract_ints {
748 0     0   0 my $self = shift;
749 0         0 my $end = shift;
750 0         0 my @what = @_;
751              
752 0 0       0 my $template = ($end =~ /^l/i) ? 'V': 'N';
753              
754 0         0 $self->_extract_and_unpack(4, $template, @what);
755 0         0 foreach (@what) {
756 0         0 $self->{$_} = $self->{$_};
757             }
758             }
759              
760             sub get_shp_record {
761 10681     10681 1 5117054 my $self = shift;
762 10681         16870 my $entry = shift;
763              
764 10681         19674 my $shape = $self->cache('shp', $entry);
765 10681 100       20360 if (!$shape) {
766 7416         14558 my($offset, $content_length) = $self->get_shx_record($entry);
767              
768 7416         19327 my $record = $self->_get_bytes('shp', $offset * 2, $content_length * 2 + 8);
769              
770 7416         28977 $shape = Geo::ShapeFile::Shape->new();
771 7416         20407 $shape->parse_shp($record);
772 7416         18412 $self->cache('shp', $entry, $shape);
773             }
774              
775 10681         24311 return $shape;
776             }
777              
778             sub shx_handle {
779 12560     12560 1 23202 shift()->_get_handle('shx');
780             }
781              
782             sub shp_handle {
783 14006     14006 1 24365 shift()->_get_handle('shp');
784             }
785              
786             sub dbf_handle {
787 7062     7062 1 14532 shift()->_get_handle('dbf');
788             }
789              
790             sub _get_handle {
791 33628     33628   46328 my $self = shift;
792 33628         45553 my $which = shift;
793              
794 33628         47092 my $han = $which . '_handle';
795              
796 33628 100       77489 if (!$self->{$han}) {
797 121         376 my $file = join '.', $self->{filebase}, $which;
798 121 50       1300 if (-e $file) {
799 121         1023 $self->{$han} = IO::File->new;
800             croak "Couldn't get file handle for $file: $!"
801 121 50       6086 if not $self->{$han}->open($file, O_RDONLY | O_BINARY);
802             }
803 0         0 elsif (ON_WINDOWS) {
804             my $fh;
805             openL (\$fh, '<', $file)
806             or croak ("unable to open $file ($^E)");
807             #$fh = IO::File->new_from_fd ($fh);
808             $self->{$han} = $fh;
809             }
810 121         8108 binmode $self->{$han}; # fix windows bug reported by Patrick Dughi
811             }
812              
813 33628         63508 return $self->{$han};
814             }
815              
816             sub _get_bytes {
817 33628     33628   55124 my $self = shift;
818 33628         45753 my $file = shift;
819 33628         44185 my $offset = shift;
820 33628         42351 my $length = shift;
821              
822 33628         58418 my $handle = $file . '_handle';
823 33628         75859 my $h = $self->$handle();
824 33628 50       95725 $h->seek ($offset, 0)
825             || croak "Couldn't seek to $offset for $file";
826              
827 33628         647823 my $tmp;
828 33628         101335 my $res = $h->read($tmp, $length);
829              
830 33628 50       604162 croak "Couldn't read $length bytes from $file at offset $offset ($!)"
831             if !defined $res;
832              
833 33628 50       65299 croak "EOF reading $length bytes from $file at offset $offset"
834             if $res == 0;
835              
836 33628         83175 return $tmp;
837             }
838              
839              
840             sub type {
841 10333     10333 1 16019 my $self = shift;
842 10333         14310 my $shape = shift;
843              
844             # should make this a package lexical
845 10333         83913 my %shape_types = qw(
846             0 Null
847             1 Point
848             3 PolyLine
849             5 Polygon
850             8 MultiPoint
851             11 PointZ
852             13 PolyLineZ
853             15 PolygonZ
854             18 MultiPointZ
855             21 PointM
856             23 PolyLineM
857             25 PolygonM
858             28 MultiPointM
859             31 MultiPatch
860             );
861              
862 10333         46663 return $shape_types{$shape};
863             }
864              
865             sub find_bounds {
866 28     28 1 910 my $self = shift;
867 28         576 my @objects = @_;
868              
869 28 100       126 return if !scalar @objects;
870              
871 26         70 my $obj1 = shift @objects;
872              
873             # assign values from first object to start
874 26         127 my $x_min = $obj1->x_min();
875 26         144 my $y_min = $obj1->y_min();
876 26         89 my $x_max = $obj1->x_max();
877 26         114 my $y_max = $obj1->y_max();
878              
879              
880 26         112 foreach my $obj (@objects) {
881 4600         8862 $x_min = min ($x_min, $obj->x_min());
882 4600         8041 $y_min = min ($y_min, $obj->y_min());
883 4600         7788 $x_max = max ($x_max, $obj->x_max());
884 4600         7793 $y_max = max ($y_max, $obj->y_max());
885             }
886              
887 26         240 my %bounds = (
888             x_min => $x_min,
889             y_min => $y_min,
890             x_max => $x_max,
891             y_max => $y_max,
892             );
893              
894 26         395 return %bounds;
895             }
896              
897             # XML::Generator::SVG::ShapeFile fails because it is calling this method
898             # and it does not exist in 2.52 and earlier
899       0     sub DESTROY {}
900              
901              
902             1;
903             __END__