File Coverage

blib/lib/Math/Geometry/Voronoi.pm
Criterion Covered Total %
statement 120 127 94.4
branch 45 56 80.3
condition 21 36 58.3
subroutine 14 15 93.3
pod 3 8 37.5
total 203 242 83.8


line stmt bran cond sub pod time code
1             package Math::Geometry::Voronoi;
2              
3 2     2   48884 use 5.008;
  2         9  
  2         88  
4 2     2   11 use strict;
  2         4  
  2         77  
5 2     2   17 use warnings;
  2         12  
  2         160  
6              
7             our $VERSION = '1.3';
8              
9             require XSLoader;
10             XSLoader::load('Math::Geometry::Voronoi', $VERSION);
11              
12 2     2   2331 use Params::Validate qw(validate ARRAYREF CODEREF);
  2         28829  
  2         201  
13 2     2   20 use List::Util qw(min max sum);
  2         4  
  2         290  
14              
15 2     2   12 use base 'Class::Accessor::Fast';
  2         5  
  2         2175  
16             __PACKAGE__->mk_accessors(
17             qw(points lines edges vertices xmin ymin xmax ymax));
18              
19             sub new {
20 3     3 1 16951 my $pkg = shift;
21 3         404 my %args = validate(@_, {points => {type => ARRAYREF}});
22 2         18 my $self = bless({points => $args{points}}, $pkg);
23              
24 2         11 $self->sort_points();
25              
26 2         14 return $self;
27             }
28              
29             # C code needs points sorted by y then by x and needs min and max for
30             # both - should provide a way for the client to provide this
31             sub sort_points {
32 2     2 0 3 my $self = shift;
33 2         13 my $points = $self->points();
34              
35 7227 50       12031 @$points =
36 2         55 sort { $a->[1] <=> $b->[1] || $a->[0] <=> $b->[0] } @$points;
37              
38 2         43 $self->ymin($points->[0][1]);
39 2         24 $self->ymax($points->[-1][1]);
40              
41 2         15 my @x = map { $_->[0] } @$points;
  1101         1430  
42 2         129 $self->xmin(min(@x));
43 2         37 $self->xmax(max(@x));
44              
45 2         47 return;
46             }
47              
48             sub compute {
49 2     2 1 4338 my $self = shift;
50              
51 2         10 my $result = compute_voronoi_xs($self->points,
52             $self->xmin,
53             $self->xmax,
54             $self->ymin,
55             $self->ymax);
56              
57 2         10453 $self->lines($result->{lines});
58 2         154 $self->vertices($result->{vertices});
59 2         17 $self->edges($result->{edges});
60              
61 2         20 return;
62             }
63              
64 3277     3277 0 5803 sub cmp_verts_ab { return cmp_verts($a,$b); }
65              
66             # a low x value is placed before a high x value. if both x values
67             # are the same, a high y value is placed before a low y value.
68             sub cmp_verts {
69 3319   100 3319 0 22636 return ($_[0]->[0] <=> $_[1]->[0] || $_[1]->[1] <=> $_[0]->[1] );
70             }
71              
72              
73             sub vert_inside_bounds {
74 12390     12390 0 16601 my ($self, $x,$y) = @_;
75             return (
76 12390   100     25875 $x >= $self->xmin and $x <= $self->xmax and
77             $y >= $self->ymin and $y <= $self->ymax
78             );
79             }
80              
81              
82             sub boundry_interesection_verts {
83 3271     3271 0 4385 my($self, $a,$b,$c) = @_;
84 3271         4124 my $verts = [];
85              
86 3271 100       6080 if($b){
87 3111         7382 my $v1 = [$self->xmin,($c-$a*$self->xmin)/$b];
88 3111         28245 my $v2 = [$self->xmax,($c-$a*$self->xmax)/$b];
89 3111 100       24146 push ( @$verts, $v1 ) if ( $self->vert_inside_bounds( @$v1 ) );
90 3111 100       79670 push ( @$verts, $v2 ) if ( $self->vert_inside_bounds( @$v2 ) );
91             }
92              
93 3271 100       74307 if($a){
94 3084         7441 my $v1 = [($c-$b*$self->ymax)/$a,$self->ymax];
95 3084         25180 my $v2 = [($c-$b*$self->ymin)/$a,$self->ymin];
96 3084 100       24801 push ( @$verts, $v1 ) if ( $self->vert_inside_bounds( @$v1 ) );
97 3084 100       43942 push ( @$verts, $v2 ) if ( $self->vert_inside_bounds( @$v2 ) );
98             }
99              
100 3271         56366 $verts;
101             }
102              
103              
104             sub polygons {
105 2     2 1 143316 my $self = shift;
106 2         64 my %args = validate(@_,
107             {normalize_vertices => {type => CODEREF,
108             optional => 1
109             },
110             });
111 2         19 my $points = $self->points;
112 2         18 my $lines = $self->lines;
113 2         16 my $edges = $self->edges;
114 2         14 my $vertices = $self->vertices;
115              
116 2 100       17 if (my $norm = $args{normalize_vertices}) {
117 1         13 $vertices = [map { [$norm->($_->[0]), $norm->($_->[1])] } @$vertices];
  2166         9745  
118             }
119              
120 2         228 my @edges_by_point;
121 2         10 EDGE: foreach my $edge (@$edges) {
122 3271         5349 my ($l, $v1, $v2) = @$edge;
123 3271 50 66     7203 next EDGE if( $v1 == -1 and $v2 == -1 );
124 3271         3014 my ($lon1, $lat1, $lon2, $lat2);
125            
126 3271         2971 my $ivs = $self->boundry_interesection_verts(@{$lines->[$l]});
  3271         9201  
127 3271         9430 $ivs = [sort cmp_verts_ab @$ivs];
128            
129 3271 100       9482 if( my $norm = $args{normalize_vertices}) {
130 3260         4299 $ivs = [map { [$norm->($_->[0]), $norm->($_->[1])] } @$ivs];
  6522         25685  
131             }
132            
133 3271 100       29521 ($lat1,$lon1) = @{$vertices->[$v1]} if( $v1 != -1 );
  3258         9250  
134 3271 100       6075 ($lat2,$lon2) = @{$vertices->[$v2]} if( $v2 != -1 );
  3258         5806  
135              
136 3271 100       6204 if( $v1 == -1 ) {
137 13 50 33     120 next EDGE unless( @$ivs and $lat2 +0 == $lat2 and $lon2 +0 == $lon2 );
      33        
138 13 100       40 if( cmp_verts( [$lat2,$lon2], $ivs->[0] ) > 0 ) {
    50          
139 7         10 ($lat1,$lon1) = @{$ivs->[0]};
  7         16  
140             } elsif( cmp_verts( [$lat2,$lon2], $ivs->[1] ) > 0 ) {
141 0         0 ($lat1,$lon1) = @{$ivs->[1]};
  0         0  
142             } else {
143 6         25 next EDGE;
144             }
145             }
146 3265 100       5700 if( $v2 == -1 ) {
147 13 50 33     88 next EDGE unless( @$ivs and $lat1 +0 == $lat1 and $lon1 +0 == $lon1 );
      33        
148 13 100       54 if( cmp_verts( [$lat1,$lon1], $ivs->[1] ) < 0 ) {
    50          
149 3         5 ($lat2,$lon2) = @{$ivs->[1]};
  3         6  
150             } elsif( cmp_verts( [$lat1,$lon1], $ivs->[0] ) < 0 ) {
151 0         0 ($lat2,$lon2) = @{$ivs->[0]};
  0         0  
152             } else {
153 10         38 next EDGE;
154             }
155             }
156            
157             # if any of the coords are NaN things break.
158 3255 50       4159 next EDGE if( grep {$_ +0 != $_ } ($lat1,$lon1,$lat2,$lon2));
  13020         25629  
159            
160 3255         5579 my ($p1, $p2) = ($lines->[$l][3], $lines->[$l][4]);
161              
162 3255 50 33     12762 if ($p1 != -1 and $p2 != -1) {
163 3255         4503 foreach my $p ($p1, $p2) {
164 6510         6093 push @{$edges_by_point[$p]}, [$lat1, $lon1, $lat2, $lon2];
  6510         26200  
165             }
166             }
167             }
168              
169 2         6 my @polygons;
170 2         7 foreach my $p (0 .. $#$points) {
171 1101         1944 my $stack = $edges_by_point[$p];
172 1101 50       3466 next unless $stack;
173             # can't make a polygon with less than 2 edges
174 1101 100       3661 next unless @$stack >= 2;
175              
176 1100         10338 my @poly = ();
177 1100         2216 foreach my $this ( @$stack ) {
178 6509 50 66     18231 if(
      66        
179 23002 100       92845 !grep { $_->[0] == $this->[0] && $_->[1] == $this->[1] } @poly
180             and $this->[0] +0 == $this->[0]
181             and $this->[1] +0 == $this->[1]
182             ) {
183 2985         7610 push @poly, [$this->[0],$this->[1]];
184             }
185 6509 50 66     9641 if(
      66        
186 25987 100       127155 !grep { $_->[0] == $this->[2] && $_->[1] == $this->[3] } @poly
187             and $this->[2] +0 == $this->[2]
188             and $this->[3] +0 == $this->[3]
189             ) {
190 3505         10696 push @poly, [$this->[2],$this->[3]];
191             }
192             }
193              
194             #TODO: if this point is the closest point to a corner...
195             # add that corner as a vert on this poly
196              
197             # sort poly's verts (anti?) clockwise around the point $points->[$p];
198 10617         23484 @poly = sort {
199 1100         3724 my($lat1,$lon1) = (
200             $a->[0] - $points->[$p]->[0],
201             $a->[1] - $points->[$p]->[1]
202             );
203              
204 10617         23780 my($lat2,$lon2) = (
205             $b->[0] - $points->[$p]->[0],
206             $b->[1] - $points->[$p]->[1]
207             );
208 10617         26566 return atan2($lon1,$lat1) <=> atan2($lon2,$lat2);
209             } @poly;
210              
211             # make a list of the first points
212 1100         1861 push @polygons, [$p, map { [$_->[0], $_->[1]] } @poly];
  6490         23730  
213             }
214              
215 2         4666 return @polygons;
216             }
217              
218             sub _dump_poly {
219 0     0     my $poly = shift;
220             return
221 0           "[ \n\t" . join(", \n\t", map { "[$_->[0],$_->[1]]" } @$poly) . " ]\n";
  0            
222             }
223              
224             1;
225             __END__