From e8bd57d52e88d88b1f70e2575305e25db4c6a961 Mon Sep 17 00:00:00 2001 From: Mike Barry Date: Sat, 4 Nov 2023 06:24:40 -0400 Subject: [PATCH 01/11] hprtree performance improvements --- .../jts/index/hprtree/HPRtree.java | 193 ++++++++++-------- .../locationtech/jts/noding/MCIndexNoder.java | 3 +- 2 files changed, 106 insertions(+), 90 deletions(-) diff --git a/modules/core/src/main/java/org/locationtech/jts/index/hprtree/HPRtree.java b/modules/core/src/main/java/org/locationtech/jts/index/hprtree/HPRtree.java index f596673a8b..4235d6da06 100644 --- a/modules/core/src/main/java/org/locationtech/jts/index/hprtree/HPRtree.java +++ b/modules/core/src/main/java/org/locationtech/jts/index/hprtree/HPRtree.java @@ -12,16 +12,14 @@ package org.locationtech.jts.index.hprtree; import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; import java.util.List; import org.locationtech.jts.geom.Envelope; -import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.jts.index.ArrayListVisitor; import org.locationtech.jts.index.ItemVisitor; import org.locationtech.jts.index.SpatialIndex; import org.locationtech.jts.index.strtree.STRtree; +import org.locationtech.jts.util.IntArrayList; /** * A Hilbert-Packed R-tree. This is a static R-tree @@ -66,19 +64,25 @@ public class HPRtree private static final int HILBERT_LEVEL = 12; - private static int DEFAULT_NODE_CAPACITY = 16; + private static final int DEFAULT_NODE_CAPACITY = 16; - private List items = new ArrayList(); - - private int nodeCapacity = DEFAULT_NODE_CAPACITY; + private List itemsToLoad = new ArrayList<>(); + + private final int nodeCapacity; - private Envelope totalExtent = new Envelope(); + private int numItems = 0; + + private final Envelope totalExtent = new Envelope(); private int[] layerStartIndex; private double[] nodeBounds; - private boolean isBuilt = false; + private double[] itemBounds; + + private Object[] itemValues; + + private volatile boolean isBuilt = false; //public int nodeIntersectsCount; @@ -104,7 +108,7 @@ public HPRtree(int nodeCapacity) { * @return the number of items */ public int size() { - return items.size(); + return numItems; } @Override @@ -112,7 +116,8 @@ public void insert(Envelope itemEnv, Object item) { if (isBuilt) { throw new IllegalStateException("Cannot insert items after tree is built."); } - items.add( new Item(itemEnv, item) ); + numItems++; + itemsToLoad.add( new Item(itemEnv, item) ); totalExtent.expandToInclude(itemEnv); } @@ -121,7 +126,7 @@ public List query(Envelope searchEnv) { build(); if (! totalExtent.intersects(searchEnv)) - return new ArrayList(); + return new ArrayList<>(); ArrayListVisitor visitor = new ArrayListVisitor(); query(searchEnv, visitor); @@ -153,7 +158,7 @@ private void queryTopLayer(Envelope searchEnv, ItemVisitor visitor) { private void queryNode(int layerIndex, int nodeOffset, Envelope searchEnv, ItemVisitor visitor) { int layerStart = layerStartIndex[layerIndex]; int nodeIndex = layerStart + nodeOffset; - if (! intersects(nodeIndex, searchEnv)) return; + if (! intersects(nodeBounds, nodeIndex, searchEnv)) return; if (layerIndex == 0) { int childNodesOffset = nodeOffset / ENV_SIZE * nodeCapacity; queryItems(childNodesOffset, searchEnv, visitor); @@ -164,12 +169,12 @@ private void queryNode(int layerIndex, int nodeOffset, Envelope searchEnv, ItemV } } - private boolean intersects(int nodeIndex, Envelope env) { + private static boolean intersects(double[] bounds, int nodeIndex, Envelope env) { //nodeIntersectsCount++; - boolean isBeyond = (env.getMaxX() < nodeBounds[nodeIndex]) - || (env.getMaxY() < nodeBounds[nodeIndex+1]) - || (env.getMinX() > nodeBounds[nodeIndex+2]) - || (env.getMinY() > nodeBounds[nodeIndex+3]); + boolean isBeyond = (env.getMaxX() < bounds[nodeIndex]) + || (env.getMaxY() < bounds[nodeIndex+1]) + || (env.getMinX() > bounds[nodeIndex+2]) + || (env.getMinY() > bounds[nodeIndex+3]); return ! isBeyond; } @@ -187,34 +192,14 @@ private void queryNodeChildren(int layerIndex, int blockOffset, Envelope searchE private void queryItems(int blockStart, Envelope searchEnv, ItemVisitor visitor) { for (int i = 0; i < nodeCapacity; i++) { - int itemIndex = blockStart + i; + int itemIndex = blockStart + i; // don't query past end of items - if (itemIndex >= items.size()) break; - - // visit the item if its envelope intersects search env - Item item = items.get(itemIndex); - //nodeIntersectsCount++; - if (intersects( item.getEnvelope(), searchEnv) ) { - //if (item.getEnvelope().intersects(searchEnv)) { - visitor.visitItem(item.getItem()); + if (itemIndex >= numItems) break; + if (intersects(itemBounds, itemIndex * ENV_SIZE, searchEnv)) { + visitor.visitItem(itemValues[itemIndex]); } } } - - /** - * Tests whether two envelopes intersect. - * Avoids the null check in {@link Envelope#intersects(Envelope)}. - * - * @param env1 an envelope - * @param env2 an envelope - * @return true if the envelopes intersect - */ - private static boolean intersects(Envelope env1, Envelope env2) { - return !(env2.getMinX() > env1.getMaxX() || - env2.getMaxX() < env1.getMinX() || - env2.getMinY() > env1.getMaxY() || - env2.getMaxY() < env1.getMinY()); - } private int layerSize(int layerIndex) { int layerStart = layerStartIndex[layerIndex]; @@ -233,25 +218,51 @@ public boolean remove(Envelope itemEnv, Object item) { */ public synchronized void build() { // skip if already built - if (isBuilt) return; - isBuilt = true; - // don't need to build an empty or very small tree - if (items.size() <= nodeCapacity) return; + if (!isBuilt) { + synchronized (this) { + if (!isBuilt) { + this.isBuilt = true; + // don't need to build an empty or very small tree + if (itemsToLoad.size() <= nodeCapacity) { + prepareItems(); + itemsToLoad = null; + return; + } + + sortItems(); + prepareItems(); + //dumpItems(items); + + layerStartIndex = computeLayerIndices(numItems, nodeCapacity); + // allocate storage + int nodeCount = layerStartIndex[ layerStartIndex.length - 1 ] / 4; + nodeBounds = createBoundsArray(nodeCount); + + // compute tree nodes + computeLeafNodes(layerStartIndex[1]); + for (int i = 1; i < layerStartIndex.length - 1; i++) { + computeLayerNodes(i); + } + itemsToLoad = null; + //dumpNodes(); + } + } + } + } - sortItems(); - //dumpItems(items); - - layerStartIndex = computeLayerIndices(items.size(), nodeCapacity); - // allocate storage - int nodeCount = layerStartIndex[ layerStartIndex.length - 1 ] / 4; - nodeBounds = createBoundsArray(nodeCount); - - // compute tree nodes - computeLeafNodes(layerStartIndex[1]); - for (int i = 1; i < layerStartIndex.length - 1; i++) { - computeLayerNodes(i); + private void prepareItems() { + int boundsIndex = 0; + int valueIndex = 0; + itemBounds = new double[itemsToLoad.size() * 4]; + itemValues = new Object[itemsToLoad.size()]; + for (Item item : itemsToLoad) { + Envelope envelope = item.getEnvelope(); + itemBounds[boundsIndex++] = envelope.getMinX(); + itemBounds[boundsIndex++] = envelope.getMinY(); + itemBounds[boundsIndex++] = envelope.getMaxX(); + itemBounds[boundsIndex++] = envelope.getMaxY(); + itemValues[valueIndex++] = item.getItem(); } - //dumpNodes(); } /* @@ -313,8 +324,8 @@ private void computeLeafNodes(int layerSize) { private void computeLeafNodeBounds(int nodeIndex, int blockStart) { for (int i = 0; i <= nodeCapacity; i++ ) { int itemIndex = blockStart + i; - if (itemIndex >= items.size()) break; - Envelope env = items.get(itemIndex).getEnvelope(); + if (itemIndex >= itemsToLoad.size()) break; + Envelope env = itemsToLoad.get(itemIndex).getEnvelope(); updateNodeBounds(nodeIndex, env.getMinX(), env.getMinY(), env.getMaxX(), env.getMaxY()); } } @@ -325,13 +336,9 @@ private void updateNodeBounds(int nodeIndex, double minX, double minY, double ma if (maxX > nodeBounds[nodeIndex+2]) nodeBounds[nodeIndex+2] = maxX; if (maxY > nodeBounds[nodeIndex+3]) nodeBounds[nodeIndex+3] = maxY; } - - private Envelope getNodeEnvelope(int i) { - return new Envelope(nodeBounds[i], nodeBounds[i+1], nodeBounds[i+2], nodeBounds[i+3]); - } private static int[] computeLayerIndices(int itemSize, int nodeCapacity) { - List layerIndexList = new ArrayList(); + IntArrayList layerIndexList = new IntArrayList(); int layerSize = itemSize; int index = 0; do { @@ -339,7 +346,7 @@ private static int[] computeLayerIndices(int itemSize, int nodeCapacity) { layerSize = numNodesToCover(layerSize, nodeCapacity); index += ENV_SIZE * layerSize; } while (layerSize > 1); - return toIntArray(layerIndexList); + return layerIndexList.toArray(); } /** @@ -356,14 +363,6 @@ private static int numNodesToCover(int nChild, int nodeCapacity) { if (total == nChild) return mult; return mult + 1; } - - private static int[] toIntArray(List list) { - int[] array = new int[list.size()]; - for (int i = 0; i < array.length; i++) { - array[i] = list.get(i); - } - return array; - } /** * Gets the extents of the internal index nodes @@ -383,24 +382,40 @@ public Envelope[] getBounds() { } private void sortItems() { - ItemComparator comp = new ItemComparator(new HilbertEncoder(HILBERT_LEVEL, totalExtent)); - Collections.sort(items, comp); + HilbertEncoder encoder = new HilbertEncoder(HILBERT_LEVEL, totalExtent); + int[] hilbertValues = new int[itemsToLoad.size()]; + int pos = 0; + for (Item item : itemsToLoad) { + hilbertValues[pos++] = encoder.encode(item.getEnvelope()); + } + sortItemsIntoNodes(itemsToLoad, hilbertValues, 0, itemsToLoad.size() - 1, nodeCapacity); } - - static class ItemComparator implements Comparator { - private HilbertEncoder encoder; - - public ItemComparator(HilbertEncoder encoder) { - this.encoder = encoder; + private static void sortItemsIntoNodes(List items, int[] values, int left, int right, int nodeCapacity) { + if (left / nodeCapacity >= right / nodeCapacity) return; + long pivot = values[(left + right) >> 1]; + int i = left - 1; + int j = right + 1; + + while (true) { + do i++; while (values[i] < pivot); + do j--; while (values[j] > pivot); + if (i >= j) break; + swap(items, values, i, j); } - @Override - public int compare(Item item1, Item item2) { - int hcode1 = encoder.encode(item1.getEnvelope()); - int hcode2 = encoder.encode(item2.getEnvelope()); - return Integer.compare(hcode1, hcode2); - } + sortItemsIntoNodes(items, values, left, j, nodeCapacity); + sortItemsIntoNodes(items, values, j + 1, right, nodeCapacity); + } + + private static void swap(List items, int[] values, int i, int j) { + Item tmpItemp = items.get(i); + items.set(i, items.get(j)); + items.set(j, tmpItemp); + + int tmpValue = values[i]; + values[i] = values[j]; + values[j] = tmpValue; } } diff --git a/modules/core/src/main/java/org/locationtech/jts/noding/MCIndexNoder.java b/modules/core/src/main/java/org/locationtech/jts/noding/MCIndexNoder.java index 9536abc3d5..3c8793fbfd 100644 --- a/modules/core/src/main/java/org/locationtech/jts/noding/MCIndexNoder.java +++ b/modules/core/src/main/java/org/locationtech/jts/noding/MCIndexNoder.java @@ -21,6 +21,7 @@ import org.locationtech.jts.index.chain.MonotoneChain; import org.locationtech.jts.index.chain.MonotoneChainBuilder; import org.locationtech.jts.index.chain.MonotoneChainOverlapAction; +import org.locationtech.jts.index.hprtree.HPRtree; import org.locationtech.jts.index.strtree.STRtree; /** @@ -40,7 +41,7 @@ public class MCIndexNoder extends SinglePassNoder { private List monoChains = new ArrayList(); - private SpatialIndex index= new STRtree(); + private SpatialIndex index= new HPRtree(); private int idCounter = 0; private Collection nodedSegStrings; // statistics From f4da3541864d40042f6b1c500a115749f18488e7 Mon Sep 17 00:00:00 2001 From: Mike Barry Date: Sat, 4 Nov 2023 13:26:22 -0400 Subject: [PATCH 02/11] add generics --- .../jts/index/ArrayListVisitor.java | 10 ++--- .../locationtech/jts/index/ItemVisitor.java | 4 +- .../locationtech/jts/index/SpatialIndex.java | 10 ++--- .../jts/index/hprtree/HPRtree.java | 42 +++++++++---------- .../locationtech/jts/index/hprtree/Item.java | 10 ++--- .../locationtech/jts/noding/MCIndexNoder.java | 3 +- 6 files changed, 39 insertions(+), 40 deletions(-) diff --git a/modules/core/src/main/java/org/locationtech/jts/index/ArrayListVisitor.java b/modules/core/src/main/java/org/locationtech/jts/index/ArrayListVisitor.java index 2a18fefc54..0a357afe82 100644 --- a/modules/core/src/main/java/org/locationtech/jts/index/ArrayListVisitor.java +++ b/modules/core/src/main/java/org/locationtech/jts/index/ArrayListVisitor.java @@ -19,11 +19,11 @@ * * @version 1.7 */ -public class ArrayListVisitor - implements ItemVisitor +public class ArrayListVisitor + implements ItemVisitor { - private ArrayList items = new ArrayList(); + private final ArrayList items = new ArrayList<>(); /** * Creates a new instance. @@ -36,7 +36,7 @@ public ArrayListVisitor() { * * @param item the item to visit */ - public void visitItem(Object item) + public void visitItem(T item) { items.add(item); } @@ -46,6 +46,6 @@ public void visitItem(Object item) * * @return the array of items */ - public ArrayList getItems() { return items; } + public ArrayList getItems() { return items; } } diff --git a/modules/core/src/main/java/org/locationtech/jts/index/ItemVisitor.java b/modules/core/src/main/java/org/locationtech/jts/index/ItemVisitor.java index 589d9cf188..d8e138b9cf 100644 --- a/modules/core/src/main/java/org/locationtech/jts/index/ItemVisitor.java +++ b/modules/core/src/main/java/org/locationtech/jts/index/ItemVisitor.java @@ -18,12 +18,12 @@ * @version 1.7 */ -public interface ItemVisitor +public interface ItemVisitor { /** * Visits an item in the index. * * @param item the index item to be visited */ - void visitItem(Object item); + void visitItem(T item); } diff --git a/modules/core/src/main/java/org/locationtech/jts/index/SpatialIndex.java b/modules/core/src/main/java/org/locationtech/jts/index/SpatialIndex.java index c44fcf9fca..8ad13c02cf 100644 --- a/modules/core/src/main/java/org/locationtech/jts/index/SpatialIndex.java +++ b/modules/core/src/main/java/org/locationtech/jts/index/SpatialIndex.java @@ -26,12 +26,12 @@ * * @version 1.7 */ -public interface SpatialIndex +public interface SpatialIndex { /** * Adds a spatial item with an extent specified by the given {@link Envelope} to the index */ - void insert(Envelope itemEnv, Object item); + void insert(Envelope itemEnv, T item); /** * Queries the index for all items whose extents intersect the given search {@link Envelope} @@ -41,7 +41,7 @@ public interface SpatialIndex * @param searchEnv the envelope to query for * @return a list of the items found by the query */ - List query(Envelope searchEnv); + List query(Envelope searchEnv); /** * Queries the index for all items whose extents intersect the given search {@link Envelope}, @@ -52,7 +52,7 @@ public interface SpatialIndex * @param searchEnv the envelope to query for * @param visitor a visitor object to apply to the items found */ - void query(Envelope searchEnv, ItemVisitor visitor); + void query(Envelope searchEnv, ItemVisitor visitor); /** * Removes a single item from the tree. @@ -61,6 +61,6 @@ public interface SpatialIndex * @param item the item to remove * @return true if the item was found */ - boolean remove(Envelope itemEnv, Object item); + boolean remove(Envelope itemEnv, T item); } diff --git a/modules/core/src/main/java/org/locationtech/jts/index/hprtree/HPRtree.java b/modules/core/src/main/java/org/locationtech/jts/index/hprtree/HPRtree.java index 4235d6da06..2359eacf45 100644 --- a/modules/core/src/main/java/org/locationtech/jts/index/hprtree/HPRtree.java +++ b/modules/core/src/main/java/org/locationtech/jts/index/hprtree/HPRtree.java @@ -57,8 +57,8 @@ * @author Martin Davis * */ -public class HPRtree - implements SpatialIndex +public class HPRtree + implements SpatialIndex { private static final int ENV_SIZE = 4; @@ -66,7 +66,7 @@ public class HPRtree private static final int DEFAULT_NODE_CAPACITY = 16; - private List itemsToLoad = new ArrayList<>(); + private List> itemsToLoad = new ArrayList<>(); private final int nodeCapacity; @@ -84,8 +84,6 @@ public class HPRtree private volatile boolean isBuilt = false; - //public int nodeIntersectsCount; - /** * Creates a new index with the default node capacity. */ @@ -112,29 +110,29 @@ public int size() { } @Override - public void insert(Envelope itemEnv, Object item) { + public void insert(Envelope itemEnv, T item) { if (isBuilt) { throw new IllegalStateException("Cannot insert items after tree is built."); } numItems++; - itemsToLoad.add( new Item(itemEnv, item) ); + itemsToLoad.add( new Item<>(itemEnv, item) ); totalExtent.expandToInclude(itemEnv); } @Override - public List query(Envelope searchEnv) { + public List query(Envelope searchEnv) { build(); if (! totalExtent.intersects(searchEnv)) return new ArrayList<>(); - ArrayListVisitor visitor = new ArrayListVisitor(); + ArrayListVisitor visitor = new ArrayListVisitor<>(); query(searchEnv, visitor); return visitor.getItems(); } @Override - public void query(Envelope searchEnv, ItemVisitor visitor) { + public void query(Envelope searchEnv, ItemVisitor visitor) { build(); if (! totalExtent.intersects(searchEnv)) return; @@ -146,7 +144,7 @@ public void query(Envelope searchEnv, ItemVisitor visitor) { } } - private void queryTopLayer(Envelope searchEnv, ItemVisitor visitor) { + private void queryTopLayer(Envelope searchEnv, ItemVisitor visitor) { int layerIndex = layerStartIndex.length - 2; int layerSize = layerSize(layerIndex); // query each node in layer @@ -155,7 +153,7 @@ private void queryTopLayer(Envelope searchEnv, ItemVisitor visitor) { } } - private void queryNode(int layerIndex, int nodeOffset, Envelope searchEnv, ItemVisitor visitor) { + private void queryNode(int layerIndex, int nodeOffset, Envelope searchEnv, ItemVisitor visitor) { int layerStart = layerStartIndex[layerIndex]; int nodeIndex = layerStart + nodeOffset; if (! intersects(nodeBounds, nodeIndex, searchEnv)) return; @@ -178,7 +176,7 @@ private static boolean intersects(double[] bounds, int nodeIndex, Envelope env) return ! isBeyond; } - private void queryNodeChildren(int layerIndex, int blockOffset, Envelope searchEnv, ItemVisitor visitor) { + private void queryNodeChildren(int layerIndex, int blockOffset, Envelope searchEnv, ItemVisitor visitor) { int layerStart = layerStartIndex[layerIndex]; int layerEnd = layerStartIndex[layerIndex + 1]; for (int i = 0; i < nodeCapacity; i++) { @@ -190,13 +188,15 @@ private void queryNodeChildren(int layerIndex, int blockOffset, Envelope searchE } } - private void queryItems(int blockStart, Envelope searchEnv, ItemVisitor visitor) { + private void queryItems(int blockStart, Envelope searchEnv, ItemVisitor visitor) { for (int i = 0; i < nodeCapacity; i++) { int itemIndex = blockStart + i; // don't query past end of items if (itemIndex >= numItems) break; if (intersects(itemBounds, itemIndex * ENV_SIZE, searchEnv)) { - visitor.visitItem(itemValues[itemIndex]); + @SuppressWarnings("unchecked") + T item = (T) itemValues[itemIndex]; + visitor.visitItem(item); } } } @@ -208,7 +208,7 @@ private int layerSize(int layerIndex) { } @Override - public boolean remove(Envelope itemEnv, Object item) { + public boolean remove(Envelope itemEnv, T item) { // TODO Auto-generated method stub return false; } @@ -255,7 +255,7 @@ private void prepareItems() { int valueIndex = 0; itemBounds = new double[itemsToLoad.size() * 4]; itemValues = new Object[itemsToLoad.size()]; - for (Item item : itemsToLoad) { + for (Item item : itemsToLoad) { Envelope envelope = item.getEnvelope(); itemBounds[boundsIndex++] = envelope.getMinX(); itemBounds[boundsIndex++] = envelope.getMinY(); @@ -385,13 +385,13 @@ private void sortItems() { HilbertEncoder encoder = new HilbertEncoder(HILBERT_LEVEL, totalExtent); int[] hilbertValues = new int[itemsToLoad.size()]; int pos = 0; - for (Item item : itemsToLoad) { + for (Item item : itemsToLoad) { hilbertValues[pos++] = encoder.encode(item.getEnvelope()); } sortItemsIntoNodes(itemsToLoad, hilbertValues, 0, itemsToLoad.size() - 1, nodeCapacity); } - private static void sortItemsIntoNodes(List items, int[] values, int left, int right, int nodeCapacity) { + private static void sortItemsIntoNodes(List> items, int[] values, int left, int right, int nodeCapacity) { if (left / nodeCapacity >= right / nodeCapacity) return; long pivot = values[(left + right) >> 1]; int i = left - 1; @@ -408,8 +408,8 @@ private static void sortItemsIntoNodes(List items, int[] values, int left, sortItemsIntoNodes(items, values, j + 1, right, nodeCapacity); } - private static void swap(List items, int[] values, int i, int j) { - Item tmpItemp = items.get(i); + private static void swap(List> items, int[] values, int i, int j) { + Item tmpItemp = items.get(i); items.set(i, items.get(j)); items.set(j, tmpItemp); diff --git a/modules/core/src/main/java/org/locationtech/jts/index/hprtree/Item.java b/modules/core/src/main/java/org/locationtech/jts/index/hprtree/Item.java index 7de93a1001..5710b9a212 100644 --- a/modules/core/src/main/java/org/locationtech/jts/index/hprtree/Item.java +++ b/modules/core/src/main/java/org/locationtech/jts/index/hprtree/Item.java @@ -13,12 +13,12 @@ import org.locationtech.jts.geom.Envelope; -public class Item { +public class Item { - private Envelope env; - private Object item; + private final Envelope env; + private final T item; - public Item(Envelope env, Object item) { + public Item(Envelope env, T item) { this.env = env; this.item = item; } @@ -27,7 +27,7 @@ public Envelope getEnvelope() { return env; } - public Object getItem() { + public T getItem() { return item; } diff --git a/modules/core/src/main/java/org/locationtech/jts/noding/MCIndexNoder.java b/modules/core/src/main/java/org/locationtech/jts/noding/MCIndexNoder.java index 3c8793fbfd..399efda0d9 100644 --- a/modules/core/src/main/java/org/locationtech/jts/noding/MCIndexNoder.java +++ b/modules/core/src/main/java/org/locationtech/jts/noding/MCIndexNoder.java @@ -22,14 +22,13 @@ import org.locationtech.jts.index.chain.MonotoneChainBuilder; import org.locationtech.jts.index.chain.MonotoneChainOverlapAction; import org.locationtech.jts.index.hprtree.HPRtree; -import org.locationtech.jts.index.strtree.STRtree; /** * Nodes a set of {@link SegmentString}s using a index based * on {@link MonotoneChain}s and a {@link SpatialIndex}. * The {@link SpatialIndex} used should be something that supports * envelope (range) queries efficiently (such as a Quadtree} - * or {@link STRtree} (which is the default index provided). + * or {@link HPRtree} (which is the default index provided). *

* The noder supports using an overlap tolerance distance . * This allows determining segment intersection using a buffer for uses From 990ed27aec726fad15543d5817a84da3e8f0f8e0 Mon Sep 17 00:00:00 2001 From: Mike Barry Date: Sat, 4 Nov 2023 13:56:39 -0400 Subject: [PATCH 03/11] quicksort with hoare partitioning Signed-off-by: Mike Barry --- .../jts/index/hprtree/HPRtree.java | 86 ++++++++++--------- 1 file changed, 47 insertions(+), 39 deletions(-) diff --git a/modules/core/src/main/java/org/locationtech/jts/index/hprtree/HPRtree.java b/modules/core/src/main/java/org/locationtech/jts/index/hprtree/HPRtree.java index 2359eacf45..7743815d00 100644 --- a/modules/core/src/main/java/org/locationtech/jts/index/hprtree/HPRtree.java +++ b/modules/core/src/main/java/org/locationtech/jts/index/hprtree/HPRtree.java @@ -216,41 +216,41 @@ public boolean remove(Envelope itemEnv, T item) { /** * Builds the index, if not already built. */ - public synchronized void build() { + public void build() { // skip if already built if (!isBuilt) { synchronized (this) { if (!isBuilt) { - this.isBuilt = true; - // don't need to build an empty or very small tree - if (itemsToLoad.size() <= nodeCapacity) { - prepareItems(); - itemsToLoad = null; - return; - } - - sortItems(); + prepareIndex(); prepareItems(); - //dumpItems(items); - - layerStartIndex = computeLayerIndices(numItems, nodeCapacity); - // allocate storage - int nodeCount = layerStartIndex[ layerStartIndex.length - 1 ] / 4; - nodeBounds = createBoundsArray(nodeCount); - - // compute tree nodes - computeLeafNodes(layerStartIndex[1]); - for (int i = 1; i < layerStartIndex.length - 1; i++) { - computeLayerNodes(i); - } - itemsToLoad = null; - //dumpNodes(); + this.isBuilt = true; } } } } + private void prepareIndex() { + // don't need to build an empty or very small tree + if (itemsToLoad.size() <= nodeCapacity) return; + + sortItems(); + //dumpItems(items); + + layerStartIndex = computeLayerIndices(numItems, nodeCapacity); + // allocate storage + int nodeCount = layerStartIndex[ layerStartIndex.length - 1 ] / 4; + nodeBounds = createBoundsArray(nodeCount); + + // compute tree nodes + computeLeafNodes(layerStartIndex[1]); + for (int i = 1; i < layerStartIndex.length - 1; i++) { + computeLayerNodes(i); + } + //dumpNodes(); + } + private void prepareItems() { + // copy item contents out to arrays for querying int boundsIndex = 0; int valueIndex = 0; itemBounds = new double[itemsToLoad.size() * 4]; @@ -263,6 +263,8 @@ private void prepareItems() { itemBounds[boundsIndex++] = envelope.getMaxY(); itemValues[valueIndex++] = item.getItem(); } + // and let GC free the original list + itemsToLoad = null; } /* @@ -388,30 +390,36 @@ private void sortItems() { for (Item item : itemsToLoad) { hilbertValues[pos++] = encoder.encode(item.getEnvelope()); } - sortItemsIntoNodes(itemsToLoad, hilbertValues, 0, itemsToLoad.size() - 1, nodeCapacity); + quickSortItemsIntoNodes(hilbertValues, 0, itemsToLoad.size() - 1); + } + + private void quickSortItemsIntoNodes(int[] values, int lo, int hi) { + // stop sorting when left/right pointers are within the same node + // because queryItems just searches through them all sequentially + if (lo / nodeCapacity < hi / nodeCapacity) { + int pivot = hoarePartition(values, lo, hi); + quickSortItemsIntoNodes(values, lo, pivot); + quickSortItemsIntoNodes(values, pivot + 1, hi); + } } - private static void sortItemsIntoNodes(List> items, int[] values, int left, int right, int nodeCapacity) { - if (left / nodeCapacity >= right / nodeCapacity) return; - long pivot = values[(left + right) >> 1]; - int i = left - 1; - int j = right + 1; + private int hoarePartition(int[] values, int lo, int hi) { + int pivot = values[(lo + hi) >> 1]; + int i = lo - 1; + int j = hi + 1; while (true) { do i++; while (values[i] < pivot); do j--; while (values[j] > pivot); - if (i >= j) break; - swap(items, values, i, j); + if (i >= j) return j; + swapItems(values, i, j); } - - sortItemsIntoNodes(items, values, left, j, nodeCapacity); - sortItemsIntoNodes(items, values, j + 1, right, nodeCapacity); } - private static void swap(List> items, int[] values, int i, int j) { - Item tmpItemp = items.get(i); - items.set(i, items.get(j)); - items.set(j, tmpItemp); + private void swapItems(int[] values, int i, int j) { + Item tmpItemp = itemsToLoad.get(i); + itemsToLoad.set(i, itemsToLoad.get(j)); + itemsToLoad.set(j, tmpItemp); int tmpValue = values[i]; values[i] = values[j]; From bbd687b3c909ef7639132aa9bbabfe564ef1798e Mon Sep 17 00:00:00 2001 From: Mike Barry Date: Sat, 4 Nov 2023 14:07:31 -0400 Subject: [PATCH 04/11] remove commented code Signed-off-by: Mike Barry --- .../jts/index/hprtree/HPRtree.java | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/modules/core/src/main/java/org/locationtech/jts/index/hprtree/HPRtree.java b/modules/core/src/main/java/org/locationtech/jts/index/hprtree/HPRtree.java index 7743815d00..7e7ab315cc 100644 --- a/modules/core/src/main/java/org/locationtech/jts/index/hprtree/HPRtree.java +++ b/modules/core/src/main/java/org/locationtech/jts/index/hprtree/HPRtree.java @@ -168,7 +168,6 @@ private void queryNode(int layerIndex, int nodeOffset, Envelope searchEnv, ItemV } private static boolean intersects(double[] bounds, int nodeIndex, Envelope env) { - //nodeIntersectsCount++; boolean isBeyond = (env.getMaxX() < bounds[nodeIndex]) || (env.getMaxY() < bounds[nodeIndex+1]) || (env.getMinX() > bounds[nodeIndex+2]) @@ -234,7 +233,6 @@ private void prepareIndex() { if (itemsToLoad.size() <= nodeCapacity) return; sortItems(); - //dumpItems(items); layerStartIndex = computeLayerIndices(numItems, nodeCapacity); // allocate storage @@ -246,7 +244,6 @@ private void prepareIndex() { for (int i = 1; i < layerStartIndex.length - 1; i++) { computeLayerNodes(i); } - //dumpNodes(); } private void prepareItems() { @@ -267,24 +264,6 @@ private void prepareItems() { itemsToLoad = null; } - /* - private void dumpNodes() { - GeometryFactory fact = new GeometryFactory(); - for (int i = 0; i < nodeMinX.length; i++) { - Envelope env = new Envelope(nodeMinX[i], nodeMaxX[i], nodeMinY[i], nodeMaxY[i]);; - System.out.println(fact.toGeometry(env)); - } - } - - private static void dumpItems(List items) { - GeometryFactory fact = new GeometryFactory(); - for (Item item : items) { - Envelope env = item.getEnvelope(); - System.out.println(fact.toGeometry(env)); - } - } - */ - private static double[] createBoundsArray(int size) { double[] a = new double[4*size]; for (int i = 0; i < size; i++) { @@ -305,7 +284,6 @@ private void computeLayerNodes(int layerIndex) { for (int i = 0; i < layerSize; i += ENV_SIZE) { int childStart = childLayerStart + nodeCapacity * i; computeNodeBounds(layerStart + i, childStart, childLayerEnd); - //System.out.println("Layer: " + layerIndex + " node: " + i + " - " + getNodeEnvelope(layerStart + i)); } } From 45a64fb8dc6dfaf480e9c1f66314829c33d615ce Mon Sep 17 00:00:00 2001 From: Mike Barry Date: Sun, 5 Nov 2023 10:09:06 -0500 Subject: [PATCH 05/11] use SpatialIndex generics in MCIndexNoder Signed-off-by: Mike Barry --- .../locationtech/jts/noding/MCIndexNoder.java | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/modules/core/src/main/java/org/locationtech/jts/noding/MCIndexNoder.java b/modules/core/src/main/java/org/locationtech/jts/noding/MCIndexNoder.java index 399efda0d9..185672310d 100644 --- a/modules/core/src/main/java/org/locationtech/jts/noding/MCIndexNoder.java +++ b/modules/core/src/main/java/org/locationtech/jts/noding/MCIndexNoder.java @@ -39,8 +39,8 @@ public class MCIndexNoder extends SinglePassNoder { - private List monoChains = new ArrayList(); - private SpatialIndex index= new HPRtree(); + private final List monoChains = new ArrayList<>(); + private final SpatialIndex index = new HPRtree<>(); private int idCounter = 0; private Collection nodedSegStrings; // statistics @@ -69,9 +69,9 @@ public MCIndexNoder(SegmentIntersector si, double overlapTolerance) this.overlapTolerance = overlapTolerance; } - public List getMonotoneChains() { return monoChains; } + public List getMonotoneChains() { return monoChains; } - public SpatialIndex getIndex() { return index; } + public SpatialIndex getIndex() { return index; } public Collection getNodedSubstrings() { @@ -92,25 +92,26 @@ private void intersectChains() { MonotoneChainOverlapAction overlapAction = new SegmentOverlapAction(segInt); - for (Iterator i = monoChains.iterator(); i.hasNext(); ) { - MonotoneChain queryChain = (MonotoneChain) i.next(); - Envelope queryEnv = queryChain.getEnvelope(overlapTolerance); - List overlapChains = index.query(queryEnv); - for (Iterator j = overlapChains.iterator(); j.hasNext(); ) { - MonotoneChain testChain = (MonotoneChain) j.next(); - /** - * following test makes sure we only compare each pair of chains once - * and that we don't compare a chain to itself - */ - if (testChain.getId() > queryChain.getId()) { - queryChain.computeOverlaps(testChain, overlapTolerance, overlapAction); - nOverlaps++; - } - // short-circuit if possible - if (segInt.isDone()) - return; + /** + * following test makes sure we only compare each pair of chains once + * and that we don't compare a chain to itself + */ + for (MonotoneChain queryChain : monoChains) { + Envelope queryEnv = queryChain.getEnvelope(overlapTolerance); + for (MonotoneChain testChain : index.query(queryEnv)) { + /** + * following test makes sure we only compare each pair of chains once + * and that we don't compare a chain to itself + */ + if (testChain.getId() > queryChain.getId()) { + queryChain.computeOverlaps(testChain, overlapTolerance, overlapAction); + nOverlaps++; + } + // short-circuit if possible + if (segInt.isDone()) + return; + } } - } } private void add(SegmentString segStr) From 7f99fb331a807f796540a897ef40bccd1c3b2709 Mon Sep 17 00:00:00 2001 From: Mike Barry Date: Sun, 5 Nov 2023 10:11:46 -0500 Subject: [PATCH 06/11] indent Signed-off-by: Mike Barry --- .../locationtech/jts/noding/MCIndexNoder.java | 34 ++++++++----------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/modules/core/src/main/java/org/locationtech/jts/noding/MCIndexNoder.java b/modules/core/src/main/java/org/locationtech/jts/noding/MCIndexNoder.java index 185672310d..e21feecc84 100644 --- a/modules/core/src/main/java/org/locationtech/jts/noding/MCIndexNoder.java +++ b/modules/core/src/main/java/org/locationtech/jts/noding/MCIndexNoder.java @@ -91,27 +91,21 @@ public void computeNodes(Collection inputSegStrings) private void intersectChains() { MonotoneChainOverlapAction overlapAction = new SegmentOverlapAction(segInt); - - /** - * following test makes sure we only compare each pair of chains once - * and that we don't compare a chain to itself - */ - for (MonotoneChain queryChain : monoChains) { - Envelope queryEnv = queryChain.getEnvelope(overlapTolerance); - for (MonotoneChain testChain : index.query(queryEnv)) { - /** - * following test makes sure we only compare each pair of chains once - * and that we don't compare a chain to itself - */ - if (testChain.getId() > queryChain.getId()) { - queryChain.computeOverlaps(testChain, overlapTolerance, overlapAction); - nOverlaps++; - } - // short-circuit if possible - if (segInt.isDone()) - return; - } + for (MonotoneChain queryChain : monoChains) { + Envelope queryEnv = queryChain.getEnvelope(overlapTolerance); + for (MonotoneChain testChain : index.query(queryEnv)) { + /** + * following test makes sure we only compare each pair of chains once + * and that we don't compare a chain to itself + */ + if (testChain.getId() > queryChain.getId()) { + queryChain.computeOverlaps(testChain, overlapTolerance, overlapAction); + nOverlaps++; + } + // short-circuit if possible + if (segInt.isDone()) return; } + } } private void add(SegmentString segStr) From 436d9011d7e447fe55cd84b07974d6a873ba3582 Mon Sep 17 00:00:00 2001 From: Mike Barry Date: Mon, 6 Nov 2023 19:57:19 -0500 Subject: [PATCH 07/11] undo generics for now Signed-off-by: Mike Barry --- .../jts/index/ArrayListVisitor.java | 10 ++--- .../locationtech/jts/index/ItemVisitor.java | 4 +- .../locationtech/jts/index/SpatialIndex.java | 10 ++--- .../jts/index/hprtree/HPRtree.java | 38 +++++++++---------- .../locationtech/jts/index/hprtree/Item.java | 10 ++--- .../locationtech/jts/noding/MCIndexNoder.java | 19 ++++++---- 6 files changed, 47 insertions(+), 44 deletions(-) diff --git a/modules/core/src/main/java/org/locationtech/jts/index/ArrayListVisitor.java b/modules/core/src/main/java/org/locationtech/jts/index/ArrayListVisitor.java index 0a357afe82..2a18fefc54 100644 --- a/modules/core/src/main/java/org/locationtech/jts/index/ArrayListVisitor.java +++ b/modules/core/src/main/java/org/locationtech/jts/index/ArrayListVisitor.java @@ -19,11 +19,11 @@ * * @version 1.7 */ -public class ArrayListVisitor - implements ItemVisitor +public class ArrayListVisitor + implements ItemVisitor { - private final ArrayList items = new ArrayList<>(); + private ArrayList items = new ArrayList(); /** * Creates a new instance. @@ -36,7 +36,7 @@ public ArrayListVisitor() { * * @param item the item to visit */ - public void visitItem(T item) + public void visitItem(Object item) { items.add(item); } @@ -46,6 +46,6 @@ public void visitItem(T item) * * @return the array of items */ - public ArrayList getItems() { return items; } + public ArrayList getItems() { return items; } } diff --git a/modules/core/src/main/java/org/locationtech/jts/index/ItemVisitor.java b/modules/core/src/main/java/org/locationtech/jts/index/ItemVisitor.java index d8e138b9cf..589d9cf188 100644 --- a/modules/core/src/main/java/org/locationtech/jts/index/ItemVisitor.java +++ b/modules/core/src/main/java/org/locationtech/jts/index/ItemVisitor.java @@ -18,12 +18,12 @@ * @version 1.7 */ -public interface ItemVisitor +public interface ItemVisitor { /** * Visits an item in the index. * * @param item the index item to be visited */ - void visitItem(T item); + void visitItem(Object item); } diff --git a/modules/core/src/main/java/org/locationtech/jts/index/SpatialIndex.java b/modules/core/src/main/java/org/locationtech/jts/index/SpatialIndex.java index 8ad13c02cf..c44fcf9fca 100644 --- a/modules/core/src/main/java/org/locationtech/jts/index/SpatialIndex.java +++ b/modules/core/src/main/java/org/locationtech/jts/index/SpatialIndex.java @@ -26,12 +26,12 @@ * * @version 1.7 */ -public interface SpatialIndex +public interface SpatialIndex { /** * Adds a spatial item with an extent specified by the given {@link Envelope} to the index */ - void insert(Envelope itemEnv, T item); + void insert(Envelope itemEnv, Object item); /** * Queries the index for all items whose extents intersect the given search {@link Envelope} @@ -41,7 +41,7 @@ public interface SpatialIndex * @param searchEnv the envelope to query for * @return a list of the items found by the query */ - List query(Envelope searchEnv); + List query(Envelope searchEnv); /** * Queries the index for all items whose extents intersect the given search {@link Envelope}, @@ -52,7 +52,7 @@ public interface SpatialIndex * @param searchEnv the envelope to query for * @param visitor a visitor object to apply to the items found */ - void query(Envelope searchEnv, ItemVisitor visitor); + void query(Envelope searchEnv, ItemVisitor visitor); /** * Removes a single item from the tree. @@ -61,6 +61,6 @@ public interface SpatialIndex * @param item the item to remove * @return true if the item was found */ - boolean remove(Envelope itemEnv, T item); + boolean remove(Envelope itemEnv, Object item); } diff --git a/modules/core/src/main/java/org/locationtech/jts/index/hprtree/HPRtree.java b/modules/core/src/main/java/org/locationtech/jts/index/hprtree/HPRtree.java index 7e7ab315cc..f6f166712a 100644 --- a/modules/core/src/main/java/org/locationtech/jts/index/hprtree/HPRtree.java +++ b/modules/core/src/main/java/org/locationtech/jts/index/hprtree/HPRtree.java @@ -57,8 +57,8 @@ * @author Martin Davis * */ -public class HPRtree - implements SpatialIndex +public class HPRtree + implements SpatialIndex { private static final int ENV_SIZE = 4; @@ -66,7 +66,7 @@ public class HPRtree private static final int DEFAULT_NODE_CAPACITY = 16; - private List> itemsToLoad = new ArrayList<>(); + private List itemsToLoad = new ArrayList<>(); private final int nodeCapacity; @@ -110,29 +110,29 @@ public int size() { } @Override - public void insert(Envelope itemEnv, T item) { + public void insert(Envelope itemEnv, Object item) { if (isBuilt) { throw new IllegalStateException("Cannot insert items after tree is built."); } numItems++; - itemsToLoad.add( new Item<>(itemEnv, item) ); + itemsToLoad.add( new Item(itemEnv, item) ); totalExtent.expandToInclude(itemEnv); } @Override - public List query(Envelope searchEnv) { + public List query(Envelope searchEnv) { build(); if (! totalExtent.intersects(searchEnv)) - return new ArrayList<>(); + return new ArrayList(); - ArrayListVisitor visitor = new ArrayListVisitor<>(); + ArrayListVisitor visitor = new ArrayListVisitor(); query(searchEnv, visitor); return visitor.getItems(); } @Override - public void query(Envelope searchEnv, ItemVisitor visitor) { + public void query(Envelope searchEnv, ItemVisitor visitor) { build(); if (! totalExtent.intersects(searchEnv)) return; @@ -144,7 +144,7 @@ public void query(Envelope searchEnv, ItemVisitor visitor) { } } - private void queryTopLayer(Envelope searchEnv, ItemVisitor visitor) { + private void queryTopLayer(Envelope searchEnv, ItemVisitor visitor) { int layerIndex = layerStartIndex.length - 2; int layerSize = layerSize(layerIndex); // query each node in layer @@ -153,7 +153,7 @@ private void queryTopLayer(Envelope searchEnv, ItemVisitor visitor) { } } - private void queryNode(int layerIndex, int nodeOffset, Envelope searchEnv, ItemVisitor visitor) { + private void queryNode(int layerIndex, int nodeOffset, Envelope searchEnv, ItemVisitor visitor) { int layerStart = layerStartIndex[layerIndex]; int nodeIndex = layerStart + nodeOffset; if (! intersects(nodeBounds, nodeIndex, searchEnv)) return; @@ -175,7 +175,7 @@ private static boolean intersects(double[] bounds, int nodeIndex, Envelope env) return ! isBeyond; } - private void queryNodeChildren(int layerIndex, int blockOffset, Envelope searchEnv, ItemVisitor visitor) { + private void queryNodeChildren(int layerIndex, int blockOffset, Envelope searchEnv, ItemVisitor visitor) { int layerStart = layerStartIndex[layerIndex]; int layerEnd = layerStartIndex[layerIndex + 1]; for (int i = 0; i < nodeCapacity; i++) { @@ -187,15 +187,13 @@ private void queryNodeChildren(int layerIndex, int blockOffset, Envelope searchE } } - private void queryItems(int blockStart, Envelope searchEnv, ItemVisitor visitor) { + private void queryItems(int blockStart, Envelope searchEnv, ItemVisitor visitor) { for (int i = 0; i < nodeCapacity; i++) { int itemIndex = blockStart + i; // don't query past end of items if (itemIndex >= numItems) break; if (intersects(itemBounds, itemIndex * ENV_SIZE, searchEnv)) { - @SuppressWarnings("unchecked") - T item = (T) itemValues[itemIndex]; - visitor.visitItem(item); + visitor.visitItem(itemValues[itemIndex]); } } } @@ -207,7 +205,7 @@ private int layerSize(int layerIndex) { } @Override - public boolean remove(Envelope itemEnv, T item) { + public boolean remove(Envelope itemEnv, Object item) { // TODO Auto-generated method stub return false; } @@ -252,7 +250,7 @@ private void prepareItems() { int valueIndex = 0; itemBounds = new double[itemsToLoad.size() * 4]; itemValues = new Object[itemsToLoad.size()]; - for (Item item : itemsToLoad) { + for (Item item : itemsToLoad) { Envelope envelope = item.getEnvelope(); itemBounds[boundsIndex++] = envelope.getMinX(); itemBounds[boundsIndex++] = envelope.getMinY(); @@ -365,7 +363,7 @@ private void sortItems() { HilbertEncoder encoder = new HilbertEncoder(HILBERT_LEVEL, totalExtent); int[] hilbertValues = new int[itemsToLoad.size()]; int pos = 0; - for (Item item : itemsToLoad) { + for (Item item : itemsToLoad) { hilbertValues[pos++] = encoder.encode(item.getEnvelope()); } quickSortItemsIntoNodes(hilbertValues, 0, itemsToLoad.size() - 1); @@ -395,7 +393,7 @@ private int hoarePartition(int[] values, int lo, int hi) { } private void swapItems(int[] values, int i, int j) { - Item tmpItemp = itemsToLoad.get(i); + Item tmpItemp = itemsToLoad.get(i); itemsToLoad.set(i, itemsToLoad.get(j)); itemsToLoad.set(j, tmpItemp); diff --git a/modules/core/src/main/java/org/locationtech/jts/index/hprtree/Item.java b/modules/core/src/main/java/org/locationtech/jts/index/hprtree/Item.java index 5710b9a212..7de93a1001 100644 --- a/modules/core/src/main/java/org/locationtech/jts/index/hprtree/Item.java +++ b/modules/core/src/main/java/org/locationtech/jts/index/hprtree/Item.java @@ -13,12 +13,12 @@ import org.locationtech.jts.geom.Envelope; -public class Item { +public class Item { - private final Envelope env; - private final T item; + private Envelope env; + private Object item; - public Item(Envelope env, T item) { + public Item(Envelope env, Object item) { this.env = env; this.item = item; } @@ -27,7 +27,7 @@ public Envelope getEnvelope() { return env; } - public T getItem() { + public Object getItem() { return item; } diff --git a/modules/core/src/main/java/org/locationtech/jts/noding/MCIndexNoder.java b/modules/core/src/main/java/org/locationtech/jts/noding/MCIndexNoder.java index e21feecc84..399efda0d9 100644 --- a/modules/core/src/main/java/org/locationtech/jts/noding/MCIndexNoder.java +++ b/modules/core/src/main/java/org/locationtech/jts/noding/MCIndexNoder.java @@ -39,8 +39,8 @@ public class MCIndexNoder extends SinglePassNoder { - private final List monoChains = new ArrayList<>(); - private final SpatialIndex index = new HPRtree<>(); + private List monoChains = new ArrayList(); + private SpatialIndex index= new HPRtree(); private int idCounter = 0; private Collection nodedSegStrings; // statistics @@ -69,9 +69,9 @@ public MCIndexNoder(SegmentIntersector si, double overlapTolerance) this.overlapTolerance = overlapTolerance; } - public List getMonotoneChains() { return monoChains; } + public List getMonotoneChains() { return monoChains; } - public SpatialIndex getIndex() { return index; } + public SpatialIndex getIndex() { return index; } public Collection getNodedSubstrings() { @@ -91,9 +91,13 @@ public void computeNodes(Collection inputSegStrings) private void intersectChains() { MonotoneChainOverlapAction overlapAction = new SegmentOverlapAction(segInt); - for (MonotoneChain queryChain : monoChains) { + + for (Iterator i = monoChains.iterator(); i.hasNext(); ) { + MonotoneChain queryChain = (MonotoneChain) i.next(); Envelope queryEnv = queryChain.getEnvelope(overlapTolerance); - for (MonotoneChain testChain : index.query(queryEnv)) { + List overlapChains = index.query(queryEnv); + for (Iterator j = overlapChains.iterator(); j.hasNext(); ) { + MonotoneChain testChain = (MonotoneChain) j.next(); /** * following test makes sure we only compare each pair of chains once * and that we don't compare a chain to itself @@ -103,7 +107,8 @@ private void intersectChains() nOverlaps++; } // short-circuit if possible - if (segInt.isDone()) return; + if (segInt.isDone()) + return; } } } From d4079daf00a1120cbafbb70c43efaf51aa6f0aea Mon Sep 17 00:00:00 2001 From: Mike Barry Date: Mon, 6 Nov 2023 21:00:41 -0500 Subject: [PATCH 08/11] remove cast --- .../jts/noding/snapround/MCIndexPointSnapper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/core/src/main/java/org/locationtech/jts/noding/snapround/MCIndexPointSnapper.java b/modules/core/src/main/java/org/locationtech/jts/noding/snapround/MCIndexPointSnapper.java index 043ac92640..163ba2a9e1 100644 --- a/modules/core/src/main/java/org/locationtech/jts/noding/snapround/MCIndexPointSnapper.java +++ b/modules/core/src/main/java/org/locationtech/jts/noding/snapround/MCIndexPointSnapper.java @@ -32,10 +32,10 @@ public class MCIndexPointSnapper { //public static final int nSnaps = 0; - private STRtree index; + private SpatialIndex index; public MCIndexPointSnapper(SpatialIndex index) { - this.index = (STRtree) index; + this.index = index; } /** From 0b87dfda9e77036df218df942f8070b9c041f56a Mon Sep 17 00:00:00 2001 From: Mike Barry Date: Tue, 7 Nov 2023 05:01:29 -0500 Subject: [PATCH 09/11] rm import Signed-off-by: Mike Barry --- .../locationtech/jts/noding/snapround/MCIndexPointSnapper.java | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/core/src/main/java/org/locationtech/jts/noding/snapround/MCIndexPointSnapper.java b/modules/core/src/main/java/org/locationtech/jts/noding/snapround/MCIndexPointSnapper.java index 163ba2a9e1..e4c6f95cdb 100644 --- a/modules/core/src/main/java/org/locationtech/jts/noding/snapround/MCIndexPointSnapper.java +++ b/modules/core/src/main/java/org/locationtech/jts/noding/snapround/MCIndexPointSnapper.java @@ -18,7 +18,6 @@ import org.locationtech.jts.index.SpatialIndex; import org.locationtech.jts.index.chain.MonotoneChain; import org.locationtech.jts.index.chain.MonotoneChainSelectAction; -import org.locationtech.jts.index.strtree.STRtree; import org.locationtech.jts.noding.NodedSegmentString; import org.locationtech.jts.noding.SegmentString; From 39a3b2590dd4e3529a7c9186f32922e66bf11af5 Mon Sep 17 00:00:00 2001 From: Mike Barry Date: Thu, 7 Dec 2023 05:53:48 -0500 Subject: [PATCH 10/11] flatbush tests --- .../test/jts/perf/index/FlatbushPerfTest.java | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 modules/core/src/test/java/test/jts/perf/index/FlatbushPerfTest.java diff --git a/modules/core/src/test/java/test/jts/perf/index/FlatbushPerfTest.java b/modules/core/src/test/java/test/jts/perf/index/FlatbushPerfTest.java new file mode 100644 index 0000000000..25cc55a332 --- /dev/null +++ b/modules/core/src/test/java/test/jts/perf/index/FlatbushPerfTest.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2019 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 test.jts.perf.index; + +import org.locationtech.jts.geom.Envelope; +import org.locationtech.jts.index.hprtree.HPRtree; +import org.locationtech.jts.index.strtree.STRtree; +import org.locationtech.jts.util.Stopwatch; +import test.jts.perf.PerformanceTestCase; +import test.jts.perf.PerformanceTestRunner; + +import java.util.Random; + +/** + * Reproduce the performance benchmark scenario that + * Flatbush + * uses, and run against spatial indexes. + */ +public class FlatbushPerfTest extends PerformanceTestCase { + private static final int ITEMS = 1_000_000; + private static final int QUERIES = 1_000; + + public static void main(String[] args) { + PerformanceTestRunner.run(FlatbushPerfTest.class); + } + + private HPRtree hprtree; + private STRtree strtree; + private Envelope[] boxes; + + public FlatbushPerfTest(String name) { + super(name); + setRunSize(new int[] { 1, 10, (int) (100 * Math.sqrt(0.1))}); + setRunIterations(1); + } + + private static Envelope randomBox(Random random, double boxSize) { + double x = random.nextDouble() * (100d - boxSize); + double y = random.nextDouble() * (100d - boxSize); + double x2 = x + random.nextDouble() * boxSize; + double y2 = y + random.nextDouble() * boxSize; + return new Envelope(x, x2, y, y2); + } + + public void setUp() + { + Random random = new Random(0); + Envelope[] envs = new Envelope[ITEMS]; + + for (int i = 0; i < ITEMS; i++) { + envs[i] = randomBox(random, 1); + } + + hprtree = new HPRtree(); + Stopwatch sw = new Stopwatch(); + for (Envelope env : envs) { + hprtree.insert(env, env); + } + hprtree.build(); + System.out.println("HPRTree Build time = " + sw.getTimeString()); + + strtree = new STRtree(); + sw = new Stopwatch(); + for (Envelope env : envs) { + strtree.insert(env, env); + } + strtree.build(); + System.out.println("STRTree Build time = " + sw.getTimeString()); + } + + public void startRun(int size) + { + System.out.println("----- Query size: " + size); + Random random = new Random(0); + boxes = new Envelope[QUERIES]; + for (int i = 0; i < QUERIES; i++) { + boxes[i] = randomBox(random, size); + } + } + + public void runQueriesHPR() { + CountItemVisitor visitor = new CountItemVisitor(); + for (Envelope box : boxes) { + hprtree.query(box, visitor); + } + System.out.println("HPRTree query result items = " + visitor.count); + } + + public void runQueriesSTR() { + CountItemVisitor visitor = new CountItemVisitor(); + for (Envelope box : boxes) { + strtree.query(box, visitor); + } + System.out.println("STRTree query result items = " + visitor.count); + } +} From 0001c1634b35845064cd22c2b350e4259c36f327 Mon Sep 17 00:00:00 2001 From: Mike Barry Date: Thu, 7 Dec 2023 06:53:16 -0500 Subject: [PATCH 11/11] add warmup step --- .../test/jts/perf/index/FlatbushPerfTest.java | 64 ++++++++++++------- 1 file changed, 40 insertions(+), 24 deletions(-) diff --git a/modules/core/src/test/java/test/jts/perf/index/FlatbushPerfTest.java b/modules/core/src/test/java/test/jts/perf/index/FlatbushPerfTest.java index 25cc55a332..989208119f 100644 --- a/modules/core/src/test/java/test/jts/perf/index/FlatbushPerfTest.java +++ b/modules/core/src/test/java/test/jts/perf/index/FlatbushPerfTest.java @@ -12,6 +12,7 @@ package test.jts.perf.index; import org.locationtech.jts.geom.Envelope; +import org.locationtech.jts.index.SpatialIndex; import org.locationtech.jts.index.hprtree.HPRtree; import org.locationtech.jts.index.strtree.STRtree; import org.locationtech.jts.util.Stopwatch; @@ -19,6 +20,8 @@ import test.jts.perf.PerformanceTestRunner; import java.util.Random; +import java.util.function.Consumer; +import java.util.function.Supplier; /** * Reproduce the performance benchmark scenario that @@ -26,17 +29,17 @@ * uses, and run against spatial indexes. */ public class FlatbushPerfTest extends PerformanceTestCase { - private static final int ITEMS = 1_000_000; - private static final int QUERIES = 1_000; + private static final int NUM_ITEMS = 1_000_000; + private static final int NUM_QUERIES = 1_000; + private Envelope[] items; + private Envelope[] queries; + private HPRtree hprtree; + private STRtree strtree; public static void main(String[] args) { PerformanceTestRunner.run(FlatbushPerfTest.class); } - private HPRtree hprtree; - private STRtree strtree; - private Envelope[] boxes; - public FlatbushPerfTest(String name) { super(name); setRunSize(new int[] { 1, 10, (int) (100 * Math.sqrt(0.1))}); @@ -54,42 +57,55 @@ private static Envelope randomBox(Random random, double boxSize) { public void setUp() { Random random = new Random(0); - Envelope[] envs = new Envelope[ITEMS]; + items = new Envelope[NUM_ITEMS]; - for (int i = 0; i < ITEMS; i++) { - envs[i] = randomBox(random, 1); + for (int i = 0; i < NUM_ITEMS; i++) { + items[i] = randomBox(random, 1); } - hprtree = new HPRtree(); + // warmup the jvm by building once and running queries + warmupQueries(createIndex(HPRtree::new, HPRtree::build)); + warmupQueries(createIndex(STRtree::new, STRtree::build)); + Stopwatch sw = new Stopwatch(); - for (Envelope env : envs) { - hprtree.insert(env, env); - } - hprtree.build(); + hprtree = createIndex(HPRtree::new, HPRtree::build); System.out.println("HPRTree Build time = " + sw.getTimeString()); - strtree = new STRtree(); sw = new Stopwatch(); - for (Envelope env : envs) { - strtree.insert(env, env); - } - strtree.build(); + strtree = createIndex(STRtree::new, STRtree::build); System.out.println("STRTree Build time = " + sw.getTimeString()); } + private T createIndex(Supplier supplier, Consumer builder) { + T index = supplier.get(); + for (Envelope env : items) { + index.insert(env, env); + } + builder.accept(index); + return index; + } + + private void warmupQueries(SpatialIndex index) { + Random random = new Random(0); + CountItemVisitor visitor = new CountItemVisitor(); + for (int i = 0; i < NUM_QUERIES; i++) { + index.query(randomBox(random, 1), visitor); + } + } + public void startRun(int size) { System.out.println("----- Query size: " + size); Random random = new Random(0); - boxes = new Envelope[QUERIES]; - for (int i = 0; i < QUERIES; i++) { - boxes[i] = randomBox(random, size); + queries = new Envelope[NUM_QUERIES]; + for (int i = 0; i < NUM_QUERIES; i++) { + queries[i] = randomBox(random, size); } } public void runQueriesHPR() { CountItemVisitor visitor = new CountItemVisitor(); - for (Envelope box : boxes) { + for (Envelope box : queries) { hprtree.query(box, visitor); } System.out.println("HPRTree query result items = " + visitor.count); @@ -97,7 +113,7 @@ public void runQueriesHPR() { public void runQueriesSTR() { CountItemVisitor visitor = new CountItemVisitor(); - for (Envelope box : boxes) { + for (Envelope box : queries) { strtree.query(box, visitor); } System.out.println("STRTree query result items = " + visitor.count);