File Coverage

blib/lib/Algorithm/QuadTree.pm
Criterion Covered Total %
statement 68 70 97.1
branch 14 18 77.7
condition 3 5 60.0
subroutine 15 15 100.0
pod 7 7 100.0
total 107 115 93.0


line stmt bran cond sub pod time code
1             package Algorithm::QuadTree;
2             $Algorithm::QuadTree::VERSION = '0.4';
3 6     6   405982 use strict;
  6         64  
  6         179  
4 6     6   33 use warnings;
  6         22  
  6         155  
5 6     6   29 use Carp;
  6         13  
  6         835  
6              
7             ###############################
8             #
9             # Creating a new QuadTree objects automatically
10             # segments the given area into quadtrees of the
11             # specified depth.
12             #
13             # Arguments are a hash:
14             #
15             # -xmin => minimum x value
16             # -xmax => maximum x value
17             # -ymin => minimum y value
18             # -ymax => maximum y value
19             # -depth => depth of tree
20             #
21             ###############################
22              
23             BEGIN {
24 6     6   2682 require Algorithm::QuadTree::PP;
25 6         19 my $backend = 'Algorithm::QuadTree::PP';
26              
27 6   100     38 my $check_backend = $ENV{ALGORITHM_QUADTREE_BACKEND} || 'Algorithm::QuadTree::XS';
28 6 100       345 if (eval "require $check_backend; 1;") {
29 5         12 $backend = $check_backend;
30             }
31              
32 6         525 $backend->import;
33             }
34              
35             # List::Util 1.45 added 'uniqstr'
36 6     6   49 use constant HAS_LIST_UTIL => eval { require List::Util; List::Util->VERSION('1.45'); 1 };
  6         11  
  6         11  
  6         24  
  6         134  
  6         5247  
37              
38             sub new
39             {
40 5     5 1 620 my $self = shift;
41 5   33     46 my $class = ref($self) || $self;
42 5         35 my %args = @_;
43              
44 5         20 my $obj = bless {}, $class;
45              
46 5         18 for my $arg (qw/xmin ymin xmax ymax depth/) {
47 25 50       69 unless (exists $args{"-$arg"}) {
48 0         0 carp "- must specify $arg";
49 0         0 return undef;
50             }
51              
52 25         89 $obj->{uc $arg} = $args{"-$arg"};
53             }
54              
55 5         19 $obj->{ORIGIN} = [0, 0];
56 5         14 $obj->{SCALE} = 1;
57              
58 5         24 _AQT_init($obj);
59              
60 5         27 return $obj;
61             }
62              
63             sub DESTROY
64             {
65 5     5   13347 my ($self) = @_;
66              
67 5         27 _AQT_deinit($self);
68             }
69              
70             # modify coords according to window
71             sub _adjustCoords
72             {
73 6     6   14 my ($self, @coords) = @_;
74              
75 6 100       19 if (@coords == 4) {
    50          
76             # rectangle
77              
78             $_ = $self->{ORIGIN}[0] + $_ / $self->{SCALE}
79 4         20 for $coords[0], $coords[2];
80             $_ = $self->{ORIGIN}[1] + $_ / $self->{SCALE}
81 4         14 for $coords[1], $coords[3];
82             }
83             elsif (@coords == 3) {
84             # circle
85              
86 2         5 $coords[0] = $self->{ORIGIN}[0] + $coords[0] / $self->{SCALE};
87 2         15 $coords[1] = $self->{ORIGIN}[1] + $coords[1] / $self->{SCALE};
88 2         6 $coords[2] /= $self->{SCALE};
89             }
90              
91 6         18 return @coords;
92             }
93              
94             sub add
95             {
96 55     55 1 7132 my ($self, $object, @coords) = @_;
97              
98             # assume that $object is unique.
99             # assume coords are (xmin, ymix, xmax, ymax) or (centerx, centery, radius)
100              
101             @coords = $self->_adjustCoords(@coords)
102 55 100       139 unless $self->{SCALE} == 1;
103              
104             # if the object is rectangular, make sure the lower coordinate is always
105             # the first one
106 55 100       122 if (@coords == 4) {
107 50 50       104 ($coords[0], $coords[2]) = ($coords[2], $coords[0])
108             if $coords[2] < $coords[0];
109              
110 50 50       98 ($coords[1], $coords[3]) = ($coords[3], $coords[1])
111             if $coords[3] < $coords[1];
112             }
113              
114 55         165 _AQT_addObject($self, $object, @coords);
115              
116 55         138 return;
117             }
118              
119             sub delete
120             {
121 1     1 1 1097 my ($self, $object) = @_;
122              
123 1         14 _AQT_delete($self, $object);
124              
125 1         3 return;
126             }
127              
128             sub clear
129             {
130 3     3 1 8020 my $self = shift;
131              
132 3         12 _AQT_clear($self);
133              
134 3         6 return;
135             }
136              
137             sub getEnclosedObjects
138             {
139 70     70 1 50294 my ($self, @coords) = @_;
140              
141             @coords = $self->_adjustCoords(@coords)
142 70 100       201 unless $self->{SCALE} == 1;
143              
144 70         205 return _uniq(_AQT_findObjects($self, @coords));
145              
146             # PS. I don't check explicitly if those objects
147             # are enclosed in the given area. They are just
148             # part of the segments that are enclosed in the
149             # given area. TBD.
150             }
151              
152             sub setWindow
153             {
154 2     2 1 5183 my ($self, $sx, $sy, $s) = @_;
155              
156 2         8 $self->{ORIGIN}[0] += $sx / $self->{SCALE};
157 2         6 $self->{ORIGIN}[1] += $sy / $self->{SCALE};
158 2         6 $self->{SCALE} *= $s;
159             }
160              
161             sub resetWindow
162             {
163 1     1 1 2413 my $self = shift;
164              
165 1         6 $self->{ORIGIN}[$_] = 0 for 0 .. 1;
166 1         4 $self->{SCALE} = 1;
167             }
168              
169             # HELPERS
170             # not called in object context
171              
172             sub _uniq
173             {
174 70     70   112 if (HAS_LIST_UTIL) {
175 70         110 return [ List::Util::uniqstr(@{$_[0]}) ];
  70         436  
176             }
177             else {
178             my %temp = map { $_ => $_ } @{$_[0]};
179             return [ values %temp ];
180             }
181             }
182              
183             1;
184              
185             __END__