File Coverage

lib/Geo/ShapeFile.pm
Criterion Covered Total %
statement 324 387 83.7
branch 80 128 62.5
condition 12 29 41.3
subroutine 58 74 78.3
pod 44 48 91.6
total 518 666 77.7


line stmt bran cond sub pod time code
1             package Geo::ShapeFile;
2            
3 2     2   145431 use strict;
  2         14  
  2         57  
4 2     2   10 use warnings;
  2         3  
  2         46  
5 2     2   9 use Carp;
  2         4  
  2         89  
6 2     2   992 use IO::File;
  2         17280  
  2         213  
7 2     2   990 use Geo::ShapeFile::Shape;
  2         14  
  2         133  
8 2     2   21 use Config;
  2         5  
  2         91  
9 2     2   14 use List::Util qw /min max/;
  2         4  
  2         129  
10 2     2   13 use Scalar::Util qw/weaken/;
  2         5  
  2         92  
11 2     2   22 use Tree::R;
  2         5  
  2         96  
12            
13 2     2   14 use constant ON_WINDOWS => ($^O eq 'MSWin32');
  2         6  
  2         202  
14 2     2   1471 use if ON_WINDOWS, 'Win32::LongPath';
  2         27  
  2         11  
15            
16             our $VERSION = '3.00';
17            
18             my $little_endian_sys = unpack 'b', (pack 'S', 1 );
19            
20             # Preloaded methods go here.
21             sub new {
22 42     42 1 81983 my $proto = shift;
23 42   33     202 my $filebase = shift || croak "Must specify filename!";
24 42   100     192 my $args = shift || {}; # should check it's a haashref
25            
26 42   33     248 my $class = ref($proto) || $proto;
27 42         115 my $self = {};
28            
29 42         145 $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 42         240 $self->{filebase} =~ s/\.\w{3}$//;
33            
34             $self->{_enable_caching} = {
35 42         233 shp => 1,
36             dbf => 1,
37             shx => 1,
38             shapes_in_area => 1,
39             };
40 42         137 $self->{has_shx} = 0;
41 42         123 $self->{has_shp} = 0;
42 42         83 $self->{has_dbf} = 0;
43            
44 42         93 bless $self, $class;
45            
46             # control overall caching
47 42 100       137 if ($args->{no_cache}) {
48 14         37 $self->{_no_cache} = 1;
49             }
50            
51             # not sure what this does - possible residual from early plans
52             $self->{_change_cache} = {
53 42         253 shape_type => undef,
54             records => undef,
55             shp => {},
56             dbf => {},
57             shx => {},
58             };
59             $self->{_object_cache} = {
60 42         276 shp => {},
61             dbf => {},
62             shx => {},
63             shapes_in_area => {},
64             };
65            
66 42 100       211 if ($self->file_exists ($self->{filebase} . '.shx')) {
67 39         241 $self->_read_shx_header();
68 39         102 $self->{has_shx} = 1;
69             }
70            
71 42 100       226 if ($self->file_exists ($self->{filebase} . '.shp')) {
72 39         228 $self->_read_shp_header();
73 39         147 $self->{has_shp} = 1;
74             }
75            
76 42 100       214 if ($self->file_exists ($self->{filebase} . '.dbf')) {
77 40         223 $self->_read_dbf_header();
78 40         100 $self->{has_dbf} = 1;
79             }
80            
81 42 100       185 if (!$self->{has_dbf}) {
82             croak "$self->{filebase}: shp and/or shx file do not exist or are invalid"
83 2 50 33     249 if !($self->{has_shp} && $self->{has_shx});
84            
85 0         0 croak "$self->{filebase}.dbf does not exist or is invalid";
86             }
87            
88 40         366 return $self;
89             }
90            
91             sub get_file_size {
92 40     40 0 114 my ($self, $file_name) = @_;
93            
94 40         81 my $file_size;
95            
96 40 50       597 if (-e $file_name) {
97 40         439 $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 40         138 return $file_size;
109             }
110            
111             sub file_exists {
112 126     126 0 314 my ($self, $file_name) = @_;
113            
114 126 100       2462 return 1 if -e $file_name;
115            
116 8         20 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 42376 my $self = shift;
137 27430         37979 my $what = shift;
138 27430         39081 my $flag = shift;
139            
140 27430 50       51648 if (defined $flag) {
141 0         0 $self->{_enable_caching}->{$what} = $flag;
142             }
143 27430         66093 return $self->{_enable_caching}->{$what};
144             }
145            
146             sub cache {
147 64438     64438 0 128626 my ($self, $type, $obj, $cache) = @_;
148            
149 64438 100       182008 return if $self->{_no_cache};
150            
151             return $self->{_change_cache}->{$type}->{$obj}
152 27430 50 33     109898 if $self->{_change_cache}->{$type} && $self->{_change_cache}->{$type}->{$obj};
153            
154 27430 50       55380 return if !$self->caching($type);
155            
156 27430 100       52205 if ($cache) {
157 8371         27392 $self->{_object_cache}->{$type}->{$obj} = $cache;
158             }
159 27430         74169 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 22596 my $self = shift;
167            
168 8         22 my @shapes;
169            
170 8         48 foreach my $id (1 .. $self->shapes()) {
171 1427         2558 my $shape = $self->get_shp_record($id);
172 1427         2764 push @shapes, $shape;
173             }
174            
175 8 100       122 return wantarray ? @shapes : \@shapes;
176             }
177            
178             sub get_shapes_sorted {
179 2     2 1 966 my $self = shift;
180 2         5 my $shapes = shift;
181 2         4 my $sub = shift;
182            
183 2 50       11 if (!defined $sub) {
184             $sub = sub {
185 3593     3593   5355 my ($s1, $s2) = @_;
186 3593         6716 return $s1->{shp_record_number} <=> $s2->{shp_record_number};
187 2         14 };
188             }
189            
190 2 100       10 if (!defined $shapes) {
191 1         4 $shapes = $self->get_all_shapes;
192             }
193            
194 2         51 my @sorted = sort {$sub->($a, $b)} @$shapes;
  3593         5010  
195            
196 2 50       168 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 11 my $self = shift;
222            
223 1         6 my $shapes = $self->get_all_shapes;
224            
225 1         12 my $rtree = Tree::R->new();
226 1         28 foreach my $shape (@$shapes) {
227 474         297300 my @bbox = ($shape->x_min, $shape->y_min, $shape->x_max, $shape->y_max);
228 474         1050 $rtree->insert($shape, @bbox);
229             }
230            
231 1         871 $self->{_spatial_index} = $rtree;
232            
233 1         122 return $rtree;
234             }
235            
236             sub get_spatial_index {
237 12     12 1 23 my $self = shift;
238 12         49 return $self->{_spatial_index};
239             }
240            
241            
242             sub _read_shx_header {
243 39     39   192 shift()->_read_shx_shp_header('shx', @_);
244             }
245            
246             sub _read_shp_header {
247 39     39   148 shift()->_read_shx_shp_header('shp', @_);
248             }
249            
250             sub _read_shx_shp_header {
251 78     78   182 my $self = shift;
252 78         150 my $which = shift;
253 78         121 my $doubles;
254            
255 78         247 $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 78         956 ) = 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 78 50       994 $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 78         224 return 1;
273             }
274            
275             sub type_is {
276 0     0 1 0 my $self = shift;
277 0         0 my $type = shift;
278            
279 0         0 return (lc $self->type($self->shape_type)) eq (lc $type);
280             }
281            
282             sub get_dbf_field_names {
283 28     28 1 923 my $self = shift;
284            
285             croak 'dbf field names not loaded yet'
286 28 50       109 if !defined $self->{dbf_field_names};
287            
288             # make sure we return a copy
289 28         85 my @fld_names = @{$self->{dbf_field_names}};
  28         207  
290            
291 28 50       168 return wantarray ? @fld_names : \@fld_names;
292             }
293            
294             sub _read_dbf_header {
295 40     40   123 my $self = shift;
296            
297 40         121 $self->{dbf_header} = $self->_get_bytes('dbf', 0, 12);
298             (
299             $self->{dbf_version},
300             $self->{dbf_updated_year},
301             $self->{dbf_updated_month},
302             $self->{dbf_updated_day},
303             $self->{dbf_num_records},
304             $self->{dbf_header_length},
305             $self->{dbf_record_length},
306 40         405 ) = unpack 'C4 V v v', $self->{dbf_header};
307             # unpack changed from c4 l s s to fix endianess problem
308             # reported by Daniel Gildea
309            
310             my $ls = $self->{dbf_header_length}
311 40         158 + $self->{dbf_num_records} * $self->{dbf_record_length};
312 40         217 my $li = $self->get_file_size($self->{filebase} . '.dbf');
313            
314             # some shapefiles (such as are produced by the NOAA NESDIS) don't
315             # have a end-of-file marker in their dbf files, Aleksandar Jelenak
316             # says the ESRI tools don't have a problem with this, so we shouldn't
317             # either
318 40         192 my $last_byte = $self->_get_bytes('dbf', $li-1, 1);
319 40 50       168 $ls ++ if ord $last_byte == 0x1A;
320            
321 40 50       140 croak "dbf: file wrong size (should be $ls, but found $li)"
322             if $ls != $li;
323            
324 40         151 my $header = $self->_get_bytes('dbf', 32, $self->{dbf_header_length} - 32);
325 40         116 my $count = 0;
326 40         137 $self->{dbf_header_info} = [];
327            
328 40         136 while ($header) {
329 388         787 my $tmp = substr $header, 0, 32, '';
330 388         571 my $chr = substr $tmp, 0, 1;
331            
332 388 100       755 last if ord $chr == 0x0D;
333 352 100       971 last if length ($tmp) < 32;
334            
335 348         519 my %tmp = ();
336             (
337             $tmp{name},
338             $tmp{type},
339             $tmp{size},
340             $tmp{decimals}
341 348         1234 ) = unpack 'Z11 Z x4 C2', $tmp;
342            
343 348         1451 $self->{dbf_field_info}->[$count] = {%tmp};
344            
345 348         996 $count++;
346             }
347            
348 40         135 $self->{dbf_fields} = $count;
349 40 50       114 croak "dbf: Not enough fields ($count < 1)"
350             if $count < 1;
351            
352 40         109 my @template = ();
353 40         69 foreach (@{$self->{dbf_field_info}}) {
  40         122  
354             croak "dbf: Field $_->{name} too short ($_->{size} bytes)"
355 348 50       709 if $_->{size} < 1;
356            
357             croak "dbf: Field $_->{name} too long ($_->{size} bytes)"
358 348 50       619 if $_->{size} > 4000;
359            
360 348         795 push @template, 'A' . $_->{size};
361             }
362 40         216 $self->{dbf_record_template} = join ' ', @template;
363            
364 40         115 my @field_names = ();
365 40         90 foreach (@{$self->{dbf_field_info}}) {
  40         101  
366 348         685 push @field_names, $_->{name};
367             }
368 40         220 $self->{dbf_field_names} = [@field_names];
369            
370             # should return field names?
371 40         156 return 1;
372             }
373            
374             # needed now there is Geo::ShapeFile::Writer?
375             sub _generate_dbf_header {
376 0     0   0 my $self = shift;
377            
378             #$self->{dbf_header} = $self->_get_bytes('dbf',0,12);
379             (
380             $self->{dbf_version},
381             $self->{dbf_updated_year},
382             $self->{dbf_updated_month},
383             $self->{dbf_updated_day},
384             $self->{dbf_num_records},
385             $self->{dbf_header_length},
386             $self->{dbf_record_length},
387 0         0 ) = unpack 'C4 V v v', $self->{dbf_header};
388            
389             $self->{_change_cache}->{dbf_cache}->{header}
390 0         0 = pack
391             'C4 V v v',
392             3,
393             (localtime)[5],
394             (localtime)[4]+1,
395             (localtime)[3],
396             0, # TODO - num_records,
397             0, # TODO - header_length,
398             0, # TODO - record_length,
399             ;
400             }
401            
402             sub get_dbf_field_info {
403 0     0 1 0 my $self = shift;
404            
405 0         0 my $header = $self->{dbf_field_info};
406            
407 0 0       0 return if !$header;
408            
409             # Return a deep copy to avoid callers
410             # messing up the internals
411 0         0 my @hdr;
412 0         0 foreach my $field (@$header) {
413 0         0 my %h = %$field;
414 0         0 push @hdr, \%h;
415             }
416            
417 0 0       0 return wantarray ? @hdr : \@hdr;
418             }
419            
420             sub get_dbf_record {
421 9252     9252 1 4947519 my $self = shift;
422 9252         16096 my $entry = shift;
423            
424 9252         22225 my $dbf = $self->cache('dbf', $entry);
425            
426 9252 100       19947 if (!$dbf) {
427 6939         10327 $entry--; # make entry 0-indexed
428            
429             my $record = $self->_get_bytes(
430             'dbf',
431             $self->{dbf_header_length}+($self->{dbf_record_length} * $entry),
432 6939         23217 $self->{dbf_record_length}+1, # +1 for deleted flag
433             );
434 6939         54592 my ($del, @data) = unpack 'c' . $self->{dbf_record_template}, $record;
435            
436 6939         17896 map { s/^\s*//; s/\s*$//; } @data;
  79611         182852  
  79611         243548  
437            
438 6939         12555 my %record;
439 6939         11701 @record{@{$self->{dbf_field_names}}} = @data;
  6939         65542  
440 6939         17977 $record{_deleted} = (ord $del == 0x2A);
441 6939         70498 $dbf = {%record};
442 6939         25505 $self->cache('dbf', $entry + 1, $dbf);
443             }
444            
445 9252 100       34391 return wantarray ? %{$dbf} : $dbf;
  4626         52013  
446             }
447            
448             # needed? not called anywhere
449             sub _set_dbf_record {
450 0     0   0 my $self = shift;
451 0         0 my $entry = shift;
452 0         0 my %record = @_;
453            
454 0         0 $self->{_change_cache}->{dbf}->{$entry} = {%record};
455             }
456            
457             sub _get_shp_shx_header_value {
458 146     146   284 my $self = shift;
459 146         248 my $val = shift;
460            
461 146 0 33     487 if (!defined($self->{'shx_' . $val}) && !defined($self->{'shp_' . $val})) {
462 0         0 $self->_read_shx_header(); # ensure we load at least one of the headers
463             }
464            
465             return defined($self->{'shx_' . $val})
466             ? $self->{'shx_' . $val}
467 146 50       986 : $self->{'shp_' . $val};
468             }
469            
470             # factory these
471 29     29 1 119 sub x_min { shift()->_get_shp_shx_header_value('x_min'); }
472 29     29 1 146 sub x_max { shift()->_get_shp_shx_header_value('x_max'); }
473 29     29 1 87 sub y_min { shift()->_get_shp_shx_header_value('y_min'); }
474 29     29 1 139 sub y_max { shift()->_get_shp_shx_header_value('y_max'); }
475 0     0 1 0 sub z_min { shift()->_get_shp_shx_header_value('z_min'); }
476 0     0 1 0 sub z_max { shift()->_get_shp_shx_header_value('z_max'); }
477 0     0 1 0 sub m_min { shift()->_get_shp_shx_header_value('m_min'); }
478 0     0 1 0 sub m_max { shift()->_get_shp_shx_header_value('m_max'); }
479            
480             sub upper_left_corner {
481 1     1 1 6 my $self = shift;
482            
483 1         5 return Geo::ShapeFile::Point->new(X => $self->x_min, Y => $self->y_max);
484             }
485            
486             sub upper_right_corner {
487 1     1 1 4 my $self = shift;
488            
489 1         4 return Geo::ShapeFile::Point->new(X => $self->x_max, Y => $self->y_max);
490             }
491            
492             sub lower_right_corner {
493 1     1 1 5 my $self = shift;
494            
495 1         3 return Geo::ShapeFile::Point->new(X => $self->x_max, Y => $self->y_min);
496             }
497            
498             sub lower_left_corner {
499 1     1 1 7 my $self = shift;
500            
501 1         4 return Geo::ShapeFile::Point->new(X => $self->x_min, Y => $self->y_min);
502             }
503            
504             sub height {
505 28     28 1 74462 my $self = shift;
506            
507 28 100       150 return if !$self->records;
508            
509 26         127 return $self->y_max - $self->y_min;
510             }
511            
512             sub width {
513 28     28 1 105 my $self = shift;
514            
515 28 100       101 return if !$self->records;
516            
517 26         125 return $self->x_max - $self->x_min;
518             }
519            
520             sub corners {
521 0     0 1 0 my $self = shift;
522            
523             return (
524 0         0 $self->upper_left_corner,
525             $self->upper_right_corner,
526             $self->lower_right_corner,
527             $self->lower_left_corner,
528             );
529             }
530            
531             sub area_contains_point {
532 59     59 1 115 my $self = shift;
533 59         128 my $point = shift;
534            
535 59         138 my ($x_min, $y_min, $x_max, $y_max) = @_;
536            
537 59         225 my $x = $point->get_x;
538 59         137 my $y = $point->get_y;
539            
540 59   100     501 my $result =
541             ($x >= $x_min) &&
542             ($x <= $x_max) &&
543             ($y >= $y_min) &&
544             ($y <= $y_max);
545            
546 59         257 return $result;
547             }
548            
549             sub bounds_contains_point {
550 59     59 1 139 my $self = shift;
551 59         111 my $point = shift;
552            
553 59         194 return $self->area_contains_point (
554             $point,
555             $self->x_min, $self->y_min,
556             $self->x_max, $self->y_max,
557             );
558             }
559            
560             sub file_version {
561 2     2 1 16 shift()->_get_shp_shx_header_value('version');
562             }
563            
564             sub shape_type {
565 28     28 1 61 my $self = shift;
566            
567             return $self->{_change_cache}->{shape_type}
568 28 50       138 if defined $self->{_change_cache}->{shape_type};
569            
570 28         107 return $self->_get_shp_shx_header_value('shape_type');
571             }
572            
573             sub shapes {
574 214     214 1 25838 my $self = shift;
575            
576             return $self->{_change_cache}->{records}
577 214 50       791 if defined $self->{_change_cache}->{records};
578            
579 214 50       582 if (!$self->{shx_file_length}) {
580 0         0 $self->_read_shx_header();
581             }
582            
583 214         381 my $filelength = $self->{shx_file_length};
584 214         407 $filelength -= 50; # don't count the header
585            
586 214         1073 return $filelength / 4;
587             }
588            
589             sub records {
590 197     197 1 72133 my $self = shift;
591            
592             return $self->{_change_cache}->{records}
593 197 50       763 if defined $self->{_change_cache}->{records};
594            
595 197 100       537 if ($self->{shx_file_length}) {
    50          
596 196         370 my $filelength = $self->{shx_file_length};
597 196         322 $filelength -= 50; # don't count the header
598 196         873 return $filelength / 4;
599             }
600             # should perhaps just return dbf_num_records if we get this far?
601             elsif ($self->{dbf_num_records}) {
602 0         0 return $self->{dbf_num_records};
603             }
604            
605 1         4 return 0;
606             }
607            
608             sub shape_type_text {
609 28     28 1 18356 my $self = shift;
610            
611 28         124 return $self->type($self->shape_type());
612             }
613            
614             sub get_shx_record_header {
615 0     0 1 0 shift()->get_shx_record(@_);
616             }
617            
618             sub get_shx_record {
619 17630     17630 1 4262197 my $self = shift;
620 17630         27024 my $entry = shift;
621            
622 17630 50       35333 croak 'must specify entry index'
623             if !$entry;
624            
625 17630         32914 my $shx = $self->cache('shx', $entry);
626            
627 17630 100       33645 if (!$shx) {
628 12520         35732 my $record = $self->_get_bytes('shx', (($entry - 1) * 8) + 100, 8);
629 12520         46643 $shx = [unpack 'N N', $record];
630 12520         36474 $self->cache('shx', $entry, $shx);
631             }
632            
633 17630         24784 return @{$shx};
  17630         45731  
634             }
635            
636             sub get_shp_record_header {
637 4626     4626 1 15199 my $self = shift;
638 4626         6504 my $entry = shift;
639            
640 4626         9081 my($offset) = $self->get_shx_record($entry);
641            
642 4626         10904 my $record = $self->_get_bytes('shp', $offset * 2, 8);
643 4626         15698 my ($number, $content_length) = unpack 'N N', $record;
644            
645 4626         14881 return ($number, $content_length);
646             }
647            
648            
649             # returns indexes, not objects - need to change that or add method for shape_objects_in_area
650             sub shapes_in_area {
651 12     12 1 74023 my $self = shift;
652 12         31 my @area = @_; # x_min, y_min, x_max, y_max,
653            
654 12 100       43 if (my $sp_index = $self->get_spatial_index) {
655 2         9 my $shapes = [];
656 2         17 $sp_index->query_partly_within_rect (@area, $shapes);
657 2         16818 my @indexes;
658 2         14 foreach my $shape (@$shapes) {
659 604         1273 push @indexes, $shape->shape_id;
660             }
661 2 50       50 return wantarray ? @indexes : \@indexes;
662             }
663            
664 10         26 my @results = ();
665             SHAPE:
666 10         31 foreach my $shp_id (1 .. $self->shapes) {
667 962         2441 my ($offset, $content_length) = $self->get_shx_record($shp_id);
668 962         2292 my $type = unpack 'V', $self->_get_bytes ('shp', $offset * 2 + 8, 4);
669            
670 962 50       2572 next SHAPE if $self->type($type) eq 'Null';
671            
672 962 50       1736 if ($self->type($type) =~ /^Point/) {
    50          
673 0         0 my $bytes = $self->_get_bytes('shp', $offset * 2 + 12, 16);
674 0 0       0 my ($x, $y) = (
675             $little_endian_sys
676             ? (unpack 'dd', $bytes )
677             : (reverse unpack 'dd', scalar reverse $bytes)
678             );
679 0         0 my $pt = Geo::ShapeFile::Point->new(X => $x, Y => $y);
680 0 0       0 if ($self->area_contains_point($pt, @area)) {
681 0         0 push @results, $shp_id;
682             }
683             }
684             elsif ($self->type($type) =~ /^(PolyLine|Polygon|MultiPoint|MultiPatch)/) {
685 962         2245 my $bytes = $self->_get_bytes('shp', ($offset * 2) + 12, 32);
686 962 50       4032 my @p = (
687             $little_endian_sys
688             ? (unpack 'd4', $bytes )
689             : (reverse unpack 'd4', scalar reverse $bytes )
690             );
691 962 100       2463 if ($self->check_in_area(@p, @area)) {
692 615         1872 push @results, $shp_id;
693             }
694             }
695             else {
696 0         0 print 'type=' . $self->type($type) . "\n";
697             }
698             }
699            
700 10 100       51 return wantarray ? @results : \@results;
701             }
702            
703             sub check_in_area {
704 962     962 1 1497 my $self = shift;
705             my (
706 962         2204 $x1_min, $y1_min, $x1_max, $y1_max,
707             $x2_min, $y2_min, $x2_max, $y2_max,
708             ) = @_;
709            
710 962   66     4853 my $result = !(
711             $x1_min > $x2_max
712             or $x1_max < $x2_min
713             or $y1_min > $y2_max
714             or $y1_max < $y2_min
715             );
716            
717 962         2795 return $result;
718             }
719            
720             # SWL: not used anymore - remove?
721             sub _between {
722 0     0   0 my $self = shift;
723 0         0 my $check = shift;
724            
725             # ensure min then max
726 0 0       0 if ($_[0] > $_[1]) {
727 0         0 @_ = reverse @_;
728             }
729            
730 0   0     0 return ($check >= $_[0]) && ($check <= $_[1]);
731             }
732            
733             sub bounds {
734 1     1 1 666 my $self = shift;
735            
736             return (
737 1         6 $self->x_min, $self->y_min,
738             $self->x_max, $self->y_max,
739             );
740             }
741            
742             # is this ever called?
743             sub _extract_ints {
744 0     0   0 my $self = shift;
745 0         0 my $end = shift;
746 0         0 my @what = @_;
747            
748 0 0       0 my $template = ($end =~ /^l/i) ? 'V': 'N';
749            
750 0         0 $self->_extract_and_unpack(4, $template, @what);
751 0         0 foreach (@what) {
752 0         0 $self->{$_} = $self->{$_};
753             }
754             }
755            
756             sub get_shp_record {
757 10681     10681 1 5096355 my $self = shift;
758 10681         17546 my $entry = shift;
759            
760 10681         20832 my $shape = $self->cache('shp', $entry);
761 10681 100       20880 if (!$shape) {
762 7416         16332 my($offset, $content_length) = $self->get_shx_record($entry);
763            
764 7416         19418 my $record = $self->_get_bytes('shp', $offset * 2, $content_length * 2 + 8);
765            
766 7416         31933 $shape = Geo::ShapeFile::Shape->new();
767 7416         20877 $shape->parse_shp($record);
768 7416         20447 $self->cache('shp', $entry, $shape);
769             }
770            
771 10681         26246 return $shape;
772             }
773            
774             sub shx_handle {
775 12559     12559 1 25590 shift()->_get_handle('shx');
776             }
777            
778             sub shp_handle {
779 14005     14005 1 28049 shift()->_get_handle('shp');
780             }
781            
782             sub dbf_handle {
783 7059     7059 1 14968 shift()->_get_handle('dbf');
784             }
785            
786             sub _get_handle {
787 33623     33623   48431 my $self = shift;
788 33623         48344 my $which = shift;
789            
790 33623         51849 my $han = $which . '_handle';
791            
792 33623 100       84399 if (!$self->{$han}) {
793 118         351 my $file = join '.', $self->{filebase}, $which;
794 118 50       1366 if (-e $file) {
795 118         924 $self->{$han} = IO::File->new;
796             croak "Couldn't get file handle for $file: $!"
797 118 50       4811 if not $self->{$han}->open($file, O_RDONLY | O_BINARY);
798             }
799 0         0 elsif (ON_WINDOWS) {
800             my $fh;
801             openL (\$fh, '<', $file)
802             or croak ("unable to open $file ($^E)");
803             #$fh = IO::File->new_from_fd ($fh);
804             $self->{$han} = $fh;
805             }
806 118         7235 binmode $self->{$han}; # fix windows bug reported by Patrick Dughi
807             }
808            
809 33623         64298 return $self->{$han};
810             }
811            
812             sub _get_bytes {
813 33623     33623   53247 my $self = shift;
814 33623         52900 my $file = shift;
815 33623         49096 my $offset = shift;
816 33623         47163 my $length = shift;
817            
818 33623         62983 my $handle = $file . '_handle';
819 33623         82277 my $h = $self->$handle();
820 33623 50       100694 $h->seek ($offset, 0)
821             || croak "Couldn't seek to $offset for $file";
822            
823 33623         590212 my $tmp;
824 33623         102616 my $res = $h->read($tmp, $length);
825            
826 33623 50       524249 croak "Couldn't read $length bytes from $file at offset $offset ($!)"
827             if !defined $res;
828            
829 33623 50       67787 croak "EOF reading $length bytes from $file at offset $offset"
830             if $res == 0;
831            
832 33623         90448 return $tmp;
833             }
834            
835            
836             sub type {
837 10330     10330 1 16875 my $self = shift;
838 10330         15022 my $shape = shift;
839            
840             # should make this a package lexical
841 10330         76258 my %shape_types = qw(
842             0 Null
843             1 Point
844             3 PolyLine
845             5 Polygon
846             8 MultiPoint
847             11 PointZ
848             13 PolyLineZ
849             15 PolygonZ
850             18 MultiPointZ
851             21 PointM
852             23 PolyLineM
853             25 PolygonM
854             28 MultiPointM
855             31 MultiPatch
856             );
857            
858 10330         48096 return $shape_types{$shape};
859             }
860            
861             sub find_bounds {
862 28     28 1 882 my $self = shift;
863 28         666 my @objects = @_;
864            
865 28 100       105 return if !scalar @objects;
866            
867 26         77 my $obj1 = shift @objects;
868            
869             # assign values from first object to start
870 26         122 my $x_min = $obj1->x_min();
871 26         110 my $y_min = $obj1->y_min();
872 26         112 my $x_max = $obj1->x_max();
873 26         122 my $y_max = $obj1->y_max();
874            
875            
876 26         78 foreach my $obj (@objects) {
877 4600         9807 $x_min = min ($x_min, $obj->x_min());
878 4600         8198 $y_min = min ($y_min, $obj->y_min());
879 4600         8305 $x_max = max ($x_max, $obj->x_max());
880 4600         8453 $y_max = max ($y_max, $obj->y_max());
881             }
882            
883 26         173 my %bounds = (
884             x_min => $x_min,
885             y_min => $y_min,
886             x_max => $x_max,
887             y_max => $y_max,
888             );
889            
890 26         496 return %bounds;
891             }
892            
893             # XML::Generator::SVG::ShapeFile fails because it is calling this method
894             # and it does not exist in 2.52 and earlier
895       0     sub DESTROY {}
896            
897            
898             1;
899             __END__