File Coverage

blib/lib/Tree/R.pm
Criterion Covered Total %
statement 225 248 90.7
branch 78 106 73.5
condition 46 63 73.0
subroutine 22 23 95.6
pod 0 20 0.0
total 371 460 80.6


line stmt bran cond sub pod time code
1             package Tree::R;
2              
3 1     1   25756 use strict;
  1         3  
  1         23  
4 1     1   5 use warnings;
  1         2  
  1         41  
5              
6             require Exporter;
7 1     1   7810 use AutoLoader qw(AUTOLOAD);
  1         1406  
  1         5  
8              
9             our @ISA = qw(Exporter);
10              
11             # Items to export into callers namespace by default. Note: do not export
12             # names by default without a very good reason. Use EXPORT_OK instead.
13             # Do not simply export all your public functions/methods/constants.
14              
15             # This allows declaration use Tree::R ':all';
16             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
17             # will save memory.
18             our %EXPORT_TAGS = ( 'all' => [ qw(
19              
20             ) ] );
21              
22             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
23              
24             our @EXPORT = qw(
25              
26             );
27              
28             our $VERSION = '0.07';
29              
30             =pod
31              
32             =head1 NAME
33              
34             Tree::R - Perl extension for the R-tree data structure and algorithms
35              
36             =head1 SYNOPSIS
37              
38             use Tree::R;
39              
40             my $rtree = Tree::R->new
41              
42             for my $object (@objects) {
43             my @bbox = $object->bbox(); # (minx,miny,maxx,maxy)
44             $rtree->insert($object,@bbox);
45             }
46              
47             my @point = (123, 456); # (x,y)
48             my @results;
49             $rtree->query_point(@point,\@results);
50             for my $object (@results) {
51             # point is in object's bounding box
52             }
53              
54             my @rect = (123, 456, 789, 1234); # (minx,miny,maxx,maxy)
55             @results = ();
56             $rtree->query_completely_within_rect(@rect,\@results);
57             for my $object (@results) {
58             # object is within rectangle
59             }
60              
61             @results = ();
62             $rtree->query_partly_within_rect(@rect,\@results);
63             for my $object (@results) {
64             # object's bounding box and rectangle overlap
65             }
66              
67             =head1 DESCRIPTION
68              
69             R-tree is a data structure for storing and indexing and efficiently
70             looking up non-zero-size spatial objects.
71              
72             =head2 EXPORT
73              
74             None by default.
75              
76             =head1 SEE ALSO
77              
78             A. Guttman: R-trees: a dynamic index structure for spatial
79             indexing. ACM SIGMOD'84, Proc. of Annual Meeting (1984), 47--57.
80              
81             N. Beckmann, H.-P. Kriegel, R. Schneider & B. Seeger: The R*-tree: an
82             efficient and robust access method for points and rectangles. Proc. of
83             the 1990 ACM SIGMOD Internat. Conf. on Management of Data (1990),
84             322--331.
85              
86             The homepage of this module is on github:
87             https://github.com/ajolma/Tree-R
88              
89             =head1 AUTHOR
90              
91             Ari Jolma
92              
93             =head1 COPYRIGHT AND LICENSE
94              
95             Copyright (C) 2005- by Ari Jolma
96              
97             This library is free software; you can redistribute it and/or modify
98             it under the terms of The Artistic License 2.0.
99              
100             =head1 REPOSITORY
101              
102             L
103              
104             =cut
105              
106             sub new {
107 4     4 0 1387 my $package = shift;
108 4         8 my %opt = @_;
109 4         7 my $self = {};
110 4         11 for my $k (keys %opt) {
111 4         9 $self->{$k} = $opt{$k};
112             }
113 4 100       13 $self->{m} = 2 unless $self->{m};
114 4 100       11 $self->{M} = 5 unless $self->{M};
115             # $self->{root} = [1,$child,@rect];
116             # $child == [[0,$object,@rect],...] if leaf or [[1,$child,@rect],...] if non-leaf
117 4   33     16 bless $self => (ref($package) or $package);
118 4         12 return $self;
119             }
120              
121             sub objects {
122 88     88 0 11049 my ($self,$objects,$N) = @_;
123 88 100       183 return unless $self->{root};
124 87 100       144 $N = $self->{root} unless $N;
125 87 50       1092 return unless $N;
126 87 100       143 unless ($N->[0]) {
127 55         841 push @$objects,$N->[1];
128             } else {
129             # check entries
130 32         31 for my $entry (@{$N->[1]}) {
  32         56  
131 75         127 $self->objects($objects,$entry);
132             }
133             }
134             }
135              
136             sub query_point {
137 17     17 0 41 my($self,$x,$y,$objects,$N) = @_;
138 17 100       36 return unless $self->{root};
139 16 100       28 $N = $self->{root} unless $N;
140 16 100 100     123 return unless $x >= $N->[2] and $x <= $N->[4] and $y >= $N->[3] and $y <= $N->[5];
      100        
      66        
141 10 100       16 unless ($N->[0]) {
142 4         12 push @$objects,$N->[1];
143             } else {
144             # check entries
145 6         10 for my $entry (@{$N->[1]}) {
  6         11  
146 14         25 $self->query_point($x,$y,$objects,$entry);
147             }
148             }
149             }
150              
151             #non-recursive from liuyi at cis.uab.edu
152             sub query_completely_within_rect {
153 2     2 0 724 my($self,$minx,$miny,$maxx,$maxy,$objects,$Node) = @_;
154 2 50       7 return unless $self->{root};
155              
156 2 50       6 $Node = $self->{root} unless $Node;
157 2         2 my @entries;
158 2         3 push @entries,\$Node;
159              
160 2         6 while (@entries>0) {
161 16         23 my $N = pop @entries;
162 16 100 66     17 if (${$N}->[2] > $maxx or # right
  16   66     39  
      66        
163 16         56 ${$N}->[4] < $minx or # left
164 14         42 ${$N}->[3] > $maxy or # above
165 14         50 ${$N}->[5] < $miny) # below
166             {
167 2         6 next;
168             }
169             else {
170 14 50 100     12 if ((!${$N}->[0])
  14   100     35  
      66        
      66        
171 8         26 and (${$N}->[2] >= $minx)
172 6         21 and (${$N}->[4] <= $maxx)
173 4         14 and (${$N}->[3] >= $miny)
174 4         13 and (${$N}->[5] <= $maxy))
175             {
176 4         5 push @$objects,${$N}->[1];
  4         8  
177             }
178            
179 14 100       21 if (${$N}->[0]) {
  14         41  
180 6         7 foreach my $e (@{${$N}->[1]}) {
  6         5  
  6         12  
181 14         41 push @entries,\$e;
182             }
183             }
184             }
185             }
186 2         5 return $objects;
187             }
188              
189             #non-recursive from liuyi at cis.uab.edu
190             sub query_partly_within_rect {
191 2     2 0 722 my($self,$minx,$miny,$maxx,$maxy,$objects,$Node) = @_;
192 2 50       7 return unless $self->{root};
193              
194 2 50       5 $Node = $self->{root} unless $Node;
195 2         2 my @entries;
196 2         3 push @entries,\$Node;
197              
198 2         5 while (@entries>0) {
199 16         20 my $N = pop @entries;
200 16 100 66     22 if (${$N}->[2] > $maxx or # right
  16   66     36  
      66        
201 16         48 ${$N}->[4] < $minx or # left
202 14         43 ${$N}->[3] > $maxy or # above
203 14         38 ${$N}->[5] < $miny) # below
204             {
205 2         7 next;
206             }
207             else {
208 14 100       14 if (!${$N}->[0]) {
  14         28  
209 8         9 push @$objects,${$N}->[1];
  8         21  
210             }
211             else {
212 6         6 foreach my $e (@{${$N}->[1]}) {
  6         7  
  6         10  
213 14         29 push @entries,\$e;
214             }
215             }
216             }
217             }
218 2         4 return $objects;
219             }
220              
221             sub insert {
222 22     22 0 1024 my ($self,$object,@rect) = @_; # rect = $minX,$minY,$maxX,$maxY
223 22         51 my $child = [0,$object,@rect];
224 22 100       46 unless ($self->{root}) {
225 4         19 $self->{root} = [1,[$child],@rect];
226             } else {
227 18         41 my $N = $self->ChooseSubTree(@rect);
228 18         208 push @{$N->[1]},$child;
  18         36  
229 18 100       20 $self->QuadraticSplit($N->[1]) if @{$N->[1]} > $self->{M};
  18         72  
230             }
231             }
232              
233             # returns the leaf which contains the object, the index of the object
234             # in the leaf, and the parent of the leaf
235              
236             sub get_leaf {
237 24     24 0 31 my ($self,$object,$leaf,$index_of_leaf,$parent) = @_;
238 24 100       50 $leaf = $self->{root} unless $leaf;
239 24         73 for my $index (0..$#{$leaf->[1]}) {
  24         50  
240 42         96 my $entry = $leaf->[1]->[$index];
241 42 100       67 unless ($entry->[0]) {
242 29 100       96 return ($parent,$index_of_leaf,$leaf,$index) if $entry->[1] == $object;
243             } else {
244 13         28 my @ret = $self->get_leaf($object,$entry,$index,$leaf);
245 13 100       44 return @ret if @ret;
246             }
247             }
248 6         14 return ();
249             }
250              
251             sub set_bboxes {
252 56     56 0 64 my ($self,$N) = @_;
253 56 100       93 $N = $self->{root} unless $N;
254 56 100       143 return @$N[2..5] if $N->[0] == 0;
255 23         20 my @bbox;
256 23         23 for my $child (@{$N->[1]}) {
  23         100  
257 45         75 my @bbox_of_child = $self->set_bboxes($child);
258 45 100       118 @bbox = @bbox ? enlarged_rect(@bbox_of_child,@bbox) : @bbox_of_child;
259             }
260 23         42 @$N[2..5] = @bbox;
261 23         49 return @bbox;
262             }
263              
264             sub remove {
265 11     11 0 1177 my ($self,$object) = @_;
266 11         23 my ($parent,$index_of_leaf,$leaf,$index) = $self->get_leaf($object);
267              
268 11 50       27 return unless $leaf;
269              
270             # remove the object
271 11         16 splice(@{$leaf->[1]},$index,1);
  11         18  
272              
273             # is the leaf too small now?
274 11 100 100     36 if ($parent and @{$leaf->[1]} < $self->{m}) {
  7         30  
275              
276             # remove the leaf
277 1         2 splice(@{$parent->[1]},$index_of_leaf,1);
  1         2  
278              
279             # is the parent now too small?
280 1 50       7 if (@{$parent->[1]} < $self->{m}) {
  1         3  
281              
282             # yes, move the children up
283 1         2 my @new_child_list;
284 1         2 for my $entry (@{$parent->[1]}) {
  1         2  
285 1         2 for my $child (@{$entry->[1]}) {
  1         2  
286 3         5 push @new_child_list,$child;
287             }
288             }
289 1         3 $parent->[1] = [@new_child_list];
290              
291             }
292              
293 1         3 $self->set_bboxes();
294              
295             # reinsert the orphans
296 1         2 for my $child (@{$leaf->[1]}) {
  1         2  
297 1         4 my $N = $self->ChooseSubTree(@$child[2..5]);
298 1         2 push @{$N->[1]},$child;
  1         2  
299 1 50       2 $self->QuadraticSplit($N->[1]) if @{$N->[1]} > $self->{M};
  1         5  
300             }
301              
302             } else {
303              
304 10         17 $self->set_bboxes();
305              
306             }
307 11 100       37 delete $self->{root} unless defined $self->{root}->[2];
308             }
309              
310             sub dump {
311 0     0 0 0 my ($self,$N,$level) = @_;
312 0 0       0 return unless $self->{root};
313 0 0       0 $N = $self->{root} unless $N;
314 0 0       0 return unless $N;
315 0 0       0 $level = 0 unless $level;
316 0 0       0 unless ($N->[0]) {
317 0         0 print "($level) object $N $N->[1] rect @$N[2..5]\n";
318             } else {
319 0         0 print "($level) subtree $N $N->[1] rect @$N[2..5]\n";
320 0         0 for my $entry (@{$N->[1]}) {
  0         0  
321 0         0 $self->dump($entry,$level+1);
322             }
323             }
324             }
325              
326             sub ChooseSubTree {
327 19     19 0 30 my ($self,@rect) = @_;
328             # CS1
329 19 50       45 unless ($self->{root}) {
330 0         0 $self->{root} = [1,[],@rect];
331 0         0 return $self->{root};
332             }
333 19         21 my $N = $self->{root};
334 28         59 CS2:
335             @$N[2..5] = enlarged_rect(@$N[2..5],@rect);
336             # print STDERR "N = $N, $N->[0], @{$N->[1]}\n";
337 28 100       64 unless ($N->[1]->[0]->[0]) { # is leaf
338 19         33 return $N;
339             } else {
340 9         11 my $chosen;
341             my $needed_enlargement_of_chosen;
342 0         0 my $area_of_chosen;
343 9         8 for my $entry (@{$N->[1]}) {
  9         18  
344 18         47 my @rect_of_entry = @$entry[2..5];
345 18         32 my $area = area_of_rect(@rect_of_entry);
346 18         34 my $needed_enlargement = area_of_rect(enlarged_rect(@rect_of_entry,@rect)) - $area;
347 18 100 100     79 if (!$chosen or
      100        
348             $needed_enlargement < $needed_enlargement_of_chosen or
349             $area < $area_of_chosen)
350             {
351 16         17 $chosen = $entry;
352 16         14 $needed_enlargement_of_chosen = $needed_enlargement;
353 16         229 $area_of_chosen = $area;
354             }
355             }
356             # CS3
357 9         10 $N = $chosen;
358 9         293 goto CS2;
359             }
360             }
361              
362             sub QuadraticSplit {
363 3     3 0 6 my($self,$group) = @_;
364 3         13 my($E1,$E2) = PickSeeds($group);
365 3         5 $E2 = splice(@$group,$E2,1);
366 3         5 $E1 = splice(@$group,$E1,1);
367 3         9 $E1 = [1,[$E1],@$E1[2..5]];
368 3         7 $E2 = [1,[$E2],@$E2[2..5]];
369             do {
370 6         13 DistributeEntry($group,$E1,$E2);
371             } until @$group == 0 or
372             @$E1 == $self->{M}-$self->{m}+1 or
373 3   66     18 @$E2 == $self->{M}-$self->{m}+1;
      66        
374 3 50       7 unless (@$group == 0) {
375 0 0       0 if (@$E1 < @$E2) {
376 0         0 while (@$group > 1) {
377 0         0 add_to_group($E1,pop @$group);
378             }
379             } else {
380 0         0 while (@$group > 1) {
381 0         0 add_to_group($E2,pop @$group);
382             }
383             }
384             }
385 3         13 push @$group,($E1,$E2);
386             }
387              
388             sub PickSeeds {
389 3     3 0 4 my($group) = @_;
390 3         7 my ($seed1,$seed2,$d,$e1);
391 3         10 for ($e1 = 0; $e1 < @$group-1; $e1++) {
392 9         9 my @rect1 = @{$group->[$e1]}[2..5];
  9         23  
393 9         18 my $a1 = area_of_rect(@rect1);
394 9         10 my $e2;
395 9         20 for ($e2 = $e1+1; $e2 < @$group; $e2++) {
396 18         20 my @rect2 = @{$group->[$e2]}[2..5];
  18         36  
397 18         30 my @R = enlarged_rect(@rect1,@rect2);
398 18         27 my $d_test = area_of_rect(@R) - $a1 - area_of_rect(@rect2);
399 18 50 33     40 if (!$d or $d_test > $d) {
400 18         29 $seed1 = min($e1,$e2);
401 18         30 $seed2 = max($e1,$e2);
402             }
403             }
404             }
405 3         7 return ($seed1,$seed2);
406             }
407              
408             sub DistributeEntry {
409 6     6 0 8 my($from,$to1,$to2) = @_;
410 6         14 my $area_of_to1 = area_of_rect(@$to1[2..5]);
411 6         11 my $area_of_to2 = area_of_rect(@$to2[2..5]);
412 6         14 my ($next,$area_of_enlarged1,$area_of_enlarged2) =
413             PickNext($from,$to1,$to2,$area_of_to1,$area_of_to2);
414 6         10 my $cmp = $area_of_enlarged1 - $area_of_to1 <=> $area_of_enlarged2 - $area_of_to2;
415 6 50       11 $cmp = $area_of_to1 <=> $area_of_to2 if $cmp == 0;
416 6 50       12 $cmp = @{$to1->[1]} <=> @{$to2->[1]} if $cmp == 0;
  0         0  
  0         0  
417 6 100       18 if ($cmp <= 0) {
    50          
418 1         3 add_to_group($to1,$from->[$next]);
419 1         8 splice(@$from,$next,1);
420             } elsif ($cmp > 0) {
421 5         12 add_to_group($to2,$from->[$next]);
422 5         37 splice(@$from,$next,1);
423             }
424             }
425              
426             sub PickNext {
427 6     6 0 8 my($from,$to1,$to2,$area_of_to1,$area_of_to2) = @_;
428 6         7 my $next;
429             my $max_diff;
430 0         0 my $area_of_enlarged1;
431 0         0 my $area_of_enlarged2;
432 6         12 my @cover_of_to1 = @$to1[2..5];
433 6         10 my @cover_of_to2 = @$to2[2..5];
434 6         12 for my $i (0..$#$from) {
435 9         13 my $a1 = area_of_rect(enlarged_rect(@cover_of_to1,@{$from->[$i]}[2..5]));
  9         22  
436 9 100       20 $area_of_enlarged1 = $a1 unless defined $area_of_enlarged1;
437 9         15 my $a2 = area_of_rect(enlarged_rect(@cover_of_to2,@{$from->[$i]}[2..5]));
  9         16  
438 9 100       19 $area_of_enlarged2 = $a2 unless defined $area_of_enlarged2;
439 9         10 my $diff = abs(($area_of_enlarged1 - $area_of_to1) - ($area_of_enlarged2 - $area_of_to2));
440 9 50 33     21 if (!$next or $diff > $max_diff) {
441 9         8 $next = $i;
442 9         10 $max_diff = $diff;
443 9         9 $area_of_enlarged1 = $a1;
444 9         12 $area_of_enlarged2 = $a2;
445             }
446             }
447 6         14 return ($next,$area_of_enlarged1,$area_of_enlarged2);
448             }
449              
450             sub add_to_group {
451 6     6 0 7 my($to,$entry) = @_;
452 6         7 push @{$to->[1]},$entry;
  6         10  
453 6         15 @$to[2..5] = enlarged_rect(@$to[2..5],@$entry[2..5]);
454             }
455              
456             sub enlarged_rect {
457 111     111 0 198 return (min($_[0],$_[4]),min($_[1],$_[5]),max($_[2],$_[6]),max($_[3],$_[7]));
458             }
459              
460             sub area_of_rect {
461 111     111 0 188 ($_[3]-$_[1])*($_[2]-$_[0]);
462             }
463              
464             sub min {
465 240 100   240 0 4497 $_[0] > $_[1] ? $_[1] : $_[0];
466             }
467              
468             sub max {
469 240 100   240 0 816 $_[0] > $_[1] ? $_[0] : $_[1];
470             }
471              
472             1;
473             __END__