File Coverage

blib/lib/Algorithm/QuadTree.pm
Criterion Covered Total %
statement 6 95 6.3
branch 0 20 0.0
condition 0 21 0.0
subroutine 2 13 15.3
pod 6 6 100.0
total 14 155 9.0


line stmt bran cond sub pod time code
1             package Algorithm::QuadTree;
2              
3 1     1   7198 use strict;
  1         2  
  1         33  
4 1     1   6 use Carp;
  1         2  
  1         1340  
5              
6             our $VERSION = 0.1;
7              
8             1;
9              
10             ###############################
11             #
12             # sub new() - constructor
13             #
14             # Arguments are a hash:
15             #
16             # -xmin => minimum x value
17             # -xmax => maximum x value
18             # -ymin => minimum y value
19             # -ymax => maximum y value
20             # -depth => depth of tree
21             #
22             # Creating a new QuadTree objects automatically
23             # segments the given area into quadtrees of the
24             # specified depth.
25             #
26             ###############################
27              
28             sub new {
29 0     0 1   my $self = shift;
30 0   0       my $class = ref($self) || $self;
31              
32 0           my $obj = bless {} => $class;
33              
34 0           $obj->{BACKREF} = {};
35 0           $obj->{OBJECTS} = [];
36 0           $obj->{ORIGIN} = [0, 0];
37 0           $obj->{SCALE} = 1;
38              
39 0           my %args = @_;
40              
41 0           for my $arg (qw/xmin ymin xmax ymax depth/) {
42 0 0         unless (exists $args{"-$arg"}) {
43 0           carp "- must specify $arg";
44 0           return undef;
45             }
46              
47 0           $obj->{uc $arg} = $args{"-$arg"};
48             }
49              
50 0           $obj->_segment;
51              
52 0           return $obj;
53             }
54              
55             ###############################
56             #
57             # sub _segment() - private method
58             #
59             # This method does the actual segmentation
60             # and stores everything internally.
61             #
62             ###############################
63              
64             sub _segment {
65 0     0     my $obj = shift;
66              
67 0           $obj->_addLevel(
68             $obj->{XMIN},
69             $obj->{YMIN},
70             $obj->{XMAX},
71             $obj->{YMAX},
72             1, # current depth
73             0, # current index
74             undef, # parent index
75             );
76              
77             }
78              
79             ###############################
80             #
81             # sub _addLevel() - private method
82             #
83             # This method segments a given area
84             # and adds a level to the tree.
85             #
86             ###############################
87              
88             sub _addLevel {
89 0     0     my ($obj,
90             $xmin,
91             $ymin,
92             $xmax,
93             $ymax,
94             $curDepth,
95             $index,
96             $parent,
97             ) = @_;
98              
99 0           $obj->{AREA} [$index] = [$xmin, $ymin, $xmax, $ymax];
100 0           $obj->{PARENT} [$index] = $parent;
101 0           $obj->{CHILDREN}[$index] = [];
102 0           $obj->{OBJECTS} [$index] = [];
103              
104 0 0         if (defined $parent) {
105 0           push @{$obj->{CHILDREN}[$parent]} => $index;
  0            
106             }
107              
108 0 0         return if $curDepth == $obj->{DEPTH};
109              
110 0           my $xmid = $xmin + ($xmax - $xmin) / 2;
111 0           my $ymid = $ymin + ($ymax - $ymin) / 2;
112              
113             # now segment in the following order (doesn't matter):
114             # top left, top right, bottom left, bottom right
115 0           $obj->_addLevel($xmin, $ymid, $xmid, $ymax, # tl
116             $curDepth + 1, 4 * $index + 1, $index);
117 0           $obj->_addLevel($xmid, $ymid, $xmax, $ymax, # tr
118             $curDepth + 1, 4 * $index + 2, $index);
119 0           $obj->_addLevel($xmin, $ymin, $xmid, $ymid, # bl
120             $curDepth + 1, 4 * $index + 3, $index);
121 0           $obj->_addLevel($xmid, $ymin, $xmax, $ymid, # br
122             $curDepth + 1, 4 * $index + 4, $index);
123             }
124              
125             ###############################
126             #
127             # sub add() - public method
128             #
129             # This method adds an object to the tree.
130             # The arguments are a unique tag to identify
131             # the object, and the bounding box of the object.
132             # It automatically assigns the proper quadtree
133             # sections to each object.
134             #
135             ###############################
136              
137             sub add {
138 0     0 1   my ($self,
139             $objRef,
140             @coords,
141             ) = @_;
142              
143             # assume that $objRef is unique.
144             # assume coords are (xmin, ymix, xmax, ymax).
145              
146             # modify coords according to window.
147 0           @coords = $self->_adjustCoords(@coords);
148              
149 0 0         ($coords[0], $coords[2]) = ($coords[2], $coords[0]) if
150             $coords[2] < $coords[0];
151 0 0         ($coords[1], $coords[3]) = ($coords[3], $coords[1]) if
152             $coords[3] < $coords[1];
153              
154 0           $self->_addObjToChild(
155             0, # current index
156             $objRef,
157             @coords,
158             );
159             }
160              
161             ###############################
162             #
163             # sub _addObjToChild() - private method
164             #
165             # This method is used internally. Given
166             # a tree segment, an object and its area,
167             # it checks to see whether the object is to
168             # be included in the segment or not.
169             # The object is not included if it does not
170             # overlap the segment.
171             #
172             ###############################
173              
174             sub _addObjToChild {
175 0     0     my ($self,
176             $index,
177             $objRef,
178             @coords,
179             ) = @_;
180              
181             # first check if obj overlaps current segment.
182             # if not, return.
183 0           my ($cxmin, $cymin, $cxmax, $cymax) = @{$self->{AREA}[$index]};
  0            
184              
185             return if
186 0 0 0       $coords[0] > $cxmax ||
      0        
      0        
187             $coords[2] < $cxmin ||
188             $coords[1] > $cymax ||
189             $coords[3] < $cymin;
190              
191             # Only add the object to the segment if we are at the last
192             # level of the tree.
193             # Else, keep traversing down.
194              
195 0 0         unless (@{$self->{CHILDREN}[$index]}) {
  0            
196 0           push @{$self->{OBJECTS}[$index]} => $objRef; # points from leaf to object
  0            
197 0           push @{$self->{BACKREF}{$objRef}} => $index; # points from object to leaf
  0            
198              
199             } else {
200             # Now, traverse down the hierarchy.
201 0           for my $child (@{$self->{CHILDREN}[$index]}) {
  0            
202 0           $self->_addObjToChild(
203             $child,
204             $objRef,
205             @coords,
206             );
207             }
208             }
209             }
210              
211             ###############################
212             #
213             # sub delete() - public method
214             #
215             # This method deletes an object from the tree.
216             #
217             ###############################
218              
219             sub delete {
220 0     0 1   my ($self,
221             $objRef,
222             ) = @_;
223              
224 0 0         return unless exists $self->{BACKREF}{$objRef};
225              
226 0           for my $i (@{$self->{BACKREF}{$objRef}}) {
  0            
227 0           $self->{OBJECTS}[$i] = grep {$_ ne $objRef} @{$self->{OBJECTS}[$i]};
  0            
  0            
228             }
229              
230 0           delete $self->{BACKREF}{$objRef};
231             }
232              
233             ###############################
234             #
235             # sub getEnclosedObjects() - public method
236             #
237             # This method takes an area, and returns all objects
238             # enclosed in that area.
239             #
240             ###############################
241              
242             sub getEnclosedObjects {
243 0     0 1   my ($self,
244             @coords) = @_;
245              
246 0           $self->{TEMP} = [];
247              
248 0           @coords = $self->_adjustCoords(@coords);
249              
250 0           $self->_checkOverlap(
251             0, # current index
252             @coords,
253             );
254              
255             # uniquify {TEMP}.
256 0           my %temp;
257 0           @temp{@{$self->{TEMP}}} = undef;
  0            
258              
259             # PS. I don't check explicitly if those objects
260             # are enclosed in the given area. They are just
261             # part of the segments that are enclosed in the
262             # given area. TBD.
263              
264 0           return [keys %temp];
265             }
266              
267             ###############################
268             #
269             # sub _adjustCoords() - private method
270             #
271             # This method adjusts the given coordinates
272             # according to the stored window. This is used
273             # when we 'zoom in' to avoid searching in areas
274             # that are not visible in the canvas.
275             #
276             ###############################
277              
278             sub _adjustCoords {
279 0     0     my ($self, @coords) = @_;
280              
281             # modify coords according to window.
282             $_ = $self->{ORIGIN}[0] + $_ / $self->{SCALE}
283 0           for $coords[0], $coords[2];
284             $_ = $self->{ORIGIN}[1] + $_ / $self->{SCALE}
285 0           for $coords[1], $coords[3];
286              
287 0           return @coords;
288             }
289              
290             ###############################
291             #
292             # sub _checkOverlap() - private method
293             #
294             # This method checks if the given coordinates overlap
295             # the specified tree segment. If not, nothing happens.
296             # If it does overlap, then it is called recuresively
297             # on all the segment's children. If the segment is a
298             # leaf, then its associated objects are pushed onto
299             # a temporary array for later access.
300             #
301             ###############################
302              
303             sub _checkOverlap {
304 0     0     my ($self,
305             $index,
306             @coords,
307             ) = @_;
308              
309             # first check if obj overlaps current segment.
310             # if not, return.
311 0           my ($cxmin, $cymin, $cxmax, $cymax) = @{$self->{AREA}[$index]};
  0            
312              
313             return if
314 0 0 0       $coords[0] >= $cxmax ||
      0        
      0        
315             $coords[2] <= $cxmin ||
316             $coords[1] >= $cymax ||
317             $coords[3] <= $cymin;
318              
319 0 0         unless (@{$self->{CHILDREN}[$index]}) {
  0            
320 0           push @{$self->{TEMP}} => @{$self->{OBJECTS}[$index]};
  0            
  0            
321             } else {
322             # Now, traverse down the hierarchy.
323 0           for my $child (@{$self->{CHILDREN}[$index]}) {
  0            
324 0           $self->_checkOverlap(
325             $child,
326             @coords,
327             );
328             }
329             }
330             }
331              
332             ###############################
333             #
334             # sub setWindow() - public method
335             #
336             # This method takes an area as input, and
337             # sets it as the active window. All new
338             # calls to any method will refer to that area.
339             #
340             ###############################
341              
342             sub setWindow {
343 0     0 1   my ($self, $sx, $sy, $s) = @_;
344              
345 0           $self->{ORIGIN}[0] += $sx / $self->{SCALE};
346 0           $self->{ORIGIN}[1] += $sy / $self->{SCALE};
347 0           $self->{SCALE} *= $s;
348             }
349              
350             ###############################
351             #
352             # sub setWindow() - public method
353             # This resets the window.
354             #
355             ###############################
356              
357             sub resetWindow {
358 0     0 1   my $self = shift;
359              
360 0           $self->{ORIGIN}[$_] = 0 for 0 .. 1;
361 0           $self->{SCALE} = 1;
362             }
363              
364             __END__