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

com.hazelcast.shaded.org.locationtech.jts.index.VertexSequencePackedRtree Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2021 Martin Davis.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
 * and the Eclipse Distribution License is available at
 *
 * http://www.eclipse.org/org/documents/edl-v10.php.
 */
package com.hazelcast.shaded.org.locationtech.jts.index;

import com.hazelcast.shaded.org.locationtech.jts.geom.Coordinate;
import com.hazelcast.shaded.org.locationtech.jts.geom.Envelope;
import com.hazelcast.shaded.org.locationtech.jts.math.MathUtil;
import com.hazelcast.shaded.org.locationtech.jts.util.IntArrayList;

/**
 * A semi-static spatial index for points which occur 
 * in a spatially-coherent sequence.
 * In particular, this is suitable for indexing the vertices
 * of a {@link LineString} or {@link Polygon} ring.
 * 

* The index is constructed in a batch fashion on a given sequence of coordinates. * Coordinates can be removed via the {@link #remove(int)} method. *

* Note that this index queries only the individual points * of the input coordinate sequence, * not any line segments which might be lie between them. *

* The input coordinate array is read-only, * and is not changed when vertices are removed. * * @author Martin Davis * */ public class VertexSequencePackedRtree { /** * Number of items/nodes in a parent node. * Determined empirically. Performance is not too sensitive to this. */ private static final int NODE_CAPACITY = 16; private Coordinate[] items; private int[] levelOffset; private int nodeCapacity = NODE_CAPACITY; private Envelope[] bounds; private boolean[] isRemoved; /** * Creates a new tree over the given sequence of coordinates. * The sequence should be spatially coherent to provide query performance. * * @param pts a sequence of points */ public VertexSequencePackedRtree(Coordinate[] pts) { this.items = pts; isRemoved = new boolean[pts.length]; build(); } public Envelope[] getBounds() { return bounds.clone(); } private void build() { levelOffset = computeLevelOffsets(); bounds = createBounds(); } /** * Computes the level offsets. * This is the position in the bounds array of each level. * * The levelOffsets array includes a sentinel value of offset[0] = 0. * The top level is always of size 1, * and so also indicates the total number of bounds. * * @return the level offsets */ private int[] computeLevelOffsets() { IntArrayList offsets = new IntArrayList(); offsets.add(0); int levelSize = items.length; int currOffset = 0; do { levelSize = levelNodeCount(levelSize); currOffset += levelSize; offsets.add(currOffset); } while (levelSize > 1); return offsets.toArray(); } private int levelNodeCount(int numNodes) { return MathUtil.ceil(numNodes, nodeCapacity); } private Envelope[] createBounds() { int boundsSize = levelOffset[levelOffset.length - 1] + 1; Envelope[] bounds = new Envelope[boundsSize]; fillItemBounds(bounds); for (int lvl = 1; lvl < levelOffset.length; lvl++) { fillLevelBounds(lvl, bounds); } return bounds; } private void fillLevelBounds(int lvl, Envelope[] bounds) { int levelStart = levelOffset[lvl - 1]; int levelEnd = levelOffset[lvl]; int nodeStart = levelStart; int levelBoundIndex = levelOffset[lvl]; do { int nodeEnd = MathUtil.clampMax(nodeStart + nodeCapacity, levelEnd); bounds[levelBoundIndex++] = computeNodeEnvelope(bounds, nodeStart, nodeEnd); nodeStart = nodeEnd; } while (nodeStart < levelEnd); } private void fillItemBounds(Envelope[] bounds) { int nodeStart = 0; int boundIndex = 0; do { int nodeEnd = MathUtil.clampMax(nodeStart + nodeCapacity, items.length); bounds[boundIndex++] = computeItemEnvelope(items, nodeStart, nodeEnd); nodeStart = nodeEnd; } while (nodeStart < items.length); } private static Envelope computeNodeEnvelope(Envelope[] bounds, int start, int end) { Envelope env = new Envelope(); for (int i = start; i < end; i++) { env.expandToInclude(bounds[i]); } return env; } private static Envelope computeItemEnvelope(Coordinate[] items, int start, int end) { Envelope env = new Envelope(); for (int i = start; i < end; i++) { env.expandToInclude(items[i]); } return env; } //------------------------ /** * Queries the index to find all items which intersect an extent. * The query result is a list of the indices of input coordinates * which intersect the extent. * * @param queryEnv the query extent * @return an array of the indices of the input coordinates */ public int[] query(Envelope queryEnv) { IntArrayList resultList = new IntArrayList(); int level = levelOffset.length - 1; queryNode(queryEnv, level, 0, resultList); int[] result = resultList.toArray(); return result; } private void queryNode(Envelope queryEnv, int level, int nodeIndex, IntArrayList resultList) { int boundsIndex = levelOffset[level] + nodeIndex; Envelope nodeEnv = bounds[boundsIndex]; //--- node is empty if (nodeEnv == null) return; if (! queryEnv.intersects(nodeEnv)) return; int childNodeIndex = nodeIndex * nodeCapacity; if (level == 0) { queryItemRange(queryEnv, childNodeIndex, resultList); } else { queryNodeRange(queryEnv, level - 1, childNodeIndex, resultList); } } private void queryNodeRange(Envelope queryEnv, int level, int nodeStartIndex, IntArrayList resultList) { int levelMax = levelSize(level); for (int i = 0; i < nodeCapacity; i++) { int index = nodeStartIndex + i; if (index >= levelMax) return; queryNode(queryEnv, level, index, resultList); } } private int levelSize(int level) { return levelOffset[level + 1] - levelOffset[level]; } private void queryItemRange(Envelope queryEnv, int itemIndex, IntArrayList resultList) { for (int i = 0; i < nodeCapacity; i++) { int index = itemIndex + i; if (index >= items.length) return; Coordinate p = items[index]; if (! isRemoved[index] && queryEnv.contains(p)) resultList.add(index); } } //------------------------ /** * Removes the input item at the given index from the spatial index. * This does not change the underlying coordinate array. * * @param index the index of the item in the input */ public void remove(int index) { isRemoved[index] = true; //--- prune the item parent node if all its items are removed int nodeIndex = index / nodeCapacity; if (! isItemsNodeEmpty(nodeIndex)) return; bounds[nodeIndex] = null; if (levelOffset.length <= 2) return; //-- prune the node parent if all children removed int nodeLevelIndex = nodeIndex / nodeCapacity; if (! isNodeEmpty(1, nodeLevelIndex)) return; int nodeIndex1 = levelOffset[1] + nodeLevelIndex; bounds[nodeIndex1] = null; //TODO: propagate removal up the tree nodes? } private boolean isNodeEmpty(int level, int index) { int start = index * nodeCapacity; int end = MathUtil.clampMax(start + nodeCapacity, levelOffset[level]); for (int i = start; i < end; i++) { if (bounds[i] != null) return false; } return true; } private boolean isItemsNodeEmpty(int nodeIndex) { int start = nodeIndex * nodeCapacity; int end = MathUtil.clampMax(start + nodeCapacity, items.length); for (int i = start; i < end; i++) { if (! isRemoved[i]) return false; } return true; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy