File Coverage

blib/lib/Tree/R.pm
Criterion Covered Total %
statement 225 248 90.7
branch 78 106 73.5
condition 45 63 71.4
subroutine 22 23 95.6
pod 0 20 0.0
total 370 460 80.4


line stmt bran cond sub pod time code
1             package Tree::R;
2              
3 1     1   271053 use strict;
  1         3  
  1         36  
4 1     1   6 use warnings;
  1         3  
  1         56  
5              
6             require Exporter;
7 1     1   33044 use AutoLoader qw(AUTOLOAD);
  1         2951  
  1         12  
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.072';
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 1244 my $package = shift;
108 4         10 my %opt = @_;
109 4         6 my $self = {};
110 4         10 for my $k (keys %opt) {
111 4         11 $self->{$k} = $opt{$k};
112             }
113 4 100       14 $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     14 bless $self => (ref($package) or $package);
118 4         12 return $self;
119             }
120              
121             sub objects {
122 88     88 0 2664 my ($self,$objects,$N) = @_;
123 88 100       159 return unless $self->{root};
124 87 100       148 $N = $self->{root} unless $N;
125 87 50       130 return unless $N;
126 87 100       129 unless ($N->[0]) {
127 55         152 push @$objects,$N->[1];
128             } else {
129             # check entries
130 32         29 for my $entry (@{$N->[1]}) {
  32         53  
131 75         134 $self->objects($objects,$entry);
132             }
133             }
134             }
135              
136             sub query_point {
137 13     13 0 40 my($self,$x,$y,$objects,$N) = @_;
138 13 100       31 return unless $self->{root};
139 12 100       21 $N = $self->{root} unless $N;
140 12 100 66     95 return unless $x >= $N->[2] and $x <= $N->[4] and $y >= $N->[3] and $y <= $N->[5];
      100        
      66        
141 8 100       16 unless ($N->[0]) {
142 4         11 push @$objects,$N->[1];
143             } else {
144             # check entries
145 4         5 for my $entry (@{$N->[1]}) {
  4         9  
146 10         21 $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 727 my($self,$minx,$miny,$maxx,$maxy,$objects,$Node) = @_;
154 2 50       7 return unless $self->{root};
155              
156 2 50       5 $Node = $self->{root} unless $Node;
157 2         3 my @entries;
158 2         3 push @entries,\$Node;
159              
160 2         6 while (@entries>0) {
161 16         22 my $N = pop @entries;
162 16 100 66     17 if (${$N}->[2] > $maxx or # right
  16   66     35  
      66        
163 16         54 ${$N}->[4] < $minx or # left
164 14         40 ${$N}->[3] > $maxy or # above
165 14         46 ${$N}->[5] < $miny) # below
166             {
167 2         5 next;
168             }
169             else {
170 14 50 100     13 if ((!${$N}->[0])
  14   100     38  
      66        
      66        
171 8         24 and (${$N}->[2] >= $minx)
172 6         20 and (${$N}->[4] <= $maxx)
173 4         13 and (${$N}->[3] >= $miny)
174 4         12 and (${$N}->[5] <= $maxy))
175             {
176 4         5 push @$objects,${$N}->[1];
  4         6  
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         36 push @entries,\$e;
182             }
183             }
184             }
185             }
186 2         3 return $objects;
187             }
188              
189             #non-recursive from liuyi at cis.uab.edu
190             sub query_partly_within_rect {
191 2     2 0 732 my($self,$minx,$miny,$maxx,$maxy,$objects,$Node) = @_;
192 2 50       6 return unless $self->{root};
193              
194 2 50       6 $Node = $self->{root} unless $Node;
195 2         2 my @entries;
196 2         3 push @entries,\$Node;
197              
198 2         6 while (@entries>0) {
199 16         18 my $N = pop @entries;
200 16 100 66     54 if (${$N}->[2] > $maxx or # right
  16   66     55  
      66        
201 16         55 ${$N}->[4] < $minx or # left
202 14         42 ${$N}->[3] > $maxy or # above
203 14         32 ${$N}->[5] < $miny) # below
204             {
205 2         5 next;
206             }
207             else {
208 14 100       13 if (!${$N}->[0]) {
  14         25  
209 8         9 push @$objects,${$N}->[1];
  8         23  
210             }
211             else {
212 6         5 foreach my $e (@{${$N}->[1]}) {
  6         5  
  6         14  
213 14         29 push @entries,\$e;
214             }
215             }
216             }
217             }
218 2         4 return $objects;
219             }
220              
221             sub insert {
222 22     22 0 1033 my ($self,$object,@rect) = @_; # rect = $minX,$minY,$maxX,$maxY
223 22         44 my $child = [0,$object,@rect];
224 22 100       49 unless ($self->{root}) {
225 4         17 $self->{root} = [1,[$child],@rect];
226             } else {
227 18         36 my $N = $self->ChooseSubTree(@rect);
228 18         19 push @{$N->[1]},$child;
  18         32  
229 18 100       20 $self->QuadraticSplit($N->[1]) if @{$N->[1]} > $self->{M};
  18         73  
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 22     22 0 28 my ($self,$object,$leaf,$index_of_leaf,$parent) = @_;
238 22 100       42 $leaf = $self->{root} unless $leaf;
239 22         25 for my $index (0..$#{$leaf->[1]}) {
  22         47  
240 37         49 my $entry = $leaf->[1]->[$index];
241 37 100       62 unless ($entry->[0]) {
242 26 100       134 return ($parent,$index_of_leaf,$leaf,$index) if $entry->[1] == $object;
243             } else {
244 11         24 my @ret = $self->get_leaf($object,$entry,$index,$leaf);
245 11 100       46 return @ret if @ret;
246             }
247             }
248 4         20 return ();
249             }
250              
251             sub set_bboxes {
252 50     50 0 60 my ($self,$N) = @_;
253 50 100       88 $N = $self->{root} unless $N;
254 50 100       131 return @$N[2..5] if $N->[0] == 0;
255 19         50 my @bbox;
256 19         21 for my $child (@{$N->[1]}) {
  19         34  
257 39         72 my @bbox_of_child = $self->set_bboxes($child);
258 39 100       105 @bbox = @bbox ? enlarged_rect(@bbox_of_child,@bbox) : @bbox_of_child;
259             }
260 19         34 @$N[2..5] = @bbox;
261 19         34 return @bbox;
262             }
263              
264             sub remove {
265 11     11 0 1247 my ($self,$object) = @_;
266 11         21 my ($parent,$index_of_leaf,$leaf,$index) = $self->get_leaf($object);
267              
268 11 50       23 return unless $leaf;
269              
270             # remove the object
271 11         11 splice(@{$leaf->[1]},$index,1);
  11         20  
272              
273             # is the leaf too small now?
274 11 100 100     34 if ($parent and @{$leaf->[1]} < $self->{m}) {
  7         42  
275              
276             # remove the leaf
277 3         4 splice(@{$parent->[1]},$index_of_leaf,1);
  3         4  
278              
279             # is the parent now too small?
280 3 50       4 if (@{$parent->[1]} < $self->{m}) {
  3         9  
281              
282             # yes, move the children up
283 3         4 my @new_child_list;
284 3         4 for my $entry (@{$parent->[1]}) {
  3         4  
285 3         4 for my $child (@{$entry->[1]}) {
  3         5  
286 9         13 push @new_child_list,$child;
287             }
288             }
289 3         9 $parent->[1] = [@new_child_list];
290              
291             }
292              
293 3         8 $self->set_bboxes();
294              
295             # reinsert the orphans
296 3         4 for my $child (@{$leaf->[1]}) {
  3         6  
297 3         9 my $N = $self->ChooseSubTree(@$child[2..5]);
298 3         3 push @{$N->[1]},$child;
  3         7  
299 3 50       3 $self->QuadraticSplit($N->[1]) if @{$N->[1]} > $self->{M};
  3         13  
300             }
301              
302             } else {
303              
304 8         16 $self->set_bboxes();
305              
306             }
307 11 100       42 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 21     21 0 31 my ($self,@rect) = @_;
328             # CS1
329 21 50       43 unless ($self->{root}) {
330 0         0 $self->{root} = [1,[],@rect];
331 0         0 return $self->{root};
332             }
333 21         25 my $N = $self->{root};
334 30         71 CS2:
335             @$N[2..5] = enlarged_rect(@$N[2..5],@rect);
336             # print STDERR "N = $N, $N->[0], @{$N->[1]}\n";
337 30 100       61 unless ($N->[1]->[0]->[0]) { # is leaf
338 21         96 return $N;
339             } else {
340 9         11 my $chosen;
341             my $needed_enlargement_of_chosen;
342 0         0 my $area_of_chosen;
343 9         11 for my $entry (@{$N->[1]}) {
  9         19  
344 18         31 my @rect_of_entry = @$entry[2..5];
345 18         29 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     77 if (!$chosen or
      100        
348             $needed_enlargement < $needed_enlargement_of_chosen or
349             $area < $area_of_chosen)
350             {
351 17         17 $chosen = $entry;
352 17         17 $needed_enlargement_of_chosen = $needed_enlargement;
353 17         30 $area_of_chosen = $area;
354             }
355             }
356             # CS3
357 9         12 $N = $chosen;
358 9         60 goto CS2;
359             }
360             }
361              
362             sub QuadraticSplit {
363 5     5 0 6 my($self,$group) = @_;
364 5         11 my($E1,$E2) = PickSeeds($group);
365 5         10 $E2 = splice(@$group,$E2,1);
366 5         6 $E1 = splice(@$group,$E1,1);
367 5         15 $E1 = [1,[$E1],@$E1[2..5]];
368 5         15 $E2 = [1,[$E2],@$E2[2..5]];
369             do {
370 10         21 DistributeEntry($group,$E1,$E2);
371             } until @$group == 0 or
372             @$E1 == $self->{M}-$self->{m}+1 or
373 5   66     20 @$E2 == $self->{M}-$self->{m}+1;
      66        
374 5 50       12 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 5         13 push @$group,($E1,$E2);
386             }
387              
388             sub PickSeeds {
389 5     5 0 8 my($group) = @_;
390 5         4 my ($seed1,$seed2,$d,$e1);
391 5         15 for ($e1 = 0; $e1 < @$group-1; $e1++) {
392 15         15 my @rect1 = @{$group->[$e1]}[2..5];
  15         35  
393 15         30 my $a1 = area_of_rect(@rect1);
394 15         18 my $e2;
395 15         32 for ($e2 = $e1+1; $e2 < @$group; $e2++) {
396 30         35 my @rect2 = @{$group->[$e2]}[2..5];
  30         62  
397 30         55 my @R = enlarged_rect(@rect1,@rect2);
398 30         58 my $d_test = area_of_rect(@R) - $a1 - area_of_rect(@rect2);
399 30 50 33     64 if (!$d or $d_test > $d) {
400 30         54 $seed1 = min($e1,$e2);
401 30         45 $seed2 = max($e1,$e2);
402             }
403             }
404             }
405 5         9 return ($seed1,$seed2);
406             }
407              
408             sub DistributeEntry {
409 10     10 0 12 my($from,$to1,$to2) = @_;
410 10         20 my $area_of_to1 = area_of_rect(@$to1[2..5]);
411 10         20 my $area_of_to2 = area_of_rect(@$to2[2..5]);
412 10         21 my ($next,$area_of_enlarged1,$area_of_enlarged2) =
413             PickNext($from,$to1,$to2,$area_of_to1,$area_of_to2);
414 10         14 my $cmp = $area_of_enlarged1 - $area_of_to1 <=> $area_of_enlarged2 - $area_of_to2;
415 10 50       15 $cmp = $area_of_to1 <=> $area_of_to2 if $cmp == 0;
416 10 50       21 $cmp = @{$to1->[1]} <=> @{$to2->[1]} if $cmp == 0;
  0         0  
  0         0  
417 10 100       23 if ($cmp <= 0) {
    50          
418 6         14 add_to_group($to1,$from->[$next]);
419 6         48 splice(@$from,$next,1);
420             } elsif ($cmp > 0) {
421 4         9 add_to_group($to2,$from->[$next]);
422 4         18 splice(@$from,$next,1);
423             }
424             }
425              
426             sub PickNext {
427 10     10 0 12 my($from,$to1,$to2,$area_of_to1,$area_of_to2) = @_;
428 10         10 my $next;
429             my $max_diff;
430 0         0 my $area_of_enlarged1;
431 0         0 my $area_of_enlarged2;
432 10         20 my @cover_of_to1 = @$to1[2..5];
433 10         18 my @cover_of_to2 = @$to2[2..5];
434 10         18 for my $i (0..$#$from) {
435 15         20 my $a1 = area_of_rect(enlarged_rect(@cover_of_to1,@{$from->[$i]}[2..5]));
  15         36  
436 15 100       31 $area_of_enlarged1 = $a1 unless defined $area_of_enlarged1;
437 15         22 my $a2 = area_of_rect(enlarged_rect(@cover_of_to2,@{$from->[$i]}[2..5]));
  15         29  
438 15 100       31 $area_of_enlarged2 = $a2 unless defined $area_of_enlarged2;
439 15         20 my $diff = abs(($area_of_enlarged1 - $area_of_to1) - ($area_of_enlarged2 - $area_of_to2));
440 15 50 33     33 if (!$next or $diff > $max_diff) {
441 15         16 $next = $i;
442 15         13 $max_diff = $diff;
443 15         14 $area_of_enlarged1 = $a1;
444 15         22 $area_of_enlarged2 = $a2;
445             }
446             }
447 10         25 return ($next,$area_of_enlarged1,$area_of_enlarged2);
448             }
449              
450             sub add_to_group {
451 10     10 0 11 my($to,$entry) = @_;
452 10         12 push @{$to->[1]},$entry;
  10         16  
453 10         26 @$to[2..5] = enlarged_rect(@$to[2..5],@$entry[2..5]);
454             }
455              
456             sub enlarged_rect {
457 139     139 0 250 return (min($_[0],$_[4]),min($_[1],$_[5]),max($_[2],$_[6]),max($_[3],$_[7]));
458             }
459              
460             sub area_of_rect {
461 161     161 0 262 ($_[3]-$_[1])*($_[2]-$_[0]);
462             }
463              
464             sub min {
465 308 100   308 0 742 $_[0] > $_[1] ? $_[1] : $_[0];
466             }
467              
468             sub max {
469 308 100   308 0 796 $_[0] > $_[1] ? $_[0] : $_[1];
470             }
471              
472             1;
473             __END__