From 35bcd8b6113efb1893d7d0400564456bb8cca4e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=BCth?= Date: Mon, 3 Jan 2022 19:33:54 +0100 Subject: [PATCH] Add interfaces, cleanup code, remove log4j --- pom.xml | 45 +++--- .../at/jotschi/quadtree/AbstractNode.java | 93 ----------- .../jotschi/quadtree/AbstractNodeElement.java | 41 ----- .../at/jotschi/quadtree/AbstractQuadTree.java | 46 ------ .../jotschi/quadtree/point/PointQuadTree.java | 137 ---------------- .../jotschi/quadtree/spacial/SpatialNode.java | 152 ------------------ .../quadtree/spacial/SpatialNodeElement.java | 32 ---- .../quadtree/spacial/SpatialQuadTree.java | 107 ------------ .../io/metaloom/quadtree/AbstractNode.java | 71 ++++++++ .../quadtree/AbstractNodeElement.java | 54 +++++++ .../metaloom/quadtree/AbstractQuadTree.java | 31 ++++ src/main/java/io/metaloom/quadtree/Cell.java | 5 + src/main/java/io/metaloom/quadtree/Node.java | 50 ++++++ .../io/metaloom/quadtree/NodeElement.java | 14 ++ .../java/io/metaloom/quadtree/QuadTree.java | 50 ++++++ src/main/java/io/metaloom/quadtree/Size.java | 19 +++ .../io/metaloom/quadtree/impl/SizeImpl.java | 25 +++ .../io/metaloom/quadtree/point/Point.java | 26 +++ .../quadtree/point/PointQuadTree.java | 41 +++++ .../quadtree/point/impl/PointImpl.java | 40 +++++ .../quadtree/point/impl}/PointNode.java | 69 ++++---- .../point/impl}/PointNodeElement.java | 7 +- .../point/impl/PointQuadTreeImpl.java | 104 ++++++++++++ .../quadtree/spatial/SpatialNode.java | 25 +++ .../quadtree/spatial/SpatialNodeElement.java | 15 ++ .../quadtree/spatial/SpatialQuadTree.java | 12 ++ .../spatial/impl/SpatialNodeElementImpl.java | 34 ++++ .../spatial/impl/SpatialNodeImpl.java | 142 ++++++++++++++++ .../spatial/impl/SpatialQuadTreeImpl.java | 72 +++++++++ src/main/resources/log4j.properties | 9 -- .../at/jotschi/quadtree/QuadTreeTest.java | 49 ------ .../io/metaloom/quadtree/QuadTreeTest.java | 54 +++++++ .../quadtree/RenderPointQuadTree.java | 60 +++---- .../quadtree/RenderSpatialQuadTree.java | 51 +++--- .../metaloom}/quadtree/gui/QuadTreePanel.java | 32 ++-- src/test/resources/logback.xml | 12 ++ 36 files changed, 1017 insertions(+), 809 deletions(-) delete mode 100644 src/main/java/at/jotschi/quadtree/AbstractNode.java delete mode 100644 src/main/java/at/jotschi/quadtree/AbstractNodeElement.java delete mode 100644 src/main/java/at/jotschi/quadtree/AbstractQuadTree.java delete mode 100644 src/main/java/at/jotschi/quadtree/point/PointQuadTree.java delete mode 100644 src/main/java/at/jotschi/quadtree/spacial/SpatialNode.java delete mode 100644 src/main/java/at/jotschi/quadtree/spacial/SpatialNodeElement.java delete mode 100644 src/main/java/at/jotschi/quadtree/spacial/SpatialQuadTree.java create mode 100644 src/main/java/io/metaloom/quadtree/AbstractNode.java create mode 100644 src/main/java/io/metaloom/quadtree/AbstractNodeElement.java create mode 100644 src/main/java/io/metaloom/quadtree/AbstractQuadTree.java create mode 100644 src/main/java/io/metaloom/quadtree/Cell.java create mode 100644 src/main/java/io/metaloom/quadtree/Node.java create mode 100644 src/main/java/io/metaloom/quadtree/NodeElement.java create mode 100644 src/main/java/io/metaloom/quadtree/QuadTree.java create mode 100644 src/main/java/io/metaloom/quadtree/Size.java create mode 100644 src/main/java/io/metaloom/quadtree/impl/SizeImpl.java create mode 100644 src/main/java/io/metaloom/quadtree/point/Point.java create mode 100644 src/main/java/io/metaloom/quadtree/point/PointQuadTree.java create mode 100644 src/main/java/io/metaloom/quadtree/point/impl/PointImpl.java rename src/main/java/{at/jotschi/quadtree/point => io/metaloom/quadtree/point/impl}/PointNode.java (66%) rename src/main/java/{at/jotschi/quadtree/point => io/metaloom/quadtree/point/impl}/PointNodeElement.java (55%) create mode 100644 src/main/java/io/metaloom/quadtree/point/impl/PointQuadTreeImpl.java create mode 100644 src/main/java/io/metaloom/quadtree/spatial/SpatialNode.java create mode 100644 src/main/java/io/metaloom/quadtree/spatial/SpatialNodeElement.java create mode 100644 src/main/java/io/metaloom/quadtree/spatial/SpatialQuadTree.java create mode 100644 src/main/java/io/metaloom/quadtree/spatial/impl/SpatialNodeElementImpl.java create mode 100644 src/main/java/io/metaloom/quadtree/spatial/impl/SpatialNodeImpl.java create mode 100644 src/main/java/io/metaloom/quadtree/spatial/impl/SpatialQuadTreeImpl.java delete mode 100644 src/main/resources/log4j.properties delete mode 100644 src/test/java/at/jotschi/quadtree/QuadTreeTest.java create mode 100644 src/test/java/io/metaloom/quadtree/QuadTreeTest.java rename src/test/java/{at/jotschi => io/metaloom}/quadtree/RenderPointQuadTree.java (58%) rename src/test/java/{at/jotschi => io/metaloom}/quadtree/RenderSpatialQuadTree.java (67%) rename src/test/java/{at/jotschi => io/metaloom}/quadtree/gui/QuadTreePanel.java (70%) create mode 100644 src/test/resources/logback.xml diff --git a/pom.xml b/pom.xml index 158e178..96dccdb 100644 --- a/pom.xml +++ b/pom.xml @@ -1,15 +1,16 @@ - 4.0.0 at.jotschi QuadTree - 0.5.0-SNAPSHOT + 1.0.0-SNAPSHOT jar - de.jotschi - oss-parent + io.metaloom + maven-parent 1.0.0 @@ -21,27 +22,33 @@ + + + org.slf4j + slf4j-api + + junit junit test - log4j - log4j - 1.2.16 + ch.qos.logback + logback-classic + test - - - maven.jotschi.de - http://maven.jotschi.de/maven2 - - true - - - false - - - + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 11 + + + + diff --git a/src/main/java/at/jotschi/quadtree/AbstractNode.java b/src/main/java/at/jotschi/quadtree/AbstractNode.java deleted file mode 100644 index cdb5e8b..0000000 --- a/src/main/java/at/jotschi/quadtree/AbstractNode.java +++ /dev/null @@ -1,93 +0,0 @@ -package at.jotschi.quadtree; - -import java.awt.Dimension; -import java.awt.Point; -import java.util.Map; - -public abstract class AbstractNode { - - /** - * Default value for amount of elements - */ - protected final static int MAX_ELEMENTS = 4; - - /** - * Default value for max depth - */ - protected final static int MAX_DEPTH = 4; - - public static enum Cell { - TOP_LEFT, BOTTOM_RIGHT, BOTTOM_LEFT, TOP_RIGHT - } - - protected Dimension bounds; - protected Point startCoordinates; - protected int maxDepth; - protected int maxElements; - protected int depth; - - public AbstractNode(Point startCoordinates, Dimension bounds, int depth) { - this(startCoordinates, bounds, depth, MAX_ELEMENTS, MAX_DEPTH); - } - - public AbstractNode(Point startCoordinates, Dimension bounds, int depth, - int maxDepth, int maxElements) { - this.startCoordinates = startCoordinates; - this.bounds = bounds; - this.maxDepth = maxDepth; - this.maxElements = maxElements; - this.depth = depth; - } - - /** - * Returns the bounds for this Node - * - * @return - */ - public Dimension getBounds() { - return this.bounds; - } - - /** - * Returns the startCoordinates for this Node - * - * @return - */ - public Point getStartCoordinates() { - return this.startCoordinates; - } - - /** - * Returns the max elements - * - * @return - */ - public int getMaxElements() { - return this.maxElements; - } - - /** - * Returns the depth of this node - * - * @return - */ - public int getDepth() { - return this.depth; - } - - /** - * Returns the max depth - * - * @return - */ - public int getMaxDepth() { - return this.maxDepth; - } - - public abstract void subdivide(); - - public abstract void clear(); - - public abstract Map> getSubNodes(); - -} diff --git a/src/main/java/at/jotschi/quadtree/AbstractNodeElement.java b/src/main/java/at/jotschi/quadtree/AbstractNodeElement.java deleted file mode 100644 index 7a6d93d..0000000 --- a/src/main/java/at/jotschi/quadtree/AbstractNodeElement.java +++ /dev/null @@ -1,41 +0,0 @@ -package at.jotschi.quadtree; - -import java.awt.Point; - -/** - * Container class that holds the object within the quadtree - * - * @author jotschi - * - */ -@SuppressWarnings("serial") -public abstract class AbstractNodeElement extends Point { - - private T element; - - /** - * Create a new NodeElement that holds the element at the given coordinates. - * - * @param x - * @param y - * @param element - */ - public AbstractNodeElement(Point coordinates, T element) { - super(coordinates); - this.element = element; - } - - public AbstractNodeElement(T element) { - this.element = element; - } - - /** - * Returns the element that is contained within this NodeElement - * - * @return - */ - public T getElement() { - return element; - } - -} diff --git a/src/main/java/at/jotschi/quadtree/AbstractQuadTree.java b/src/main/java/at/jotschi/quadtree/AbstractQuadTree.java deleted file mode 100644 index 5e9b04a..0000000 --- a/src/main/java/at/jotschi/quadtree/AbstractQuadTree.java +++ /dev/null @@ -1,46 +0,0 @@ -package at.jotschi.quadtree; - -import java.awt.Dimension; -import java.awt.Point; - -public abstract class AbstractQuadTree { - - protected Dimension size; - protected Point startCoordinates; - - public AbstractQuadTree(Point startCoordinates, Dimension size) { - this.size = size; - this.startCoordinates = startCoordinates; - } - - /** - * Returns the size - * - * @return - */ - public Dimension getSize() { - return this.size; - } - - /** - * Returns the startCoordinates - * - * @return - */ - public Point getStartCoordinates() { - return this.startCoordinates; - } - - /** - * Clear the QuadTree - */ - public abstract void clear(); - - /** - * Return the root node of this quad tree - * - * @return - */ - public abstract AbstractNode getRootNode(); - -} diff --git a/src/main/java/at/jotschi/quadtree/point/PointQuadTree.java b/src/main/java/at/jotschi/quadtree/point/PointQuadTree.java deleted file mode 100644 index 83bde7d..0000000 --- a/src/main/java/at/jotschi/quadtree/point/PointQuadTree.java +++ /dev/null @@ -1,137 +0,0 @@ -package at.jotschi.quadtree.point; - -import java.awt.Dimension; -import java.awt.Point; -import java.util.Vector; - -import at.jotschi.quadtree.AbstractNodeElement; -import at.jotschi.quadtree.AbstractQuadTree; - -/** - * Creates a new QuadTree that can hold the given type of elements - * - * @author jotschi - * - * @param - */ -public class PointQuadTree extends AbstractQuadTree { - - protected PointNode rootNode; - - /** - * Create a new QuadTree with the give start coordinates and size - * - * @param startCorrdinates - * @param size - */ - public PointQuadTree(Point startCoordinates, Dimension size) { - super(startCoordinates, size); - this.rootNode = new PointNode(startCoordinates, size, 0); - } - - public PointQuadTree(Point startCoordinates, Dimension size, int maxDepth, int maxChildren) { - super(startCoordinates, size); - this.rootNode = new PointNode(startCoordinates, size, 0,maxDepth,maxChildren); - } - - /** - * Add a new element to the QuadTree - * - * @param x - * @param y - * @param element - */ - public void insert(int x, int y, T element) { - insert(new Point(x, y), element); - } - - /** - * Add a new element to the QuadTree that has a specific dimension/size - * - * @param point - * @param size - * @param element - */ - public void insert(Point point, Dimension size, T element) { - - // Check if the element coordinates are within bounds of the quadtree - if (point.x > startCoordinates.x + size.width - || point.x < startCoordinates.x) { - throw new IndexOutOfBoundsException( - "The x coordinate must be within bounds of [" - + startCoordinates.x + "] to [" + size.width + "]"); - } - if (point.y > startCoordinates.y + size.height - || point.y < startCoordinates.y) { - throw new IndexOutOfBoundsException( - "The y coordinate must be within bounds of [" - + startCoordinates.y + "] to [" + size.height + "]"); - } - - // Check if the right bottom corner is within bounds - if (point.x + size.width > startCoordinates.x + size.width - || point.x < startCoordinates.x) { - throw new IndexOutOfBoundsException( - "The x coordinate must be within bounds of [" - + startCoordinates.x + "] to [" + size.width + "]"); - } - if (point.y + size.width > startCoordinates.y + size.height - || point.y < startCoordinates.y) { - throw new IndexOutOfBoundsException( - "The y coordinate must be within bounds of [" - + startCoordinates.y + "] to [" + size.height + "]"); - } - - this.rootNode.insert(new PointNodeElement(point, element)); - - } - - /** - * Add a new element to the QuadTree - * - * @param point - * @param element - */ - public void insert(Point point, T element) { - - // Check if the element coordinates are within bounds of the quadtree - if (point.x > startCoordinates.x + size.width - || point.x < startCoordinates.x) { - throw new IndexOutOfBoundsException( - "The x coordinate must be within bounds of [" - + startCoordinates.x + "] to [" + size.width + "]"); - } - if (point.y > startCoordinates.y + size.height - || point.y < startCoordinates.y) { - throw new IndexOutOfBoundsException( - "The y coordinate must be within bounds of [" - + startCoordinates.y + "] to [" + size.height + "]"); - } - - this.rootNode.insert(new PointNodeElement(point, element)); - } - - /** - * Returns the rootNode of this tree - * - * @return - */ - public PointNode getRootNode() { - return this.rootNode; - } - - /** - * Returns all elements wihtin the cell that matches the given coordinates - * - * @param coordinates - * @return - */ - public Vector> getElements(Point coordinates) { - return this.rootNode.getElements(coordinates); - } - - @Override - public void clear() { - this.rootNode.clear(); - } -} diff --git a/src/main/java/at/jotschi/quadtree/spacial/SpatialNode.java b/src/main/java/at/jotschi/quadtree/spacial/SpatialNode.java deleted file mode 100644 index dec9976..0000000 --- a/src/main/java/at/jotschi/quadtree/spacial/SpatialNode.java +++ /dev/null @@ -1,152 +0,0 @@ -package at.jotschi.quadtree.spacial; - -import java.awt.Dimension; -import java.awt.Point; -import java.util.HashMap; -import java.util.Map; - -import org.apache.log4j.Logger; - -import at.jotschi.quadtree.AbstractNode; - -public class SpatialNode extends AbstractNode { - - protected Map> nodes = new HashMap>(); - - protected SpatialNodeElement element; - - protected static Logger log = Logger.getLogger(SpatialNode.class); - - public SpatialNode(Point startCoordinates, Dimension bounds, int depth) { - super(startCoordinates, bounds, depth); - } - - public SpatialNode(Point startCoordinates, Dimension bounds, int depth, - int maxDepth, int maxChildren) { - super(startCoordinates, bounds, depth, maxDepth, maxChildren); - - } - - /** - * Insert the element into this node. If needed a subdivision will be - * performed - * - * @param element - */ - public boolean insert(SpatialNodeElement element) { - - boolean wMatch = element.getWidth() == this.getBounds().width; - boolean hMatch = element.getHeight() == this.getBounds().height; - boolean fits = wMatch && hMatch; - - boolean wSmaller = element.getWidth() < this.getBounds().width; - boolean hSmaller = element.getHeight() < this.getBounds().height; - boolean smaller = wSmaller && hSmaller; - - log.debug("Inserting element " + element.getHeight() + " - " - + element.getWidth()); - - // Check if this node already contains a element - if (this.element != null) { - return false; - } - - // Check if the element fits into this node - if (fits && this.nodes.size() == 0) { - element.x = this.startCoordinates.x; - element.y = this.startCoordinates.y; - this.element = element; - log.debug("Inserting element at coordinates [" + element.x + "-" - + element.y + "]"); - return true; - } else if (!(this.depth >= MAX_DEPTH) && nodes.size() == 0 && smaller) { - // We need to subdivide the node if the element is smaller than then - // the dimensions of this node. - this.subdivide(); - // After subdivision we try to insert the element into one of the subnodes - for (SpatialNode current : nodes.values()) { - if (current.insert(element)) { - return true; - } - } - - } else { - // Recall insert for the element. This will try to add the element - // into - // one of the subnodes. - for (SpatialNode current : nodes.values()) { - if (current.insert(element)) { - return true; - } - } - - } - - return false; - } - - @Override - /** - * Subdivide the current node and add subnodes - */ - public void subdivide() { - log.debug("Subdividing node at depth " + depth); - int depth = this.depth + 1; - - int bx = this.startCoordinates.x; - int by = this.startCoordinates.y; - - // Create the bounds for the new cell - Dimension newBounds = new Dimension(this.bounds.width / 2, - this.bounds.height / 2); - - // Add new bounds to current start coordinates to calculate the new - // start coordinates - int newXStartCoordinate = bx + newBounds.width; - int newYStartCoordinate = by + newBounds.height; - - SpatialNode cellNode = null; - - // top left - cellNode = new SpatialNode(new Point(bx, by), newBounds, depth); - this.nodes.put(Cell.TOP_LEFT, cellNode); - - // top right - cellNode = new SpatialNode(new Point(newXStartCoordinate, by), - newBounds, depth); - this.nodes.put(Cell.TOP_RIGHT, cellNode); - - // bottom left - cellNode = new SpatialNode(new Point(bx, newYStartCoordinate), - newBounds, depth); - this.nodes.put(Cell.BOTTOM_LEFT, cellNode); - - // bottom right - cellNode = new SpatialNode(new Point(newXStartCoordinate, - newYStartCoordinate), newBounds, depth); - this.nodes.put(Cell.BOTTOM_RIGHT, cellNode); - } - - /** - * Clears this node and all subnodes - */ - public void clear() { - for (SpatialNode node : nodes.values()) { - node.clear(); - } - } - - public SpatialNodeElement getElement() { - return this.element; - } - - /** - * Returns the subnodes of this node - * - * @return - */ - public Map> getSubNodes() { - return this.nodes; - } - -} diff --git a/src/main/java/at/jotschi/quadtree/spacial/SpatialNodeElement.java b/src/main/java/at/jotschi/quadtree/spacial/SpatialNodeElement.java deleted file mode 100644 index f1853da..0000000 --- a/src/main/java/at/jotschi/quadtree/spacial/SpatialNodeElement.java +++ /dev/null @@ -1,32 +0,0 @@ -package at.jotschi.quadtree.spacial; - -import java.awt.Dimension; -import java.awt.Point; - -import at.jotschi.quadtree.AbstractNodeElement; - - -@SuppressWarnings("serial") -public class SpatialNodeElement extends AbstractNodeElement { - - protected Dimension elementSize; - - public SpatialNodeElement(T element, Dimension elementSize) { - super(element); - this.elementSize = elementSize; - } - - public SpatialNodeElement(Point coordinates, Dimension elementSize, T element) { - super(coordinates, element); - this.elementSize = elementSize; - } - - public int getWidth() { - return this.elementSize.width; - } - - public int getHeight() { - return this.elementSize.height; - } - -} diff --git a/src/main/java/at/jotschi/quadtree/spacial/SpatialQuadTree.java b/src/main/java/at/jotschi/quadtree/spacial/SpatialQuadTree.java deleted file mode 100644 index fb1ec36..0000000 --- a/src/main/java/at/jotschi/quadtree/spacial/SpatialQuadTree.java +++ /dev/null @@ -1,107 +0,0 @@ -package at.jotschi.quadtree.spacial; - -import java.awt.Dimension; -import java.awt.Point; - -import org.apache.log4j.Logger; - -import at.jotschi.quadtree.AbstractQuadTree; - -/** - * Creates a new QuadTree that can hold the given type of elements. Each element - * can also have a certain dimension. - * - * @author jotschi - * - * @param - */ -public class SpatialQuadTree extends AbstractQuadTree { - - protected static Logger log = Logger.getLogger(SpatialQuadTree.class); - - protected SpatialNode rootNode; - - /** - * Create a new QuadTree with the give start coordinates and size - * - * @param startCorrdinates - * @param size - */ - public SpatialQuadTree(Point startCoordinates, Dimension size) { - super(startCoordinates, size); - this.rootNode = new SpatialNode(startCoordinates, size, 0); - } - - /** - * Insert the element with the given size in the next free cell - * - * @param element - * @param elementSize - */ - public void insert(T element, Dimension elementSize) { - this.rootNode.insert(new SpatialNodeElement(element, elementSize)); - - } - - /** - * Add a new element to the QuadTree that has a specific dimension/size - * - * @param point - * @param size - * @param element - */ - public void insert(Point point, Dimension elementSize, T element) { - - // Check if the element coordinates are within bounds of the quadtree - if (point.x > startCoordinates.x + size.width - || point.x < startCoordinates.x) { - throw new IndexOutOfBoundsException( - "The x coordinate must be within bounds of [" - + startCoordinates.x + "] to [" + size.width - + "] / [" + point.x + "]"); - } - if (point.y > startCoordinates.y + size.height - || point.y < startCoordinates.y) { - throw new IndexOutOfBoundsException( - "The y coordinate must be within bounds of [" - + startCoordinates.y + "] to [" + size.height - + "] / [" + point.y + "]"); - } - - // Check if the right bottom corner is within bounds - if (point.x + elementSize.width > startCoordinates.x + size.width - || point.x < startCoordinates.x) { - throw new IndexOutOfBoundsException( - "The x coordinate must be within bounds of [" - + startCoordinates.x + "] to [" + size.width - + "] / [" + point.x + elementSize.width + "]"); - } - - if (point.y + elementSize.height > startCoordinates.y + size.height - || point.y < startCoordinates.y) { - throw new IndexOutOfBoundsException( - "The y coordinate must be within bounds of [" - + startCoordinates.y + "] to [" + size.height - + "] / [" + point.x + elementSize.height + "]"); - } - - this.rootNode.insert(new SpatialNodeElement(point, elementSize, - element)); - - } - - /** - * Returns the rootNode of this tree - * - * @return - */ - public SpatialNode getRootNode() { - return this.rootNode; - } - - @Override - public void clear() { - this.rootNode.clear(); - } - -} diff --git a/src/main/java/io/metaloom/quadtree/AbstractNode.java b/src/main/java/io/metaloom/quadtree/AbstractNode.java new file mode 100644 index 0000000..28e82c1 --- /dev/null +++ b/src/main/java/io/metaloom/quadtree/AbstractNode.java @@ -0,0 +1,71 @@ +package io.metaloom.quadtree; + +import java.util.Map; +import io.metaloom.quadtree.point.Point; + +public abstract class AbstractNode implements Node { + + /** + * Default value for amount of elements + */ + protected final static int MAX_ELEMENTS = 4; + + /** + * Default value for max depth + */ + protected final static int MAX_DEPTH = 4; + + protected Size bounds; + protected Point startCoordinates; + protected int maxDepth; + protected int maxElements; + protected int depth; + + public AbstractNode(Point startCoordinates, Size bounds, int depth) { + this(startCoordinates, bounds, depth, MAX_ELEMENTS, MAX_DEPTH); + } + + public AbstractNode(Point startCoordinates, Size bounds, int depth, + int maxDepth, int maxElements) { + this.startCoordinates = startCoordinates; + this.bounds = bounds; + this.maxDepth = maxDepth; + this.maxElements = maxElements; + this.depth = depth; + } + + @Override + public Size getBounds() { + return bounds; + } + + @Override + public Point getStartCoordinates() { + return startCoordinates; + } + + @Override + public int getMaxElements() { + return maxElements; + } + + @Override + public int getDepth() { + return depth; + } + + @Override + public int getMaxDepth() { + return maxDepth; + } + + @Override + public abstract void subdivide(); + + @Override + public abstract void clear(); + + @Override + public abstract Map> getSubNodes(); + +} diff --git a/src/main/java/io/metaloom/quadtree/AbstractNodeElement.java b/src/main/java/io/metaloom/quadtree/AbstractNodeElement.java new file mode 100644 index 0000000..9cd2899 --- /dev/null +++ b/src/main/java/io/metaloom/quadtree/AbstractNodeElement.java @@ -0,0 +1,54 @@ +package io.metaloom.quadtree; + +import io.metaloom.quadtree.point.Point; + +/** + * Container class that holds the object within the quadtree + */ +public abstract class AbstractNodeElement implements NodeElement { + + private T element; + + private Point coordinates; + + /** + * Create a new NodeElement that holds the element at the given coordinates. + * + * @param coordinates + * @param element + */ + public AbstractNodeElement(Point coordinates, T element) { + this.coordinates = coordinates; + this.element = element; + } + + public AbstractNodeElement(T element) { + this.element = element; + this.coordinates = Point.of(0, 0); + } + + @Override + public T getElement() { + return element; + } + + @Override + public int x() { + return coordinates.x(); + } + + @Override + public int y() { + return coordinates.y(); + } + + @Override + public void setX(int x) { + coordinates.setX(x); + } + + @Override + public void setY(int y) { + coordinates.setY(y); + } +} diff --git a/src/main/java/io/metaloom/quadtree/AbstractQuadTree.java b/src/main/java/io/metaloom/quadtree/AbstractQuadTree.java new file mode 100644 index 0000000..633c627 --- /dev/null +++ b/src/main/java/io/metaloom/quadtree/AbstractQuadTree.java @@ -0,0 +1,31 @@ +package io.metaloom.quadtree; + +import io.metaloom.quadtree.point.Point; + +public abstract class AbstractQuadTree implements QuadTree { + + protected Size size; + protected Point startCoordinates; + + public AbstractQuadTree(Point startCoordinates, Size size) { + this.size = size; + this.startCoordinates = startCoordinates; + } + + @Override + public Size getSize() { + return size; + } + + @Override + public Point getStartCoordinates() { + return startCoordinates; + } + + @Override + public abstract void clear(); + + @Override + public abstract Node getRootNode(); + +} diff --git a/src/main/java/io/metaloom/quadtree/Cell.java b/src/main/java/io/metaloom/quadtree/Cell.java new file mode 100644 index 0000000..ea3e0e9 --- /dev/null +++ b/src/main/java/io/metaloom/quadtree/Cell.java @@ -0,0 +1,5 @@ +package io.metaloom.quadtree; + +public enum Cell { + TOP_LEFT, BOTTOM_RIGHT, BOTTOM_LEFT, TOP_RIGHT +} diff --git a/src/main/java/io/metaloom/quadtree/Node.java b/src/main/java/io/metaloom/quadtree/Node.java new file mode 100644 index 0000000..cd7fb49 --- /dev/null +++ b/src/main/java/io/metaloom/quadtree/Node.java @@ -0,0 +1,50 @@ +package io.metaloom.quadtree; + +import java.util.Map; + +import io.metaloom.quadtree.point.Point; + +public interface Node { + + /** + * Returns the startCoordinates for this {@link Node} + * + * @return + */ + Point getStartCoordinates(); + + /** + * Returns the max elements + * + * @return + */ + int getMaxElements(); + + /** + * Returns the depth of this node + * + * @return + */ + int getDepth(); + + /** + * Returns the max depth + * + * @return + */ + int getMaxDepth(); + + void clear(); + + void subdivide(); + + Map> getSubNodes(); + + /** + * Returns the bounds for this Node + * + * @return + */ + Size getBounds(); + +} diff --git a/src/main/java/io/metaloom/quadtree/NodeElement.java b/src/main/java/io/metaloom/quadtree/NodeElement.java new file mode 100644 index 0000000..ab59a13 --- /dev/null +++ b/src/main/java/io/metaloom/quadtree/NodeElement.java @@ -0,0 +1,14 @@ +package io.metaloom.quadtree; + +import io.metaloom.quadtree.point.Point; + +public interface NodeElement extends Point { + + /** + * Returns the element that is contained within this NodeElement + * + * @return + */ + T getElement(); + +} diff --git a/src/main/java/io/metaloom/quadtree/QuadTree.java b/src/main/java/io/metaloom/quadtree/QuadTree.java new file mode 100644 index 0000000..3ac64b0 --- /dev/null +++ b/src/main/java/io/metaloom/quadtree/QuadTree.java @@ -0,0 +1,50 @@ +package io.metaloom.quadtree; + +import io.metaloom.quadtree.point.Point; + +public interface QuadTree { + + /** + * Returns the size of the tree + * + * @return + */ + Size getSize(); + + /** + * Returns the startCoordinates + * + * @return + */ + Point getStartCoordinates(); + + /** + * Clear the QuadTree + */ + void clear(); + + /** + * Return the root node of this quad tree + * + * @return + */ + Node getRootNode(); + + /** + * Add a new element to the QuadTree that has a specific dimension/size + * + * @param point + * @param size + * @param element + */ + void insert(Point point, Size elementSize, T element); + + /** + * Insert the element with the given size in the next free cell + * + * @param element + * @param elementSize + */ + void insert(T element, Size elementSize); + +} diff --git a/src/main/java/io/metaloom/quadtree/Size.java b/src/main/java/io/metaloom/quadtree/Size.java new file mode 100644 index 0000000..1bdaba2 --- /dev/null +++ b/src/main/java/io/metaloom/quadtree/Size.java @@ -0,0 +1,19 @@ +package io.metaloom.quadtree; + +import io.metaloom.quadtree.impl.SizeImpl; +import io.metaloom.quadtree.point.Point; + +public interface Size { + + int height(); + + int width(); + + static Size of(Point point) { + return new SizeImpl(point.x(), point.y()); + } + + static Size of(int width, int height) { + return new SizeImpl(width, height); + } +} diff --git a/src/main/java/io/metaloom/quadtree/impl/SizeImpl.java b/src/main/java/io/metaloom/quadtree/impl/SizeImpl.java new file mode 100644 index 0000000..8a09e9b --- /dev/null +++ b/src/main/java/io/metaloom/quadtree/impl/SizeImpl.java @@ -0,0 +1,25 @@ +package io.metaloom.quadtree.impl; + +import io.metaloom.quadtree.Size; + +public class SizeImpl implements Size { + + private final int width; + private final int height; + + public SizeImpl(int width, int height) { + this.width = width; + this.height = height; + } + + @Override + public int height() { + return height; + } + + @Override + public int width() { + return width; + } + +} diff --git a/src/main/java/io/metaloom/quadtree/point/Point.java b/src/main/java/io/metaloom/quadtree/point/Point.java new file mode 100644 index 0000000..9b4fc72 --- /dev/null +++ b/src/main/java/io/metaloom/quadtree/point/Point.java @@ -0,0 +1,26 @@ +package io.metaloom.quadtree.point; + +import io.metaloom.quadtree.point.impl.PointImpl; + +/** + * Representation of a two dimensional point. + */ +public interface Point { + + int x(); + + int y(); + + void setX(int x); + + void setY(int y); + + static Point of(int x, int y) { + return new PointImpl(x, y); + } + + static Point of(java.awt.Point p) { + return new PointImpl(p.x, p.y); + } + +} diff --git a/src/main/java/io/metaloom/quadtree/point/PointQuadTree.java b/src/main/java/io/metaloom/quadtree/point/PointQuadTree.java new file mode 100644 index 0000000..ca8324a --- /dev/null +++ b/src/main/java/io/metaloom/quadtree/point/PointQuadTree.java @@ -0,0 +1,41 @@ +package io.metaloom.quadtree.point; + +import io.metaloom.quadtree.QuadTree; +import io.metaloom.quadtree.Size; +import io.metaloom.quadtree.point.impl.PointNode; + +public interface PointQuadTree extends QuadTree { + + /** + * Add a new element to the QuadTree that has a specific dimension/size + * + * @param point + * @param size + * @param element + */ + void insert(Point point, Size size, T element); + + /** + * Add a new element to the QuadTree + * + * @param x + * @param y + * @param element + */ + void insert(int x, int y, T element); + + /** + * Add a new element to the QuadTree + * + * @param point + * @param element + */ + void insert(Point point, T element); + + /** + * Returns the rootNode of this tree + * + * @return + */ + PointNode getRootNode(); +} diff --git a/src/main/java/io/metaloom/quadtree/point/impl/PointImpl.java b/src/main/java/io/metaloom/quadtree/point/impl/PointImpl.java new file mode 100644 index 0000000..c19396d --- /dev/null +++ b/src/main/java/io/metaloom/quadtree/point/impl/PointImpl.java @@ -0,0 +1,40 @@ +package io.metaloom.quadtree.point.impl; + +import io.metaloom.quadtree.point.Point; + +public class PointImpl implements Point { + + public int x; + public int y; + + public PointImpl(int x, int y) { + this.x = x; + this.y = y; + } + + public PointImpl(Point point) { + this.x = point.x(); + this.y = point.y(); + } + + @Override + public int x() { + return x; + } + + @Override + public int y() { + return y; + } + + @Override + public void setX(int x) { + this.x = x; + } + + @Override + public void setY(int y) { + this.y = y; + } + +} diff --git a/src/main/java/at/jotschi/quadtree/point/PointNode.java b/src/main/java/io/metaloom/quadtree/point/impl/PointNode.java similarity index 66% rename from src/main/java/at/jotschi/quadtree/point/PointNode.java rename to src/main/java/io/metaloom/quadtree/point/impl/PointNode.java index cc1c93c..c58c8af 100644 --- a/src/main/java/at/jotschi/quadtree/point/PointNode.java +++ b/src/main/java/io/metaloom/quadtree/point/impl/PointNode.java @@ -1,28 +1,27 @@ -package at.jotschi.quadtree.point; +package io.metaloom.quadtree.point.impl; -import java.awt.Dimension; -import java.awt.Point; import java.util.HashMap; import java.util.Map; import java.util.Vector; -import org.apache.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import at.jotschi.quadtree.AbstractNode; -import at.jotschi.quadtree.AbstractNodeElement; +import io.metaloom.quadtree.AbstractNode; +import io.metaloom.quadtree.AbstractNodeElement; +import io.metaloom.quadtree.Cell; +import io.metaloom.quadtree.Size; +import io.metaloom.quadtree.point.Point; /** - * Node that represents each 'cell' within the quadtree. The Node will contains - * elements {@link AbstractNodeElement} that itself will contain the final data - * within the tree. - * - * @author jotschi + * This is a Node that represents each 'cell' within the quadtree. The Node will contains elements {@link AbstractNodeElement} that itself will contain the + * final data within the tree. * * @param */ -public class PointNode extends AbstractNode { +public class PointNode extends AbstractNode { - protected static Logger log = Logger.getLogger(PointNode.class); + protected static Logger log = LoggerFactory.getLogger(PointNode.class); protected Map> nodes = new HashMap>(); @@ -31,7 +30,7 @@ public class PointNode extends AbstractNode { */ protected Vector> elements = new Vector>(); - public PointNode(Point startCoordinates, Dimension bounds, int depth) { + public PointNode(Point startCoordinates, Size bounds, int depth) { super(startCoordinates, bounds, depth); log.debug("Creating new Node at depth " + depth); } @@ -44,8 +43,8 @@ public PointNode(Point startCoordinates, Dimension bounds, int depth) { * @param maxDepth * @param maxChildren */ - public PointNode(Point startCoordinates, Dimension bounds, int depth, - int maxDepth, int maxChildren) { + public PointNode(Point startCoordinates, Size bounds, int depth, + int maxDepth, int maxChildren) { super(startCoordinates, bounds, depth, maxDepth, maxChildren); log.debug("Creating new Node at depth " + depth); } @@ -67,10 +66,8 @@ public Map> getSubNodes() { */ protected Cell findIndex(Point coordinates) { // Compute the sector for the coordinates - boolean left = (coordinates.x > (startCoordinates.x + bounds.width / 2)) ? false - : true; - boolean top = (coordinates.y > (startCoordinates.y + bounds.height / 2)) ? false - : true; + boolean left = (coordinates.x() > (startCoordinates.x() + bounds.width() / 2)) ? false : true; + boolean top = (coordinates.y() > (startCoordinates.y() + bounds.height() / 2)) ? false : true; // top left Cell index = Cell.TOP_LEFT; @@ -91,8 +88,9 @@ protected Cell findIndex(Point coordinates) { } } - log.debug("Coordinate [" + coordinates.x + "-" + coordinates.y - + "] is within " + index.toString() + " at depth " + depth); + if (log.isDebugEnabled()) { + log.debug("Coordinate [" + coordinates.x() + "-" + coordinates.y() + "] is within " + index.toString() + " at depth " + depth); + } return index; } @@ -125,8 +123,7 @@ public Vector> getElements(Point coordinates) { } /** - * Insert the element into this node. If needed a subdivision will be - * performed + * Insert the element into this node. If needed a subdivision will be performed * * @param element */ @@ -169,39 +166,33 @@ public void subdivide() { log.debug("Subdividing node at depth " + depth); int depth = this.depth + 1; - int bx = this.startCoordinates.x; - int by = this.startCoordinates.y; + int bx = this.startCoordinates.x(); + int by = this.startCoordinates.y(); // Create the bounds for the new cell - Dimension newBounds = new Dimension(this.bounds.width / 2, - this.bounds.height / 2); + Size newBounds = Size.of(bounds.width() / 2, bounds.height() / 2); // Add new bounds to current start coordinates to calculate the new // start coordinates - int newXStartCoordinate = bx + newBounds.width; - int newYStartCoordinate = by + newBounds.height; + int newXStartCoordinate = bx + newBounds.width(); + int newYStartCoordinate = by + newBounds.height(); PointNode cellNode = null; // top left - cellNode = new PointNode(new Point(bx, by), newBounds, depth, - this.maxDepth, this.maxElements); + cellNode = new PointNode(Point.of(bx, by), newBounds, depth, this.maxDepth, this.maxElements); this.nodes.put(Cell.TOP_LEFT, cellNode); // top right - cellNode = new PointNode(new Point(newXStartCoordinate, by), - newBounds, depth, this.maxDepth, this.maxElements); + cellNode = new PointNode(Point.of(newXStartCoordinate, by), newBounds, depth, this.maxDepth, this.maxElements); this.nodes.put(Cell.TOP_RIGHT, cellNode); // bottom left - cellNode = new PointNode(new Point(bx, newYStartCoordinate), - newBounds, depth, this.maxDepth, this.maxElements); + cellNode = new PointNode(Point.of(bx, newYStartCoordinate), newBounds, depth, this.maxDepth, this.maxElements); this.nodes.put(Cell.BOTTOM_LEFT, cellNode); // bottom right - cellNode = new PointNode(new Point(newXStartCoordinate, - newYStartCoordinate), newBounds, depth, this.maxDepth, - this.maxElements); + cellNode = new PointNode(Point.of(newXStartCoordinate, newYStartCoordinate), newBounds, depth, this.maxDepth, this.maxElements); this.nodes.put(Cell.BOTTOM_RIGHT, cellNode); } diff --git a/src/main/java/at/jotschi/quadtree/point/PointNodeElement.java b/src/main/java/io/metaloom/quadtree/point/impl/PointNodeElement.java similarity index 55% rename from src/main/java/at/jotschi/quadtree/point/PointNodeElement.java rename to src/main/java/io/metaloom/quadtree/point/impl/PointNodeElement.java index b94abab..06e1d79 100644 --- a/src/main/java/at/jotschi/quadtree/point/PointNodeElement.java +++ b/src/main/java/io/metaloom/quadtree/point/impl/PointNodeElement.java @@ -1,10 +1,9 @@ -package at.jotschi.quadtree.point; +package io.metaloom.quadtree.point.impl; -import java.awt.Point; -import at.jotschi.quadtree.AbstractNodeElement; +import io.metaloom.quadtree.AbstractNodeElement; +import io.metaloom.quadtree.point.Point; -@SuppressWarnings("serial") public class PointNodeElement extends AbstractNodeElement { public PointNodeElement(Point coordinates, T element) { diff --git a/src/main/java/io/metaloom/quadtree/point/impl/PointQuadTreeImpl.java b/src/main/java/io/metaloom/quadtree/point/impl/PointQuadTreeImpl.java new file mode 100644 index 0000000..426309e --- /dev/null +++ b/src/main/java/io/metaloom/quadtree/point/impl/PointQuadTreeImpl.java @@ -0,0 +1,104 @@ +package io.metaloom.quadtree.point.impl; + +import java.util.Vector; + +import io.metaloom.quadtree.AbstractQuadTree; +import io.metaloom.quadtree.Size; +import io.metaloom.quadtree.point.Point; +import io.metaloom.quadtree.point.PointQuadTree; + +/** + * Creates a new QuadTree that can hold the given type of elements + * + * @param + */ +public class PointQuadTreeImpl extends AbstractQuadTree implements PointQuadTree { + + protected PointNode rootNode; + + /** + * Create a new QuadTree with the give start coordinates and size + * + * @param startCorrdinates + * @param size + */ + public PointQuadTreeImpl(Point startCoordinates, Size size) { + super(startCoordinates, size); + this.rootNode = new PointNode(startCoordinates, size, 0); + } + + public PointQuadTreeImpl(Point startCoordinates, Size size, int maxDepth, int maxChildren) { + super(startCoordinates, size); + this.rootNode = new PointNode(startCoordinates, size, 0, maxDepth, maxChildren); + } + + @Override + public void insert(int x, int y, T element) { + insert(Point.of(x, y), element); + } + + @Override + public void insert(Point point, Size size, T element) { + + // Check if the element coordinates are within bounds of the quadtree + if (point.x() > startCoordinates.x() + size.width() || point.x() < startCoordinates.x()) { + throw new IndexOutOfBoundsException("The x coordinate must be within bounds of [" + startCoordinates.x() + "] to [" + size.width() + "]"); + } + if (point.y() > startCoordinates.y() + size.height() || point.y() < startCoordinates.y()) { + throw new IndexOutOfBoundsException( + "The y coordinate must be within bounds of [" + startCoordinates.y() + "] to [" + size.height() + "]"); + } + + // Check if the right bottom corner is within bounds + if (point.x() + size.width() > startCoordinates.x() + size.width() || point.x() < startCoordinates.x()) { + throw new IndexOutOfBoundsException("The x coordinate must be within bounds of [" + startCoordinates.x() + "] to [" + size.width() + "]"); + } + if (point.y() + size.width() > startCoordinates.y() + size.height() || point.y() < startCoordinates.y()) { + throw new IndexOutOfBoundsException( + "The y coordinate must be within bounds of [" + startCoordinates.y() + "] to [" + size.height() + "]"); + } + + rootNode.insert(new PointNodeElement(point, element)); + + } + + @Override + public void insert(T element, Size elementSize) { + throw new RuntimeException("Not supported for point quad trees"); + } + + @Override + public void insert(Point point, T element) { + + // Check if the element coordinates are within bounds of the quadtree + if (point.x() > startCoordinates.x() + size.width() || point.x() < startCoordinates.x()) { + throw new IndexOutOfBoundsException("The x coordinate must be within bounds of [" + startCoordinates.x() + "] to [" + size.width() + "]"); + } + if (point.y() > startCoordinates.y() + size.height() || point.y() < startCoordinates.y()) { + throw new IndexOutOfBoundsException( + "The y coordinate must be within bounds of [" + startCoordinates.y() + "] to [" + size.height() + "]"); + } + + rootNode.insert(new PointNodeElement(point, element)); + } + + @Override + public PointNode getRootNode() { + return rootNode; + } + + /** + * Returns all elements within the cell that matches the given coordinates + * + * @param coordinates + * @return + */ + public Vector> getElements(Point coordinates) { + return rootNode.getElements(coordinates); + } + + @Override + public void clear() { + rootNode.clear(); + } +} diff --git a/src/main/java/io/metaloom/quadtree/spatial/SpatialNode.java b/src/main/java/io/metaloom/quadtree/spatial/SpatialNode.java new file mode 100644 index 0000000..48312cc --- /dev/null +++ b/src/main/java/io/metaloom/quadtree/spatial/SpatialNode.java @@ -0,0 +1,25 @@ +package io.metaloom.quadtree.spatial; + +import java.util.Map; + +import io.metaloom.quadtree.Cell; +import io.metaloom.quadtree.Node; + +public interface SpatialNode extends Node { + + SpatialNodeElement getElement(); + + /** + * Insert the element into this node. If needed a subdivision will be performed + * + * @param element + * @return Returns false when the element could not be inserted + * + */ + boolean insert(SpatialNodeElement element); + + /** + * Return the subnodes of this parent node. + */ + Map> getSubNodes(); +} diff --git a/src/main/java/io/metaloom/quadtree/spatial/SpatialNodeElement.java b/src/main/java/io/metaloom/quadtree/spatial/SpatialNodeElement.java new file mode 100644 index 0000000..71134a5 --- /dev/null +++ b/src/main/java/io/metaloom/quadtree/spatial/SpatialNodeElement.java @@ -0,0 +1,15 @@ +package io.metaloom.quadtree.spatial; + +import io.metaloom.quadtree.NodeElement; + +public interface SpatialNodeElement extends NodeElement { + + int getWidth(); + + int getHeight(); + + void setY(int y); + + void setX(int x); + +} diff --git a/src/main/java/io/metaloom/quadtree/spatial/SpatialQuadTree.java b/src/main/java/io/metaloom/quadtree/spatial/SpatialQuadTree.java new file mode 100644 index 0000000..a60feb0 --- /dev/null +++ b/src/main/java/io/metaloom/quadtree/spatial/SpatialQuadTree.java @@ -0,0 +1,12 @@ +package io.metaloom.quadtree.spatial; + +import io.metaloom.quadtree.QuadTree; + +/** + * A QuadTree that can hold the given type of elements. Each element can also have a certain dimension. + * + * @param + */ +public interface SpatialQuadTree extends QuadTree { + +} diff --git a/src/main/java/io/metaloom/quadtree/spatial/impl/SpatialNodeElementImpl.java b/src/main/java/io/metaloom/quadtree/spatial/impl/SpatialNodeElementImpl.java new file mode 100644 index 0000000..6b1e8bd --- /dev/null +++ b/src/main/java/io/metaloom/quadtree/spatial/impl/SpatialNodeElementImpl.java @@ -0,0 +1,34 @@ +package io.metaloom.quadtree.spatial.impl; + + +import io.metaloom.quadtree.AbstractNodeElement; +import io.metaloom.quadtree.Size; +import io.metaloom.quadtree.point.Point; +import io.metaloom.quadtree.spatial.SpatialNodeElement; + + +public class SpatialNodeElementImpl extends AbstractNodeElement implements SpatialNodeElement { + + protected Size elementSize; + + public SpatialNodeElementImpl(T element, Size elementSize) { + super(element); + this.elementSize = elementSize; + } + + public SpatialNodeElementImpl(Point coordinates, Size elementSize, T element) { + super(coordinates, element); + this.elementSize = elementSize; + } + + @Override + public int getWidth() { + return elementSize.width(); + } + + @Override + public int getHeight() { + return elementSize.height(); + } + +} diff --git a/src/main/java/io/metaloom/quadtree/spatial/impl/SpatialNodeImpl.java b/src/main/java/io/metaloom/quadtree/spatial/impl/SpatialNodeImpl.java new file mode 100644 index 0000000..eeace79 --- /dev/null +++ b/src/main/java/io/metaloom/quadtree/spatial/impl/SpatialNodeImpl.java @@ -0,0 +1,142 @@ +package io.metaloom.quadtree.spatial.impl; + +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.metaloom.quadtree.AbstractNode; +import io.metaloom.quadtree.Cell; +import io.metaloom.quadtree.Size; +import io.metaloom.quadtree.point.Point; +import io.metaloom.quadtree.spatial.SpatialNode; +import io.metaloom.quadtree.spatial.SpatialNodeElement; + +public class SpatialNodeImpl extends AbstractNode implements SpatialNode { + + public static Logger log = LoggerFactory.getLogger(SpatialNodeImpl.class); + + protected Map> nodes = new HashMap<>(); + + protected SpatialNodeElement element; + + public SpatialNodeImpl(Point startCoordinates, Size bounds, int depth) { + super(startCoordinates, bounds, depth); + } + + public SpatialNodeImpl(Point startCoordinates, Size bounds, int depth, int maxDepth, int maxChildren) { + super(startCoordinates, bounds, depth, maxDepth, maxChildren); + } + + @Override + public boolean insert(SpatialNodeElement insertElement) { + + // Check if this node already contains a element + if (element != null) { + return false; + } + + boolean wMatch = insertElement.getWidth() == this.getBounds().width(); + boolean hMatch = insertElement.getHeight() == this.getBounds().height(); + boolean fits = wMatch && hMatch; + + boolean wSmaller = insertElement.getWidth() < this.getBounds().width(); + boolean hSmaller = insertElement.getHeight() < this.getBounds().height(); + boolean smaller = wSmaller && hSmaller; + + log.debug("Inserting element " + insertElement.getHeight() + " - " + insertElement.getWidth()); + + // Check if the element fits into this node + if (fits && nodes.size() == 0) { + insertElement.setX(startCoordinates.x()); + insertElement.setY(startCoordinates.y()); + this.element = insertElement; + if (log.isDebugEnabled()) { + log.debug("Inserting element at coordinates [" + insertElement.x() + "-" + insertElement.y() + "]"); + } + return true; + } else if (!(this.depth >= MAX_DEPTH) && nodes.size() == 0 && smaller) { + // We need to subdivide the node if the element is smaller than then the dimensions of this node. + subdivide(); + // After subdivision we try to insert the element into one of the subnodes + for (SpatialNode current : nodes.values()) { + if (current.insert(insertElement)) { + return true; + } + } + + } else { + // Recall insert for the element. This will try to add the element into one of the subnodes. + for (SpatialNode current : nodes.values()) { + if (current.insert(insertElement)) { + return true; + } + } + } + + return false; + } + + /** + * Subdivide the current node and add subnodes + */ + @Override + public void subdivide() { + if (log.isDebugEnabled()) { + log.debug("Subdividing node at depth " + depth); + } + int depth = this.depth + 1; + + int bx = startCoordinates.x(); + int by = startCoordinates.y(); + + // Create the bounds for the new cell + Size newBounds = Size.of(bounds.width() / 2, bounds.height() / 2); + + // Add new bounds to current start coordinates to calculate the new + // start coordinates + int newXStartCoordinate = bx + newBounds.width(); + int newYStartCoordinate = by + newBounds.height(); + + SpatialNodeImpl cellNode = null; + + // top left + cellNode = new SpatialNodeImpl(Point.of(bx, by), newBounds, depth); + nodes.put(Cell.TOP_LEFT, cellNode); + + // top right + cellNode = new SpatialNodeImpl(Point.of(newXStartCoordinate, by), newBounds, depth); + nodes.put(Cell.TOP_RIGHT, cellNode); + + // bottom left + cellNode = new SpatialNodeImpl(Point.of(bx, newYStartCoordinate), newBounds, depth); + nodes.put(Cell.BOTTOM_LEFT, cellNode); + + // bottom right + cellNode = new SpatialNodeImpl(Point.of(newXStartCoordinate, newYStartCoordinate), newBounds, depth); + nodes.put(Cell.BOTTOM_RIGHT, cellNode); + } + + @Override + public void clear() { + for (SpatialNode node : nodes.values()) { + node.clear(); + } + } + + @Override + public SpatialNodeElement getElement() { + return this.element; + } + + /** + * Returns the subnodes of this node + * + * @return + */ + public Map> getSubNodes() { + return this.nodes; + } + +} diff --git a/src/main/java/io/metaloom/quadtree/spatial/impl/SpatialQuadTreeImpl.java b/src/main/java/io/metaloom/quadtree/spatial/impl/SpatialQuadTreeImpl.java new file mode 100644 index 0000000..456360b --- /dev/null +++ b/src/main/java/io/metaloom/quadtree/spatial/impl/SpatialQuadTreeImpl.java @@ -0,0 +1,72 @@ +package io.metaloom.quadtree.spatial.impl; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.metaloom.quadtree.AbstractQuadTree; +import io.metaloom.quadtree.Size; +import io.metaloom.quadtree.point.Point; +import io.metaloom.quadtree.spatial.SpatialNode; +import io.metaloom.quadtree.spatial.SpatialQuadTree; + +public class SpatialQuadTreeImpl extends AbstractQuadTree implements SpatialQuadTree { + + protected static Logger log = LoggerFactory.getLogger(SpatialQuadTreeImpl.class); + + protected SpatialNodeImpl rootNode; + + /** + * Create a new QuadTree with the give start coordinates and size + * + * @param startCorrdinates + * @param size + */ + public SpatialQuadTreeImpl(Point startCoordinates, Size size) { + super(startCoordinates, size); + this.rootNode = new SpatialNodeImpl(startCoordinates, size, 0); + } + + @Override + public void insert(T element, Size elementSize) { + rootNode.insert(new SpatialNodeElementImpl(element, elementSize)); + } + + @Override + public void insert(Point point, Size elementSize, T element) { + + // Check if the element coordinates are within bounds of the quadtree + if (point.x() > startCoordinates.x() + size.width() || point.x() < startCoordinates.x()) { + throw new IndexOutOfBoundsException( + "The x coordinate must be within bounds of [" + startCoordinates.x() + "] to [" + size.width() + "] / [" + point.x() + "]"); + } + if (point.y() > startCoordinates.y() + size.height() || point.y() < startCoordinates.y()) { + throw new IndexOutOfBoundsException( + "The y coordinate must be within bounds of [" + startCoordinates.y() + "] to [" + size.height() + "] / [" + point.y() + "]"); + } + + // Check if the right bottom corner is within bounds + if (point.x() + elementSize.width() > startCoordinates.x() + size.width() || point.x() < startCoordinates.x()) { + throw new IndexOutOfBoundsException("The x coordinate must be within bounds of [" + startCoordinates.x() + "] to [" + size.width() + + "] / [" + point.x() + elementSize.width() + "]"); + } + + if (point.y() + elementSize.height() > startCoordinates.y() + size.height() || point.y() < startCoordinates.y()) { + throw new IndexOutOfBoundsException("The y coordinate must be within bounds of [" + startCoordinates.y() + "] to [" + size.height() + + "] / [" + point.x() + elementSize.height() + "]"); + } + + rootNode.insert(new SpatialNodeElementImpl(point, elementSize, element)); + + } + + @Override + public SpatialNode getRootNode() { + return rootNode; + } + + @Override + public void clear() { + rootNode.clear(); + } + +} diff --git a/src/main/resources/log4j.properties b/src/main/resources/log4j.properties deleted file mode 100644 index fa37c0e..0000000 --- a/src/main/resources/log4j.properties +++ /dev/null @@ -1,9 +0,0 @@ -log4j.rootLogger=DEBUG, A1 -log4j.appender.A1=org.apache.log4j.ConsoleAppender -log4j.appender.A1.layout=org.apache.log4j.PatternLayout - -# Print the date in ISO 8601 format -log4j.appender.A1.layout.ConversionPattern=%-5p %c - %m%n - -# Print only messages of level WARN or above in the package com.foo. -log4j.at.jotschi.quadtree=DEBUG \ No newline at end of file diff --git a/src/test/java/at/jotschi/quadtree/QuadTreeTest.java b/src/test/java/at/jotschi/quadtree/QuadTreeTest.java deleted file mode 100644 index cdc1a03..0000000 --- a/src/test/java/at/jotschi/quadtree/QuadTreeTest.java +++ /dev/null @@ -1,49 +0,0 @@ -package at.jotschi.quadtree; - -import java.awt.Dimension; -import java.awt.Point; -import java.util.Vector; - -import org.junit.Test; - -import at.jotschi.quadtree.point.PointQuadTree; -import at.jotschi.quadtree.spacial.SpatialQuadTree; - -/** - * Unit test for simple tree setup. - */ -public class QuadTreeTest { - - @Test - public void testApp() { - PointQuadTree tree = new PointQuadTree(new Point(0, 0), - new Dimension(600, 600)); - tree.insert(1, 3, "1"); - tree.insert(11, 32, "2"); - tree.insert(11, 52, "3"); - - tree.insert(454, 555, "4"); - tree.insert(353, 555, "5"); - tree.insert(552, 555, "6"); - tree.insert(551, 555, "7"); - - Vector> elements = (Vector>) tree - .getElements(new Point(500, 550)); - - for (AbstractNodeElement element : elements) { - System.out.println("[" + element.getX() + "-" + element.getY() - + "] " + element.getElement()); - } - } - - @Test - public void quadTreeBoundsTest() { - SpatialQuadTree tree = new SpatialQuadTree( - new Point(0, 0), new Dimension(600, 600)); - tree.insert(new Point(11, 3), new Dimension(10, 10), "1"); - tree.insert(new Point(22, 3), new Dimension(30, 10), "2"); - tree.insert(new Point(33, 3), new Dimension(40, 10), "3"); - - } - -} diff --git a/src/test/java/io/metaloom/quadtree/QuadTreeTest.java b/src/test/java/io/metaloom/quadtree/QuadTreeTest.java new file mode 100644 index 0000000..cb2b3c4 --- /dev/null +++ b/src/test/java/io/metaloom/quadtree/QuadTreeTest.java @@ -0,0 +1,54 @@ +package io.metaloom.quadtree; + +import static org.junit.Assert.assertEquals; + +import java.util.Vector; + +import org.junit.Test; + +import io.metaloom.quadtree.point.Point; +import io.metaloom.quadtree.point.impl.PointNodeElement; +import io.metaloom.quadtree.point.impl.PointQuadTreeImpl; +import io.metaloom.quadtree.spatial.impl.SpatialQuadTreeImpl; + +/** + * Unit test for simple tree setup. + */ +public class QuadTreeTest { + + @Test + public void testBasics() { + PointQuadTreeImpl tree = new PointQuadTreeImpl<>(Point.of(0, 0), Size.of(600, 600)); + tree.insert(1, 3, "1"); + tree.insert(11, 32, "2"); + tree.insert(11, 52, "3"); + + tree.insert(454, 555, "4"); + tree.insert(353, 555, "5"); + tree.insert(552, 555, "6"); + tree.insert(551, 555, "7"); + + Vector> elements = tree.getElements(Point.of(500, 550)); + + for (NodeElement element : elements) { + System.out.println("[" + element.x() + "-" + element.y() + "] " + element.getElement()); + } + } + + @Test + public void quadTreeBoundsTest() { + SpatialQuadTreeImpl tree = new SpatialQuadTreeImpl<>(Point.of(0, 0), Size.of(600, 600)); + tree.insert(Point.of(11, 3), Size.of(10, 10), "1"); + tree.insert(Point.of(22, 3), Size.of(30, 10), "2"); + tree.insert(Point.of(33, 3), Size.of(40, 10), "3"); + + } + + @Test + public void testEmptyTree() { + SpatialQuadTreeImpl tree = new SpatialQuadTreeImpl<>(Point.of(0, 0), Size.of(600, 600)); + assertEquals(600, tree.getSize().height()); + assertEquals(600, tree.getSize().width()); + } + +} diff --git a/src/test/java/at/jotschi/quadtree/RenderPointQuadTree.java b/src/test/java/io/metaloom/quadtree/RenderPointQuadTree.java similarity index 58% rename from src/test/java/at/jotschi/quadtree/RenderPointQuadTree.java rename to src/test/java/io/metaloom/quadtree/RenderPointQuadTree.java index d0e4b68..c037418 100644 --- a/src/test/java/at/jotschi/quadtree/RenderPointQuadTree.java +++ b/src/test/java/io/metaloom/quadtree/RenderPointQuadTree.java @@ -1,25 +1,25 @@ -package at.jotschi.quadtree; +package io.metaloom.quadtree; import java.awt.Color; -import java.awt.Dimension; import java.awt.Graphics; -import java.awt.Point; +import io.metaloom.quadtree.point.Point; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.util.Map; import java.util.Vector; -import at.jotschi.quadtree.AbstractNode.Cell; -import at.jotschi.quadtree.gui.QuadTreePanel; -import at.jotschi.quadtree.point.PointNode; -import at.jotschi.quadtree.point.PointQuadTree; +import io.metaloom.quadtree.gui.QuadTreePanel; +import io.metaloom.quadtree.point.impl.PointNode; +import io.metaloom.quadtree.point.impl.PointNodeElement; +import io.metaloom.quadtree.point.impl.PointQuadTreeImpl; -@SuppressWarnings("serial") public class RenderPointQuadTree extends QuadTreePanel { - protected PointQuadTree tree; + private static final long serialVersionUID = -2360219811620173423L; - protected Vector> selectedElements = new Vector>(); + protected PointQuadTreeImpl tree; + + protected Vector> selectedElements = new Vector<>(); public static void main(String[] args) { new RenderPointQuadTree(); @@ -35,9 +35,8 @@ protected RenderPointQuadTree() { * * @return */ - protected PointQuadTree createQuadTree() { - PointQuadTree tree = new PointQuadTree(new Point(100, - 100), new Dimension(400, 400), 8, 4); + protected PointQuadTreeImpl createQuadTree() { + PointQuadTreeImpl tree = new PointQuadTreeImpl<>(Point.of(100, 100), Size.of(400, 400), 8, 4); for (int i = 0; i < 200; i++) { int x = (int) (Math.random() * 1000 * 0.4) + 100; @@ -51,9 +50,7 @@ protected PointQuadTree createQuadTree() { @Override protected void paintComponent(Graphics g) { - g.drawString( - "Left click to add new points. Right click to highlight all elements of a selected cell/node.", - 100, 80); + g.drawString("Left click to add new points. Right click to highlight all elements of a selected cell/node.", 100, 80); PointNode rootNode = tree.getRootNode(); drawCells(rootNode, g); } @@ -66,11 +63,10 @@ protected void paintComponent(Graphics g) { */ protected void drawCells(PointNode node, Graphics g) { - Dimension bounds = node.getBounds(); + Size bounds = node.getBounds(); Point startCoordinates = node.getStartCoordinates(); // Draw node bounds - g.drawRect(startCoordinates.x, startCoordinates.y, bounds.width, - bounds.height); + g.drawRect(startCoordinates.x(), startCoordinates.y(), bounds.width(), bounds.height()); // Draw subnodes Map> subNodes = node.getSubNodes(); @@ -87,8 +83,8 @@ protected void drawCells(PointNode node, Graphics g) { public void drawSelectedElements(Graphics g) { g.setColor(Color.RED); - for (AbstractNodeElement element : selectedElements) { - g.drawOval((int) element.getX()-2, (int) element.getY()-2, 4, 4); + for (NodeElement element : selectedElements) { + g.drawOval(element.x() - 2, element.y() - 2, 4, 4); } g.setColor(Color.BLACK); } @@ -98,13 +94,11 @@ public void drawSelectedElements(Graphics g) { * * @param g */ - public void drawElements(PointNode node, Graphics g) { - - Vector> elements = (Vector>) node - .getElements(); - for (AbstractNodeElement element : elements) { - int x = (int) element.getX(); - int y = (int) element.getY(); + public void drawElements(PointNode node, Graphics g) { + Vector> elements = node.getElements(); + for (PointNodeElement element : elements) { + int x = element.x(); + int y = element.y(); g.drawLine(x, y, x, y); } } @@ -123,21 +117,19 @@ public void keyReleased(KeyEvent e) { public void mouseClicked(MouseEvent e) { - Point p = e.getPoint(); - // Somehow my mousepoints do not match the coordinates at the window - // (maybe this is a xorg releated bug) + java.awt.Point p = e.getPoint(); + // Somehow my mousepoints do not match the coordinates at the window (maybe this is a xorg releated bug) p.x -= 8; p.y -= 30; if (e.getButton() == 1) { try { - tree.insert(p, "MouseClick"); + tree.insert(Point.of(p), "MouseClick"); } catch (IndexOutOfBoundsException e1) { log.info("Out of bounds - omitting."); } } else if (e.getButton() == 3) { - selectedElements = (Vector>) tree - .getElements(p); + selectedElements = tree.getElements(Point.of(p)); } repaint(); } diff --git a/src/test/java/at/jotschi/quadtree/RenderSpatialQuadTree.java b/src/test/java/io/metaloom/quadtree/RenderSpatialQuadTree.java similarity index 67% rename from src/test/java/at/jotschi/quadtree/RenderSpatialQuadTree.java rename to src/test/java/io/metaloom/quadtree/RenderSpatialQuadTree.java index b940586..488a76c 100644 --- a/src/test/java/at/jotschi/quadtree/RenderSpatialQuadTree.java +++ b/src/test/java/io/metaloom/quadtree/RenderSpatialQuadTree.java @@ -1,25 +1,24 @@ -package at.jotschi.quadtree; +package io.metaloom.quadtree; -import java.awt.Dimension; import java.awt.Graphics; import java.awt.Image; -import java.awt.Point; import java.awt.event.KeyEvent; import java.io.IOException; import java.util.Map; import javax.imageio.ImageIO; -import at.jotschi.quadtree.AbstractNode.Cell; -import at.jotschi.quadtree.gui.QuadTreePanel; -import at.jotschi.quadtree.spacial.SpatialNode; -import at.jotschi.quadtree.spacial.SpatialNodeElement; -import at.jotschi.quadtree.spacial.SpatialQuadTree; +import io.metaloom.quadtree.gui.QuadTreePanel; +import io.metaloom.quadtree.point.Point; +import io.metaloom.quadtree.spatial.SpatialNode; +import io.metaloom.quadtree.spatial.SpatialNodeElement; +import io.metaloom.quadtree.spatial.impl.SpatialQuadTreeImpl; -@SuppressWarnings("serial") public class RenderSpatialQuadTree extends QuadTreePanel { - protected SpatialQuadTree tree; + private static final long serialVersionUID = -7363155496742737522L; + + protected SpatialQuadTreeImpl tree; int untilDepth = 0; @@ -45,31 +44,30 @@ public RenderSpatialQuadTree() { * @return * @throws IOException */ - protected SpatialQuadTree createSpatialQuadTree() throws IOException { - - Point startCoordinates = new Point(100, 100); - Dimension size = new Dimension(512, 512); + protected SpatialQuadTreeImpl createSpatialQuadTree() throws IOException { + Point startCoordinates = Point.of(100, 100); + Size size = Size.of(512, 512); - SpatialQuadTree tree = new SpatialQuadTree(startCoordinates, size); + SpatialQuadTreeImpl tree = new SpatialQuadTreeImpl<>(startCoordinates, size); int elementSize = 32; for (int i = 0; i < 56; i++) { - Dimension dimension = new Dimension(elementSize, elementSize); + Size dimension = Size.of(elementSize, elementSize); tree.insert(getRandomImage(elementSize), dimension); } elementSize = 64; for (int i = 0; i < 16; i++) { - Dimension dimension = new Dimension(elementSize, elementSize); + Size dimension = Size.of(elementSize, elementSize); tree.insert(getRandomImage(elementSize), dimension); } elementSize = 128; for (int i = 0; i < 3; i++) { - Dimension dimension = new Dimension(elementSize, elementSize); + Size dimension = Size.of(elementSize, elementSize); tree.insert(getRandomImage(elementSize), dimension); } elementSize = 256; for (int i = 0; i < 1; i++) { - Dimension dimension = new Dimension(elementSize, elementSize); + Size dimension = Size.of(elementSize, elementSize); tree.insert(getRandomImage(elementSize), dimension); } @@ -94,39 +92,37 @@ public Image getRandomImage(int size) throws IOException { } protected void paintComponent(Graphics g) { - g.drawString("Hit space key to draw elements of the next level of the quadtree.", 100, 80); SpatialNode rootNode = tree.getRootNode(); drawCells(rootNode, g); } protected void drawCells(SpatialNode node, Graphics g) { - Dimension bounds = node.getBounds(); + Size bounds = node.getBounds(); Point startCoordinates = node.getStartCoordinates(); // Draw node bounds - g.drawRect(startCoordinates.x, startCoordinates.y, bounds.width, bounds.height); + g.drawRect(startCoordinates.x(), startCoordinates.y(), bounds.width(), bounds.height()); // Draw subnodes - Map> subNodes = node.getSubNodes(); + Map> subNodes = node.getSubNodes(); for (SpatialNode subNode : subNodes.values()) { drawCells(subNode, g); } // Draw areas of this node - if (node.getDepth() == untilDepth) + if (node.getDepth() == untilDepth) { drawElements(node, g); + } } public void keyReleased(KeyEvent e) { - if (e.getKeyCode() == 27) { System.exit(10); } else if (e.getKeyCode() == 32) { untilDepth++; repaint(); } - } /** @@ -135,10 +131,9 @@ public void keyReleased(KeyEvent e) { * @param g */ public void drawElements(SpatialNode node, Graphics g) { - SpatialNodeElement element = node.getElement(); if (element != null) { - g.drawImage(element.getElement(), (int) element.getX(), (int) element.getY(), null); + g.drawImage(element.getElement(), element.x(), element.y(), null); } } diff --git a/src/test/java/at/jotschi/quadtree/gui/QuadTreePanel.java b/src/test/java/io/metaloom/quadtree/gui/QuadTreePanel.java similarity index 70% rename from src/test/java/at/jotschi/quadtree/gui/QuadTreePanel.java rename to src/test/java/io/metaloom/quadtree/gui/QuadTreePanel.java index c7d1407..b134ce3 100644 --- a/src/test/java/at/jotschi/quadtree/gui/QuadTreePanel.java +++ b/src/test/java/io/metaloom/quadtree/gui/QuadTreePanel.java @@ -1,4 +1,4 @@ -package at.jotschi.quadtree.gui; +package io.metaloom.quadtree.gui; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; @@ -8,13 +8,14 @@ import javax.swing.JFrame; import javax.swing.JPanel; -import org.apache.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -@SuppressWarnings("serial") public class QuadTreePanel extends JPanel implements KeyListener, MouseListener { - - protected static Logger log = Logger.getLogger(QuadTreePanel.class); + private static final long serialVersionUID = 8763986244017440078L; + + protected static Logger log = LoggerFactory.getLogger(QuadTreePanel.class); /** * Create a new panel @@ -35,13 +36,11 @@ protected void setupGui() { } public void keyTyped(KeyEvent e) { - // TODO Auto-generated method stub - + // NOOP } public void keyPressed(KeyEvent e) { - // TODO Auto-generated method stub - + // NOOP } public void keyReleased(KeyEvent e) { @@ -53,27 +52,22 @@ public void keyReleased(KeyEvent e) { } public void mousePressed(MouseEvent e) { - // TODO Auto-generated method stub - + // NOOP } public void mouseReleased(MouseEvent e) { - // TODO Auto-generated method stub - + // NOOP } public void mouseEntered(MouseEvent e) { - // TODO Auto-generated method stub - + // NOOP } public void mouseExited(MouseEvent e) { - // TODO Auto-generated method stub - + // NOOP } public void mouseClicked(MouseEvent e) { - // TODO Auto-generated method stub - + // NOOP } } diff --git a/src/test/resources/logback.xml b/src/test/resources/logback.xml new file mode 100644 index 0000000..b310f35 --- /dev/null +++ b/src/test/resources/logback.xml @@ -0,0 +1,12 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + \ No newline at end of file