File Coverage

blib/lib/Tree/R.pm
Criterion Covered Total %
statement 227 265 85.6
branch 73 110 66.3
condition 45 90 50.0
subroutine 24 27 88.8
pod 0 22 0.0
total 369 514 71.7


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