|
| 1 | +package at.jotschi.quadtree; |
| 2 | + |
| 3 | +import java.awt.Dimension; |
| 4 | +import java.awt.Point; |
| 5 | +import java.util.HashMap; |
| 6 | +import java.util.Map; |
| 7 | +import java.util.Vector; |
| 8 | + |
| 9 | +import org.apache.log4j.Logger; |
| 10 | + |
| 11 | +/** |
| 12 | + * Node that represents each 'cell' within the quadtree. The Node will contains |
| 13 | + * elements {@link NodeElement} that itself will contain the final data within |
| 14 | + * the tree. |
| 15 | + * |
| 16 | + * @author jotschi |
| 17 | + * |
| 18 | + * @param <T> |
| 19 | + */ |
| 20 | +public class Node<T> { |
| 21 | + |
| 22 | + private static Logger log = Logger.getLogger(Node.class); |
| 23 | + |
| 24 | + public static enum Cell { |
| 25 | + TOP_LEFT, BOTTOM_RIGHT, BOTTOM_LEFT, TOP_RIGHT |
| 26 | + } |
| 27 | + |
| 28 | + private Dimension bounds; |
| 29 | + private Point startCoordinates; |
| 30 | + private int maxDepth; |
| 31 | + private int maxElements; |
| 32 | + private int depth; |
| 33 | + |
| 34 | + /** |
| 35 | + * Default value for amount of elements |
| 36 | + */ |
| 37 | + private final int MAX_ELEMENTS = 4; |
| 38 | + |
| 39 | + /** |
| 40 | + * Default value for max depth |
| 41 | + */ |
| 42 | + private final int MAX_DEPTH = 4; |
| 43 | + |
| 44 | + private Map<Cell, Node<T>> nodes = new HashMap<Cell, Node<T>>(); |
| 45 | + |
| 46 | + /** |
| 47 | + * Holds all elements for this node |
| 48 | + */ |
| 49 | + private Vector<NodeElement<T>> elements = new Vector<NodeElement<T>>(); |
| 50 | + |
| 51 | + public Node(Point startCoordinates, Dimension bounds, int depth) { |
| 52 | + log.debug("Creating new Node at depth " + depth); |
| 53 | + this.startCoordinates = startCoordinates; |
| 54 | + this.bounds = bounds; |
| 55 | + this.depth = depth; |
| 56 | + this.maxDepth = MAX_DEPTH; |
| 57 | + this.maxElements = MAX_ELEMENTS; |
| 58 | + |
| 59 | + } |
| 60 | + |
| 61 | + /** |
| 62 | + * |
| 63 | + * @param startCoordinates |
| 64 | + * @param bounds |
| 65 | + * @param depth |
| 66 | + * @param maxDepth |
| 67 | + * @param maxChildren |
| 68 | + */ |
| 69 | + public Node(Point startCoordinates, Dimension bounds, int depth, |
| 70 | + int maxDepth, int maxChildren) { |
| 71 | + log.debug("Creating new Node at depth " + depth); |
| 72 | + this.startCoordinates = startCoordinates; |
| 73 | + this.bounds = bounds; |
| 74 | + this.maxDepth = maxDepth; |
| 75 | + this.maxElements = maxChildren; |
| 76 | + this.depth = depth; |
| 77 | + |
| 78 | + } |
| 79 | + |
| 80 | + /** |
| 81 | + * Returns the subnodes of this node |
| 82 | + * |
| 83 | + * @return |
| 84 | + */ |
| 85 | + public Map<Cell, Node<T>> getSubNodes() { |
| 86 | + return this.nodes; |
| 87 | + } |
| 88 | + |
| 89 | + /** |
| 90 | + * Returns the bounds for this Node |
| 91 | + * |
| 92 | + * @return |
| 93 | + */ |
| 94 | + public Dimension getBounds() { |
| 95 | + return this.bounds; |
| 96 | + } |
| 97 | + |
| 98 | + /** |
| 99 | + * Returns the startCoordinates for this Node |
| 100 | + * |
| 101 | + * @return |
| 102 | + */ |
| 103 | + public Point getStartCoordinates() { |
| 104 | + return this.startCoordinates; |
| 105 | + } |
| 106 | + |
| 107 | + /** |
| 108 | + * Returns the max elements |
| 109 | + * |
| 110 | + * @return |
| 111 | + */ |
| 112 | + public int getMaxElements() { |
| 113 | + return this.maxElements; |
| 114 | + } |
| 115 | + |
| 116 | + /** |
| 117 | + * Returns the max depth |
| 118 | + * |
| 119 | + * @return |
| 120 | + */ |
| 121 | + public int getMaxDepth() { |
| 122 | + return this.maxDepth; |
| 123 | + } |
| 124 | + |
| 125 | + /** |
| 126 | + * Returns the cell of this element |
| 127 | + * |
| 128 | + * @param element |
| 129 | + * @return |
| 130 | + */ |
| 131 | + private Cell findIndex(Point coordinates) { |
| 132 | + // Compute the sector for the coordinates |
| 133 | + boolean left = (coordinates.x > (startCoordinates.x + bounds.width / 2)) ? false |
| 134 | + : true; |
| 135 | + boolean top = (coordinates.y > (startCoordinates.y + bounds.height / 2)) ? false |
| 136 | + : true; |
| 137 | + |
| 138 | + // top left |
| 139 | + Cell index = Cell.TOP_LEFT; |
| 140 | + if (left) { |
| 141 | + // left side |
| 142 | + if (!top) { |
| 143 | + // bottom left |
| 144 | + index = Cell.BOTTOM_LEFT; |
| 145 | + } |
| 146 | + } else { |
| 147 | + // right side |
| 148 | + if (top) { |
| 149 | + // top right |
| 150 | + index = Cell.TOP_RIGHT; |
| 151 | + } else { |
| 152 | + // bottom right |
| 153 | + index = Cell.BOTTOM_RIGHT; |
| 154 | + |
| 155 | + } |
| 156 | + } |
| 157 | + log.debug("Coordinate [" + coordinates.x + "-" + coordinates.y |
| 158 | + + "] is within " + index.toString() + " at depth " + depth); |
| 159 | + return index; |
| 160 | + } |
| 161 | + |
| 162 | + /** |
| 163 | + * Returns all elements for this node |
| 164 | + * |
| 165 | + * @return |
| 166 | + */ |
| 167 | + public Vector<NodeElement<T>> getElements() { |
| 168 | + return this.elements; |
| 169 | + } |
| 170 | + |
| 171 | + /** |
| 172 | + * Returns all elements wihtin the cell that matches the given coordinates |
| 173 | + * |
| 174 | + * @param coordinates |
| 175 | + * @return |
| 176 | + */ |
| 177 | + public Vector<NodeElement<T>> getElements(Point coordinates) { |
| 178 | + |
| 179 | + // Check if this node has already been subdivided. Therefor this node |
| 180 | + // should contain no elements |
| 181 | + if (nodes.size() > 0) { |
| 182 | + Cell index = findIndex(coordinates); |
| 183 | + Node<T> node = this.nodes.get(index); |
| 184 | + return node.getElements(coordinates); |
| 185 | + } else { |
| 186 | + return this.elements; |
| 187 | + } |
| 188 | + } |
| 189 | + |
| 190 | + /** |
| 191 | + * Insert the element into this node. If needed a subdivison will be |
| 192 | + * performed |
| 193 | + * |
| 194 | + * @param element |
| 195 | + */ |
| 196 | + public void insert(NodeElement<T> element) { |
| 197 | + log.debug("Inserting element into Node at depth " + depth); |
| 198 | + // If this Node has already been subdivided just add the elements to the |
| 199 | + // appropriate cell |
| 200 | + if (this.nodes.size() != 0) { |
| 201 | + Cell index = findIndex(element); |
| 202 | + log.debug("Inserting into existing cell: " + index); |
| 203 | + this.nodes.get(index).insert(element); |
| 204 | + return; |
| 205 | + } |
| 206 | + |
| 207 | + // Add the element to this node |
| 208 | + this.elements.add(element); |
| 209 | + |
| 210 | + // Only subdivide the node if it contain more than MAX_CHILDREN and is |
| 211 | + // not the deepest node |
| 212 | + if (!(this.depth >= MAX_DEPTH) && this.elements.size() > MAX_ELEMENTS) { |
| 213 | + this.subdivide(); |
| 214 | + |
| 215 | + // Recall insert for each element. This will move all elements of |
| 216 | + // this node into the new nodes at the appropriate cell |
| 217 | + for (NodeElement<T> current : elements) { |
| 218 | + this.insert(current); |
| 219 | + } |
| 220 | + // Remove all elements from this node since they were moved into |
| 221 | + // subnodes |
| 222 | + this.elements.clear(); |
| 223 | + |
| 224 | + } |
| 225 | + |
| 226 | + } |
| 227 | + |
| 228 | + /** |
| 229 | + * Subdivide the current node and add subnodes |
| 230 | + */ |
| 231 | + public void subdivide() { |
| 232 | + log.debug("Subdividing node at depth " + depth); |
| 233 | + int depth = this.depth + 1; |
| 234 | + |
| 235 | + int bx = this.startCoordinates.x; |
| 236 | + int by = this.startCoordinates.y; |
| 237 | + |
| 238 | + // Create the bounds for the new cell |
| 239 | + Dimension newBounds = new Dimension(this.bounds.width / 2, |
| 240 | + this.bounds.height / 2); |
| 241 | + |
| 242 | + // Add new bounds to current start coordinates to calculate the new |
| 243 | + // start coordinates |
| 244 | + int newXStartCoordinate = bx + newBounds.width; |
| 245 | + int newYStartCoordinate = by + newBounds.height; |
| 246 | + |
| 247 | + Node<T> cellNode = null; |
| 248 | + |
| 249 | + // top left |
| 250 | + cellNode = new Node<T>(new Point(bx, by), newBounds, depth); |
| 251 | + this.nodes.put(Cell.TOP_LEFT, cellNode); |
| 252 | + |
| 253 | + // top right |
| 254 | + cellNode = new Node<T>(new Point(newXStartCoordinate, by), newBounds, |
| 255 | + depth); |
| 256 | + this.nodes.put(Cell.TOP_RIGHT, cellNode); |
| 257 | + |
| 258 | + // bottom left |
| 259 | + cellNode = new Node<T>(new Point(bx, newYStartCoordinate), newBounds, |
| 260 | + depth); |
| 261 | + this.nodes.put(Cell.BOTTOM_LEFT, cellNode); |
| 262 | + |
| 263 | + // bottom right |
| 264 | + cellNode = new Node<T>(new Point(newXStartCoordinate, |
| 265 | + newYStartCoordinate), newBounds, depth); |
| 266 | + this.nodes.put(Cell.BOTTOM_RIGHT, cellNode); |
| 267 | + } |
| 268 | + |
| 269 | + /** |
| 270 | + * Clears this node and all subnodes |
| 271 | + */ |
| 272 | + public void clear() { |
| 273 | + for (Node<T> node : nodes.values()) { |
| 274 | + node.clear(); |
| 275 | + } |
| 276 | + elements.clear(); |
| 277 | + } |
| 278 | +} |
0 commit comments