File Coverage

lib/Geo/ShapeFile/Shape.pm
Criterion Covered Total %
statement 263 374 70.3
branch 54 88 61.3
condition 10 21 47.6
subroutine 45 64 70.3
pod 24 29 82.7
total 396 576 68.7


line stmt bran cond sub pod time code
1             package Geo::ShapeFile::Shape;
2 2     2   12 use strict;
  2         5  
  2         60  
3 2     2   9 use warnings;
  2         5  
  2         45  
4 2     2   9 use Carp;
  2         4  
  2         113  
5 2     2   1070 use Tree::R;
  2         11127  
  2         99  
6 2     2   19 use List::Util qw /min max/;
  2         4  
  2         199  
7            
8 2     2   15 use Geo::ShapeFile;
  2         4  
  2         44  
9 2     2   1274 use Geo::ShapeFile::Point;
  2         4  
  2         9  
10 2     2   918 use Geo::ShapeFile::Shape::Index;
  2         5  
  2         70  
11            
12 2     2   998 use parent qw /Geo::ShapeFile/;
  2         651  
  2         15  
13            
14             our $VERSION = '3.01';
15            
16             my $little_endian_sys = unpack 'b', (pack 'S', 1 );
17            
18             my $index_class = 'Geo::ShapeFile::Shape::Index';
19            
20             sub new {
21 7416     7416 1 12490 my $proto = shift;
22 7416   33     29303 my $class = ref ($proto) || $proto;
23 7416         15424 my %args = @_;
24            
25 7416         87872 my $self = {
26             shp_content_length => 0,
27             source => undef,
28             shp_points => [],
29             shp_num_points => 0,
30             shp_parts => [],
31             shp_record_number => undef,
32             shp_shape_type => undef,
33             shp_num_parts => 0,
34             shp_x_min => undef,
35             shp_x_max => undef,
36             shp_y_min => undef,
37             shp_y_max => undef,
38             shp_z_min => undef,
39             shp_z_max => undef,
40             shp_m_min => undef,
41             shp_m_max => undef,
42             shp_data => undef,
43             };
44            
45 7416         23862 foreach (keys %args) {
46 0         0 $self->{$_} = $args{$_};
47             }
48            
49 7416         12531 bless $self, $class;
50            
51 7416         20285 return $self;
52             }
53            
54             sub parse_shp {
55 7416     7416 0 12032 my $self = shift;
56            
57 7416         15427 $self->{source} = $self->{shp_data} = shift;
58            
59 7416         21883 $self->_extract_ints('big', 'shp_record_number', 'shp_content_length');
60 7416         17620 $self->_extract_ints('little', 'shp_shape_type');
61            
62 7416         22785 my $parser = '_parse_shp_' . $self->type($self->{shp_shape_type});
63            
64 7416 50       31026 croak "Can't parse shape_type $self->{shp_shape_type}"
65             if !$self->can($parser);
66            
67 7416         23932 $self->$parser();
68            
69 7416 50       22806 if (length($self->{shp_data})) {
70 0         0 my $len = length($self->{shp_data});
71 0 0       0 my $byte_plural = $len > 1 ? 's' : '';
72 0         0 carp "$len byte$byte_plural remaining in buffer after parsing "
73             . $self->shape_type_text()
74             . ' #'
75             . $self->shape_id();
76             }
77             }
78            
79             sub _parse_shp_Null {
80 0     0   0 my $self = shift;
81             }
82            
83             # TODO - document this
84             sub add_point {
85 0     0 0 0 my $self = shift;
86            
87 0 0       0 if(@_ == 1) {
88 0         0 my $point = shift;
89 0 0       0 if($point->isa('Geo::ShapeFile::Point')) {
90 0         0 push @{$self->{shp_points}}, $point;
  0         0  
91             }
92             }
93             else {
94 0         0 my %point_opts = @_;
95            
96 0         0 push @{$self->{shp_points}}, Geo::ShapeFile::Point->new(%point_opts);
  0         0  
97 0         0 $self->{shp_num_points}++;
98             }
99             }
100            
101             # TODO - document this
102             sub add_part {
103 0     0 0 0 my $self = shift;
104            
105 0         0 push @{$self->{shp_parts}}, $self->{shp_num_parts}++;
  0         0  
106             }
107            
108             # TODO - finish me
109             sub calculate_bounds {
110 0     0 0 0 my $self = shift;
111            
112 0         0 my %bounds = $self->find_bounds($self->points);
113 0         0 foreach (keys %bounds) {
114 0         0 $self->{'shp_' . $_} = $bounds{$_};
115             }
116 0         0 return %bounds;
117             }
118            
119             sub _parse_shp_Point {
120 2553     2553   4322 my $self = shift;
121 2553         7233 $self->_extract_doubles('shp_X', 'shp_Y');
122             $self->{shp_points} = [Geo::ShapeFile::Point->new(
123             X => $self->{shp_X},
124             Y => $self->{shp_Y},
125 2553         9958 )];
126 2553         4624 $self->{shp_num_points} = 1;
127 2553         4767 $self->{shp_x_min} = $self->{shp_X};
128 2553         4165 $self->{shp_x_max} = $self->{shp_X};
129 2553         4443 $self->{shp_y_min} = $self->{shp_Y};
130 2553         4161 $self->{shp_y_max} = $self->{shp_Y};
131             }
132             # Point
133             # Double X // X coordinate
134             # Double Y // Y coordinate
135            
136             sub _parse_shp_PolyLine {
137 2253     2253   4007 my $self = shift;
138            
139 2253         5996 $self->_extract_bounds();
140 2253         5724 $self->_extract_parts_and_points();
141             }
142             # PolyLine
143             # Double[4] Box // Bounding Box
144             # Integer NumParts // Number of parts
145             # Integer NumPoints // Number of points
146             # Integer[NumParts] Parts // Index to first point in part
147             # Point[NumPoints] Points // Points for all parts
148            
149             sub _parse_shp_Polygon {
150 2607     2607   3924 my $self = shift;
151            
152 2607         6622 $self->_extract_bounds();
153 2607         6222 $self->_extract_parts_and_points();
154             }
155             # Polygon
156             # Double[4] Box // Bounding Box
157             # Integer NumParts // Number of Parts
158             # Integer NumPoints // Total Number of Points
159             # Integer[NumParts] Parts // Index to First Point in Part
160             # Point[NumPoints] Points // Points for All Parts
161            
162             sub _parse_shp_MultiPoint {
163 3     3   7 my $self = shift;
164            
165 3         11 $self->_extract_bounds();
166 3         10 $self->_extract_ints('little', 'shp_num_points');
167 3         12 $self->_extract_points($self->{shp_num_points}, 'shp_points');
168             }
169             # MultiPoint
170             # Double[4] Box // Bounding Box
171             # Integer NumPoints // Number of Points
172             # Point[NumPoints] Points // The points in the set
173            
174             sub _parse_shp_PointZ {
175 2445     2445   4098 my $self = shift;
176            
177 2445         6709 $self->_parse_shp_Point();
178 2445         5998 $self->_extract_doubles('shp_Z', 'shp_M');
179 2445         9246 $self->{shp_points}->[0]->Z($self->{shp_Z});
180 2445         6480 $self->{shp_points}->[0]->M($self->{shp_M});
181             }
182             # PointZ
183             # Point +
184             # Double Z
185             # Double M
186            
187             sub _parse_shp_PolyLineZ {
188 366     366   683 my $self = shift;
189            
190 366         906 $self->_parse_shp_PolyLine();
191 366         986 $self->_extract_z_data();
192 366         760 $self->_extract_m_data();
193             }
194             # PolyLineZ
195             # PolyLine +
196             # Double[2] Z Range
197             # Double[NumPoints] Z Array
198             # Double[2] M Range
199             # Double[NumPoints] M Array
200            
201             sub _parse_shp_PolygonZ {
202 0     0   0 my $self = shift;
203            
204 0         0 $self->_parse_shp_Polygon();
205 0         0 $self->_extract_z_data();
206 0         0 $self->_extract_m_data();
207             }
208             # PolygonZ
209             # Polygon +
210             # Double[2] Z Range
211             # Double[NumPoints] Z Array
212             # Double[2] M Range
213             # Double[NumPoints] M Array
214            
215             sub _parse_shp_MultiPointZ {
216 0     0   0 my $self = shift;
217            
218 0         0 $self->_parse_shp_MultiPoint();
219 0         0 $self->_extract_z_data();
220 0         0 $self->_extract_m_data();
221             }
222             # MultiPointZ
223             # MultiPoint +
224             # Double[2] Z Range
225             # Double[NumPoints] Z Array
226             # Double[2] M Range
227             # Double[NumPoints] M Array
228            
229             sub _parse_shp_PointM {
230 0     0   0 my $self = shift;
231            
232 0         0 $self->_parse_shp_Point();
233 0         0 $self->_extract_doubles('shp_M');
234 0         0 $self->{shp_points}->[0]->M($self->{shp_M});
235             }
236             # PointM
237             # Point +
238             # Double M // M coordinate
239            
240             sub _parse_shp_PolyLineM {
241 0     0   0 my $self = shift;
242            
243 0         0 $self->_parse_shp_PolyLine();
244 0         0 $self->_extract_m_data();
245             }
246             # PolyLineM
247             # PolyLine +
248             # Double[2] MRange // Bounding measure range
249             # Double[NumPoints] MArray // Measures for all points
250            
251             sub _parse_shp_PolygonM {
252 0     0   0 my $self = shift;
253            
254 0         0 $self->_parse_shp_Polygon();
255 0         0 $self->_extract_m_data();
256             }
257             # PolygonM
258             # Polygon +
259             # Double[2] MRange // Bounding Measure Range
260             # Double[NumPoints] MArray // Measures for all points
261            
262             sub _parse_shp_MultiPointM {
263 0     0   0 my $self = shift;
264            
265 0         0 $self->_parse_shp_MultiPoint();
266 0         0 $self->_extract_m_datextract_m_data();
267             }
268             # MultiPointM
269             # MultiPoint
270             # Double[2] MRange // Bounding measure range
271             # Double[NumPoints] MArray // Measures
272            
273             sub _parse_shp_MultiPatch {
274 0     0   0 my $self = shift;
275            
276 0         0 $self->_extract_bounds();
277 0         0 $self->_extract_parts_and_points();
278 0         0 $self->_extract_z_data();
279 0         0 $self->_extract_m_data();
280             }
281             # MultiPatch
282             # Double[4] BoundingBox
283             # Integer NumParts
284             # Integer NumPoints
285             # Integer[NumParts] Parts
286             # Integer[NumParts] PartTypes
287             # Point[NumPoints] Points
288             # Double[2] Z Range
289             # Double[NumPoints] Z Array
290             # Double[2] M Range
291             # Double[NumPoints] M Array
292            
293             sub _extract_bounds {
294 4863     4863   7449 my $self = shift;
295            
296 4863         11651 $self->_extract_doubles(qw/shp_x_min shp_y_min shp_x_max shp_y_max/);
297             }
298            
299             sub _extract_ints {
300 19695     19695   26662 my $self = shift;
301 19695         26414 my $end = shift;
302 19695         38003 my @what = @_;
303            
304 19695 100       65817 my $template = ($end =~ /^l/i) ? 'V' :'N';
305            
306 19695         43424 $self->_extract_and_unpack(4, $template, @what);
307             }
308            
309             sub _extract_count_ints {
310 4860     4860   6862 my $self = shift;
311 4860         6664 my $count = shift;
312 4860         7297 my $end = shift;
313 4860         6719 my $label = shift;
314            
315 4860 50       14521 my $template = ($end =~ /^l/i) ? 'V' :'N';
316            
317 4860         11760 my $tmp = substr $self->{shp_data}, 0, ($count * 4), '';
318 4860         15739 my @tmp = unpack $template . $count, $tmp;
319             #my @tmp = unpack($template."[$count]",$tmp);
320            
321 4860         15337 $self->{$label} = [@tmp];
322             }
323            
324             sub _extract_doubles {
325 10593     10593   14744 my $self = shift;
326 10593         21835 my @what = @_;
327 10593         14403 my $size = 8;
328 10593         15104 my $template = 'd';
329            
330 10593         17687 foreach ( @what ) {
331 30912         56405 my $tmp = substr $self->{shp_data}, 0, $size, '';
332 30912 50       82591 $self->{ $_ } = $little_endian_sys
333             ? (unpack $template, $tmp )
334             : (unpack $template, scalar reverse $tmp );
335             }
336             }
337            
338             sub _extract_count_doubles {
339 732     732   1003 my $self = shift;
340 732         979 my $count = shift;
341 732         1002 my $label = shift;
342            
343 732         1440 my $tmp = substr $self->{shp_data}, 0, $count*8, '';
344 732 50       2141 my @tmp = $little_endian_sys
345             ? (unpack 'd'.$count, $tmp )
346             : (reverse unpack( 'd' . $count, scalar ( reverse( $tmp ) ) ) );
347            
348 732         2556 $self->{$label} = [@tmp];
349             }
350            
351             sub _extract_points {
352 4863     4863   7576 my $self = shift;
353 4863         8110 my $count = shift;
354 4863         6698 my $label = shift;
355            
356 4863         12144 my $data = substr $self->{shp_data}, 0, $count * 16, '';
357            
358 4863 50       28537 my @ps = $little_endian_sys
359             ? (unpack 'd*', $data )
360             : (reverse unpack 'd*', scalar reverse $data );
361            
362 4863         8805 my @p = (); # points
363 4863         12217 while(@ps) {
364 80764         135778 my ($x, $y) = (shift @ps, shift @ps);
365 80764         171126 push @p, Geo::ShapeFile::Point->new(X => $x, Y => $y);
366             }
367 4863         27433 $self->{$label} = [@p];
368             }
369            
370             sub _extract_and_unpack {
371 19695     19695   27034 my $self = shift;
372 19695         25489 my $size = shift;
373 19695         26924 my $template = shift;
374 19695         34747 my @what = @_;
375            
376 19695         32545 foreach(@what) {
377 31971         69034 my $tmp = substr $self->{shp_data}, 0, $size, '';
378 31971 50       61757 if ( $template eq 'd' ) {
379 0         0 $tmp = Geo::ShapeFile->byteswap( $tmp );
380             }
381 31971         89905 $self->{$_} = unpack $template, $tmp;
382             }
383             }
384            
385 4644     4644 1 19009 sub num_parts { shift()->{shp_num_parts}; }
386             sub parts {
387 4751     4751 0 14618 my $self = shift;
388            
389 4751         8141 my $parts = $self->{shp_parts};
390            
391 4751 50       9725 return wantarray ? @{$parts || []} : $parts;
  4666 100       15133  
392             }
393            
394 4626     4626 1 2664768 sub num_points { shift()->{shp_num_points}; }
395             sub points {
396 4711     4711 1 16801 my $self = shift;
397            
398 4711         8360 my $points = $self->{shp_points};
399            
400 4711 50       10762 return wantarray ? @{$points || []} : $points;
  4626 100       20759  
401             }
402            
403             sub get_part {
404 85     85 1 155 my $self = shift;
405 85         141 my $index = shift;
406            
407 85 50       204 croak 'index passed to get_part must be >0'
408             if $index <= 0;
409            
410 85         154 $index -= 1; # shift to a 0 index
411            
412             # $parts is an array of starting indexes in the $points array
413 85         188 my $parts = $self->parts;
414 85 50       222 croak 'index exceeds number of parts'
415             if $index > $#$parts;
416            
417 85         219 my $points = $self->points;
418 85   100     336 my $beg = $parts->[$index] || 0;
419 85   100     295 my $end = $parts->[$index+1] || 0; # if we use 5.010 then we can use the // operator here
420 85         134 $end -= 1;
421 85 100       209 if ($end < 0) {
422 27         135 $end = $#$points;
423             }
424            
425 85 50       4353 return wantarray ? @$points[$beg .. $end] : [@$points[$beg .. $end]];
426             }
427            
428             sub shape_type {
429 4626     4626 1 17556 my $self = shift;
430 4626         10676 return $self->{shp_shape_type};
431             }
432            
433             sub shape_id {
434 604     604 1 732 my $self = shift;
435 604         1872 return $self->{shp_record_number};
436             }
437            
438             sub _extract_z_data {
439 366     366   588 my $self = shift;
440            
441 366         939 $self->_extract_doubles('shp_z_min', 'shp_z_max');
442 366         1023 $self->_extract_count_doubles($self->{shp_num_points}, 'shp_z_data');
443 366         608 my @zdata = @{delete $self->{shp_z_data}};
  366         1250  
444 366         1276 for (0 .. $#zdata) {
445 5208         11436 $self->{shp_points}->[$_]->Z($zdata[$_]);
446             }
447             }
448            
449             sub _extract_m_data {
450 366     366   556 my $self = shift;
451            
452 366         866 $self->_extract_doubles ('shp_m_min', 'shp_m_max');
453 366         955 $self->_extract_count_doubles($self->{shp_num_points}, 'shp_m_data');
454 366         539 my @mdata = @{delete $self->{shp_m_data}};
  366         865  
455 366         946 for (0 .. $#mdata) {
456 0         0 $self->{shp_points}->[$_]->M($mdata[$_]);
457             }
458             }
459            
460             sub _extract_parts_and_points {
461 4860     4860   8249 my $self = shift;
462            
463 4860         11056 $self->_extract_ints('little', 'shp_num_parts', 'shp_num_points');
464 4860         13751 $self->_extract_count_ints($self->{shp_num_parts}, 'little', 'shp_parts');
465 4860         12207 $self->_extract_points($self->{shp_num_points}, 'shp_points');
466             }
467            
468            
469             # these could be factory generated
470 5164     5164 1 13148 sub x_min { shift()->{shp_x_min}; }
471 5164     5164 1 11077 sub x_max { shift()->{shp_x_max}; }
472 5164     5164 1 11901 sub y_min { shift()->{shp_y_min}; }
473 5164     5164 1 12692 sub y_max { shift()->{shp_y_max}; }
474 0     0 1 0 sub z_min { shift()->{shp_z_min}; }
475 0     0 1 0 sub z_max { shift()->{shp_z_max}; }
476 0     0 1 0 sub m_min { shift()->{shp_m_min}; }
477 0     0 1 0 sub m_max { shift()->{shp_m_max}; }
478            
479             sub bounds {
480 5     5 1 15 my $self = shift;
481            
482 5         28 my @results = (
483             $self->x_min,
484             $self->y_min,
485             $self->x_max,
486             $self->y_max,
487             );
488            
489 5 50       34 return wantarray ? @results : \@results;
490             }
491            
492             sub has_point {
493 0     0 1 0 my $self = shift;
494 0         0 my $point = shift;
495            
496 0 0       0 return 0 if !$self->bounds_contains_point($point);
497            
498 0         0 foreach my $check_pt ($self->points) {
499 0 0       0 return 1 if $check_pt == $point;
500             }
501            
502 0         0 return 0;
503             }
504            
505             sub contains_point {
506 59     59 1 390 my ( $self, $point, $index_res ) = @_;
507            
508 59 100 66     233 return $self->_contains_point_use_index ($point, $index_res)
509             if $self->get_spatial_index || defined $index_res;
510            
511 22 100       143 return 0 if !$self->bounds_contains_point( $point );
512            
513 18         56 my $a = 0;
514 18         57 my ( $x0, $y0 ) = ( $point->get_x, $point->get_y );
515            
516             # one day we will track the bounds of the parts
517             # so we can more easily skip parts of multipart polygons
518 18         65 my $num_parts = $self->num_parts;
519            
520             # $x1, $x2, $y1 and $y2 are offsets from the point we are checking
521 18         69 foreach my $part_num (1 .. $num_parts) {
522 66         154 my $points = $self->get_part( $part_num );
523            
524 66         136 my $p_start = shift @$points; # $points is a copy, so no harm in shifting
525 66         183 my $x1 = $p_start->get_x - $x0;
526 66         191 my $y1 = $p_start->get_y - $y0;
527            
528 66         129 foreach my $p2 ( @$points ) {
529 1986         3640 my $x2 = $p2->get_x - $x0;
530 1986         3308 my $y2 = $p2->get_y - $y0;
531            
532             # does the ray intersect the segment?
533 1986 100       3646 if (($y2 >= 0) != ($y1 >= 0)) { # $y0 is between $y1 and $y2
534 52         107 my $isl = $x1 * $y2 - $y1 * $x2; # is left of $p2
535 52 100       119 if ( $y2 > $y1 ) {
536 26 100       88 if ($isl > 0) {
537 9         23 $a--;
538             }
539             }
540             else {
541 26 100       87 if ($isl < 0) {
542 18         33 $a++;
543             }
544             }
545             }
546 1986         3245 ( $x1, $y1 ) = ( $x2, $y2 );
547             }
548             }
549            
550 18         69 return $a;
551             }
552            
553             sub _contains_point_use_index {
554 37     37   107 my ( $self, $point, $index_res ) = @_;
555            
556 37 100       210 return 0 if !$self->bounds_contains_point( $point );
557            
558 33   33     113 my $sp_index_hash = $self->get_spatial_index || $self->build_spatial_index ($index_res);
559            
560 33         74 my $a = 0;
561 33         89 my ( $x0, $y0 ) = ( $point->get_x, $point->get_y );
562            
563 33         142 my @parts = $self->parts;
564 33         82 my $num_parts = scalar @parts;
565            
566             # $x1, $x2, $y1 and $y2 are offsets from the point we are checking
567             PART:
568 33         125 foreach my $part_index (1 .. $num_parts) {
569 81         204 my $sp_index = $sp_index_hash->{$part_index};
570            
571 81         283 my @results = $sp_index->query_point($x0, $y0);
572            
573             # skip if not in this part's bounding box
574 81 100       606 next PART if !scalar @results;
575            
576             # segments spanning the index's bounding box
577 39         120 for my $segment (@results) {
578            
579             # index stores bare x and y coords to avoid method overhead here
580 22962         44002 my $x1 = $segment->[0][0] - $x0;
581 22962         31957 my $y1 = $segment->[0][1] - $y0;
582 22962         38015 my $x2 = $segment->[1][0] - $x0;
583 22962         32849 my $y2 = $segment->[1][1] - $y0;
584            
585             # does the ray intersect the segment?
586 22962 100       44517 if (($y2 >= 0) != ($y1 >= 0)) {
587 82         540 my $isl = $x1 * $y2 - $y1 * $x2;
588 82 100       246 if ( $y2 > $y1 ) {
589 41 100       150 if ($isl > 0) {
590 9         22 $a--;
591             }
592             }
593             else {
594 41 100       162 if ($isl < 0) {
595 33         59 $a++;
596             }
597             }
598             }
599             }
600             }
601            
602 33         207 return $a;
603             }
604            
605            
606             # We could trigger a build if undefined,
607             # but save that for later.
608             sub get_spatial_index {
609 92     92 1 194 my $self = shift;
610            
611 92         543 return $self->{_spatial_indexes};
612             }
613            
614             # Add the polygon's segments to a spatial index
615             # where the index boxes are as wide as the part
616             # they are in.
617             # The set of spatial indexes is a hash keyed by
618             # the part ID.
619             # $n is the number of boxes - need an automatic way of calculating, poss f(y_range / x_range)
620             sub build_spatial_index {
621 7     7 1 4063 my $self = shift;
622 7   100     43 my $n = shift || 10;
623            
624 7         20 $n = int $n;
625            
626 7 50       34 croak 'Cannot build spatial index with <1 boxes'
627             if $n < 1;
628            
629 7         22 my %sp_indexes;
630            
631 7         37 my @parts = $self->parts;
632            
633 7         22 my ($x_min, $x_max, $y_min, $y_max);
634            
635 7         50 my $part_id = 0;
636 7         30 foreach my $part (@parts) {
637 12         39 $part_id ++; # parts are indexed base 1
638            
639 12         52 my $segments = $self->get_segments ($part_id);
640            
641 12 100       53 if (@parts > 1) {
642 7         23 my %bounds = $self->_get_part_bounds ($part_id);
643 7         32 ($x_min, $y_min, $x_max, $y_max) = @bounds{qw /x_min y_min x_max y_max/};
644             }
645             else {
646 5         38 ($x_min, $y_min, $x_max, $y_max) = $self->bounds; # faster than searching all points
647             }
648            
649 12 100       53 my $n_boxes = @$segments > 20 ? $n : 1;
650 12         125 my $sp_index = $index_class->new ($n_boxes, $x_min, $y_min, $x_max, $y_max);
651            
652 12         37 foreach my $segment (@$segments) {
653 7289         16045 my $p1 = $segment->[0];
654 7289         13244 my $p2 = $segment->[1];
655            
656             # bare metal version
657 7289         16478 my $coords = [
658             [$p1->get_x, $p1->get_y],
659             [$p2->get_x, $p2->get_y],
660             ];
661            
662 7289         15287 my @bbox = ($x_min, $y_min, $x_max, $y_max);
663 7289         15075 $sp_index->insert($coords, @bbox);
664             }
665            
666 12         7013 $sp_indexes{$part_id} = $sp_index;
667             }
668            
669 7         16767 $self->{_spatial_indexes} = \%sp_indexes;
670            
671 7 100       116 return wantarray ? %sp_indexes : \%sp_indexes;
672             }
673            
674             sub _get_part_bounds {
675 7     7   16 my $self = shift;
676 7         16 my $part = shift;
677            
678 7         19 my $points = $self->get_part($part);
679            
680 7         16 my $pt1 = shift @$points;
681 7         31 my ($x_min, $y_min) = ($pt1->get_x, $pt1->get_y);
682 7         18 my ($x_max, $y_max) = ($x_min, $y_min);
683            
684 7         17 foreach my $pt (@$points) {
685 229         435 my $x = $pt->get_x;
686 229         386 my $y = $pt->get_y;
687            
688 229         347 $x_min = min ($x_min, $x);
689 229         331 $y_min = min ($y_min, $y);
690 229         318 $x_max = max ($x_max, $x);
691 229         430 $y_max = max ($y_max, $y);
692             }
693            
694 7         43 my %bounds = (
695             x_min => $x_min,
696             x_max => $x_max,
697             y_min => $y_min,
698             y_max => $y_max,
699             );
700            
701 7 50       80 return wantarray ? %bounds : \%bounds;
702             }
703            
704             sub get_segments {
705 12     12 1 34 my $self = shift;
706 12         33 my $part = shift;
707            
708 12         90 my $points = $self->get_part($part);
709            
710 12         29 my @segments;
711 12         57 foreach my $i (0 .. $#$points - 1) {
712 7289         18256 push @segments, [$points->[$i], $points->[$i+1]];
713             }
714            
715 12 50       487 return wantarray ? @segments : \@segments;
716             }
717            
718             sub vertex_centroid {
719 0     0 1   my $self = shift;
720 0           my $part = shift;
721            
722 0           my ($cx, $cy) = (0, 0);
723            
724 0           my @points = ();
725 0 0         if ($part) {
726 0           @points = $self->get_part($part);
727             }
728             else {
729 0           @points = $self->points;
730             }
731            
732 0           foreach (@points) {
733 0           $cx += $_->X;
734 0           $cy += $_->Y;
735             }
736            
737             Geo::ShapeFile::Point->new(
738 0           X => $cx / @points,
739             Y => $cy / @points,
740             );
741             }
742             *centroid = \&vertex_centroid;
743            
744             sub area_centroid {
745 0     0 1   my ( $self, $part ) = @_;
746            
747 0           my ( $cx, $cy ) = ( 0, 0 );
748 0           my $A = 0;
749            
750 0           my (@points, @parts);
751            
752 0 0         if ( defined $part ) {
753 0           @parts = ( $part );
754             }
755             else {
756 0           @parts = (1 .. $self->num_parts);
757             }
758            
759 0           for my $part ( @parts ) {
760 0           my ( $p0, @pts ) = $self->get_part( $part );
761 0           my ( $x0, $y0 ) = ( $p0->X, $p0->Y );
762 0           my ( $x1, $y1 ) = ( 0, 0 );
763 0           my ( $cxp, $cyp ) = ( 0, 0 );
764 0           my $Ap = 0;
765            
766 0           for ( @pts ) {
767 0           my $x2 = $_->X - $x0;
768 0           my $y2 = $_->Y - $y0;
769 0           $Ap += ( my $a = $x2*$y1 - $x1*$y2 );
770 0           $cxp += $a * ( $x2 + $x1 ) / 3;
771 0           $cyp += $a * ( $y2 + $y1 ) / 3;
772 0           ( $x1, $y1 ) = ( $x2, $y2 );
773             }
774            
775 0           $cx += $Ap * $x0 + $cxp;
776 0           $cy += $Ap * $y0 + $cyp;
777 0           $A += $Ap;
778             }
779            
780 0           return Geo::ShapeFile::Point->new(
781             X => $cx / $A,
782             Y => $cy / $A,
783             );
784             }
785            
786             sub dump {
787 0     0 1   my $self = shift;
788            
789 0           my $return = '';
790            
791             #$self->points();
792             #$self->get_part();
793             #$self->x_min, x_max, y_min, y_max, z_min, z_max, m_min, m_max
794            
795 0           $return .= sprintf
796             "Shape Type: %s (id: %d) Parts: %d Points: %d\n",
797             $self->shape_type_text(),
798             $self->shape_id(),
799             $self->num_parts(),
800             $self->num_points();
801            
802 0           $return .= sprintf
803             "\tX bounds(min=%s, max=%s)\n",
804             $self->x_min(),
805             $self->x_max();
806            
807 0           $return .= sprintf
808             "\tY bounds(min=%s, max=%s)\n",
809             $self->y_min(),
810             $self->y_max();
811            
812 0 0 0       if (defined $self->z_min() && defined $self->z_max()) {
813 0           $return .= sprintf
814             "\tZ bounds(min=%s, max=%s)\n",
815             $self->z_min(),
816             $self->z_max();
817             }
818            
819 0 0 0       if (defined $self->m_min() && defined $self->m_max()) {
820 0           $return .= sprintf
821             "\tM bounds(min=%s, max=%s)\n",
822             $self->m_min(),
823             $self->m_max();
824             }
825            
826 0           for (1 .. $self->num_parts()) {
827 0           $return .= "\tPart $_:\n";
828 0           foreach ($self->get_part($_)) {
829 0           $return .= "\t\t$_\n";
830             }
831             }
832            
833 0           $return .= "\n";
834            
835 0           return $return;
836             }
837            
838             1;
839             __END__