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   151632 use strict;
  2         13  
  2         56  
4 2     2   10 use warnings;
  2         3  
  2         45  
5 2     2   10 use Carp;
  2         3  
  2         90  
6 2     2   1072 use IO::File;
  2         18051  
  2         273  
7 2     2   1024 use Geo::ShapeFile::Shape;
  2         13  
  2         72  
8 2     2   17 use Config;
  2         4  
  2         90  
9 2     2   14 use List::Util qw /min max/;
  2         4  
  2         129  
10 2     2   14 use Scalar::Util qw/weaken/;
  2         3  
  2         88  
11 2     2   23 use Tree::R;
  2         4  
  2         88  
12            
13 2     2   12 use constant ON_WINDOWS => ($^O eq 'MSWin32');
  2         3  
  2         199  
14 2     2   1482 use if ON_WINDOWS, 'Win32::LongPath';
  2         28  
  2         10  
15            
16             our $VERSION = '3.01';
17            
18             my $little_endian_sys = unpack 'b', (pack 'S', 1 );
19            
20             # Preloaded methods go here.
21             sub new {
22 43     43 1 80110 my $proto = shift;
23 43   33     190 my $filebase = shift || croak "Must specify filename!";
24 43   100     183 my $args = shift || {}; # should check it's a haashref
25            
26 43   33     222 my $class = ref($proto) || $proto;
27 43         105 my $self = {};
28            
29 43         125 $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         213 $self->{filebase} =~ s/\.\w{3}$//;
33            
34             $self->{_enable_caching} = {
35 43         268 shp => 1,
36             dbf => 1,
37             shx => 1,
38             shapes_in_area => 1,
39             };
40 43         105 $self->{has_shx} = 0;
41 43         87 $self->{has_shp} = 0;
42 43         91 $self->{has_dbf} = 0;
43            
44 43         113 bless $self, $class;
45            
46             # control overall caching
47 43 100       154 if ($args->{no_cache}) {
48 14         31 $self->{_no_cache} = 1;
49             }
50            
51             # not sure what this does - possible residual from early plans
52             $self->{_change_cache} = {
53 43         255 shape_type => undef,
54             records => undef,
55             shp => {},
56             dbf => {},
57             shx => {},
58             };
59             $self->{_object_cache} = {
60 43         286 shp => {},
61             dbf => {},
62             shx => {},
63             shapes_in_area => {},
64             };
65            
66 43 100       228 if ($self->file_exists ($self->{filebase} . '.shx')) {
67 40         239 $self->_read_shx_header();
68 40         121 $self->{has_shx} = 1;
69             }
70            
71 43 100       234 if ($self->file_exists ($self->{filebase} . '.shp')) {
72 40         227 $self->_read_shp_header();
73 40         140 $self->{has_shp} = 1;
74             }
75            
76 43 100       199 if ($self->file_exists ($self->{filebase} . '.dbf')) {
77 41         281 $self->_read_dbf_header();
78 41         181 $self->{has_dbf} = 1;
79             }
80            
81 43 100       165 if (!$self->{has_dbf}) {
82             croak "$self->{filebase}: shp and/or shx file do not exist or are invalid"
83 2 50 33     244 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         373 return $self;
89             }
90            
91             sub get_file_size {
92 41     41 0 125 my ($self, $file_name) = @_;
93            
94 41         80 my $file_size;
95            
96 41 50       642 if (-e $file_name) {
97 41         519 $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         165 return $file_size;
109             }
110            
111             sub file_exists {
112 129     129 0 316 my ($self, $file_name) = @_;
113            
114 129 100       2680 return 1 if -e $file_name;
115            
116 8         19 if (ON_WINDOWS) {
117             return testL ('e', $file_name);
118             }
119            
120 8         29 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 37322 my $self = shift;
137 27430         34653 my $what = shift;
138 27430         33919 my $flag = shift;
139            
140 27430 50       54382 if (defined $flag) {
141 0         0 $self->{_enable_caching}->{$what} = $flag;
142             }
143 27430         64536 return $self->{_enable_caching}->{$what};
144             }
145            
146             sub cache {
147 64438     64438 0 132581 my ($self, $type, $obj, $cache) = @_;
148            
149 64438 100       179071 return if $self->{_no_cache};
150            
151             return $self->{_change_cache}->{$type}->{$obj}
152 27430 50 33     114044 if $self->{_change_cache}->{$type} && $self->{_change_cache}->{$type}->{$obj};
153            
154 27430 50       53008 return if !$self->caching($type);
155            
156 27430 100       55178 if ($cache) {
157 8371         27201 $self->{_object_cache}->{$type}->{$obj} = $cache;
158             }
159 27430         70815 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 23150 my $self = shift;
167            
168 8         25 my @shapes;
169            
170 8         50 foreach my $id (1 .. $self->shapes()) {
171 1427         2715 my $shape = $self->get_shp_record($id);
172 1427         2708 push @shapes, $shape;
173             }
174            
175 8 100       185 return wantarray ? @shapes : \@shapes;
176             }
177            
178             sub get_shapes_sorted {
179 2     2 1 1103 my $self = shift;
180 2         6 my $shapes = shift;
181 2         5 my $sub = shift;
182            
183 2 50       14 if (!defined $sub) {
184             $sub = sub {
185 3593     3593   5184 my ($s1, $s2) = @_;
186 3593         7260 return $s1->{shp_record_number} <=> $s2->{shp_record_number};
187 2         20 };
188             }
189            
190 2 100       9 if (!defined $shapes) {
191 1         5 $shapes = $self->get_all_shapes;
192             }
193            
194 2         103 my @sorted = sort {$sub->($a, $b)} @$shapes;
  3593         4900  
195            
196 2 50       214 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 10 my $self = shift;
222            
223 1         6 my $shapes = $self->get_all_shapes;
224            
225 1         16 my $rtree = Tree::R->new();
226 1         31 foreach my $shape (@$shapes) {
227 474         319274 my @bbox = ($shape->x_min, $shape->y_min, $shape->x_max, $shape->y_max);
228 474         1201 $rtree->insert($shape, @bbox);
229             }
230            
231 1         973 $self->{_spatial_index} = $rtree;
232            
233 1         180 return $rtree;
234             }
235            
236             sub get_spatial_index {
237 12     12 1 26 my $self = shift;
238 12         53 return $self->{_spatial_index};
239             }
240            
241            
242             sub _read_shx_header {
243 40     40   164 shift()->_read_shx_shp_header('shx', @_);
244             }
245            
246             sub _read_shp_header {
247 40     40   151 shift()->_read_shx_shp_header('shp', @_);
248             }
249            
250             sub _read_shx_shp_header {
251 80     80   158 my $self = shift;
252 80         145 my $which = shift;
253 80         141 my $doubles;
254            
255 80         270 $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         924 ) = 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       1046 $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         224 return 1;
273             }
274            
275             sub type_is {
276 5     5 1 16 my $self = shift;
277 5         11 my $type = shift;
278            
279             # numeric code
280 5 100       43 return $self->shape_type == $type
281             if ($type =~ /^[0-9]+$/);
282            
283 3         8 return (lc $self->type($self->shape_type)) eq (lc $type);
284             }
285            
286             sub get_dbf_field_names {
287 28     28 1 941 my $self = shift;
288            
289             croak 'dbf field names not loaded yet'
290 28 50       109 if !defined $self->{dbf_field_names};
291            
292             # make sure we return a copy
293 28         62 my @fld_names = @{$self->{dbf_field_names}};
  28         121  
294            
295 28 50       169 return wantarray ? @fld_names : \@fld_names;
296             }
297            
298             sub _read_dbf_header {
299 41     41   124 my $self = shift;
300            
301 41         137 $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         419 ) = 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         163 + $self->{dbf_num_records} * $self->{dbf_record_length};
316 41         236 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         235 my $last_byte = $self->_get_bytes('dbf', $li-1, 1);
323 41 50       162 $ls ++ if ord $last_byte == 0x1A;
324            
325 41 50       116 croak "dbf: file wrong size (should be $ls, but found $li)"
326             if $ls != $li;
327            
328 41         162 my $header = $self->_get_bytes('dbf', 32, $self->{dbf_header_length} - 32);
329 41         200 my $count = 0;
330 41         131 $self->{dbf_header_info} = [];
331            
332 41         132 while ($header) {
333 394         745 my $tmp = substr $header, 0, 32, '';
334 394         537 my $chr = substr $tmp, 0, 1;
335            
336 394 100       711 last if ord $chr == 0x0D;
337 357 100       744 last if length ($tmp) < 32;
338            
339 353         509 my %tmp = ();
340             (
341             $tmp{name},
342             $tmp{type},
343             $tmp{size},
344             $tmp{decimals}
345 353         1147 ) = unpack 'Z11 Z x4 C2', $tmp;
346            
347 353         1694 $self->{dbf_field_info}->[$count] = {%tmp};
348            
349 353         934 $count++;
350             }
351            
352 41         134 $self->{dbf_fields} = $count;
353 41 50       260 croak "dbf: Not enough fields ($count < 1)"
354             if $count < 1;
355            
356 41         98 my @template = ();
357 41         72 foreach (@{$self->{dbf_field_info}}) {
  41         142  
358             croak "dbf: Field $_->{name} too short ($_->{size} bytes)"
359 353 50       713 if $_->{size} < 1;
360            
361             croak "dbf: Field $_->{name} too long ($_->{size} bytes)"
362 353 50       642 if $_->{size} > 4000;
363            
364 353         858 push @template, 'A' . $_->{size};
365             }
366 41         191 $self->{dbf_record_template} = join ' ', @template;
367            
368 41         98 my @field_names = ();
369 41         68 foreach (@{$self->{dbf_field_info}}) {
  41         94  
370 353         613 push @field_names, $_->{name};
371             }
372 41         270 $self->{dbf_field_names} = [@field_names];
373            
374             # should return field names?
375 41         162 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 5009308 my $self = shift;
426 9252         14932 my $entry = shift;
427            
428 9252         21676 my $dbf = $self->cache('dbf', $entry);
429            
430 9252 100       19744 if (!$dbf) {
431 6939         9325 $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         22054 $self->{dbf_record_length}+1, # +1 for deleted flag
437             );
438 6939         55012 my ($del, @data) = unpack 'c' . $self->{dbf_record_template}, $record;
439            
440 6939         17859 map { s/^\s*//; s/\s*$//; } @data;
  79611         174124  
  79611         231778  
441            
442 6939         10571 my %record;
443 6939         10052 @record{@{$self->{dbf_field_names}}} = @data;
  6939         67191  
444 6939         18227 $record{_deleted} = (ord $del == 0x2A);
445 6939         68180 $dbf = {%record};
446 6939         25538 $self->cache('dbf', $entry + 1, $dbf);
447             }
448            
449 9252 100       33865 return wantarray ? %{$dbf} : $dbf;
  4626         51095  
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   238 my $self = shift;
463 151         243 my $val = shift;
464            
465 151 0 33     449 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       913 : $self->{'shp_' . $val};
472             }
473            
474             # factory these
475 29     29 1 101 sub x_min { shift()->_get_shp_shx_header_value('x_min'); }
476 29     29 1 85 sub x_max { shift()->_get_shp_shx_header_value('x_max'); }
477 29     29 1 84 sub y_min { shift()->_get_shp_shx_header_value('y_min'); }
478 29     29 1 122 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         4 return Geo::ShapeFile::Point->new(X => $self->x_min, Y => $self->y_max);
488             }
489            
490             sub upper_right_corner {
491 1     1 1 4 my $self = shift;
492            
493 1         4 return Geo::ShapeFile::Point->new(X => $self->x_max, Y => $self->y_max);
494             }
495            
496             sub lower_right_corner {
497 1     1 1 4 my $self = shift;
498            
499 1         3 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         3 return Geo::ShapeFile::Point->new(X => $self->x_min, Y => $self->y_min);
506             }
507            
508             sub height {
509 28     28 1 75718 my $self = shift;
510            
511 28 100       106 return if !$self->records;
512            
513 26         114 return $self->y_max - $self->y_min;
514             }
515            
516             sub width {
517 28     28 1 81 my $self = shift;
518            
519 28 100       85 return if !$self->records;
520            
521 26         101 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 116 my $self = shift;
537 59         116 my $point = shift;
538            
539 59         180 my ($x_min, $y_min, $x_max, $y_max) = @_;
540            
541 59         205 my $x = $point->get_x;
542 59         179 my $y = $point->get_y;
543            
544 59   100     748 my $result =
545             ($x >= $x_min) &&
546             ($x <= $x_max) &&
547             ($y >= $y_min) &&
548             ($y <= $y_max);
549            
550 59         293 return $result;
551             }
552            
553             sub bounds_contains_point {
554 59     59 1 181 my $self = shift;
555 59         139 my $point = shift;
556            
557 59         300 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 18 shift()->_get_shp_shx_header_value('version');
566             }
567            
568             sub shape_type {
569 33     33 1 76 my $self = shift;
570            
571             return $self->{_change_cache}->{shape_type}
572 33 50       140 if defined $self->{_change_cache}->{shape_type};
573            
574 33         114 return $self->_get_shp_shx_header_value('shape_type');
575             }
576            
577             sub shapes {
578 214     214 1 25794 my $self = shift;
579            
580             return $self->{_change_cache}->{records}
581 214 50       811 if defined $self->{_change_cache}->{records};
582            
583 214 50       583 if (!$self->{shx_file_length}) {
584 0         0 $self->_read_shx_header();
585             }
586            
587 214         357 my $filelength = $self->{shx_file_length};
588 214         351 $filelength -= 50; # don't count the header
589            
590 214         997 return $filelength / 4;
591             }
592            
593             sub records {
594 197     197 1 73775 my $self = shift;
595            
596             return $self->{_change_cache}->{records}
597 197 50       784 if defined $self->{_change_cache}->{records};
598            
599 197 100       497 if ($self->{shx_file_length}) {
    50          
600 196         316 my $filelength = $self->{shx_file_length};
601 196         338 $filelength -= 50; # don't count the header
602 196         796 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         3 return 0;
610             }
611            
612             sub shape_type_text {
613 28     28 1 18980 my $self = shift;
614            
615 28         129 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 4324288 my $self = shift;
624 17630         24461 my $entry = shift;
625            
626 17630 50       38781 croak 'must specify entry index'
627             if !$entry;
628            
629 17630         33569 my $shx = $self->cache('shx', $entry);
630            
631 17630 100       32485 if (!$shx) {
632 12520         36149 my $record = $self->_get_bytes('shx', (($entry - 1) * 8) + 100, 8);
633 12520         49870 $shx = [unpack 'N N', $record];
634 12520         32450 $self->cache('shx', $entry, $shx);
635             }
636            
637 17630         23523 return @{$shx};
  17630         45343  
638             }
639            
640             sub get_shp_record_header {
641 4626     4626 1 17774 my $self = shift;
642 4626         7384 my $entry = shift;
643            
644 4626         8678 my($offset) = $self->get_shx_record($entry);
645            
646 4626         12926 my $record = $self->_get_bytes('shp', $offset * 2, 8);
647 4626         17172 my ($number, $content_length) = unpack 'N N', $record;
648            
649 4626         13277 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 77034 my $self = shift;
656 12         34 my @area = @_; # x_min, y_min, x_max, y_max,
657            
658 12 100       44 if (my $sp_index = $self->get_spatial_index) {
659 2         7 my $shapes = [];
660 2         21 $sp_index->query_partly_within_rect (@area, $shapes);
661 2         15738 my @indexes;
662 2         14 foreach my $shape (@$shapes) {
663 604         1283 push @indexes, $shape->shape_id;
664             }
665 2 50       83 return wantarray ? @indexes : \@indexes;
666             }
667            
668 10         18 my @results = ();
669             SHAPE:
670 10         33 foreach my $shp_id (1 .. $self->shapes) {
671 962         1893 my ($offset, $content_length) = $self->get_shx_record($shp_id);
672 962         2277 my $type = unpack 'V', $self->_get_bytes ('shp', $offset * 2 + 8, 4);
673            
674 962 50       2758 next SHAPE if $self->type($type) eq 'Null';
675            
676 962 50       1692 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         2333 my $bytes = $self->_get_bytes('shp', ($offset * 2) + 12, 32);
690 962 50       4080 my @p = (
691             $little_endian_sys
692             ? (unpack 'd4', $bytes )
693             : (reverse unpack 'd4', scalar reverse $bytes )
694             );
695 962 100       2284 if ($self->check_in_area(@p, @area)) {
696 615         1996 push @results, $shp_id;
697             }
698             }
699             else {
700 0         0 print 'type=' . $self->type($type) . "\n";
701             }
702             }
703            
704 10 100       47 return wantarray ? @results : \@results;
705             }
706            
707             sub check_in_area {
708 962     962 1 1435 my $self = shift;
709             my (
710 962         1765 $x1_min, $y1_min, $x1_max, $y1_max,
711             $x2_min, $y2_min, $x2_max, $y2_max,
712             ) = @_;
713            
714 962   66     5215 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         2909 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 628 my $self = shift;
739            
740             return (
741 1         9 $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 5145383 my $self = shift;
762 10681         16352 my $entry = shift;
763            
764 10681         21578 my $shape = $self->cache('shp', $entry);
765 10681 100       23023 if (!$shape) {
766 7416         16871 my($offset, $content_length) = $self->get_shx_record($entry);
767            
768 7416         19612 my $record = $self->_get_bytes('shp', $offset * 2, $content_length * 2 + 8);
769            
770 7416         31970 $shape = Geo::ShapeFile::Shape->new();
771 7416         21198 $shape->parse_shp($record);
772 7416         19976 $self->cache('shp', $entry, $shape);
773             }
774            
775 10681         27192 return $shape;
776             }
777            
778             sub shx_handle {
779 12560     12560 1 28420 shift()->_get_handle('shx');
780             }
781            
782             sub shp_handle {
783 14006     14006 1 29771 shift()->_get_handle('shp');
784             }
785            
786             sub dbf_handle {
787 7062     7062 1 15504 shift()->_get_handle('dbf');
788             }
789            
790             sub _get_handle {
791 33628     33628   45022 my $self = shift;
792 33628         44061 my $which = shift;
793            
794 33628         50380 my $han = $which . '_handle';
795            
796 33628 100       86020 if (!$self->{$han}) {
797 121         348 my $file = join '.', $self->{filebase}, $which;
798 121 50       1580 if (-e $file) {
799 121         935 $self->{$han} = IO::File->new;
800             croak "Couldn't get file handle for $file: $!"
801 121 50       4908 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         8025 binmode $self->{$han}; # fix windows bug reported by Patrick Dughi
811             }
812            
813 33628         62602 return $self->{$han};
814             }
815            
816             sub _get_bytes {
817 33628     33628   49327 my $self = shift;
818 33628         46989 my $file = shift;
819 33628         42784 my $offset = shift;
820 33628         44055 my $length = shift;
821            
822 33628         57791 my $handle = $file . '_handle';
823 33628         76812 my $h = $self->$handle();
824 33628 50       96338 $h->seek ($offset, 0)
825             || croak "Couldn't seek to $offset for $file";
826            
827 33628         660467 my $tmp;
828 33628         102739 my $res = $h->read($tmp, $length);
829            
830 33628 50       551897 croak "Couldn't read $length bytes from $file at offset $offset ($!)"
831             if !defined $res;
832            
833 33628 50       65799 croak "EOF reading $length bytes from $file at offset $offset"
834             if $res == 0;
835            
836 33628         82840 return $tmp;
837             }
838            
839            
840             sub type {
841 10333     10333 1 16119 my $self = shift;
842 10333         14116 my $shape = shift;
843            
844             # should make this a package lexical
845 10333         75673 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         46730 return $shape_types{$shape};
863             }
864            
865             sub find_bounds {
866 28     28 1 845 my $self = shift;
867 28         848 my @objects = @_;
868            
869 28 100       139 return if !scalar @objects;
870            
871 26         70 my $obj1 = shift @objects;
872            
873             # assign values from first object to start
874 26         131 my $x_min = $obj1->x_min();
875 26         93 my $y_min = $obj1->y_min();
876 26         96 my $x_max = $obj1->x_max();
877 26         84 my $y_max = $obj1->y_max();
878            
879            
880 26         82 foreach my $obj (@objects) {
881 4600         8553 $x_min = min ($x_min, $obj->x_min());
882 4600         7839 $y_min = min ($y_min, $obj->y_min());
883 4600         8006 $x_max = max ($x_max, $obj->x_max());
884 4600         8145 $y_max = max ($y_max, $obj->y_max());
885             }
886            
887 26         175 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         683 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__