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   13 use strict;
  2         4  
  2         57  
3 2     2   9 use warnings;
  2         5  
  2         47  
4 2     2   11 use Carp;
  2         4  
  2         142  
5 2     2   1003 use Tree::R;
  2         10751  
  2         107  
6 2     2   20 use List::Util qw /min max/;
  2         3  
  2         244  
7            
8 2     2   14 use Geo::ShapeFile;
  2         6  
  2         48  
9 2     2   901 use Geo::ShapeFile::Point;
  2         8  
  2         11  
10 2     2   880 use Geo::ShapeFile::Shape::Index;
  2         6  
  2         72  
11            
12 2     2   929 use parent qw /Geo::ShapeFile/;
  2         626  
  2         13  
13            
14             our $VERSION = '3.00';
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 14475 my $proto = shift;
22 7416   33     25244 my $class = ref ($proto) || $proto;
23 7416         15431 my %args = @_;
24            
25 7416         84573 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         23048 foreach (keys %args) {
46 0         0 $self->{$_} = $args{$_};
47             }
48            
49 7416         13214 bless $self, $class;
50            
51 7416         19844 return $self;
52             }
53            
54             sub parse_shp {
55 7416     7416 0 11075 my $self = shift;
56            
57 7416         15848 $self->{source} = $self->{shp_data} = shift;
58            
59 7416         20355 $self->_extract_ints('big', 'shp_record_number', 'shp_content_length');
60 7416         19754 $self->_extract_ints('little', 'shp_shape_type');
61            
62 7416         21083 my $parser = '_parse_shp_' . $self->type($self->{shp_shape_type});
63            
64 7416 50       29742 croak "Can't parse shape_type $self->{shp_shape_type}"
65             if !$self->can($parser);
66            
67 7416         20187 $self->$parser();
68            
69 7416 50       21489 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   4340 my $self = shift;
121 2553         6600 $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         10034 )];
126 2553         4741 $self->{shp_num_points} = 1;
127 2553         5034 $self->{shp_x_min} = $self->{shp_X};
128 2553         4463 $self->{shp_x_max} = $self->{shp_X};
129 2553         4168 $self->{shp_y_min} = $self->{shp_Y};
130 2553         4518 $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   3748 my $self = shift;
138            
139 2253         5699 $self->_extract_bounds();
140 2253         5277 $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   4019 my $self = shift;
151            
152 2607         6995 $self->_extract_bounds();
153 2607         6114 $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   9 my $self = shift;
164            
165 3         10 $self->_extract_bounds();
166 3         13 $self->_extract_ints('little', 'shp_num_points');
167 3         15 $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   4250 my $self = shift;
176            
177 2445         6908 $self->_parse_shp_Point();
178 2445         6035 $self->_extract_doubles('shp_Z', 'shp_M');
179 2445         9373 $self->{shp_points}->[0]->Z($self->{shp_Z});
180 2445         6891 $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   608 my $self = shift;
189            
190 366         1030 $self->_parse_shp_PolyLine();
191 366         1118 $self->_extract_z_data();
192 366         812 $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   7257 my $self = shift;
295            
296 4863         10035 $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   28259 my $self = shift;
301 19695         27895 my $end = shift;
302 19695         38574 my @what = @_;
303            
304 19695 100       62800 my $template = ($end =~ /^l/i) ? 'V' :'N';
305            
306 19695         41716 $self->_extract_and_unpack(4, $template, @what);
307             }
308            
309             sub _extract_count_ints {
310 4860     4860   8060 my $self = shift;
311 4860         7009 my $count = shift;
312 4860         6824 my $end = shift;
313 4860         6891 my $label = shift;
314            
315 4860 50       12222 my $template = ($end =~ /^l/i) ? 'V' :'N';
316            
317 4860         11169 my $tmp = substr $self->{shp_data}, 0, ($count * 4), '';
318 4860         12299 my @tmp = unpack $template . $count, $tmp;
319             #my @tmp = unpack($template."[$count]",$tmp);
320            
321 4860         14599 $self->{$label} = [@tmp];
322             }
323            
324             sub _extract_doubles {
325 10593     10593   17189 my $self = shift;
326 10593         23428 my @what = @_;
327 10593         15438 my $size = 8;
328 10593         16419 my $template = 'd';
329            
330 10593         18349 foreach ( @what ) {
331 30912         57497 my $tmp = substr $self->{shp_data}, 0, $size, '';
332 30912 50       83494 $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   1068 my $self = shift;
340 732         1139 my $count = shift;
341 732         1084 my $label = shift;
342            
343 732         1741 my $tmp = substr $self->{shp_data}, 0, $count*8, '';
344 732 50       2218 my @tmp = $little_endian_sys
345             ? (unpack 'd'.$count, $tmp )
346             : (reverse unpack( 'd' . $count, scalar ( reverse( $tmp ) ) ) );
347            
348 732         2893 $self->{$label} = [@tmp];
349             }
350            
351             sub _extract_points {
352 4863     4863   7908 my $self = shift;
353 4863         7451 my $count = shift;
354 4863         7365 my $label = shift;
355            
356 4863         12100 my $data = substr $self->{shp_data}, 0, $count * 16, '';
357            
358 4863 50       26560 my @ps = $little_endian_sys
359             ? (unpack 'd*', $data )
360             : (reverse unpack 'd*', scalar reverse $data );
361            
362 4863         9263 my @p = (); # points
363 4863         10639 while(@ps) {
364 80764         146858 my ($x, $y) = (shift @ps, shift @ps);
365 80764         177739 push @p, Geo::ShapeFile::Point->new(X => $x, Y => $y);
366             }
367 4863         27667 $self->{$label} = [@p];
368             }
369            
370             sub _extract_and_unpack {
371 19695     19695   28902 my $self = shift;
372 19695         26961 my $size = shift;
373 19695         27742 my $template = shift;
374 19695         36915 my @what = @_;
375            
376 19695         33370 foreach(@what) {
377 31971         72754 my $tmp = substr $self->{shp_data}, 0, $size, '';
378 31971 50       62454 if ( $template eq 'd' ) {
379 0         0 $tmp = Geo::ShapeFile->byteswap( $tmp );
380             }
381 31971         92547 $self->{$_} = unpack $template, $tmp;
382             }
383             }
384            
385 4644     4644 1 18192 sub num_parts { shift()->{shp_num_parts}; }
386             sub parts {
387 4751     4751 0 12854 my $self = shift;
388            
389 4751         8069 my $parts = $self->{shp_parts};
390            
391 4751 50       9148 return wantarray ? @{$parts || []} : $parts;
  4666 100       14774  
392             }
393            
394 4626     4626 1 2627943 sub num_points { shift()->{shp_num_points}; }
395             sub points {
396 4711     4711 1 14747 my $self = shift;
397            
398 4711         8520 my $points = $self->{shp_points};
399            
400 4711 50       10234 return wantarray ? @{$points || []} : $points;
  4626 100       20198  
401             }
402            
403             sub get_part {
404 85     85 1 139 my $self = shift;
405 85         129 my $index = shift;
406            
407 85 50       179 croak 'index passed to get_part must be >0'
408             if $index <= 0;
409            
410 85         139 $index -= 1; # shift to a 0 index
411            
412             # $parts is an array of starting indexes in the $points array
413 85         175 my $parts = $self->parts;
414 85 50       202 croak 'index exceeds number of parts'
415             if $index > $#$parts;
416            
417 85         231 my $points = $self->points;
418 85   100     238 my $beg = $parts->[$index] || 0;
419 85   100     263 my $end = $parts->[$index+1] || 0; # if we use 5.010 then we can use the // operator here
420 85         135 $end -= 1;
421 85 100       190 if ($end < 0) {
422 27         68 $end = $#$points;
423             }
424            
425 85 50       3632 return wantarray ? @$points[$beg .. $end] : [@$points[$beg .. $end]];
426             }
427            
428             sub shape_type {
429 4626     4626 1 15900 my $self = shift;
430 4626         9838 return $self->{shp_shape_type};
431             }
432            
433             sub shape_id {
434 604     604 1 807 my $self = shift;
435 604         2538 return $self->{shp_record_number};
436             }
437            
438             sub _extract_z_data {
439 366     366   594 my $self = shift;
440            
441 366         893 $self->_extract_doubles('shp_z_min', 'shp_z_max');
442 366         1043 $self->_extract_count_doubles($self->{shp_num_points}, 'shp_z_data');
443 366         685 my @zdata = @{delete $self->{shp_z_data}};
  366         1285  
444 366         1189 for (0 .. $#zdata) {
445 5208         12195 $self->{shp_points}->[$_]->Z($zdata[$_]);
446             }
447             }
448            
449             sub _extract_m_data {
450 366     366   570 my $self = shift;
451            
452 366         855 $self->_extract_doubles ('shp_m_min', 'shp_m_max');
453 366         975 $self->_extract_count_doubles($self->{shp_num_points}, 'shp_m_data');
454 366         599 my @mdata = @{delete $self->{shp_m_data}};
  366         868  
455 366         1036 for (0 .. $#mdata) {
456 0         0 $self->{shp_points}->[$_]->M($mdata[$_]);
457             }
458             }
459            
460             sub _extract_parts_and_points {
461 4860     4860   7214 my $self = shift;
462            
463 4860         11345 $self->_extract_ints('little', 'shp_num_parts', 'shp_num_points');
464 4860         13316 $self->_extract_count_ints($self->{shp_num_parts}, 'little', 'shp_parts');
465 4860         11249 $self->_extract_points($self->{shp_num_points}, 'shp_points');
466             }
467            
468            
469             # these could be factory generated
470 5164     5164 1 14523 sub x_min { shift()->{shp_x_min}; }
471 5164     5164 1 12982 sub x_max { shift()->{shp_x_max}; }
472 5164     5164 1 11852 sub y_min { shift()->{shp_y_min}; }
473 5164     5164 1 13359 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 14 my $self = shift;
481            
482 5         27 my @results = (
483             $self->x_min,
484             $self->y_min,
485             $self->x_max,
486             $self->y_max,
487             );
488            
489 5 50       38 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 322 my ( $self, $point, $index_res ) = @_;
507            
508 59 100 66     168 return $self->_contains_point_use_index ($point, $index_res)
509             if $self->get_spatial_index || defined $index_res;
510            
511 22 100       165 return 0 if !$self->bounds_contains_point( $point );
512            
513 18         40 my $a = 0;
514 18         46 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         45 my $num_parts = $self->num_parts;
519            
520             # $x1, $x2, $y1 and $y2 are offsets from the point we are checking
521 18         53 foreach my $part_num (1 .. $num_parts) {
522 66         132 my $points = $self->get_part( $part_num );
523            
524 66         110 my $p_start = shift @$points; # $points is a copy, so no harm in shifting
525 66         167 my $x1 = $p_start->get_x - $x0;
526 66         145 my $y1 = $p_start->get_y - $y0;
527            
528 66         125 foreach my $p2 ( @$points ) {
529 1986         3686 my $x2 = $p2->get_x - $x0;
530 1986         3635 my $y2 = $p2->get_y - $y0;
531            
532             # does the ray intersect the segment?
533 1986 100       3746 if (($y2 >= 0) != ($y1 >= 0)) { # $y0 is between $y1 and $y2
534 52         90 my $isl = $x1 * $y2 - $y1 * $x2; # is left of $p2
535 52 100       113 if ( $y2 > $y1 ) {
536 26 100       64 if ($isl > 0) {
537 9         19 $a--;
538             }
539             }
540             else {
541 26 100       68 if ($isl < 0) {
542 18         37 $a++;
543             }
544             }
545             }
546 1986         3456 ( $x1, $y1 ) = ( $x2, $y2 );
547             }
548             }
549            
550 18         60 return $a;
551             }
552            
553             sub _contains_point_use_index {
554 37     37   84 my ( $self, $point, $index_res ) = @_;
555            
556 37 100       172 return 0 if !$self->bounds_contains_point( $point );
557            
558 33   33     98 my $sp_index_hash = $self->get_spatial_index || $self->build_spatial_index ($index_res);
559            
560 33         63 my $a = 0;
561 33         89 my ( $x0, $y0 ) = ( $point->get_x, $point->get_y );
562            
563 33         101 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         110 foreach my $part_index (1 .. $num_parts) {
569 81         187 my $sp_index = $sp_index_hash->{$part_index};
570            
571 81         231 my @results = $sp_index->query_point($x0, $y0);
572            
573             # skip if not in this part's bounding box
574 81 100       530 next PART if !scalar @results;
575            
576             # segments spanning the index's bounding box
577 39         99 for my $segment (@results) {
578            
579             # index stores bare x and y coords to avoid method overhead here
580 22962         44712 my $x1 = $segment->[0][0] - $x0;
581 22962         34281 my $y1 = $segment->[0][1] - $y0;
582 22962         38057 my $x2 = $segment->[1][0] - $x0;
583 22962         34454 my $y2 = $segment->[1][1] - $y0;
584            
585             # does the ray intersect the segment?
586 22962 100       45642 if (($y2 >= 0) != ($y1 >= 0)) {
587 82         159 my $isl = $x1 * $y2 - $y1 * $x2;
588 82 100       212 if ( $y2 > $y1 ) {
589 41 100       130 if ($isl > 0) {
590 9         19 $a--;
591             }
592             }
593             else {
594 41 100       131 if ($isl < 0) {
595 33         79 $a++;
596             }
597             }
598             }
599             }
600             }
601            
602 33         169 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 158 my $self = shift;
610            
611 92         400 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 3314 my $self = shift;
622 7   100     42 my $n = shift || 10;
623            
624 7         23 $n = int $n;
625            
626 7 50       33 croak 'Cannot build spatial index with <1 boxes'
627             if $n < 1;
628            
629 7         17 my %sp_indexes;
630            
631 7         28 my @parts = $self->parts;
632            
633 7         25 my ($x_min, $x_max, $y_min, $y_max);
634            
635 7         18 my $part_id = 0;
636 7         51 foreach my $part (@parts) {
637 12         29 $part_id ++; # parts are indexed base 1
638            
639 12         46 my $segments = $self->get_segments ($part_id);
640            
641 12 100       56 if (@parts > 1) {
642 7         24 my %bounds = $self->_get_part_bounds ($part_id);
643 7         29 ($x_min, $y_min, $x_max, $y_max) = @bounds{qw /x_min y_min x_max y_max/};
644             }
645             else {
646 5         35 ($x_min, $y_min, $x_max, $y_max) = $self->bounds; # faster than searching all points
647             }
648            
649 12 100       49 my $n_boxes = @$segments > 20 ? $n : 1;
650 12         86 my $sp_index = $index_class->new ($n_boxes, $x_min, $y_min, $x_max, $y_max);
651            
652 12         35 foreach my $segment (@$segments) {
653 7289         17900 my $p1 = $segment->[0];
654 7289         14482 my $p2 = $segment->[1];
655            
656             # bare metal version
657 7289         17818 my $coords = [
658             [$p1->get_x, $p1->get_y],
659             [$p2->get_x, $p2->get_y],
660             ];
661            
662 7289         16057 my @bbox = ($x_min, $y_min, $x_max, $y_max);
663 7289         16789 $sp_index->insert($coords, @bbox);
664             }
665            
666 12         6744 $sp_indexes{$part_id} = $sp_index;
667             }
668            
669 7         17260 $self->{_spatial_indexes} = \%sp_indexes;
670            
671 7 100       112 return wantarray ? %sp_indexes : \%sp_indexes;
672             }
673            
674             sub _get_part_bounds {
675 7     7   13 my $self = shift;
676 7         16 my $part = shift;
677            
678 7         16 my $points = $self->get_part($part);
679            
680 7         20 my $pt1 = shift @$points;
681 7         27 my ($x_min, $y_min) = ($pt1->get_x, $pt1->get_y);
682 7         19 my ($x_max, $y_max) = ($x_min, $y_min);
683            
684 7         20 foreach my $pt (@$points) {
685 229         451 my $x = $pt->get_x;
686 229         408 my $y = $pt->get_y;
687            
688 229         375 $x_min = min ($x_min, $x);
689 229         352 $y_min = min ($y_min, $y);
690 229         335 $x_max = max ($x_max, $x);
691 229         393 $y_max = max ($y_max, $y);
692             }
693            
694 7         31 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       56 return wantarray ? %bounds : \%bounds;
702             }
703            
704             sub get_segments {
705 12     12 1 31 my $self = shift;
706 12         24 my $part = shift;
707            
708 12         50 my $points = $self->get_part($part);
709            
710 12         42 my @segments;
711 12         47 foreach my $i (0 .. $#$points - 1) {
712 7289         17347 push @segments, [$points->[$i], $points->[$i+1]];
713             }
714            
715 12 50       430 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__