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   14 use strict;
  2         4  
  2         57  
3 2     2   10 use warnings;
  2         4  
  2         48  
4 2     2   10 use Carp;
  2         4  
  2         100  
5 2     2   987 use Tree::R;
  2         11099  
  2         101  
6 2     2   20 use List::Util qw /min max/;
  2         4  
  2         229  
7              
8 2     2   14 use Geo::ShapeFile;
  2         4  
  2         45  
9 2     2   918 use Geo::ShapeFile::Point;
  2         7  
  2         10  
10 2     2   958 use Geo::ShapeFile::Shape::Index;
  2         6  
  2         75  
11              
12 2     2   912 use parent qw /Geo::ShapeFile/;
  2         600  
  2         11  
13              
14             our $VERSION = '3.03';
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 11910 my $proto = shift;
22 7416   33     24646 my $class = ref ($proto) || $proto;
23 7416         14972 my %args = @_;
24              
25 7416         89851 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         23483 foreach (keys %args) {
46 0         0 $self->{$_} = $args{$_};
47             }
48              
49 7416         12769 bless $self, $class;
50              
51 7416         18777 return $self;
52             }
53              
54             sub parse_shp {
55 7416     7416 0 10799 my $self = shift;
56              
57 7416         14252 $self->{source} = $self->{shp_data} = shift;
58              
59 7416         18201 $self->_extract_ints('big', 'shp_record_number', 'shp_content_length');
60 7416         17401 $self->_extract_ints('little', 'shp_shape_type');
61              
62 7416         19959 my $parser = '_parse_shp_' . $self->type($self->{shp_shape_type});
63              
64 7416 50       29658 croak "Can't parse shape_type $self->{shp_shape_type}"
65             if !$self->can($parser);
66              
67 7416         20189 $self->$parser();
68              
69 7416 50       23029 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   3817 my $self = shift;
121 2553         6496 $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         9582 )];
126 2553         4401 $self->{shp_num_points} = 1;
127 2553         4660 $self->{shp_x_min} = $self->{shp_X};
128 2553         4816 $self->{shp_x_max} = $self->{shp_X};
129 2553         4157 $self->{shp_y_min} = $self->{shp_Y};
130 2553         4122 $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   3604 my $self = shift;
138              
139 2253         5337 $self->_extract_bounds();
140 2253         5169 $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   3757 my $self = shift;
151              
152 2607         5782 $self->_extract_bounds();
153 2607         5771 $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   10 my $self = shift;
164              
165 3         17 $self->_extract_bounds();
166 3         14 $self->_extract_ints('little', 'shp_num_points');
167 3         34 $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   3905 my $self = shift;
176              
177 2445         5752 $self->_parse_shp_Point();
178 2445         5697 $self->_extract_doubles('shp_Z', 'shp_M');
179 2445         8686 $self->{shp_points}->[0]->Z($self->{shp_Z});
180 2445         6893 $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   585 my $self = shift;
189              
190 366         839 $self->_parse_shp_PolyLine();
191 366         1030 $self->_extract_z_data();
192 366         791 $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_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   6580 my $self = shift;
295              
296 4863         10981 $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   26719 my $self = shift;
301 19695         24851 my $end = shift;
302 19695         36552 my @what = @_;
303              
304 19695 100       61372 my $template = ($end =~ /^l/i) ? 'V' :'N';
305              
306 19695         39733 $self->_extract_and_unpack(4, $template, @what);
307             }
308              
309             sub _extract_count_ints {
310 4860     4860   6734 my $self = shift;
311 4860         6383 my $count = shift;
312 4860         6308 my $end = shift;
313 4860         6891 my $label = shift;
314              
315 4860 50       12500 my $template = ($end =~ /^l/i) ? 'V' :'N';
316              
317 4860         11176 my $tmp = substr $self->{shp_data}, 0, ($count * 4), '';
318 4860         12139 my @tmp = unpack $template . $count, $tmp;
319             #my @tmp = unpack($template."[$count]",$tmp);
320              
321 4860         15504 $self->{$label} = [@tmp];
322             }
323              
324             sub _extract_doubles {
325 10593     10593   14172 my $self = shift;
326 10593         20692 my @what = @_;
327 10593         14280 my $size = 8;
328 10593         13691 my $template = 'd';
329              
330 10593         19408 foreach ( @what ) {
331 30912         53381 my $tmp = substr $self->{shp_data}, 0, $size, '';
332 30912 50       78404 $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   1006 my $self = shift;
340 732         1011 my $count = shift;
341 732         1060 my $label = shift;
342              
343 732         1539 my $tmp = substr $self->{shp_data}, 0, $count*8, '';
344 732 50       2136 my @tmp = $little_endian_sys
345             ? (unpack 'd'.$count, $tmp )
346             : (reverse unpack( 'd' . $count, scalar ( reverse( $tmp ) ) ) );
347              
348 732         3096 $self->{$label} = [@tmp];
349             }
350              
351             sub _extract_points {
352 4863     4863   7010 my $self = shift;
353 4863         6351 my $count = shift;
354 4863         7021 my $label = shift;
355              
356 4863         11491 my $data = substr $self->{shp_data}, 0, $count * 16, '';
357              
358 4863 50       29600 my @ps = $little_endian_sys
359             ? (unpack 'd*', $data )
360             : (reverse unpack 'd*', scalar reverse $data );
361              
362 4863         8610 my @p = (); # points
363 4863         11131 while(@ps) {
364 80764         138151 my ($x, $y) = (shift @ps, shift @ps);
365 80764         164158 push @p, Geo::ShapeFile::Point->new(X => $x, Y => $y);
366             }
367 4863         33732 $self->{$label} = [@p];
368             }
369              
370             sub _extract_and_unpack {
371 19695     19695   25333 my $self = shift;
372 19695         24298 my $size = shift;
373 19695         23669 my $template = shift;
374 19695         33388 my @what = @_;
375              
376 19695         32993 foreach(@what) {
377 31971         73917 my $tmp = substr $self->{shp_data}, 0, $size, '';
378 31971 50       58656 if ( $template eq 'd' ) {
379 0         0 $tmp = Geo::ShapeFile->byteswap( $tmp );
380             }
381 31971         88979 $self->{$_} = unpack $template, $tmp;
382             }
383             }
384              
385 4644     4644 1 17317 sub num_parts { shift()->{shp_num_parts}; }
386             sub parts {
387 4751     4751 0 12893 my $self = shift;
388              
389 4751         7573 my $parts = $self->{shp_parts};
390              
391 4751 50       8827 return wantarray ? @{$parts || []} : $parts;
  4666 100       14228  
392             }
393              
394 4626     4626 1 2664213 sub num_points { shift()->{shp_num_points}; }
395             sub points {
396 4711     4711 1 14063 my $self = shift;
397              
398 4711         7645 my $points = $self->{shp_points};
399              
400 4711 50       9769 return wantarray ? @{$points || []} : $points;
  4626 100       19552  
401             }
402              
403             sub get_part {
404 85     85 1 123 my $self = shift;
405 85         129 my $index = shift;
406              
407 85 50       175 croak 'index passed to get_part must be >0'
408             if $index <= 0;
409              
410 85         123 $index -= 1; # shift to a 0 index
411              
412             # $parts is an array of starting indexes in the $points array
413 85         150 my $parts = $self->parts;
414 85 50       205 croak 'index exceeds number of parts'
415             if $index > $#$parts;
416              
417 85         208 my $points = $self->points;
418 85   100     251 my $beg = $parts->[$index] || 0;
419 85   100     229 my $end = $parts->[$index+1] || 0; # if we use 5.010 then we can use the // operator here
420 85         120 $end -= 1;
421 85 100       174 if ($end < 0) {
422 27         55 $end = $#$points;
423             }
424              
425 85 50       2389 return wantarray ? @$points[$beg .. $end] : [@$points[$beg .. $end]];
426             }
427              
428             sub shape_type {
429 4626     4626 1 15179 my $self = shift;
430 4626         9441 return $self->{shp_shape_type};
431             }
432              
433             sub shape_id {
434 604     604 1 753 my $self = shift;
435 604         1857 return $self->{shp_record_number};
436             }
437              
438             sub _extract_z_data {
439 366     366   522 my $self = shift;
440              
441 366         875 $self->_extract_doubles('shp_z_min', 'shp_z_max');
442 366         950 $self->_extract_count_doubles($self->{shp_num_points}, 'shp_z_data');
443 366         574 my @zdata = @{delete $self->{shp_z_data}};
  366         1287  
444 366         1220 for (0 .. $#zdata) {
445 5208         11196 $self->{shp_points}->[$_]->Z($zdata[$_]);
446             }
447             }
448              
449             sub _extract_m_data {
450 366     366   539 my $self = shift;
451              
452 366         823 $self->_extract_doubles ('shp_m_min', 'shp_m_max');
453 366         859 $self->_extract_count_doubles($self->{shp_num_points}, 'shp_m_data');
454 366         562 my @mdata = @{delete $self->{shp_m_data}};
  366         870  
455 366         993 for (0 .. $#mdata) {
456 0         0 $self->{shp_points}->[$_]->M($mdata[$_]);
457             }
458             }
459              
460             sub _extract_parts_and_points {
461 4860     4860   7295 my $self = shift;
462              
463 4860         10374 $self->_extract_ints('little', 'shp_num_parts', 'shp_num_points');
464 4860         13794 $self->_extract_count_ints($self->{shp_num_parts}, 'little', 'shp_parts');
465 4860         14606 $self->_extract_points($self->{shp_num_points}, 'shp_points');
466             }
467              
468              
469             # these could be factory generated
470 5164     5164 1 12892 sub x_min { shift()->{shp_x_min}; }
471 5164     5164 1 11647 sub x_max { shift()->{shp_x_max}; }
472 5164     5164 1 12532 sub y_min { shift()->{shp_y_min}; }
473 5164     5164 1 13853 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 12 my $self = shift;
481              
482 5         17 my @results = (
483             $self->x_min,
484             $self->y_min,
485             $self->x_max,
486             $self->y_max,
487             );
488              
489 5 50       28 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 327 my ( $self, $point, $index_res ) = @_;
507              
508 59 100 66     155 return $self->_contains_point_use_index ($point, $index_res)
509             if $self->get_spatial_index || defined $index_res;
510              
511 22 100       109 return 0 if !$self->bounds_contains_point( $point );
512              
513 18         38 my $a = 0;
514 18         39 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         50 my $num_parts = $self->num_parts;
519              
520             # $x1, $x2, $y1 and $y2 are offsets from the point we are checking
521 18         49 foreach my $part_num (1 .. $num_parts) {
522 66         129 my $points = $self->get_part( $part_num );
523              
524 66         105 my $p_start = shift @$points; # $points is a copy, so no harm in shifting
525 66         181 my $x1 = $p_start->get_x - $x0;
526 66         127 my $y1 = $p_start->get_y - $y0;
527              
528 66         113 foreach my $p2 ( @$points ) {
529 1986         3360 my $x2 = $p2->get_x - $x0;
530 1986         3340 my $y2 = $p2->get_y - $y0;
531              
532             # does the ray intersect the segment?
533 1986 100       3676 if (($y2 >= 0) != ($y1 >= 0)) { # $y0 is between $y1 and $y2
534 52         96 my $isl = $x1 * $y2 - $y1 * $x2; # is left of $p2
535 52 100       102 if ( $y2 > $y1 ) {
536 26 100       64 if ($isl > 0) {
537 9         14 $a--;
538             }
539             }
540             else {
541 26 100       59 if ($isl < 0) {
542 18         32 $a++;
543             }
544             }
545             }
546 1986         3118 ( $x1, $y1 ) = ( $x2, $y2 );
547             }
548             }
549              
550 18         61 return $a;
551             }
552              
553             sub _contains_point_use_index {
554 37     37   103 my ( $self, $point, $index_res ) = @_;
555              
556 37 100       153 return 0 if !$self->bounds_contains_point( $point );
557              
558 33   33     104 my $sp_index_hash = $self->get_spatial_index || $self->build_spatial_index ($index_res);
559              
560 33         68 my $a = 0;
561 33         93 my ( $x0, $y0 ) = ( $point->get_x, $point->get_y );
562              
563 33         96 my @parts = $self->parts;
564 33         72 my $num_parts = scalar @parts;
565              
566             # $x1, $x2, $y1 and $y2 are offsets from the point we are checking
567             PART:
568 33         91 foreach my $part_index (1 .. $num_parts) {
569 81         180 my $sp_index = $sp_index_hash->{$part_index};
570              
571 81         229 my @results = $sp_index->query_point($x0, $y0);
572              
573             # skip if not in this part's bounding box
574 81 100       498 next PART if !scalar @results;
575              
576             # segments spanning the index's bounding box
577 39         115 for my $segment (@results) {
578              
579             # index stores bare x and y coords to avoid method overhead here
580 22962         42597 my $x1 = $segment->[0][0] - $x0;
581 22962         31213 my $y1 = $segment->[0][1] - $y0;
582 22962         34699 my $x2 = $segment->[1][0] - $x0;
583 22962         31397 my $y2 = $segment->[1][1] - $y0;
584              
585             # does the ray intersect the segment?
586 22962 100       44917 if (($y2 >= 0) != ($y1 >= 0)) {
587 82         160 my $isl = $x1 * $y2 - $y1 * $x2;
588 82 100       206 if ( $y2 > $y1 ) {
589 41 100       138 if ($isl > 0) {
590 9         18 $a--;
591             }
592             }
593             else {
594 41 100       109 if ($isl < 0) {
595 33         64 $a++;
596             }
597             }
598             }
599             }
600             }
601              
602 33         182 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 142 my $self = shift;
610              
611 92         404 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 3149 my $self = shift;
622 7   100     33 my $n = shift || 10;
623              
624 7         22 $n = int $n;
625              
626 7 50       31 croak 'Cannot build spatial index with <1 boxes'
627             if $n < 1;
628              
629 7         20 my %sp_indexes;
630              
631 7         26 my @parts = $self->parts;
632              
633 7         25 my ($x_min, $x_max, $y_min, $y_max);
634              
635 7         27 my $part_id = 0;
636 7         25 foreach my $part (@parts) {
637 12         31 $part_id ++; # parts are indexed base 1
638              
639 12         43 my $segments = $self->get_segments ($part_id);
640              
641 12 100       67 if (@parts > 1) {
642 7         16 my %bounds = $self->_get_part_bounds ($part_id);
643 7         30 ($x_min, $y_min, $x_max, $y_max) = @bounds{qw /x_min y_min x_max y_max/};
644             }
645             else {
646 5         29 ($x_min, $y_min, $x_max, $y_max) = $self->bounds; # faster than searching all points
647             }
648              
649 12 100       46 my $n_boxes = @$segments > 20 ? $n : 1;
650 12         94 my $sp_index = $index_class->new ($n_boxes, $x_min, $y_min, $x_max, $y_max);
651              
652 12         39 foreach my $segment (@$segments) {
653 7289         19611 my $p1 = $segment->[0];
654 7289         14235 my $p2 = $segment->[1];
655              
656             # bare metal version
657 7289         16021 my $coords = [
658             [$p1->get_x, $p1->get_y],
659             [$p2->get_x, $p2->get_y],
660             ];
661              
662 7289         15730 my @bbox = ($x_min, $y_min, $x_max, $y_max);
663 7289         15741 $sp_index->insert($coords, @bbox);
664             }
665              
666 12         5733 $sp_indexes{$part_id} = $sp_index;
667             }
668              
669 7         15897 $self->{_spatial_indexes} = \%sp_indexes;
670              
671 7 100       111 return wantarray ? %sp_indexes : \%sp_indexes;
672             }
673              
674             sub _get_part_bounds {
675 7     7   10 my $self = shift;
676 7         12 my $part = shift;
677              
678 7         17 my $points = $self->get_part($part);
679              
680 7         16 my $pt1 = shift @$points;
681 7         24 my ($x_min, $y_min) = ($pt1->get_x, $pt1->get_y);
682 7         16 my ($x_max, $y_max) = ($x_min, $y_min);
683              
684 7         17 foreach my $pt (@$points) {
685 229         409 my $x = $pt->get_x;
686 229         387 my $y = $pt->get_y;
687              
688 229         356 $x_min = min ($x_min, $x);
689 229         322 $y_min = min ($y_min, $y);
690 229         351 $x_max = max ($x_max, $x);
691 229         731 $y_max = max ($y_max, $y);
692             }
693              
694 7         26 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       54 return wantarray ? %bounds : \%bounds;
702             }
703              
704             sub get_segments {
705 12     12 1 22 my $self = shift;
706 12         22 my $part = shift;
707              
708 12         33 my $points = $self->get_part($part);
709              
710 12         35 my @segments;
711 12         41 foreach my $i (0 .. $#$points - 1) {
712 7289         16244 push @segments, [$points->[$i], $points->[$i+1]];
713             }
714              
715 12 50       373 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__