/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2007-2008, Open Source Geospatial Foundation (OSGeo) * * 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; * version 2.1 of the License. * * 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. */ package org.geotools.renderer3d.utils.quadtree; import org.geotools.renderer3d.utils.BoundingRectangle; import org.geotools.renderer3d.utils.BoundingRectangleImpl; import org.geotools.renderer3d.utils.ParameterChecker; import java.util.ArrayList; import java.util.List; /** * @author Hans H�ggstr�m */ public final class QuadTreeNodeImpl<N> implements QuadTreeNode<N> { //====================================================================== // Private Fields private final QuadTree<N> myQuadTree; private final List<NodeListener<N>> myNodeListeners = new ArrayList<NodeListener<N>>( 3 ); private QuadTreeNode<N> myParent = null; private BoundingRectangle myBoundingRectangle; private QuadTreeNode<N>[] myChildren = null; private N myNodeData = null; private boolean myExpanded = false; private boolean myAttached = true; //====================================================================== // Private Constants private static final int NUMBER_OF_SUBNODES = 4; private static final BoundingRectangleImpl ORIGO_BOUNDING_RECTANGLE = new BoundingRectangleImpl( 0, 0, 10 ); //====================================================================== // Public Methods //---------------------------------------------------------------------- // Constructors public QuadTreeNodeImpl( final QuadTree<N> quadTree, BoundingRectangle boundingRectangle ) { this( quadTree, boundingRectangle, null ); } public QuadTreeNodeImpl( final QuadTree<N> quadTree, final BoundingRectangle boundingRectangle, final QuadTreeNode<N> parentNode ) { ParameterChecker.checkNotNull( quadTree, "quadTree" ); setBounds( boundingRectangle ); myQuadTree = quadTree; myParent = parentNode; } public QuadTreeNodeImpl( QuadTree<N> quadTree, double centerX, double centerY, double radius ) { this( quadTree, centerX - radius, centerY - radius, centerX + radius, centerY + radius ); ParameterChecker.checkPositiveNonZeroNormalNumber( radius, "radius" ); ParameterChecker.checkNormalNumber( centerX, "centerX" ); ParameterChecker.checkNormalNumber( centerY, "centerY" ); } public QuadTreeNodeImpl( final QuadTree<N> quadTree, final double x1, final double y1, final double x2, final double y2 ) { this( quadTree, new BoundingRectangleImpl( x1, y1, x2, y2 ) ); } //---------------------------------------------------------------------- // QuadTreeNode Implementation public BoundingRectangle getBounds() { checkNodeIsAttached(); return myBoundingRectangle; } public QuadTreeNode<N> getRootNode() { checkNodeIsAttached(); if ( myParent != null ) { return myParent.getRootNode(); } else { return this; } } public boolean hasNodeData() { return myNodeData != null; } public N getNodeData() { checkNodeIsAttached(); // Lazy creation if ( myNodeData == null ) { myNodeData = myQuadTree.getNodeDataFactory().createNodeDataObject( this ); } return myNodeData; } public void setNodeData( final N nodeData ) { checkNodeIsAttached(); myNodeData = nodeData; } public boolean visitChildren( final NodeVisitor<N> visitor ) { checkNodeIsAttached(); if ( myChildren != null ) { for ( QuadTreeNode<N> child : myChildren ) { if ( !visitor.visitNode( child ) ) { return false; } } } return true; } public boolean visitDecendants( final NodeVisitor<N> visitor ) { checkNodeIsAttached(); if ( myChildren != null ) { for ( QuadTreeNode<N> child : myChildren ) { if ( !child.visitSelfAndDecendants( visitor ) ) { return false; } } } return true; } public boolean visitSelfAndDecendants( final NodeVisitor<N> visitor ) { checkNodeIsAttached(); if ( visitor.visitNode( this ) ) { return visitDecendants( visitor ); } else { return false; } } public int getNumberOfChildren() { checkNodeIsAttached(); return NUMBER_OF_SUBNODES; } public QuadTreeNode<N> getChild( final int index ) { checkNodeIsAttached(); if ( myChildren == null ) { return null; } else { return myChildren[ index ]; } } public void setExpanded( final boolean expanded ) { checkNodeIsAttached(); if ( expanded ) { expand(); } else { collapse(); } } public boolean isExpanded() { checkNodeIsAttached(); return myExpanded; } public boolean covers( final double x, final double y, final double radius ) { checkNodeIsAttached(); return getBounds().isInside( x, y, radius ); } public void grow( double x, double y ) { checkNodeIsAttached(); if ( isRootNode() ) { // Calcualte which direction the new parent should expand into final int parentSubsector = myBoundingRectangle.getSubsectorAt( x, y ); // Create a new parent final BoundingRectangle parentBounds = myBoundingRectangle.createParentBoundingRectangle( parentSubsector ); final QuadTreeNode<N> parentNode = myQuadTree.createQuadTreeNode( parentBounds, null ); myQuadTree.initnodedata( parentNode ); // Add this node as a child of the parent node (in the opposite corner of where we expanded) final int childSubquadrant = myBoundingRectangle.getOppositeSubquadrant( parentSubsector ); parentNode.expandWithChild( childSubquadrant, this ); myParent = parentNode; // Notify model that we have a new root node myQuadTree.setRootNode( parentNode ); } else { getRootNode().grow( x, y ); } } public void growToInclude( double x, double y, final double radius ) { checkNodeIsAttached(); while ( !getRootNode().covers( x, y, radius ) ) { grow( x, y ); } } public boolean isRootNode() { checkNodeIsAttached(); return myParent == null; } public void addNodeListener( NodeListener<N> addedNodeListener ) { checkNodeIsAttached(); ParameterChecker.checkNotNull( addedNodeListener, "addedNodeListener" ); ParameterChecker.checkNotAlreadyContained( addedNodeListener, myNodeListeners, "myNodeListeners" ); myNodeListeners.add( addedNodeListener ); } public void removeNodeListener( NodeListener<N> removedNodeListener ) { checkNodeIsAttached(); ParameterChecker.checkNotNull( removedNodeListener, "removedNodeListener" ); ParameterChecker.checkContained( removedNodeListener, myNodeListeners, "myNodeListeners" ); myNodeListeners.remove( removedNodeListener ); } public void detach() { checkNodeIsAttached(); myParent = null; myAttached = false; myChildren = null; myBoundingRectangle = ORIGO_BOUNDING_RECTANGLE; } public void attach( final BoundingRectangle bounds, QuadTreeNode<N> parent ) { if ( myAttached ) { throw new IllegalStateException( "A node that was already attached to '" + myParent + "' was attempted to be attached to '" + parent + "' " ); } myParent = parent; myAttached = true; setBounds( bounds ); } public boolean isAttached() { return myAttached; } public void expandWithChild( final int childSubquadrant, final QuadTreeNode<N> childNode ) { checkNodeIsAttached(); if ( !myExpanded ) { myExpanded = true; // Create the child array if needed if ( myChildren == null ) { //noinspection unchecked myChildren = new QuadTreeNode[NUMBER_OF_SUBNODES]; } // Create child nodes for ( int i = 0; i < NUMBER_OF_SUBNODES; i++ ) { if ( myChildren[ i ] != null ) { throw new IllegalStateException( "A newly expanded node should not have any children" ); } if ( i == childSubquadrant ) { myChildren[ i ] = childNode; } else { final BoundingRectangle rectangle = myBoundingRectangle.createSubquadrantBoundingRectangle( i ); myChildren[ i ] = myQuadTree.createQuadTreeNode( rectangle, this ); } } // Init node data for the other children for ( int i = 0; i < myChildren.length; i++ ) { if ( i != childSubquadrant ) { myQuadTree.initnodedata( myChildren[ i ] ); } } for ( NodeListener<N> nodeListener : myNodeListeners ) { nodeListener.onExpanded( this ); } } } public QuadTreeNode<N> getParent() { return myParent; } public int getIndexOfChild( final QuadTreeNode<N> childNode ) { for ( int i = 0; i < myChildren.length; i++ ) { if ( childNode == myChildren[ i ] ) { return i; } } return -1; } //====================================================================== // Private Methods private void setBounds( final BoundingRectangle boundingRectangle ) { checkNodeIsAttached(); ParameterChecker.checkNotNull( boundingRectangle, "boundingRectangle" ); if ( boundingRectangle.isEmpty() ) { throw new IllegalArgumentException( "The bounding rectangle '" + boundingRectangle + "' specified for the QuadTreeNode should not be empty." ); } myBoundingRectangle = boundingRectangle; } private void checkNodeIsAttached() { if ( !isAttached() ) { throw new IllegalStateException( "Can not do any operations to a detached node." ); } } private void expand() { checkNodeIsAttached(); expandWithChild( -1, null ); } private void collapse() { checkNodeIsAttached(); if ( myExpanded ) { myExpanded = false; if ( myChildren != null ) { // Delete all child nodes for ( int i = 0; i < NUMBER_OF_SUBNODES; i++ ) { final QuadTreeNode<N> child = myChildren[ i ]; if ( child != null ) { child.setExpanded( false ); myQuadTree.releaseQuadTreeNode( child ); } myChildren[ i ] = null; } } myChildren = null; for ( NodeListener<N> nodeListener : myNodeListeners ) { nodeListener.onCollapsed( this ); } } } }