/* * The JTS Topology Suite is a collection of Java classes that * implement the fundamental operations required to validate a given * geo-spatial data set to a known topological specification. * * Copyright (C) 2001 Vivid Solutions * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * For more information, contact: * * Vivid Solutions * Suite #1A * 2328 Government Street * Victoria BC V8T 5G5 * Canada * * (250)385-6040 * www.vividsolutions.com */ package com.vividsolutions.jts.index.kdtree; import java.util.ArrayList; import java.util.List; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Envelope; /** * An implementation of a 2-D KD-Tree. KD-trees provide fast range searching on * point data. * <p> * This implementation supports detecting and snapping points which are closer than a given * tolerance value. If the same point (up to tolerance) is inserted more than once a new node is * not created but the count of the existing node is incremented. * * * @author David Skea * @author Martin Davis */ public class KdTree { private KdNode root = null; private KdNode last = null; private long numberOfNodes; private double tolerance; /** * Creates a new instance of a KdTree * with a snapping tolerance of 0.0. * (I.e. distinct points will <i>not</i> be snapped) */ public KdTree() { this(0.0); } /** * Creates a new instance of a KdTree, specifying a snapping distance tolerance. * Points which lie closer than the tolerance to a point already * in the tree will be treated as identical to the existing point. * * @param tolerance * the tolerance distance for considering two points equal */ public KdTree(double tolerance) { this.tolerance = tolerance; } /** * Tests whether the index contains any items. * * @return true if the index does not contain any items */ public boolean isEmpty() { if (root == null) return true; return false; } /** * Inserts a new point in the kd-tree, with no data. * * @param p * the point to insert * @return the kdnode containing the point */ public KdNode insert(Coordinate p) { return insert(p, null); } /** * Inserts a new point into the kd-tree. * * @param p * the point to insert * @param data * a data item for the point * @return returns a new KdNode if a new point is inserted, else an existing * node is returned with its counter incremented. This can be checked * by testing returnedNode.getCount() > 1. */ public KdNode insert(Coordinate p, Object data) { if (root == null) { root = new KdNode(p, data); return root; } KdNode currentNode = root; KdNode leafNode = root; boolean isOddLevel = true; boolean isLessThan = true; /** * Traverse the tree, * first cutting the plane left-right (by X ordinate) * then top-bottom (by Y ordinate) */ while (currentNode != last) { // test if point is already a node if (currentNode != null) { boolean isInTolerance = p.distance(currentNode.getCoordinate()) <= tolerance; // check if point is already in tree (up to tolerance) and if so simply // return existing node if (isInTolerance) { currentNode.increment(); return currentNode; } } if (isOddLevel) { isLessThan = p.x < currentNode.getX(); } else { isLessThan = p.y < currentNode.getY(); } leafNode = currentNode; if (isLessThan) { currentNode = currentNode.getLeft(); } else { currentNode = currentNode.getRight(); } isOddLevel = !isOddLevel; } // no node found, add new leaf node to tree numberOfNodes = numberOfNodes + 1; KdNode node = new KdNode(p, data); node.setLeft(last); node.setRight(last); if (isLessThan) { leafNode.setLeft(node); } else { leafNode.setRight(node); } return node; } private void queryNode(KdNode currentNode, KdNode bottomNode, Envelope queryEnv, boolean odd, List result) { if (currentNode == bottomNode) return; double min; double max; double discriminant; if (odd) { min = queryEnv.getMinX(); max = queryEnv.getMaxX(); discriminant = currentNode.getX(); } else { min = queryEnv.getMinY(); max = queryEnv.getMaxY(); discriminant = currentNode.getY(); } boolean searchLeft = min < discriminant; boolean searchRight = discriminant <= max; if (searchLeft) { queryNode(currentNode.getLeft(), bottomNode, queryEnv, !odd, result); } if (queryEnv.contains(currentNode.getCoordinate())) { result.add((Object) currentNode); } if (searchRight) { queryNode(currentNode.getRight(), bottomNode, queryEnv, !odd, result); } } /** * Performs a range search of the points in the index. * * @param queryEnv * the range rectangle to query * @return a list of the KdNodes found */ public List query(Envelope queryEnv) { List result = new ArrayList(); queryNode(root, last, queryEnv, true, result); return result; } /** * Performs a range search of the points in the index. * * @param queryEnv * the range rectangle to query * @param result * a list to accumulate the result nodes into */ public void query(Envelope queryEnv, List result) { queryNode(root, last, queryEnv, true, result); } }