File Coverage

blib/lib/Algorithm/QuadTree/PP.pm
Criterion Covered Total %
statement 99 107 92.5
branch 37 46 80.4
condition 15 15 100.0
subroutine 15 16 93.7
pod n/a
total 166 184 90.2


line stmt bran cond sub pod time code
1             package Algorithm::QuadTree::PP;
2             $Algorithm::QuadTree::PP::VERSION = '0.8';
3 7     7   46 use strict;
  7         12  
  7         220  
4 7     7   26 use warnings;
  7         9  
  7         315  
5 7     7   63 use Exporter qw(import);
  7         12  
  7         253  
6              
7 7     7   33 use Scalar::Util qw(weaken);
  7         43  
  7         791  
8              
9             our @EXPORT = qw(
10             _AQT_init
11             _AQT_deinit
12             _AQT_addObject
13             _AQT_findObjects
14             _AQT_delete
15             _AQT_clear
16             );
17              
18 7     7   47 use constant UNIQUE_RESULTS => 1;
  7         11  
  7         10485  
19              
20             # recursive method which adds levels to the quadtree
21             sub _addLevel
22             {
23 89     89   263 my ($self, $depth, $parent, @coords) = @_;
24 89         320 my $node = {
25             PARENT => $parent,
26             HAS_OBJECTS => 0,
27             AREA => \@coords,
28             };
29              
30 89 100       192 weaken $node->{PARENT} if $parent;
31              
32 89 100       171 if ($depth < $self->{DEPTH}) {
33 21         72 my ($xmin, $ymin, $xmax, $ymax) = @coords;
34 21         54 my $xmid = $xmin + ($xmax - $xmin) / 2;
35 21         42 my $ymid = $ymin + ($ymax - $ymin) / 2;
36 21         28 $depth += 1;
37              
38             # segment in the following order:
39             # top left, top right, bottom left, bottom right
40             $node->{CHILDREN} = [
41 21         94 _addLevel($self, $depth, $node, $xmin, $ymid, $xmid, $ymax),
42             _addLevel($self, $depth, $node, $xmid, $ymid, $xmax, $ymax),
43             _addLevel($self, $depth, $node, $xmin, $ymin, $xmid, $ymid),
44             _addLevel($self, $depth, $node, $xmid, $ymin, $xmax, $ymid),
45             ];
46             }
47             else {
48             # leaves must have empty aref in objects
49 68         100 $node->{OBJECTS} = [];
50             }
51              
52 89         298 return $node;
53             }
54              
55             # this private method executes $code on every leaf node of the tree
56             # which is within the circular shape
57             sub _loopOnNodesInCircle
58             {
59 12     12   33 my ($self, $finding, @coords) = @_;
60              
61             # avoid squaring the radius on each iteration
62 12         67 my $radius_squared = $coords[2] ** 2;
63              
64 12         26 my @nodes;
65 12         28 my @loopargs = $self->{ROOT};
66 12         34 while (my $current = shift @loopargs) {
67 84 100 100     277 next if $finding && !$current->{HAS_OBJECTS};
68              
69 55         92 my ($cxmin, $cymin, $cxmax, $cymax) = @{$current->{AREA}};
  55         120  
70              
71 55 100       110 my $cx = $coords[0] < $cxmin
    100          
72             ? $cxmin - $coords[0]
73             : $coords[0] > $cxmax
74             ? $cxmax - $coords[0]
75             : 0
76             ;
77              
78 55 100       131 my $cy = $coords[1] < $cymin
    100          
79             ? $cymin - $coords[1]
80             : $coords[1] > $cymax
81             ? $cymax - $coords[1]
82             : 0
83             ;
84              
85             # first check if obj overlaps current segment.
86 55 100       143 next if $cx ** 2 + $cy ** 2
87             > $radius_squared;
88              
89 35 100       66 $current->{HAS_OBJECTS} = 1 if !$finding;
90 35 100       67 if ($current->{CHILDREN}) {
91 18         26 push @loopargs, @{$current->{CHILDREN}};
  18         58  
92             } else {
93             # segment is a leaf and overlaps the obj
94 17         91 push @nodes, $current;
95             }
96             }
97              
98 12         40 return \@nodes;
99             }
100              
101             # this private method executes $code on every leaf node of the tree
102             # which is within the rectangular shape
103             sub _loopOnNodesInRectangle
104             {
105 114     114   288 my ($self, $finding, @coords) = @_;
106              
107 114         155 my @nodes;
108 114         265 my @loopargs = $self->{ROOT};
109 114         278 while (my $current = shift @loopargs) {
110 1070 100 100     2608 next if $finding && !$current->{HAS_OBJECTS};
111              
112             # first check if obj overlaps current segment.
113             next if
114             $coords[0] > $current->{AREA}[2] ||
115             $coords[2] < $current->{AREA}[0] ||
116             $coords[1] > $current->{AREA}[3] ||
117 1049 100 100     5114 $coords[3] < $current->{AREA}[1];
      100        
      100        
118              
119 426 100       736 $current->{HAS_OBJECTS} = 1 if !$finding;
120 426 100       840 if ($current->{CHILDREN}) {
121 239         316 push @loopargs, @{$current->{CHILDREN}};
  239         726  
122             } else {
123             # segment is a leaf and overlaps the obj
124 187         470 push @nodes, $current;
125             }
126             }
127              
128 114         374 return \@nodes;
129             }
130              
131             # choose the right function based on argument count
132             # first argument is always $self, second is $finding, the rest are coords
133             sub _loopOnNodes
134             {
135 126 100   126   328 goto \&_loopOnNodesInCircle if @_ == 5;
136 114         386 goto \&_loopOnNodesInRectangle;
137             }
138              
139             sub _clearHasObjects
140             {
141 0     0   0 my $node = shift;
142              
143 0 0       0 if ($node->{CHILDREN}) {
144 0         0 for my $child (@{$node->{CHILDREN}}) {
  0         0  
145 0 0       0 return if $child->{HAS_OBJECTS};
146             }
147             }
148              
149 0         0 $node->{HAS_OBJECTS} = 0;
150 0 0       0 if ($node->{PARENT}) {
151 0         0 _clearHasObjects($node->{PARENT});
152             }
153             }
154              
155             sub _AQT_init
156             {
157 5     5   13 my $obj = shift;
158              
159 5         16 $obj->{BACKREF} = {};
160             $obj->{ROOT} = _addLevel(
161             $obj,
162             1, #current depth
163             undef, # parent - none
164             $obj->{XMIN},
165             $obj->{YMIN},
166             $obj->{XMAX},
167             $obj->{YMAX},
168 5         26 );
169             }
170              
171             sub _AQT_deinit
172       5     {
173             # do nothing in PP implementation
174             }
175              
176             sub _AQT_addObject
177             {
178 55     55   176 my ($self, $object, @coords) = @_;
179              
180 55         167 my $nodes = _loopOnNodes($self, 0, @coords);
181 55         132 for my $node (@$nodes) {
182 60         89 push @{$node->{OBJECTS}}, $object;
  60         208  
183             }
184              
185 55 50       317 $self->{BACKREF}{$object} = \@coords
186             unless @$nodes == 0;
187             }
188              
189             sub _AQT_findObjects
190             {
191 70     70   136 my ($self, @coords) = @_;
192              
193             # map returned nodes to an array containing all of
194             # their objects
195 70         115 my %hash;
196 70         104 foreach my $node (@{_loopOnNodes($self, 1, @coords)}) {
  70         154  
197 143         186 foreach my $object (@{$node->{OBJECTS}}) {
  143         238  
198 159         408 $hash{$object} = $object;
199             }
200             }
201              
202 70         424 return [values %hash];
203             }
204              
205             sub _AQT_delete
206             {
207 1     1   3 my ($self, $object) = @_;
208              
209 1 50       5 return unless exists $self->{BACKREF}{$object};
210 1         3 my @coords = @{$self->{BACKREF}{$object}};
  1         17  
211              
212 1         9 for my $node (@{_loopOnNodes($self, 1, @coords)}) {
  1         4  
213 1         3 @{$node->{OBJECTS}} = grep {$_ ne $object} @{$node->{OBJECTS}};
  1         4  
  2         6  
  1         5  
214 1 50       3 _clearHasObjects($node) if !@{$node->{OBJECTS}};
  1         5  
215             }
216              
217 1         6 delete $self->{BACKREF}{$object};
218             }
219              
220             sub _AQT_clear
221             {
222 3     3   10 my ($self) = @_;
223              
224 3         13 my @loopargs = $self->{ROOT};
225 3         44 while (my $current = shift @loopargs) {
226 31 100       81 next unless $current->{HAS_OBJECTS};
227 30         44 $current->{HAS_OBJECTS} = 0;
228              
229 30 100       112 if ($current->{CHILDREN}) {
230 7         54 push @loopargs, @{$current->{CHILDREN}};
  7         34  
231             } else {
232 23         35 @{$current->{OBJECTS}} = ();
  23         78  
233             }
234             }
235              
236 3         8 %{$self->{BACKREF}} = ();
  3         35  
237             }
238              
239             1;
240