All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.gov.nasa.worldwind.util.BitSetQuadTreeFilter Maven / Gradle / Ivy

Go to download

World Wind is a collection of components that interactively display 3D geographic information within Java applications or applets.

There is a newer version: 2.0.0-986
Show newest version
/*
 * Copyright (C) 2012 United States Government as represented by the Administrator of the
 * National Aeronautics and Space Administration.
 * All Rights Reserved.
 */

package gov.nasa.worldwind.util;

import gov.nasa.worldwind.geom.Sector;

import java.util.*;

/**
 * This abstract class filters items through a quad tree to perform an operation on those items that intersect the
 * tree's cells. For each submitted item, this class' {@link #doOperation} method is called for each leaf or
 * intermediate cell that intersects the item. That method typically stores the item's association with the intersecting
 * cell, either to populate a quadtree membership list or to mark it as a visible item.
 * 

* The filter uses a bit-set to identify cells that intersect submitted items. This minimal memory eliminates the need * to retain cell information other than identity. Only cell identities are retained by this abstract class. Subclasses * provide the means to retain item information associated with intersecting cells. * * @author tag * @version $Id: BitSetQuadTreeFilter.java 1939 2014-04-15 22:50:19Z tgaskins $ */ public abstract class BitSetQuadTreeFilter { protected BitSet bits; protected int maxLevel; protected int numLevels; protected int[] powersOf4; protected int[] levelSizes; // Cumulative bits at start of each level. Used for position calculations. protected int[] path; // valid only during traversal. Maintains the path to a cell from level 0. protected boolean stopped; /** * A method implemented by subclasses and called during tree traversal to perform an operation on an intersecting * item. The method's implementation typically stores a reference to the intersecting item, or stores the cell's * identity for later reference. See for example {@link gov.nasa.worldwind.util.BasicQuadTree} and {@link * gov.nasa.worldwind.util.BitSetQuadTreeFilter.FindIntersectingBitsOp}. * * @param level the quadtree level currently being traversed. * @param position the position of the cell in its parent cell, either 0, 1, 2, or 3. Cell positions starts with 0 * at the southwest corner of the parent cell and increment counter-clockwise: cell 1 is SE, cell * 2 is NE and cell 3 is NW. * @param cellRegion an array specifying the coordinates of the cell's region. The first two entries are the minimum * and maximum values on the Y axis (typically latitude). The last two entries are the minimum and * maximum values on the X axis, (typically longitude). * @param itemCoords an array specifying the region or location of the intersecting item. If the array's length is 2 * it represents a location in [latitude, longitude]. If its length is 4 it represents a region, * with the same layout as the nodeRegion argument. * * @return true if traversal should continue to the cell's descendants, false if traversal should not continue to * the cell's descendants. */ abstract protected boolean doOperation(int level, int position, double[] cellRegion, double[] itemCoords); /** * Constructs an instance of this class. * * @param numLevels the number of levels in the quadtree. * @param bitSet a {@link BitSet} to use as the quadtree index. If null, a new set is created. Depending on the * operation, the bit-set is modified or only read. * * @throws IllegalArgumentException if numLevels is less than 1. */ public BitSetQuadTreeFilter(int numLevels, BitSet bitSet) { if (numLevels < 1) { String message = Logging.getMessage("generic.DepthOutOfRange", numLevels); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.numLevels = numLevels; this.maxLevel = numLevels - 1; this.powersOf4 = WWMath.computePowers(4, numLevels); this.levelSizes = computeLevelSizes(numLevels); this.path = new int[this.numLevels]; this.bits = bitSet != null ? bitSet : new BitSet(this.levelSizes[numLevels]); } /** * Returns the number of levels in the filter. * * @return the number of levels in the filter. */ public int getNumLevels() { return this.numLevels; } /** * Stop the current traversal of the quadtree. {@link #start()} must be called before attempting a subsequent * traversal. */ public void stop() { this.stopped = true; } /** * Indicates whether traversal has been stopped. * * @return true if traversal has been stopped, otherwise false. */ public boolean isStopped() { return stopped; } /** * Re-initialize for traversal. Must be called to perform subsequent traversals after having called {@link * #stop()}. */ public void start() { this.stopped = false; } /** * An internal method that computes the number of ancestor cells at each level. Level 0 has 0 ancestor cells, level * 1 has 4, level 2 has 20 (16 + 4), etc. * * @param numLevels the number of quadtree levels. * * @return an array of numLevels + 1 elements containing the sums of ancestor-level cell counts for * each quadtree level. The last element in the array contains the total number of cells in the tree. */ protected static int[] computeLevelSizes(int numLevels) { int[] sizes = new int[numLevels + 1]; sizes[0] = 0; double accumulatedSize = 0; for (int i = 1; i <= numLevels; i++) { accumulatedSize += Math.pow(4, i); sizes[i] = (int) accumulatedSize; } return sizes; } /** * The main, recursive traversal method. Called once for each cell. Tests for intersection between items and cells, * subdivides cells and continues depth-first traversal on those descendants. Terminates traversal when the end of * the tree is reached or when the subclass's {@link #doOperation} method returns false. * * @param level the quadtree level currently being traversed. * @param position the position o f the cell in its parent cell, either 0, 1, 2, or 3. Cell positions starts with * 0 at the southwest corner of the parent cell and increment counter-clockwise: cell 1 is SE, * cell 2 is NE and cell 3 is NW. * @param cellRegion an array specifying the coordinates of the cell's region. The first two entries are the minimum * and maximum values on the Y axis (typically latitude). The last two entries are the minimum and * maximum values on the X axis, (typically longitude). * @param itemCoords an array specifying the region or location of the item. If the array's length is 2 it * represents a location in [latitude, longitude]. If its length is 4 it represents a region, with * the same layout as the nodeRegion argument. */ protected void testAndDo(int level, int position, double[] cellRegion, double[] itemCoords) { if (this.stopped) return; if (this.intersects(cellRegion, itemCoords) == 0) return; this.path[level] = position; if (!this.doOperation(level, position, cellRegion, itemCoords) || this.stopped) return; if (level == this.maxLevel) return; double latMid = (cellRegion[1] + cellRegion[0]) / 2; double lonMid = (cellRegion[3] + cellRegion[2]) / 2; double[] subRegion = new double[4]; subRegion[0] = cellRegion[0]; subRegion[1] = latMid; subRegion[2] = cellRegion[2]; subRegion[3] = lonMid; this.testAndDo(level + 1, 0, subRegion, itemCoords); if (this.stopped) return; subRegion[2] = lonMid; subRegion[3] = cellRegion[3]; this.testAndDo(level + 1, 1, subRegion, itemCoords); if (this.stopped) return; subRegion[0] = latMid; subRegion[1] = cellRegion[1]; this.testAndDo(level + 1, 2, subRegion, itemCoords); if (this.stopped) return; subRegion[2] = cellRegion[2]; subRegion[3] = lonMid; this.testAndDo(level + 1, 3, subRegion, itemCoords); } /** * Determines whether an item intersects a cell. * * @param cellRegion an array specifying the coordinates of the cell's region. The first two entries are the minimum * and maximum values on the Y axis (typically latitude). The last two entries are the minimum and * maximum values on the X axis, (typically longitude). * @param itemCoords an array specifying the region or location of the item. If the array's length is 2 it * represents a location in [latitude, longitude]. If its length is 4 it represents a region, with * the same layout as the nodeRegion argument. * * @return non-zero if the item intersects the region. 0 if no intersection. */ protected int intersects(double[] cellRegion, double[] itemCoords) { if (itemCoords.length == 4) // treat test region as a sector return !(itemCoords[1] < cellRegion[0] || itemCoords[0] > cellRegion[1] || itemCoords[3] < cellRegion[2] || itemCoords[2] > cellRegion[3]) ? 1 : 0; else // assume test region is a 2-tuple location return itemCoords[0] >= cellRegion[0] && itemCoords[0] <= cellRegion[1] && itemCoords[1] >= cellRegion[2] && itemCoords[1] <= cellRegion[3] ? 1 : 0; } /** * Computes the bit position of a quadtree cell. * * @param level the quadtree level currently being traversed. * @param position the position of the cell in its parent cell, either 0, 1, 2, or 3. Cell positions starts with 0 * at the southwest corner of the parent cell and increment counter-clockwise: cell 1 is SE, cell 2 * is NE and cell 3 is NW. * * @return the cell's bit position in the class' bit-list. */ protected int computeBitPosition(int level, int position) { int bitPosition = position; // Compute the index of the position within the level for (int i = 0; i < level; i++) { bitPosition += this.path[i] * this.powersOf4[level - i]; } // The index within the BitSet is the index within the level plus the number of bits prior to the level's bits return bitPosition + this.levelSizes[level]; } /** * A quadtree filter that determines the bit positions of cells associated with items and intersecting a specified * region. Typically used to traverse the bit-set index of a populated quadtree to determine potentially visible * items. *

* This class requires a previously populated filter and determines which of its cells intersect a specified * sector. */ public static class FindIntersectingBitsOp extends BitSetQuadTreeFilter { protected List intersectingBits; /** * Constructs a filter instance. * * @param filter a filter identifying significant cells in a quadtree, typically produced by applying a filter * that identifies quadtree cells associated with items. * * @throws NullPointerException if filter is null. * @throws IllegalArgumentException if filter is invalid. */ public FindIntersectingBitsOp(BitSetQuadTreeFilter filter) { super(filter.getNumLevels(), filter.bits); } /** * Returns the bit positions of significant cells that intersect a specified sector. Significant cells are those * identified by the filter specified to the constructor. * * @param topRegions the zeroth-level regions of a quadtree. * @param testSector the sector of interest. Significant cells that intersect this sector are returned. * @param outIds a list in which to place the bit positions of intersecting significant cells. May be null, * in which case a new list is created. In either case the list is the return value of the * method. * * @return the bit positions of intersecting significant cells. This is the list specified as the non-null * outIds argument, or a new list if that argument is null. * * @throws IllegalArgumentException if either topRegions or testSector is null. */ public List getOnBits(List topRegions, Sector testSector, List outIds) { if (testSector == null) { String message = Logging.getMessage("nullValue.SectorIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } return this.getOnBits(topRegions, testSector.asDegreesArray(), outIds); } /** * Returns the bit positions of significant cells that intersect a specified sector. Significant cells are those * identified by the filter specified to the constructor. * * @param topRegions the zeroth-level regions of a quadtree. * @param testRegion the sector of interest, specified as a four-element array containing minimum latitude, * maximum latitude, minimum longitude and maximum longitude, in that order. Significant cells * that intersect this sector are returned. * @param outIds a list in which to place the bit positions of intersecting significant cells. May be null, * in which case a new list is created. In either case the list is the return value of the * method. * * @return the bit positions of intersecting significant cells. This is the list specified as the non-null * outIds argument, or a new list if that argument is null. * * @throws IllegalArgumentException if either topRegions or testSector is null. */ public List getOnBits(List topRegions, double[] testRegion, List outIds) { if (topRegions == null) { String message = Logging.getMessage("generic.DepthOutOfRange", numLevels); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (testRegion == null) { String message = Logging.getMessage("nullValue.ArrayIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.intersectingBits = outIds != null ? outIds : new ArrayList(); for (int i = 0; i < topRegions.size(); i++) { this.testAndDo(0, i, topRegions.get(i), testRegion); } return this.intersectingBits; } /** * Assembles the output bit-list during traversal. Implements the abstract method {@link * BitSetQuadTreeFilter#doOperation(int, int, double[], double[])}. See the description of that method for * further detail. * * @param level the quadtree level currently being traversed. * @param position the position of the cell in its parent cell, either 0, 1, 2, or 3. Cell positions starts * with 0 at the southwest corner of the parent cell and increment counter-clockwise: cell 1 * is SE, cell 2 is NE and cell 3 is NW. * @param cellRegion an array specifying the coordinates of the cell's region. The first two entries are the * minimum and maximum values on the Y axis (typically latitude). The last two entries are the * minimum and maximum values on the X axis, (typically longitude). * @param testSector an array specifying the region or location of the intersecting item. If the array's length * is 2 then it represents a location in [latitude, longitude]. If its length is 4 it * represents a region, with the same layout as the nodeRegion argument. * * @return true if traversal should continue to the cell's descendants, false if traversal should not continue * to the cell's descendants. */ protected boolean doOperation(int level, int position, double[] cellRegion, double[] testSector) { int bitNum = this.computeBitPosition(level, position); if (!this.bits.get(bitNum)) return false; if (level < this.maxLevel) return true; this.intersectingBits.add(bitNum); return false; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy