From 7ce715c1da8f6b683b4134c5fd0ba7d7cd5a5de2 Mon Sep 17 00:00:00 2001 From: misaochan Date: Tue, 5 Jan 2016 18:10:10 +1300 Subject: [PATCH] Copying Quadtree src files manually --- .../fr/free/nrw/commons/caching/Func.java | 7 + .../fr/free/nrw/commons/caching/Node.java | 123 +++++ .../fr/free/nrw/commons/caching/NodeType.java | 11 + .../fr/free/nrw/commons/caching/Point.java | 69 +++ .../fr/free/nrw/commons/caching/QuadTree.java | 477 ++++++++++++++++++ .../commons/caching/QuadTreeException.java | 8 + 6 files changed, 695 insertions(+) create mode 100644 commons/src/main/java/fr/free/nrw/commons/caching/Func.java create mode 100644 commons/src/main/java/fr/free/nrw/commons/caching/Node.java create mode 100644 commons/src/main/java/fr/free/nrw/commons/caching/NodeType.java create mode 100644 commons/src/main/java/fr/free/nrw/commons/caching/Point.java create mode 100644 commons/src/main/java/fr/free/nrw/commons/caching/QuadTree.java create mode 100644 commons/src/main/java/fr/free/nrw/commons/caching/QuadTreeException.java diff --git a/commons/src/main/java/fr/free/nrw/commons/caching/Func.java b/commons/src/main/java/fr/free/nrw/commons/caching/Func.java new file mode 100644 index 000000000..9703fc98f --- /dev/null +++ b/commons/src/main/java/fr/free/nrw/commons/caching/Func.java @@ -0,0 +1,7 @@ +package fr.free.nrw.commons.caching; + +import fr.free.nrw.commons.caching.QuadTree; + +public interface Func { + public void call(QuadTree quadTree, Node node); +} diff --git a/commons/src/main/java/fr/free/nrw/commons/caching/Node.java b/commons/src/main/java/fr/free/nrw/commons/caching/Node.java new file mode 100644 index 000000000..1ab054a99 --- /dev/null +++ b/commons/src/main/java/fr/free/nrw/commons/caching/Node.java @@ -0,0 +1,123 @@ +package fr.free.nrw.commons.caching; + + class Node { + + private double x; + private double y; + private double w; + private double h; + private Node opt_parent; + private Point point; + private NodeType nodetype = NodeType.EMPTY; + private Node nw; + private Node ne; + private Node sw; + private Node se; + + /** + * Constructs a new quad tree node. + * + * @param {double} x X-coordiate of node. + * @param {double} y Y-coordinate of node. + * @param {double} w Width of node. + * @param {double} h Height of node. + * @param {Node} opt_parent Optional parent node. + * @constructor + */ + public Node(double x, double y, double w, double h, Node opt_parent) { + this.x = x; + this.y = y; + this.w = w; + this.h = h; + this.opt_parent = opt_parent; + } + + public double getX() { + return x; + } + + public void setX(double x) { + this.x = x; + } + + public double getY() { + return y; + } + + public void setY(double y) { + this.y = y; + } + + public double getW() { + return w; + } + + public void setW(double w) { + this.w = w; + } + + public double getH() { + return h; + } + + public void setH(double h) { + this.h = h; + } + + public Node getParent() { + return opt_parent; + } + + public void setParent(Node opt_parent) { + this.opt_parent = opt_parent; + } + + public void setPoint(Point point) { + this.point = point; + } + + public Point getPoint() { + return this.point; + } + + public void setNodeType(NodeType nodetype) { + this.nodetype = nodetype; + } + + public NodeType getNodeType() { + return this.nodetype; + } + + + public void setNw(Node nw) { + this.nw = nw; + } + + public void setNe(Node ne) { + this.ne = ne; + } + + public void setSw(Node sw) { + this.sw = sw; + } + + public void setSe(Node se) { + this.se = se; + } + + public Node getNe() { + return ne; + } + + public Node getNw() { + return nw; + } + + public Node getSw() { + return sw; + } + + public Node getSe() { + return se; + } +} diff --git a/commons/src/main/java/fr/free/nrw/commons/caching/NodeType.java b/commons/src/main/java/fr/free/nrw/commons/caching/NodeType.java new file mode 100644 index 000000000..9aac701fa --- /dev/null +++ b/commons/src/main/java/fr/free/nrw/commons/caching/NodeType.java @@ -0,0 +1,11 @@ +package fr.free.nrw.commons.caching; + +/** + * Enumeration of node types. + * @enum {number} + */ +public enum NodeType { + EMPTY, + LEAF, + POINTER +} diff --git a/commons/src/main/java/fr/free/nrw/commons/caching/Point.java b/commons/src/main/java/fr/free/nrw/commons/caching/Point.java new file mode 100644 index 000000000..6b2fdb8c1 --- /dev/null +++ b/commons/src/main/java/fr/free/nrw/commons/caching/Point.java @@ -0,0 +1,69 @@ +package fr.free.nrw.commons.caching; + +public class Point implements Comparable { + + private double x; + private double y; + private Object opt_value; + + /** + * Creates a new point object. + * + * @param {double} x The x-coordinate of the point. + * @param {double} y The y-coordinate of the point. + * @param {Object} opt_value Optional value associated with the point. + */ + public Point(double x, double y, Object opt_value) { + this.x = x; + this.y = y; + this.opt_value = opt_value; + } + + public double getX() { + return x; + } + + public void setX(double x) { + this.x = x; + } + + public double getY() { + return y; + } + + public void setY(double y) { + this.y = y; + } + + public Object getValue() { + return opt_value; + } + + public void setValue(Object opt_value) { + this.opt_value = opt_value; + } + + @Override + public String toString() { + return "(" + this.x + ", " + this.y + ")"; + } + + @Override + public int compareTo(Object o) { + Point tmp = (Point) o; + if (this.x < tmp.x) { + return -1; + } else if (this.x > tmp.x) { + return 1; + } else { + if (this.y < tmp.y) { + return -1; + } else if (this.y > tmp.y) { + return 1; + } + return 0; + } + + } + +} diff --git a/commons/src/main/java/fr/free/nrw/commons/caching/QuadTree.java b/commons/src/main/java/fr/free/nrw/commons/caching/QuadTree.java new file mode 100644 index 000000000..1f77a3491 --- /dev/null +++ b/commons/src/main/java/fr/free/nrw/commons/caching/QuadTree.java @@ -0,0 +1,477 @@ +package fr.free.nrw.commons.caching; + +import java.util.ArrayList; +import java.util.List; + + +/** + * Datastructure: A point Quad Tree for representing 2D data. Each + * region has the same ratio as the bounds for the tree. + *

+ * The implementation currently requires pre-determined bounds for data as it + * can not rebalance itself to that degree. + */ +public class QuadTree { + + + private Node root_; + private int count_ = 0; + + /** + * Constructs a new quad tree. + * + * @param {double} minX Minimum x-value that can be held in tree. + * @param {double} minY Minimum y-value that can be held in tree. + * @param {double} maxX Maximum x-value that can be held in tree. + * @param {double} maxY Maximum y-value that can be held in tree. + */ + public QuadTree(double minX, double minY, double maxX, double maxY) { + this.root_ = new Node(minX, minY, maxX - minX, maxY - minY, null); + } + + /** + * Returns a reference to the tree's root node. Callers shouldn't modify nodes, + * directly. This is a convenience for visualization and debugging purposes. + * + * @return {Node} The root node. + */ + public Node getRootNode() { + return this.root_; + } + + //TODO: Marked by Nicolas as: Set an object at particular coordinates + /** + * Sets the value of an (x, y) point within the quad-tree. + * + * @param {double} x The x-coordinate. + * @param {double} y The y-coordinate. + * @param {Object} value The value associated with the point. + */ + public void set(double x, double y, Object value) { + + Node root = this.root_; + if (x < root.getX() || y < root.getY() || x > root.getX() + root.getW() || y > root.getY() + root.getH()) { + throw new QuadTreeException("Out of bounds : (" + x + ", " + y + ")"); + } + if (this.insert(root, new Point(x, y, value))) { + this.count_++; + } + } + + /** + * Gets the value of the point at (x, y) or null if the point is empty. + * + * @param {double} x The x-coordinate. + * @param {double} y The y-coordinate. + * @param {Object} opt_default The default value to return if the node doesn't + * exist. + * @return {*} The value of the node, the default value if the node + * doesn't exist, or undefined if the node doesn't exist and no default + * has been provided. + */ + public Object get(double x, double y, Object opt_default) { + Node node = this.find(this.root_, x, y); + return node != null ? node.getPoint().getValue() : opt_default; + } + + /** + * Removes a point from (x, y) if it exists. + * + * @param {double} x The x-coordinate. + * @param {double} y The y-coordinate. + * @return {Object} The value of the node that was removed, or null if the + * node doesn't exist. + */ + public Object remove(double x, double y) { + Node node = this.find(this.root_, x, y); + if (node != null) { + Object value = node.getPoint().getValue(); + node.setPoint(null); + node.setNodeType(NodeType.EMPTY); + this.balance(node); + this.count_--; + return value; + } else { + return null; + } + } + + /** + * Returns true if the point at (x, y) exists in the tree. + * + * @param {double} x The x-coordinate. + * @param {double} y The y-coordinate. + * @return {boolean} Whether the tree contains a point at (x, y). + */ + public boolean contains(double x, double y) { + return this.get(x, y, null) != null; + } + + /** + * @return {boolean} Whether the tree is empty. + */ + public boolean isEmpty() { + return this.root_.getNodeType() == NodeType.EMPTY; + } + + /** + * @return {number} The number of items in the tree. + */ + public int getCount() { + return this.count_; + } + + /** + * Removes all items from the tree. + */ + public void clear() { + this.root_.setNw(null); + this.root_.setNe(null); + this.root_.setSw(null); + this.root_.setSe(null); + this.root_.setNodeType(NodeType.EMPTY); + this.root_.setPoint(null); + this.count_ = 0; + } + + /** + * Returns an array containing the coordinates of each point stored in the tree. + * @return {Array.} Array of coordinates. + */ + public Point[] getKeys() { + final List arr = new ArrayList(); + this.traverse(this.root_, new Func() { + @Override + public void call(QuadTree quadTree, Node node) { + arr.add(node.getPoint()); + } + }); + return arr.toArray(new Point[arr.size()]); + } + + /** + * Returns an array containing all values stored within the tree. + * @return {Array.} The values stored within the tree. + */ + public Object[] getValues() { + final List arr = new ArrayList(); + this.traverse(this.root_, new Func() { + @Override + public void call(QuadTree quadTree, Node node) { + arr.add(node.getPoint().getValue()); + } + }); + + return arr.toArray(new Object[arr.size()]); + } + + public Point[] searchIntersect(final double xmin, final double ymin, final double xmax, final double ymax) { + final List arr = new ArrayList(); + this.navigate(this.root_, new Func() { + @Override + public void call(QuadTree quadTree, Node node) { + Point pt = node.getPoint(); + if (pt.getX() < xmin || pt.getX() > xmax || pt.getY() < ymin || pt.getY() > ymax) { + // Definitely not within the polygon! + } else { + arr.add(node.getPoint()); + } + + } + }, xmin, ymin, xmax, ymax); + return arr.toArray(new Point[arr.size()]); + } + + //TODO: Marked by Nicolas as: Find objects within a rectangle + public Point[] searchWithin(final double xmin, final double ymin, final double xmax, final double ymax) { + final List arr = new ArrayList(); + this.navigate(this.root_, new Func() { + @Override + public void call(QuadTree quadTree, Node node) { + Point pt = node.getPoint(); + if (pt.getX() > xmin && pt.getX() < xmax && pt.getY() > ymin && pt.getY() < ymax) { + arr.add(node.getPoint()); + } + } + }, xmin, ymin, xmax, ymax); + return arr.toArray(new Point[arr.size()]); + } + + public void navigate(Node node, Func func, double xmin, double ymin, double xmax, double ymax) { + switch (node.getNodeType()) { + case LEAF: + func.call(this, node); + break; + + case POINTER: + if (intersects(xmin, ymax, xmax, ymin, node.getNe())) + this.navigate(node.getNe(), func, xmin, ymin, xmax, ymax); + if (intersects(xmin, ymax, xmax, ymin, node.getSe())) + this.navigate(node.getSe(), func, xmin, ymin, xmax, ymax); + if (intersects(xmin, ymax, xmax, ymin, node.getSw())) + this.navigate(node.getSw(), func, xmin, ymin, xmax, ymax); + if (intersects(xmin, ymax, xmax, ymin, node.getNw())) + this.navigate(node.getNw(), func, xmin, ymin, xmax, ymax); + break; + } + } + + private boolean intersects(double left, double bottom, double right, double top, Node node) { + return !(node.getX() > right || + (node.getX() + node.getW()) < left || + node.getY() > bottom || + (node.getY() + node.getH()) < top); + } + /** + * Clones the quad-tree and returns the new instance. + * @return {QuadTree} A clone of the tree. + */ + public QuadTree clone() { + double x1 = this.root_.getX(); + double y1 = this.root_.getY(); + double x2 = x1 + this.root_.getW(); + double y2 = y1 + this.root_.getH(); + final QuadTree clone = new QuadTree(x1, y1, x2, y2); + // This is inefficient as the clone needs to recalculate the structure of the + // tree, even though we know it already. But this is easier and can be + // optimized when/if needed. + this.traverse(this.root_, new Func() { + @Override + public void call(QuadTree quadTree, Node node) { + clone.set(node.getPoint().getX(), node.getPoint().getY(), node.getPoint().getValue()); + } + }); + + + return clone; + } + + /** + * Traverses the tree depth-first, with quadrants being traversed in clockwise + * order (NE, SE, SW, NW). The provided function will be called for each + * leaf node that is encountered. + * @param {QuadTree.Node} node The current node. + * @param {function(QuadTree.Node)} fn The function to call + * for each leaf node. This function takes the node as an argument, and its + * return value is irrelevant. + * @private + */ + public void traverse(Node node, Func func) { + switch (node.getNodeType()) { + case LEAF: + func.call(this, node); + break; + + case POINTER: + this.traverse(node.getNe(), func); + this.traverse(node.getSe(), func); + this.traverse(node.getSw(), func); + this.traverse(node.getNw(), func); + break; + } + } + + /** + * Finds a leaf node with the same (x, y) coordinates as the target point, or + * null if no point exists. + * @param {QuadTree.Node} node The node to search in. + * @param {number} x The x-coordinate of the point to search for. + * @param {number} y The y-coordinate of the point to search for. + * @return {QuadTree.Node} The leaf node that matches the target, + * or null if it doesn't exist. + * @private + */ + public Node find(Node node, double x, double y) { + Node resposne = null; + switch (node.getNodeType()) { + case EMPTY: + break; + + case LEAF: + resposne = node.getPoint().getX() == x && node.getPoint().getY() == y ? node : null; + break; + + case POINTER: + resposne = this.find(this.getQuadrantForPoint(node, x, y), x, y); + break; + + default: + throw new QuadTreeException("Invalid nodeType"); + } + return resposne; + } + + /** + * Inserts a point into the tree, updating the tree's structure if necessary. + * @param {.QuadTree.Node} parent The parent to insert the point + * into. + * @param {QuadTree.Point} point The point to insert. + * @return {boolean} True if a new node was added to the tree; False if a node + * already existed with the correpsonding coordinates and had its value + * reset. + * @private + */ + private boolean insert(Node parent, Point point) { + Boolean result = false; + switch (parent.getNodeType()) { + case EMPTY: + this.setPointForNode(parent, point); + result = true; + break; + case LEAF: + if (parent.getPoint().getX() == point.getX() && parent.getPoint().getY() == point.getY()) { + this.setPointForNode(parent, point); + result = false; + } else { + this.split(parent); + result = this.insert(parent, point); + } + break; + case POINTER: + result = this.insert( + this.getQuadrantForPoint(parent, point.getX(), point.getY()), point); + break; + + default: + throw new QuadTreeException("Invalid nodeType in parent"); + } + return result; + } + + /** + * Converts a leaf node to a pointer node and reinserts the node's point into + * the correct child. + * @param {QuadTree.Node} node The node to split. + * @private + */ + private void split(Node node) { + Point oldPoint = node.getPoint(); + node.setPoint(null); + + node.setNodeType(NodeType.POINTER); + + double x = node.getX(); + double y = node.getY(); + double hw = node.getW() / 2; + double hh = node.getH() / 2; + + node.setNw(new Node(x, y, hw, hh, node)); + node.setNe(new Node(x + hw, y, hw, hh, node)); + node.setSw(new Node(x, y + hh, hw, hh, node)); + node.setSe(new Node(x + hw, y + hh, hw, hh, node)); + + this.insert(node, oldPoint); + } + + /** + * Attempts to balance a node. A node will need balancing if all its children + * are empty or it contains just one leaf. + * @param {QuadTree.Node} node The node to balance. + * @private + */ + private void balance(Node node) { + switch (node.getNodeType()) { + case EMPTY: + case LEAF: + if (node.getParent() != null) { + this.balance(node.getParent()); + } + break; + + case POINTER: { + Node nw = node.getNw(); + Node ne = node.getNe(); + Node sw = node.getSw(); + Node se = node.getSe(); + Node firstLeaf = null; + + // Look for the first non-empty child, if there is more than one then we + // break as this node can't be balanced. + if (nw.getNodeType() != NodeType.EMPTY) { + firstLeaf = nw; + } + if (ne.getNodeType() != NodeType.EMPTY) { + if (firstLeaf != null) { + break; + } + firstLeaf = ne; + } + if (sw.getNodeType() != NodeType.EMPTY) { + if (firstLeaf != null) { + break; + } + firstLeaf = sw; + } + if (se.getNodeType() != NodeType.EMPTY) { + if (firstLeaf != null) { + break; + } + firstLeaf = se; + } + + if (firstLeaf == null) { + // All child nodes are empty: so make this node empty. + node.setNodeType(NodeType.EMPTY); + node.setNw(null); + node.setNe(null); + node.setSw(null); + node.setSe(null); + + } else if (firstLeaf.getNodeType() == NodeType.POINTER) { + // Only child was a pointer, therefore we can't rebalance. + break; + + } else { + // Only child was a leaf: so update node's point and make it a leaf. + node.setNodeType(NodeType.LEAF); + node.setNw(null); + node.setNe(null); + node.setSw(null); + node.setSe(null); + node.setPoint(firstLeaf.getPoint()); + } + + // Try and balance the parent as well. + if (node.getParent() != null) { + this.balance(node.getParent()); + } + } + break; + } + } + + /** + * Returns the child quadrant within a node that contains the given (x, y) + * coordinate. + * @param {QuadTree.Node} parent The node. + * @param {number} x The x-coordinate to look for. + * @param {number} y The y-coordinate to look for. + * @return {QuadTree.Node} The child quadrant that contains the + * point. + * @private + */ + //TODO: Is this the method we want to retrieve the 'area' given a coordinate? + private Node getQuadrantForPoint(Node parent, double x, double y) { + double mx = parent.getX() + parent.getW() / 2; + double my = parent.getY() + parent.getH() / 2; + if (x < mx) { + return y < my ? parent.getNw() : parent.getSw(); + } else { + return y < my ? parent.getNe() : parent.getSe(); + } + } + + /** + * Sets the point for a node, as long as the node is a leaf or empty. + * @param {QuadTree.Node} node The node to set the point for. + * @param {QuadTree.Point} point The point to set. + * @private + */ + private void setPointForNode(Node node, Point point) { + if (node.getNodeType() == NodeType.POINTER) { + throw new QuadTreeException("Can not set point for node of type POINTER"); + } + node.setNodeType(NodeType.LEAF); + node.setPoint(point); + } +} diff --git a/commons/src/main/java/fr/free/nrw/commons/caching/QuadTreeException.java b/commons/src/main/java/fr/free/nrw/commons/caching/QuadTreeException.java new file mode 100644 index 000000000..36b0022d9 --- /dev/null +++ b/commons/src/main/java/fr/free/nrw/commons/caching/QuadTreeException.java @@ -0,0 +1,8 @@ +package fr.free.nrw.commons.caching; + +public class QuadTreeException extends RuntimeException { + + public QuadTreeException(String s) { + super(s); + } +}