2
2
3
3
import java .util .ArrayList ;
4
4
import java .util .Comparator ;
5
- import java .util .HashSet ;
6
5
import java .util .LinkedList ;
7
6
import java .util .List ;
8
- import java .util .Set ;
9
7
10
8
/**
11
9
* A quadtree is a tree data structure in which each internal node has exactly four children. Quadtrees
18
16
*/
19
17
public class QuadTree <T extends QuadTree .XYPoint > {
20
18
21
- private static int capacity = 0 ;
22
-
23
19
private QuadNode root = null ;
24
20
21
+ /**
22
+ * Create a quadtree who's upper left coordinate is located at x,y and it's bounding box is described
23
+ * by the height and width. This uses a default leafCapacity of 4 and a maxTreeHeight of 20.
24
+ *
25
+ * @param x Upper left X coordinate
26
+ * @param y Upper left Y coordinate
27
+ * @param height Height of the bounding box containing all points
28
+ * @param width Width of the bounding box containing all points
29
+ */
25
30
public QuadTree (double x , double y , double height , double width ) {
26
- this (x ,y ,height ,width ,4 );
31
+ this (x ,y ,height ,width ,4 , 20 );
27
32
}
28
33
29
- public QuadTree (double x , double y , double height , double width , int capacity ) {
34
+ /**
35
+ * Create a quadtree who's upper left coordinate is located at x,y and it's bounding box is described
36
+ * by the height and width.
37
+ *
38
+ * @param x Upper left X coordinate
39
+ * @param y Upper left Y coordinate
40
+ * @param height Height of the bounding box containing all points
41
+ * @param width Width of the bounding box containing all points
42
+ * @param leafCapacity Max capacity of leaf nodes. (Note: All data is stored in leaf nodes)
43
+ * @param maxTreeHeight Max height of the quadtree. (Note: If this is defined, the tree will ignore the
44
+ * max capacity defined by leafCapacity)
45
+ */
46
+ public QuadTree (double x , double y , double height , double width , int leafCapacity , int maxTreeHeight ) {
30
47
XYPoint xyPoint = new XYPoint (x ,y );
31
48
AxisAlignedBoundingBox aabb = new AxisAlignedBoundingBox (xyPoint ,height ,width );
32
49
this .root = new QuadNode (aabb );
33
- QuadTree .capacity = capacity ;
50
+ QuadNode .maxCapacity = leafCapacity ;
51
+ QuadNode .maxHeight = maxTreeHeight ;
34
52
}
35
53
36
54
// insert point into tree
@@ -59,7 +77,11 @@ public String toString() {
59
77
60
78
private static class QuadNode implements Comparable <QuadNode > {
61
79
62
- private Set <XYPoint > points = new HashSet <XYPoint >(capacity );
80
+ private static int maxCapacity = 0 ;
81
+ private static int maxHeight = 0 ;
82
+
83
+ private List <XYPoint > points = new LinkedList <XYPoint >();
84
+ private int height = 1 ;
63
85
private AxisAlignedBoundingBox aabb = null ;
64
86
private QuadNode northWest = null ;
65
87
private QuadNode northEast = null ;
@@ -72,59 +94,77 @@ private QuadNode(AxisAlignedBoundingBox aabb) {
72
94
73
95
private boolean insert (XYPoint p ) {
74
96
// Ignore objects which do not belong in this quad tree
75
- if (!aabb .containsPoint (p ) || points .contains (p )) return false ; // object cannot be added
97
+ if (!aabb .containsPoint (p ) || ( isLeaf () && points .contains (p ) )) return false ; // object cannot be added
76
98
77
99
// If there is space in this quad tree, add the object here
78
- if (points .size () < capacity ) {
100
+ if (( height == maxHeight ) || ( isLeaf () && points .size () < maxCapacity ) ) {
79
101
points .add (p );
80
102
return true ;
81
103
}
82
104
83
105
// Otherwise, we need to subdivide then add the point to whichever node will accept it
84
- if (northWest == null ) subdivide ();
85
-
86
- if (northWest .insert (p )) return true ;
87
- if (northEast .insert (p )) return true ;
88
- if (southWest .insert (p )) return true ;
89
- if (southEast .insert (p )) return true ;
106
+ if (isLeaf () && height <maxHeight ) subdivide ();
107
+ insertIntoChildren (p );
90
108
91
109
// Otherwise, the point cannot be inserted for some unknown reason (which should never happen)
92
110
return false ;
93
111
}
94
112
113
+ private boolean isLeaf () {
114
+ return (northWest ==null );
115
+ }
116
+
95
117
private void subdivide () {
96
- double height = aabb .width /2 ;
97
- double width = aabb .width /2 ;
118
+ double h = aabb .height /2 ;
119
+ double w = aabb .width /2 ;
98
120
99
- AxisAlignedBoundingBox aabbNW = new AxisAlignedBoundingBox (aabb .upperLeft ,height , width );
121
+ AxisAlignedBoundingBox aabbNW = new AxisAlignedBoundingBox (aabb .upperLeft ,h , w );
100
122
northWest = new QuadNode (aabbNW );
123
+ northWest .height = height +1 ;
101
124
102
- XYPoint xyNE = new XYPoint (aabb .upperLeft .x +width ,aabb .upperLeft .y );
103
- AxisAlignedBoundingBox aabbNE = new AxisAlignedBoundingBox (xyNE ,height , width );
125
+ XYPoint xyNE = new XYPoint (aabb .upperLeft .x +w ,aabb .upperLeft .y );
126
+ AxisAlignedBoundingBox aabbNE = new AxisAlignedBoundingBox (xyNE ,h , w );
104
127
northEast = new QuadNode (aabbNE );
128
+ northEast .height = height +1 ;
105
129
106
- XYPoint xySW = new XYPoint (aabb .upperLeft .x ,aabb .upperLeft .y +height );
107
- AxisAlignedBoundingBox aabbSW = new AxisAlignedBoundingBox (xySW ,height , width );
130
+ XYPoint xySW = new XYPoint (aabb .upperLeft .x ,aabb .upperLeft .y +h );
131
+ AxisAlignedBoundingBox aabbSW = new AxisAlignedBoundingBox (xySW ,h , w );
108
132
southWest = new QuadNode (aabbSW );
133
+ southWest .height = height +1 ;
109
134
110
- XYPoint xySE = new XYPoint (aabb .upperLeft .x +width ,aabb .upperLeft .y +height );
111
- AxisAlignedBoundingBox aabbSE = new AxisAlignedBoundingBox (xySE ,height , width );
135
+ XYPoint xySE = new XYPoint (aabb .upperLeft .x +w ,aabb .upperLeft .y +h );
136
+ AxisAlignedBoundingBox aabbSE = new AxisAlignedBoundingBox (xySE ,h , w );
112
137
southEast = new QuadNode (aabbSE );
138
+ southEast .height = height +1 ;
139
+
140
+ // points live in leaf nodes, so distribute
141
+ for (XYPoint p : points ) {
142
+ insertIntoChildren (p );
143
+ }
144
+ points .clear ();
145
+ }
146
+
147
+ private boolean insertIntoChildren (XYPoint p ) {
148
+ if (northWest .insert (p )) return true ;
149
+ if (northEast .insert (p )) return true ;
150
+ if (southWest .insert (p )) return true ;
151
+ if (southEast .insert (p )) return true ;
152
+ return false ; // should never happen
113
153
}
114
154
115
155
// Find all points which appear within a range
116
156
private void queryRange (AxisAlignedBoundingBox range , List <XYPoint > pointsInRange ) {
117
157
// Automatically abort if the range does not collide with this quad
118
158
if (!aabb .intersectsBox (range )) return ;
119
159
120
- // Check objects at this quad level
121
- for (XYPoint xyPoint : points ) {
122
- if (range .containsPoint (xyPoint )) pointsInRange .add (xyPoint );
160
+ // If leaf, check objects at this level
161
+ if (isLeaf ()) {
162
+ for (XYPoint xyPoint : points ) {
163
+ if (range .containsPoint (xyPoint )) pointsInRange .add (xyPoint );
164
+ }
165
+ return ;
123
166
}
124
167
125
- // Terminate here, if there are no children
126
- if (northWest == null ) return ;
127
-
128
168
// Otherwise, add the points from the children
129
169
northWest .queryRange (range ,pointsInRange );
130
170
northEast .queryRange (range ,pointsInRange );
0 commit comments