File Coverage

blib/lib/Polygon/Simplify.pm
Criterion Covered Total %
statement 79 83 95.1
branch 14 22 63.6
condition 1 3 33.3
subroutine 10 10 100.0
pod 5 6 83.3
total 109 124 87.9


line stmt bran cond sub pod time code
1             package Polygon::Simplify;
2              
3 2     2   29171 use 5.006;
  2         5  
4 2     2   7 use strict;
  2         2  
  2         36  
5 2     2   9 use warnings;
  2         5  
  2         51  
6              
7 2     2   2312 use Math::BigFloat ':constant';
  2         35883  
  2         11  
8              
9             =head1 NAME
10              
11             Polygon::Simplify
12              
13             =head1 VERSION
14              
15             Version 0.01
16              
17             =cut
18              
19             our $VERSION = '0.01';
20              
21             =head1 SYNOPSIS
22              
23             WARNING =>>>>>> EXPERIMENTAL RELEASE <<<<<<<=
24              
25             use Poygon::Simplify;
26              
27             # Raw points requiring simplification
28             my $points = [
29             {
30             x => 12.1, y => 3.41
31             },
32             ...
33             ];
34              
35             # POINT MUST BE AN Array of x and y points and not lat lng
36              
37             my $cleaned_points = Polygon::Simplify::simplify($points);
38              
39             # $cleaned_points is an array of hash refs
40              
41             =head1 DESCRIPTION
42              
43             perl port of simplify.js
44              
45             =head2 getSqDist(\%p1, \%p2)
46              
47             square distance between 2 points
48              
49             =cut
50              
51             sub getSqDist {
52 99     99 1 50 my ($p1, $p2) = @_;
53              
54 99         135 my $dx = $p1->{x} - $p2->{x};
55 99         104 my $dy = $p1->{y} - $p2->{y};
56              
57 99         159 return ($dx * $dx) + ($dy * $dy);
58             }
59              
60             =head2 getSqSegDist(\%p \%p1, \%p2)
61              
62             square distance from a point to a segment
63              
64             =cut
65             sub getSqSegDist {
66 202     202 1 220 my ($p, $p1, $p2) = @_;
67              
68 202         191 my $x = $p1->{x};
69 202         160 my $y = $p1->{y};
70 202         168 my $dx = $p2->{x} - $x;
71 202         158 my $dy = $p2->{y} - $y;
72              
73 202 50 33     363 if($dx != 0 || $dy != 0 ) {
74              
75 202         482 my $first_block = Math::BigFloat->new($p->{x} - $x);
76 202         21169 my $second_block = Math::BigFloat->new($p->{y} - $y);
77            
78 202         19317 my $first_multiply = $first_block->bmul($dx);
79 202         37204 my $second_multiply = $second_block->bmul($dy);
80            
81 202         36463 my $top = $first_multiply->badd($second_multiply);
82 202         19840 my $dx_square = Math::BigFloat->new($dx)->bmul($dx);
83 202         54982 my $dy_square = Math::BigFloat->new($dy)->bmul($dy);
84 202         54137 my $bottom = $dx_square->badd($dy_square);
85             # my $t = (($p->{x} - $x) * $dx + ($p->{y} - $y) * $dy) / ($dx * $dx + $dy * $dy);
86              
87 202         19814 my $t = $top->bdiv($bottom)->bstr();
88              
89 202 50       83829 if($t > 1) {
    50          
90 0         0 $x = $p2->{x};
91 0         0 $y = $p2->{y};
92             } elsif( $t > 0) {
93 202         222 $x += $dx * $t;
94 202         815 $y += $dy * $t;
95             }
96             }
97              
98 202         237 $dx = $p->{x} - $x;
99 202         197 $dy = $p->{y} - $y;
100              
101 202         285 return ($dx * $dx) + ($dy * $dy);
102             }
103              
104             =head2 simplifyRadialDist($\@points, $sqTolerance)
105              
106             Basic distanec-based simplifaction
107              
108             =cut
109              
110             sub simplifyRadialDist {
111 1     1 1 2 my ( $points, $sqTolerance) = @_;
112              
113 1         2 my $prev_point = $points->[0];
114 1         2 my $new_points = [$prev_point];
115 1         1 my $point;
116              
117 1         1 my $len = @{$points};
  1         2  
118 1         6 for (my $i = 1; $i < $len; $i++) {
119 99         72 $point = $points->[$i];
120              
121 99 50       82 if (getSqDist($point, $prev_point) > $sqTolerance) {
122 99         48 push( @{$new_points}, $point);
  99         82  
123 99         125 $prev_point = $point;
124             }
125              
126             }
127            
128             # If the polygon is not complete then complete it
129 1 50       3 push (@{$new_points}, $point) if ($prev_point != $point);
  0         0  
130              
131 1         2 return $new_points;
132             }
133              
134             sub simplifyDPStep {
135 14     14 0 21 my ($points, $first, $last, $sqTolerance, $simplified) = @_;
136              
137 14         10 my $maxSqDist = $sqTolerance;
138 14         10 my $index;
139              
140 14         24 for(my $i = $first + 1; $i < $last; $i++) {
141 202         322 my $sqDist = getSqSegDist($points->[$i], $points->[$first], $points->[$last]);
142              
143 202 100       504 if ($sqDist > $maxSqDist) {
144 67         59 $index = $i;
145 67         118 $maxSqDist = $sqDist;
146             }
147              
148             }
149              
150 14 100       49 if ($maxSqDist > $sqTolerance) {
151 7         31 simplifyDPStep($points, $first, $index, $sqTolerance, $simplified);
152 7         5 push @{$simplified}, $points->[$index];
  7         11  
153 7 100       17 simplifyDPStep($points, $index, $sqTolerance, $simplified)
154             if($index - $first > 1);
155             }
156             }
157              
158             =head2 simplifyDouglasPeucker(\@points, $sqTolerance)
159              
160             Simplification using Ramer-Douglas-Peucker algorithm
161              
162             =cut
163             sub simplifyDouglasPeucker {
164 1     1 1 1 my ($points, $sqTolerance) = @_;
165              
166 1         1 my $last = @{$points} - 1;
  1         2  
167              
168 1         2 my $simplified = [$points->[0]];
169 1         3 simplifyDPStep($points, 0, $last, $sqTolerance, $simplified);
170 1         2 push @{$simplified}, $points->[$last];
  1         3  
171              
172 1         3 return $simplified;
173              
174             }
175              
176             =head2 simplify(\@points, $tolerance, $highest_quality)
177              
178             both algorithms combined for awsome performance
179              
180             my $points = [
181             {
182             x => 51.34,
183             y => 1.34,
184             },
185             ...
186             ];
187              
188             simplify($points, $tolerance, $highest_quality);
189              
190             =cut
191              
192             sub simplify {
193 1     1 1 126 my ($points, $tolerance, $highestQuality) = @_;
194              
195 1 50       1 return $points if (@{$points} <= 2);
  1         3  
196              
197 1         2 my $sqTolerance = 1;
198              
199 1 50       2 if ( $tolerance ) {
200 0         0 $sqTolerance = $tolerance * $tolerance;
201             }
202              
203 1 50       6 $points = $highestQuality ? $points : simplifyRadialDist($points, $sqTolerance);
204 1         3 $points = simplifyDouglasPeucker($points, $sqTolerance);
205              
206 1         7 return $points;
207              
208              
209             }
210              
211             =head1 AUTHOR
212              
213             Sunny Patel C<< >>
214              
215             =head1 BUGS
216              
217             please report any bugs or feature requests to C, or through
218             the we interace at L. I will be notified, and then you'll
219             automatically be notified of progress on your bug as I make changes.o
220              
221             =cut
222              
223             1;