// ********************************************************************** // // <copyright> // // BBN Technologies // 10 Moulton Street // Cambridge, MA 02138 // (617) 873-8000 // // Copyright (C) BBNT Solutions LLC. All rights reserved. // // </copyright> // ********************************************************************** // // $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/util/quadtree/QuadTreeNode.java,v $ // $RCSfile: QuadTreeNode.java,v $ // $Revision: 1.4 $ // $Date: 2004/10/14 18:06:32 $ // $Author: dietrick $ // // ********************************************************************** package com.bbn.openmap.util.quadtree; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.Vector; import com.bbn.openmap.MoreMath; /** * The QuadTreeNode is the part of the QuadTree that either holds children * nodes, or objects as leaves. Currently, the nodes that have children do not * hold items that span across children boundaries, since this was designed to * handle point data. */ public class QuadTreeNode<T> implements Serializable { static final long serialVersionUID = -6111633198469889444L; public final static float NO_MIN_SIZE = -1; public final static float DEFAULT_MIN_SIZE = 5; protected Collection<QuadTreeLeaf<T>> items; protected Collection<QuadTreeNode<T>> children; protected int maxItems; protected double minSize; public QuadTreeRect bounds; /** * Added to avoid problems when a node is completely filled with a single * point value. */ protected boolean allTheSamePoint; protected double firstLat; protected double firstLon; /** * Constructor to use if you are going to store the objects in lat/lon * space, and there is really no smallest node size. * * @param north northern border of node coverage. * @param west western border of node coverage. * @param south southern border of node coverage. * @param east eastern border of node coverage. * @param maximumItems number of items to hold in a node before splitting * itself into four children and redispensing the items into them. */ public QuadTreeNode(double north, double west, double south, double east, int maximumItems) { this(north, west, south, east, maximumItems, NO_MIN_SIZE); } /** * Constructor to use if you are going to store the objects in x/y space, * and there is a smallest node size because you don't want the nodes to be * smaller than a group of pixels. * * @param north northern border of node coverage. * @param west western border of node coverage. * @param south southern border of node coverage. * @param east eastern border of node coverage. * @param maximumItems number of items to hold in a node before splitting * itself into four children and redispensing the items into them. * @param minimumSize the minimum difference between the boundaries of the * node. */ public QuadTreeNode(double north, double west, double south, double east, int maximumItems, double minimumSize) { bounds = new QuadTreeRect(north, west, south, east); maxItems = maximumItems; minSize = minimumSize; items = new ArrayList<QuadTreeLeaf<T>>(); } /** Return true if the node has children. */ public boolean hasChildren() { return (children != null); } /** * This method splits the node into four children, and disperses the items * into the children. The split only happens if the boundary size of the * node is larger than the minimum size (if we care). The items in this node * are cleared after they are put into the children. */ protected void split() { // Make sure we're bigger than the minimum, if we care, if (minSize != NO_MIN_SIZE) { if (MoreMath.approximately_equal(bounds.north, bounds.south, minSize) && MoreMath.approximately_equal(bounds.east, bounds.west, minSize)) return; } double nsHalf = (bounds.north - (bounds.north - bounds.south) / 2.0); double ewHalf = (bounds.east - (bounds.east - bounds.west) / 2.0); children = new ArrayList<QuadTreeNode<T>>(4); children.add(new QuadTreeNode<T>(bounds.north, bounds.west, nsHalf, ewHalf, maxItems)); children.add(new QuadTreeNode<T>(bounds.north, ewHalf, nsHalf, bounds.east, maxItems)); children.add(new QuadTreeNode<T>(nsHalf, ewHalf, bounds.south, bounds.east, maxItems)); children.add(new QuadTreeNode<T>(nsHalf, bounds.west, bounds.south, ewHalf, maxItems)); Collection<QuadTreeLeaf> temp = new ArrayList<QuadTreeLeaf>(items); items.clear(); for (QuadTreeLeaf leaf : temp) { put(leaf); } } /** * Get the node that covers a certain lat/lon pair. * * @param lat up-down location in QuadTree Grid (latitude, y) * @param lon left-right location in QuadTree Grid (longitude, x) * @return node if child covers the point, null if the point is out of * range. */ protected QuadTreeNode<T> getChild(double lat, double lon) { if (bounds.pointWithinBounds(lat, lon)) { if (children != null) { for (QuadTreeNode<T> child : children) { if (child.bounds.pointWithinBounds(lat, lon)) return child.getChild(lat, lon); } } else { return this; // no children, lat, lon here... } } return null; } /** * Add a object into the tree at a location. * * @param lat up-down location in QuadTree Grid (latitude, y) * @param lon left-right location in QuadTree Grid (longitude, x) * @param obj object to add to the tree. * @return true if the put worked. */ public boolean put(double lat, double lon, T obj) { return put(new QuadTreeLeaf<T>(lat, lon, obj)); } /** * Add a QuadTreeLeaf into the tree at a location. * * @param leaf object-location composite * @return true if the pution worked. */ public boolean put(QuadTreeLeaf<T> leaf) { if (children == null) { this.items.add(leaf); if (this.items.size() == 1) { this.allTheSamePoint = true; this.firstLat = leaf.latitude; this.firstLon = leaf.longitude; } else { if (this.firstLat != leaf.latitude || this.firstLon != leaf.longitude) { this.allTheSamePoint = false; } } if (this.items.size() > maxItems && !this.allTheSamePoint) { split(); } return true; } else { QuadTreeNode<T> node = getChild(leaf.latitude, leaf.longitude); if (node != null) { return node.put(leaf); } } return false; } /** * Remove a object out of the tree at a location. * * @param lat up-down location in QuadTree Grid (latitude, y) * @param lon left-right location in QuadTree Grid (longitude, x) * @param obj the object to be removed. * @return the object removed, null if the object not found. */ public T remove(double lat, double lon, T obj) { return remove(new QuadTreeLeaf<T>(lat, lon, obj)); } /** * Remove a QuadTreeLeaf out of the tree at a location. * * @param leaf object-location composite * @return the object removed, null if the object not found. */ public T remove(QuadTreeLeaf<T> leaf) { if (children == null) { // This must be the node that has it... for (QuadTreeLeaf<T> qtl : new ArrayList<QuadTreeLeaf<T>>(items)) { if (leaf.object == qtl.object) { items.remove(qtl); return qtl.object; } } } else { QuadTreeNode<T> node = getChild(leaf.latitude, leaf.longitude); if (node != null) { return node.remove(leaf); } } return null; } /** Clear the tree below this node. */ public void clear() { this.items.clear(); if (children != null) { for (QuadTreeNode child : children) { child.clear(); } children = null; } } /** * Get an object closest to a lat/lon. * * @param lat up-down location in QuadTree Grid (latitude, y) * @param lon left-right location in QuadTree Grid (longitude, x) * @return the object that matches the best distance, null if no object was * found. */ public T get(double lat, double lon) { return get(lat, lon, Double.POSITIVE_INFINITY); } /** * Get an object closest to a lat/lon. If there are children at this node, * then the children are searched. The children are checked first, to see if * they are closer than the best distance already found. If a closer object * is found, bestDistance will be updated with a new Double object that has * the new distance. * * @param lat up-down location in QuadTree Grid (latitude, y) * @param lon left-right location in QuadTree Grid (longitude, x) * @param withinDistance maximum get distance. * @return the object that matches the best distance, null if no closer * object was found. */ public T get(double lat, double lon, double withinDistance) { return get(lat, lon, new MutableDistance(withinDistance)); } /** * Get an object closest to a lat/lon. If there are children at this node, * then the children are searched. The children are checked first, to see if * they are closer than the best distance already found. If a closer object * is found, bestDistance will be updated with a new Double object that has * the new distance. * * @param lat up-down location in QuadTree Grid (latitude, y) * @param lon left-right location in QuadTree Grid (longitude, x) * @param bestDistance the closest distance of the object found so far. * @return the object that matches the best distance, null if no closer * object was found. */ public T get(double lat, double lon, MutableDistance bestDistance) { T closest = null; // This must be the node that has it... if (children == null) { for (QuadTreeLeaf<T> qtl : items) { double dx = lon - qtl.longitude; double dy = lat - qtl.latitude; double distanceSqr = dx * dx + dy * dy; if (distanceSqr < bestDistance.value) { bestDistance.value = distanceSqr; closest = qtl.object; } } return closest; } else { // Check the distance of the bounds of the children, // versus the bestDistance. If there is a boundary that // is closer, then it is possible that another node has an // object that is closer. for (QuadTreeNode<T> child : children) { double childDistance = child.bounds.borderDistanceSqr(lat, lon); if (childDistance < bestDistance.value) { T test = child.get(lat, lon, bestDistance); if (test != null) closest = test; } } } return closest; } /** * Get all the objects within a bounding box. * * @param north top location in QuadTree Grid (latitude, y) * @param west left location in QuadTree Grid (longitude, x) * @param south lower location in QuadTree Grid (latitude, y) * @param east right location in QuadTree Grid (longitude, x) * @return Collection of objects. */ public Collection<T> get(double north, double west, double south, double east) { return get(new QuadTreeRect(north, west, south, east), new ArrayList<T>()); } /** * Get all the objects within a bounding box. * * @param north top location in QuadTree Grid (latitude, y) * @param west left location in QuadTree Grid (longitude, x) * @param south lower location in QuadTree Grid (latitude, y) * @param east right location in QuadTree Grid (longitude, x) * @param collection current Collection of objects. * @return collection of objects. */ public Collection<T> get(double north, double west, double south, double east, Collection<T> collection) { return get(new QuadTreeRect(north, west, south, east), collection); } /** * Get all the objects within a bounding box. * * @param rect boundary of area to fill. * @param collection current Collection of objects. * @return updated Collection of objects. */ public Collection<T> get(QuadTreeRect rect, Collection<T> collection) { if (children == null) { for (QuadTreeLeaf<T> qtl : this.items) { if (rect.pointWithinBounds(qtl.latitude, qtl.longitude)) { collection.add(qtl.object); } } } else { for (QuadTreeNode<T> child : children) { if (child.bounds.within(rect)) { child.get(rect, collection); } } } return collection; } }