├── .gitignore ├── RTree.Test ├── RTree.Test.csproj └── SimpleTests.cs ├── RTree.sln ├── RTree ├── LICENSE.txt ├── Node.cs ├── Point.cs ├── RTree.cs ├── RTree.csproj └── Rectangle.cs └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /.vs 3 | /RTree/bin 4 | /RTree/obj 5 | /RTree.Test/bin 6 | /RTree.Test/obj -------------------------------------------------------------------------------- /RTree.Test/RTree.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /RTree.Test/SimpleTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | using System.Threading.Tasks; 7 | 8 | namespace RTree.Test 9 | { 10 | /// 11 | /// Summary description for UnitTest1 12 | /// 13 | [TestClass] 14 | public class SimpleTests 15 | { 16 | 17 | #region Additional test attributes 18 | // 19 | // You can use the following additional attributes as you write your tests: 20 | // 21 | // Use ClassInitialize to run code before running the first test in the class 22 | // [ClassInitialize()] 23 | // public static void MyClassInitialize(TestContext testContext) { } 24 | // 25 | // Use ClassCleanup to run code after all tests in a class have run 26 | // [ClassCleanup()] 27 | // public static void MyClassCleanup() { } 28 | // 29 | // Use TestInitialize to run code before running each test 30 | // [TestInitialize()] 31 | // public void MyTestInitialize() { } 32 | // 33 | // Use TestCleanup to run code after each test has run 34 | // [TestCleanup()] 35 | // public void MyTestCleanup() { } 36 | // 37 | #endregion 38 | 39 | private RTree Instance { get; set; } 40 | 41 | [TestInitialize] 42 | public void Setup() 43 | { 44 | Instance = new RTree(); 45 | Instance.Add(new Rectangle(0, 0, 0, 0, 0, 0), "Origin"); 46 | Instance.Add(new Rectangle(1, 1, 1, 1,1,1), "Box1"); 47 | 48 | Instance.Add(new Rectangle(2, 2, 3, 3, 2, 3), "Box 2-3"); 49 | } 50 | 51 | 52 | [TestMethod] 53 | public void TestContainsFound() 54 | { 55 | var instancelist = Instance.Contains(new Rectangle(-1, -1, 2, 2, -1, 2)); 56 | Assert.AreEqual(2, instancelist.Count()); 57 | } 58 | 59 | [TestMethod] 60 | public void TestContainsNotFound() 61 | { 62 | var instancelist = Instance.Contains(new Rectangle(5, 5, 6, 6, 5, 6)); 63 | Assert.AreEqual(0, instancelist.Count()); 64 | } 65 | 66 | [TestMethod] 67 | public void TestBounds() 68 | { 69 | var bounds = Instance.getBounds(); 70 | //X 71 | Assert.AreEqual(0, bounds.get(0).Value.min); 72 | Assert.AreEqual(3, bounds.get(0).Value.max); 73 | //Y 74 | Assert.AreEqual(0, bounds.get(1).Value.min); 75 | Assert.AreEqual(3, bounds.get(1).Value.max); 76 | //Z 77 | Assert.AreEqual(0, bounds.get(2).Value.min); 78 | Assert.AreEqual(3, bounds.get(2).Value.max); 79 | } 80 | 81 | [TestMethod] 82 | public void TestIntersects() 83 | { 84 | var intersectlist = Instance.Intersects(new Rectangle(3, 3, 5, 5, 3, 5)); 85 | Assert.AreEqual(1, intersectlist.Count()); 86 | Assert.AreEqual("Box 2-3", intersectlist[0]); 87 | } 88 | 89 | [TestMethod] 90 | public void TestNearest() 91 | { 92 | var nearestlist = Instance.Nearest(new Point(5, 5, 5), 10); 93 | Assert.IsTrue(nearestlist.Count() > 0); 94 | 95 | Assert.AreEqual("Box 2-3", nearestlist[0]); 96 | } 97 | 98 | [TestMethod] 99 | public void TestDelete() 100 | { 101 | var nearestlist = Instance.Nearest(new Point(5, 5, 5), 10); 102 | 103 | Assert.AreEqual("Box 2-3", nearestlist[0]); 104 | 105 | Instance.Delete(new Rectangle(2, 2, 3, 3, 2, 3), "Box 2-3"); 106 | 107 | nearestlist = Instance.Nearest(new Point(5, 5, 5), 10); 108 | 109 | Assert.IsTrue(nearestlist.Count() > 0); 110 | 111 | Assert.AreEqual("Box1", nearestlist[0]); 112 | } 113 | 114 | /// 115 | /// Not the most reliable test, but should catch simple errors 116 | /// 117 | [TestMethod] 118 | public void TestMultithreading() 119 | { 120 | var instance = new RTree(); 121 | 122 | Parallel.For(0,100, i=>{ 123 | instance.Add(new Rectangle(0, 0, 0, 0, 0, 0), $"Origin-{Guid.NewGuid()}"); 124 | instance.Add(new Rectangle(1, 1, 1, 1, 1, 1), $"Box1-{Guid.NewGuid()}"); 125 | 126 | var rect_to_delete_name = $"Box 2-3-{Guid.NewGuid()}"; 127 | instance.Add(new Rectangle(2, 2, 3, 3, 2, 3), rect_to_delete_name); 128 | instance.Add(new Rectangle(2, 2, 3, 3, 2, 3), $"Box 2-3-{Guid.NewGuid()}"); 129 | 130 | var instancelist = instance.Contains(new Rectangle(-1, -1, 2, 2, -1, 2)); 131 | Assert.IsTrue(instancelist.Count() > 0); 132 | 133 | var intersectlist = instance.Intersects(new Rectangle(3, 3, 5, 5, 3, 5)); 134 | Assert.IsTrue(intersectlist.Count() > 1); 135 | Assert.IsTrue(intersectlist[0].StartsWith("Box 2-3")); 136 | 137 | var nearestlist = instance.Nearest(new Point(5, 5, 5), 10); 138 | 139 | Assert.IsTrue(nearestlist[0].StartsWith("Box 2-3") ); 140 | 141 | instance.Delete(new Rectangle(2, 2, 3, 3, 2, 3), rect_to_delete_name); 142 | 143 | nearestlist = instance.Nearest(new Point(5, 5, 5), 10); 144 | 145 | Assert.IsTrue(nearestlist.Count() > 0); 146 | 147 | Assert.IsTrue(nearestlist[0].StartsWith( "Box 2")); 148 | }); 149 | } 150 | 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /RTree.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27004.2009 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RTree", "RTree\RTree.csproj", "{7E646F71-14D1-49B4-9D09-D5CD8A898C6A}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RTree.Test", "RTree.Test\RTree.Test.csproj", "{27C0E4EE-82E4-4BE7-A534-4FCC3E8A9F56}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {7E646F71-14D1-49B4-9D09-D5CD8A898C6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {7E646F71-14D1-49B4-9D09-D5CD8A898C6A}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {7E646F71-14D1-49B4-9D09-D5CD8A898C6A}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {7E646F71-14D1-49B4-9D09-D5CD8A898C6A}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {27C0E4EE-82E4-4BE7-A534-4FCC3E8A9F56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {27C0E4EE-82E4-4BE7-A534-4FCC3E8A9F56}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {27C0E4EE-82E4-4BE7-A534-4FCC3E8A9F56}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {27C0E4EE-82E4-4BE7-A534-4FCC3E8A9F56}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {F4F5F289-4552-42FC-96BA-83C455806793} 30 | EndGlobalSection 31 | GlobalSection(TestCaseManagementSettings) = postSolution 32 | CategoryFile = RTree.vsmdi 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /RTree/LICENSE.txt: -------------------------------------------------------------------------------- 1 | This library is free software; you can redistribute it and/or 2 | modify it under the terms of the GNU Lesser General Public 3 | License as published by the Free Software Foundation; either 4 | version 2.1 of the License, or (at your option) any later version. 5 | 6 | This library is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | Lesser General Public License for more details. 10 | 11 | You should have received a copy of the GNU Lesser General Public 12 | License along with this library; if not, write to the Free Software 13 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -------------------------------------------------------------------------------- /RTree/Node.cs: -------------------------------------------------------------------------------- 1 | // Node.java version 1.0b2p1 2 | // Java Spatial Index Library 3 | // Copyright (C) 2002 Infomatiq Limited 4 | // Copyright (C) 2008 Aled Morris aled@sourceforge.net 5 | // 6 | // This library is free software; you can redistribute it and/or 7 | // modify it under the terms of the GNU Lesser General Public 8 | // License as published by the Free Software Foundation; either 9 | // version 2.1 of the License, or (at your option) any later version. 10 | // 11 | // This library is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | // Lesser General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU Lesser General Public 17 | // License along with this library; if not, write to the Free Software 18 | // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | 20 | // Ported to C# By Dror Gluska, April 9th, 2009 21 | namespace RTree 22 | { 23 | /// 24 | /// Used by RTree. There are no public methods in this class. 25 | /// 26 | internal class Node 27 | { 28 | internal int nodeId = 0; 29 | internal Rectangle mbr = null; 30 | internal Rectangle[] entries = null; 31 | internal int[] ids = null; 32 | internal int level; 33 | internal int entryCount; 34 | 35 | public Node(int nodeId, int level, int maxNodeEntries) 36 | { 37 | this.nodeId = nodeId; 38 | this.level = level; 39 | entries = new Rectangle[maxNodeEntries]; 40 | ids = new int[maxNodeEntries]; 41 | } 42 | 43 | internal void addEntry(Rectangle r, int id) 44 | { 45 | ids[entryCount] = id; 46 | entries[entryCount] = r.copy(); 47 | entryCount++; 48 | if (mbr == null) 49 | { 50 | mbr = r.copy(); 51 | } 52 | else 53 | { 54 | mbr.add(r); 55 | } 56 | } 57 | 58 | internal void addEntryNoCopy(Rectangle r, int id) 59 | { 60 | ids[entryCount] = id; 61 | entries[entryCount] = r; 62 | entryCount++; 63 | if (mbr == null) 64 | { 65 | mbr = r.copy(); 66 | } 67 | else 68 | { 69 | mbr.add(r); 70 | } 71 | } 72 | 73 | /// 74 | /// Return the index of the found entry, or -1 if not found 75 | /// 76 | internal int findEntry(Rectangle r, int id) 77 | { 78 | for (int i = 0; i < entryCount; i++) 79 | { 80 | if (id == ids[i] && r.Equals(entries[i])) 81 | { 82 | return i; 83 | } 84 | } 85 | return -1; 86 | } 87 | 88 | // delete entry. This is done by setting it to null and copying the last entry into its space. 89 | 90 | /// 91 | /// delete entry. This is done by setting it to null and copying the last entry into its space. 92 | /// 93 | internal void deleteEntry(int i, int minNodeEntries) 94 | { 95 | int lastIndex = entryCount - 1; 96 | Rectangle deletedRectangle = entries[i]; 97 | entries[i] = null; 98 | if (i != lastIndex) 99 | { 100 | entries[i] = entries[lastIndex]; 101 | ids[i] = ids[lastIndex]; 102 | entries[lastIndex] = null; 103 | } 104 | entryCount--; 105 | 106 | // if there are at least minNodeEntries, adjust the MBR. 107 | // otherwise, don't bother, as the Node will be 108 | // eliminated anyway. 109 | if (entryCount >= minNodeEntries) 110 | { 111 | recalculateMBR(deletedRectangle); 112 | } 113 | } 114 | 115 | /// 116 | /// oldRectangle is a rectangle that has just been deleted or made smaller. 117 | /// Thus, the MBR is only recalculated if the OldRectangle influenced the old MBR 118 | /// 119 | internal void recalculateMBR(Rectangle deletedRectangle) 120 | { 121 | if (mbr.edgeOverlaps(deletedRectangle)) 122 | { 123 | mbr.set(entries[0].min, entries[0].max); 124 | 125 | for (int i = 1; i < entryCount; i++) 126 | { 127 | mbr.add(entries[i]); 128 | } 129 | } 130 | } 131 | 132 | public int getEntryCount() 133 | { 134 | return entryCount; 135 | } 136 | 137 | public Rectangle getEntry(int index) 138 | { 139 | if (index < entryCount) 140 | { 141 | return entries[index]; 142 | } 143 | return null; 144 | } 145 | 146 | public int getId(int index) 147 | { 148 | if (index < entryCount) 149 | { 150 | return ids[index]; 151 | } 152 | return -1; 153 | } 154 | 155 | /// 156 | /// eliminate null entries, move all entries to the start of the source node 157 | /// 158 | internal void reorganize(RTree rtree) 159 | { 160 | int countdownIndex = rtree.maxNodeEntries - 1; 161 | for (int index = 0; index < entryCount; index++) 162 | { 163 | if (entries[index] == null) 164 | { 165 | while (entries[countdownIndex] == null && countdownIndex > index) 166 | { 167 | countdownIndex--; 168 | } 169 | entries[index] = entries[countdownIndex]; 170 | ids[index] = ids[countdownIndex]; 171 | entries[countdownIndex] = null; 172 | } 173 | } 174 | } 175 | 176 | internal bool isLeaf() 177 | { 178 | return (level == 1); 179 | } 180 | 181 | public int getLevel() 182 | { 183 | return level; 184 | } 185 | 186 | public Rectangle getMBR() 187 | { 188 | return mbr; 189 | } 190 | } 191 | 192 | } -------------------------------------------------------------------------------- /RTree/Point.cs: -------------------------------------------------------------------------------- 1 | // Point.java version 1.0b2p1 2 | // Java Spatial Index Library 3 | // Copyright (C) 2002 Infomatiq Limited 4 | // Copyright (C) 2008 Aled Morris aled@sourceforge.net 5 | // 6 | // This library is free software; you can redistribute it and/or 7 | // modify it under the terms of the GNU Lesser General Public 8 | // License as published by the Free Software Foundation; either 9 | // version 2.1 of the License, or (at your option) any later version. 10 | // 11 | // This library is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | // Lesser General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU Lesser General Public 17 | // License along with this library; if not, write to the Free Software 18 | // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | 20 | // Ported to C# By Dror Gluska, April 9th, 2009 21 | namespace RTree 22 | { 23 | 24 | /// 25 | /// Currently hardcoded to 3 dimensions, but could be extended. 26 | /// 27 | public class Point 28 | { 29 | /// 30 | /// Number of dimensions in a point. In theory this 31 | /// could be exended to three or more dimensions. 32 | /// 33 | private const int DIMENSIONS = 3; 34 | 35 | /// 36 | /// The (x, y) coordinates of the point. 37 | /// 38 | internal float[] coordinates; 39 | 40 | 41 | /// 42 | /// Constructor. 43 | /// 44 | /// The x coordinate of the point 45 | /// The y coordinate of the point 46 | /// The z coordinate of the point 47 | public Point(float x, float y,float z) 48 | { 49 | coordinates = new float[DIMENSIONS]; 50 | coordinates[0] = x; 51 | coordinates[1] = y; 52 | coordinates[2] = z; 53 | } 54 | 55 | /// 56 | /// retrieve coordinate from point 57 | /// probable dimensions: 58 | /// X = 0, Y = 1, Z = 2 59 | /// 60 | public float? get(int dimension) 61 | { 62 | if (coordinates.Length >= dimension) 63 | return coordinates[dimension]; 64 | 65 | return null; 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /RTree/RTree.cs: -------------------------------------------------------------------------------- 1 | // RTree.java 2 | // Java Spatial Index Library 3 | // Copyright (C) 2002 Infomatiq Limited 4 | // Copyright (C) 2008 Aled Morris aled@sourceforge.net 5 | // 6 | // This library is free software; you can redistribute it and/or 7 | // modify it under the terms of the GNU Lesser General Public 8 | // License as published by the Free Software Foundation; either 9 | // version 2.1 of the License, or (at your option) any later version. 10 | // 11 | // This library is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | // Lesser General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU Lesser General Public 17 | // License along with this library; if not, write to the Free Software 18 | // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | 20 | // Ported to C# By Dror Gluska, April 9th, 2009 21 | 22 | 23 | using System.Collections.Generic; 24 | using System; 25 | using System.Diagnostics; 26 | using System.Threading; 27 | 28 | namespace RTree 29 | { 30 | 31 | /// 32 | /// This is a lightweight RTree implementation, specifically designed 33 | /// for the following features (in order of importance): 34 | /// 35 | /// Fast intersection query performance. To achieve this, the RTree 36 | /// uses only main memory to store entries. Obviously this will only improve 37 | /// performance if there is enough physical memory to avoid paging. 38 | /// Low memory requirements. 39 | /// Fast add performance. 40 | /// 41 | /// 42 | /// The main reason for the high speed of this RTree implementation is the 43 | /// avoidance of the creation of unnecessary objects, mainly achieved by using 44 | /// primitive collections from the trove4j library. 45 | /// author aled@sourceforge.net 46 | /// version 1.0b2p1 47 | /// Ported to C# By Dror Gluska, April 9th, 2009 48 | /// 49 | /// 50 | public class RTree 51 | { 52 | private const int locking_timeout = 10000; 53 | 54 | private ReaderWriterLock locker = new ReaderWriterLock(); 55 | private const string version = "1.0b2p2"; 56 | 57 | // parameters of the tree 58 | private const int DEFAULT_MAX_NODE_ENTRIES = 10; 59 | internal int maxNodeEntries; 60 | int minNodeEntries; 61 | 62 | // map of nodeId -> Node<T> object 63 | // [x] TODO eliminate this map - it should not be needed. Nodes 64 | // can be found by traversing the tree. 65 | //private TIntObjectHashMap nodeMap = new TIntObjectHashMap(); 66 | private Dictionary> nodeMap = new Dictionary>(); 67 | 68 | // internal consistency checking - set to true if debugging tree corruption 69 | public bool INTERNAL_CONSISTENCY_CHECKING = false; 70 | 71 | // used to mark the status of entries during a Node<T> split 72 | private const int ENTRY_STATUS_ASSIGNED = 0; 73 | private const int ENTRY_STATUS_UNASSIGNED = 1; 74 | private byte[] entryStatus = null; 75 | private byte[] initialEntryStatus = null; 76 | 77 | // stacks used to store nodeId and entry index of each Node<T> 78 | // from the root down to the leaf. Enables fast lookup 79 | // of nodes when a split is propagated up the tree. 80 | //private TIntStack parents = new TIntStack(); 81 | private Stack parents = new Stack(); 82 | //private TIntStack parentsEntry = new TIntStack(); 83 | private Stack parentsEntry = new Stack(); 84 | 85 | // initialisation 86 | private int treeHeight = 1; // leaves are always level 1 87 | private int rootNodeId = 0; 88 | private int msize = 0; 89 | 90 | // Enables creation of new nodes 91 | //private int highestUsedNodeId = rootNodeId; 92 | private int highestUsedNodeId = 0; 93 | 94 | // Deleted Node<T> objects are retained in the nodeMap, 95 | // so that they can be reused. Store the IDs of nodes 96 | // which can be reused. 97 | //private TIntStack deletedNodeIds = new TIntStack(); 98 | private Stack deletedNodeIds = new Stack(); 99 | 100 | // List of nearest rectangles. Use a member variable to 101 | // avoid recreating the object each time nearest() is called. 102 | //private TIntArrayList nearestIds = new TIntArrayList(); 103 | //List nearestIds = new List(); 104 | 105 | //Added dictionaries to support generic objects.. 106 | //possibility to change the code to support objects without dictionaries. 107 | private Dictionary IdsToItems = new Dictionary(); 108 | private Dictionary ItemsToIds = new Dictionary(); 109 | private volatile int idcounter = int.MinValue; 110 | 111 | /// 112 | /// Initialize implementation dependent properties of the RTree. 113 | /// 114 | public RTree() 115 | { 116 | init(); 117 | } 118 | 119 | /// 120 | /// Initialize implementation dependent properties of the RTree. 121 | /// 122 | /// his specifies the maximum number of entries 123 | ///in a node. The default value is 10, which is used if the property is 124 | ///not specified, or is less than 2. 125 | /// This specifies the minimum number of entries 126 | ///in a node. The default value is half of the MaxNodeEntries value (rounded 127 | ///down), which is used if the property is not specified or is less than 1. 128 | /// 129 | public RTree(int MaxNodeEntries, int MinNodeEntries) 130 | { 131 | minNodeEntries = MinNodeEntries; 132 | maxNodeEntries = MaxNodeEntries; 133 | init(); 134 | } 135 | 136 | private void init() 137 | { 138 | locker.AcquireWriterLock(locking_timeout); 139 | // Obviously a Node<T> with less than 2 entries cannot be split. 140 | // The Node<T> splitting algorithm will work with only 2 entries 141 | // per node, but will be inefficient. 142 | if (maxNodeEntries < 2) 143 | { 144 | Debug.WriteLine($"Invalid MaxNodeEntries = {maxNodeEntries} Resetting to default value of {DEFAULT_MAX_NODE_ENTRIES}"); 145 | maxNodeEntries = DEFAULT_MAX_NODE_ENTRIES; 146 | } 147 | 148 | // The MinNodeEntries must be less than or equal to (int) (MaxNodeEntries / 2) 149 | if (minNodeEntries < 1 || minNodeEntries > maxNodeEntries / 2) 150 | { 151 | Debug.WriteLine("MinNodeEntries must be between 1 and MaxNodeEntries / 2"); 152 | minNodeEntries = maxNodeEntries / 2; 153 | } 154 | 155 | entryStatus = new byte[maxNodeEntries]; 156 | initialEntryStatus = new byte[maxNodeEntries]; 157 | 158 | for (int i = 0; i < maxNodeEntries; i++) 159 | { 160 | initialEntryStatus[i] = ENTRY_STATUS_UNASSIGNED; 161 | } 162 | 163 | Node root = new Node(rootNodeId, 1, maxNodeEntries); 164 | nodeMap.Add(rootNodeId, root); 165 | 166 | Debug.WriteLine($"init() MaxNodeEntries = {maxNodeEntries}, MinNodeEntries = {minNodeEntries}"); 167 | locker.ReleaseWriterLock(); 168 | } 169 | 170 | /// 171 | /// Adds an item to the spatial index 172 | /// 173 | /// 174 | /// 175 | public void Add(Rectangle r, T item) 176 | { 177 | locker.AcquireWriterLock(locking_timeout); 178 | idcounter++; 179 | int id = idcounter; 180 | 181 | IdsToItems.Add(id, item); 182 | ItemsToIds.Add(item, id); 183 | 184 | add(r, id); 185 | locker.ReleaseWriterLock(); 186 | } 187 | 188 | private void add(Rectangle r, int id) 189 | { 190 | Debug.WriteLine($"Adding rectangle {r}, id {id}"); 191 | 192 | add(r.copy(), id, 1); 193 | 194 | msize++; 195 | } 196 | 197 | /// 198 | /// Adds a new entry at a specified level in the tree 199 | /// 200 | /// 201 | /// 202 | /// 203 | private void add(Rectangle r, int id, int level) 204 | { 205 | // I1 [Find position for new record] Invoke ChooseLeaf to select a 206 | // leaf Node<T> L in which to place r 207 | Node n = chooseNode(r, level); 208 | Node newLeaf = null; 209 | 210 | // I2 [Add record to leaf node] If L has room for another entry, 211 | // install E. Otherwise invoke SplitNode to obtain L and LL containing 212 | // E and all the old entries of L 213 | if (n.entryCount < maxNodeEntries) 214 | { 215 | n.addEntryNoCopy(r, id); 216 | } 217 | else 218 | { 219 | newLeaf = splitNode(n, r, id); 220 | } 221 | 222 | // I3 [Propagate changes upwards] Invoke AdjustTree on L, also passing LL 223 | // if a split was performed 224 | Node newNode = adjustTree(n, newLeaf); 225 | 226 | // I4 [Grow tree taller] If Node<T> split propagation caused the root to 227 | // split, create a new root whose children are the two resulting nodes. 228 | if (newNode != null) 229 | { 230 | int oldRootNodeId = rootNodeId; 231 | Node oldRoot = getNode(oldRootNodeId); 232 | 233 | rootNodeId = getNextNodeId(); 234 | treeHeight++; 235 | Node root = new Node(rootNodeId, treeHeight, maxNodeEntries); 236 | root.addEntry(newNode.mbr, newNode.nodeId); 237 | root.addEntry(oldRoot.mbr, oldRoot.nodeId); 238 | nodeMap.Add(rootNodeId, root); 239 | } 240 | 241 | if (INTERNAL_CONSISTENCY_CHECKING) 242 | { 243 | checkConsistency(rootNodeId, treeHeight, null); 244 | } 245 | } 246 | 247 | /// 248 | /// Deletes an item from the spatial index 249 | /// 250 | /// 251 | /// 252 | /// 253 | public bool Delete(Rectangle r, T item) 254 | { 255 | locker.AcquireWriterLock(locking_timeout); 256 | int id = ItemsToIds[item]; 257 | 258 | bool success = delete(r, id); 259 | if (success == true) 260 | { 261 | IdsToItems.Remove(id); 262 | ItemsToIds.Remove(item); 263 | } 264 | locker.ReleaseWriterLock(); 265 | return success; 266 | } 267 | 268 | private bool delete(Rectangle r, int id) 269 | { 270 | // FindLeaf algorithm inlined here. Note the "official" algorithm 271 | // searches all overlapping entries. This seems inefficient to me, 272 | // as an entry is only worth searching if it contains (NOT overlaps) 273 | // the rectangle we are searching for. 274 | // 275 | // Also the algorithm has been changed so that it is not recursive. 276 | 277 | // FL1 [Search subtrees] If root is not a leaf, check each entry 278 | // to determine if it contains r. For each entry found, invoke 279 | // findLeaf on the Node<T> pointed to by the entry, until r is found or 280 | // all entries have been checked. 281 | parents.Clear(); 282 | parents.Push(rootNodeId); 283 | 284 | parentsEntry.Clear(); 285 | parentsEntry.Push(-1); 286 | Node n = null; 287 | int foundIndex = -1; // index of entry to be deleted in leaf 288 | 289 | while (foundIndex == -1 && parents.Count > 0) 290 | { 291 | n = getNode(parents.Peek()); 292 | int startIndex = parentsEntry.Peek() + 1; 293 | 294 | if (!n.isLeaf()) 295 | { 296 | Debug.WriteLine($"searching Node {n.nodeId}, from index {startIndex}"); 297 | bool contains = false; 298 | for (int i = startIndex; i < n.entryCount; i++) 299 | { 300 | if (n.entries[i].contains(r)) 301 | { 302 | parents.Push(n.ids[i]); 303 | parentsEntry.Pop(); 304 | parentsEntry.Push(i); // this becomes the start index when the child has been searched 305 | parentsEntry.Push(-1); 306 | contains = true; 307 | break; // ie go to next iteration of while() 308 | } 309 | } 310 | if (contains) 311 | { 312 | continue; 313 | } 314 | } 315 | else 316 | { 317 | foundIndex = n.findEntry(r, id); 318 | } 319 | 320 | parents.Pop(); 321 | parentsEntry.Pop(); 322 | } // while not found 323 | 324 | if (foundIndex != -1) 325 | { 326 | n.deleteEntry(foundIndex, minNodeEntries); 327 | condenseTree(n); 328 | msize--; 329 | } 330 | 331 | // shrink the tree if possible (i.e. if root Node<T%gt; has exactly one entry,and that 332 | // entry is not a leaf node, delete the root (it's entry becomes the new root) 333 | Node root = getNode(rootNodeId); 334 | while (root.entryCount == 1 && treeHeight > 1) 335 | { 336 | root.entryCount = 0; 337 | rootNodeId = root.ids[0]; 338 | treeHeight--; 339 | root = getNode(rootNodeId); 340 | } 341 | 342 | return (foundIndex != -1); 343 | } 344 | 345 | /// 346 | /// Retrieve nearest items to a point in radius furthestDistance 347 | /// 348 | /// Point of origin 349 | /// maximum distance 350 | /// List of items 351 | public List Nearest(Point p, float furthestDistance) 352 | { 353 | List retval = new List(); 354 | locker.AcquireReaderLock(locking_timeout); 355 | nearest(p, (int id)=> 356 | { 357 | retval.Add(IdsToItems[id]); 358 | }, furthestDistance); 359 | locker.ReleaseReaderLock(); 360 | return retval; 361 | } 362 | 363 | 364 | private void nearest(Point p, Action v, float furthestDistance) 365 | { 366 | Node rootNode = getNode(rootNodeId); 367 | 368 | List nearestIds = new List(); 369 | 370 | nearest(p, rootNode, nearestIds, furthestDistance); 371 | 372 | foreach (int id in nearestIds) 373 | v(id); 374 | nearestIds.Clear(); 375 | } 376 | 377 | /// 378 | /// Retrieve items which intersect with Rectangle r 379 | /// 380 | /// 381 | /// 382 | public List Intersects(Rectangle r) 383 | { 384 | List retval = new List(); 385 | locker.AcquireReaderLock(locking_timeout); 386 | intersects(r, (int id)=> 387 | { 388 | retval.Add(IdsToItems[id]); 389 | }); 390 | locker.ReleaseReaderLock(); 391 | return retval; 392 | } 393 | 394 | 395 | private void intersects(Rectangle r, Action v) 396 | { 397 | Node rootNode = getNode(rootNodeId); 398 | intersects(r, v, rootNode); 399 | } 400 | 401 | /// 402 | /// find all rectangles in the tree that are contained by the passed rectangle 403 | /// written to be non-recursive (should model other searches on this?) 404 | /// 405 | /// 406 | public List Contains(Rectangle r) 407 | { 408 | List retval = new List(); 409 | locker.AcquireReaderLock(locking_timeout); 410 | contains(r, (int id)=> 411 | { 412 | retval.Add(IdsToItems[id]); 413 | }); 414 | 415 | locker.ReleaseReaderLock(); 416 | return retval; 417 | } 418 | 419 | private void contains(Rectangle r, Action v) 420 | { 421 | Stack _parents = new Stack(); 422 | //private TIntStack parentsEntry = new TIntStack(); 423 | Stack _parentsEntry = new Stack(); 424 | 425 | 426 | // find all rectangles in the tree that are contained by the passed rectangle 427 | // written to be non-recursive (should model other searches on this?) 428 | 429 | _parents.Clear(); 430 | _parents.Push(rootNodeId); 431 | 432 | _parentsEntry.Clear(); 433 | _parentsEntry.Push(-1); 434 | 435 | // TODO: possible shortcut here - could test for intersection with the 436 | // MBR of the root node. If no intersection, return immediately. 437 | 438 | while (_parents.Count > 0) 439 | { 440 | Node n = getNode(_parents.Peek()); 441 | int startIndex = _parentsEntry.Peek() + 1; 442 | 443 | if (!n.isLeaf()) 444 | { 445 | // go through every entry in the index Node to check 446 | // if it intersects the passed rectangle. If so, it 447 | // could contain entries that are contained. 448 | bool intersects = false; 449 | for (int i = startIndex; i < n.entryCount; i++) 450 | { 451 | if (r.intersects(n.entries[i])) 452 | { 453 | _parents.Push(n.ids[i]); 454 | _parentsEntry.Pop(); 455 | _parentsEntry.Push(i); // this becomes the start index when the child has been searched 456 | _parentsEntry.Push(-1); 457 | intersects = true; 458 | break; // ie go to next iteration of while() 459 | } 460 | } 461 | if (intersects) 462 | { 463 | continue; 464 | } 465 | } 466 | else 467 | { 468 | // go through every entry in the leaf to check if 469 | // it is contained by the passed rectangle 470 | for (int i = 0; i < n.entryCount; i++) 471 | { 472 | if (r.contains(n.entries[i])) 473 | { 474 | v(n.ids[i]); 475 | } 476 | } 477 | } 478 | _parents.Pop(); 479 | _parentsEntry.Pop(); 480 | } 481 | } 482 | 483 | /// 484 | /// Returns the bounds of all the entries in the spatial index, or null if there are no entries. 485 | /// 486 | public Rectangle getBounds() 487 | { 488 | Rectangle bounds = null; 489 | 490 | locker.AcquireReaderLock(locking_timeout); 491 | Node n = getNode(getRootNodeId()); 492 | if (n != null && n.getMBR() != null) 493 | { 494 | bounds = n.getMBR().copy(); 495 | } 496 | locker.ReleaseReaderLock(); 497 | return bounds; 498 | } 499 | 500 | /// 501 | /// Returns a string identifying the type of spatial index, and the version number 502 | /// 503 | public string getVersion() 504 | { 505 | return "RTree-" + version; 506 | } 507 | //------------------------------------------------------------------------- 508 | // end of SpatialIndex methods 509 | //------------------------------------------------------------------------- 510 | 511 | 512 | /// 513 | /// Get the next available Node<T> ID. Reuse deleted Node<T> IDs if 514 | /// possible 515 | /// 516 | private int getNextNodeId() 517 | { 518 | int nextNodeId = 0; 519 | if (deletedNodeIds.Count > 0) 520 | { 521 | nextNodeId = deletedNodeIds.Pop(); 522 | } 523 | else 524 | { 525 | nextNodeId = 1 + highestUsedNodeId++; 526 | } 527 | return nextNodeId; 528 | } 529 | 530 | 531 | 532 | 533 | 534 | /// 535 | /// Get a Node<T> object, given the ID of the node. 536 | /// 537 | /// 538 | /// 539 | private Node getNode(int index) 540 | { 541 | return (Node)nodeMap[index]; 542 | } 543 | 544 | /// 545 | /// Get the highest used Node<T> ID 546 | /// 547 | /// 548 | private int getHighestUsedNodeId() 549 | { 550 | return highestUsedNodeId; 551 | } 552 | 553 | /// 554 | /// Get the root Node<T> ID 555 | /// 556 | /// 557 | public int getRootNodeId() 558 | { 559 | return rootNodeId; 560 | } 561 | 562 | /// 563 | /// Split a node. Algorithm is taken pretty much verbatim from 564 | /// Guttman's original paper. 565 | /// 566 | /// 567 | /// 568 | /// 569 | /// return new Node<T> object. 570 | private Node splitNode(Node n, Rectangle newRect, int newId) 571 | { 572 | // [Pick first entry for each group] Apply algorithm pickSeeds to 573 | // choose two entries to be the first elements of the groups. Assign 574 | // each to a group. 575 | 576 | // debug code 577 | #if DEBUG 578 | float initialArea = 0; 579 | Rectangle union = n.mbr.union(newRect); 580 | initialArea = union.area(); 581 | #endif 582 | 583 | System.Array.Copy(initialEntryStatus, 0, entryStatus, 0, maxNodeEntries); 584 | 585 | Node newNode = null; 586 | newNode = new Node(getNextNodeId(), n.level, maxNodeEntries); 587 | nodeMap.Add(newNode.nodeId, newNode); 588 | 589 | pickSeeds(n, newRect, newId, newNode); // this also sets the entryCount to 1 590 | 591 | // [Check if done] If all entries have been assigned, stop. If one 592 | // group has so few entries that all the rest must be assigned to it in 593 | // order for it to have the minimum number m, assign them and stop. 594 | while (n.entryCount + newNode.entryCount < maxNodeEntries + 1) 595 | { 596 | if (maxNodeEntries + 1 - newNode.entryCount == minNodeEntries) 597 | { 598 | // assign all remaining entries to original node 599 | for (int i = 0; i < maxNodeEntries; i++) 600 | { 601 | if (entryStatus[i] == ENTRY_STATUS_UNASSIGNED) 602 | { 603 | entryStatus[i] = ENTRY_STATUS_ASSIGNED; 604 | n.mbr.add(n.entries[i]); 605 | n.entryCount++; 606 | } 607 | } 608 | break; 609 | } 610 | if (maxNodeEntries + 1 - n.entryCount == minNodeEntries) 611 | { 612 | // assign all remaining entries to new node 613 | for (int i = 0; i < maxNodeEntries; i++) 614 | { 615 | if (entryStatus[i] == ENTRY_STATUS_UNASSIGNED) 616 | { 617 | entryStatus[i] = ENTRY_STATUS_ASSIGNED; 618 | newNode.addEntryNoCopy(n.entries[i], n.ids[i]); 619 | n.entries[i] = null; 620 | } 621 | } 622 | break; 623 | } 624 | 625 | // [Select entry to assign] Invoke algorithm pickNext to choose the 626 | // next entry to assign. Add it to the group whose covering rectangle 627 | // will have to be enlarged least to accommodate it. Resolve ties 628 | // by adding the entry to the group with smaller area, then to the 629 | // the one with fewer entries, then to either. Repeat from S2 630 | pickNext(n, newNode); 631 | } 632 | 633 | n.reorganize(this); 634 | 635 | // check that the MBR stored for each Node<T> is correct. 636 | if (INTERNAL_CONSISTENCY_CHECKING) 637 | { 638 | if (!n.mbr.Equals(calculateMBR(n))) 639 | { 640 | Debug.WriteLine("Error: splitNode old Node MBR wrong"); 641 | } 642 | 643 | if (!newNode.mbr.Equals(calculateMBR(newNode))) 644 | { 645 | Debug.WriteLine("Error: splitNode new Node MBR wrong"); 646 | } 647 | } 648 | 649 | // debug code 650 | #if DEBUG 651 | float newArea = n.mbr.area() + newNode.mbr.area(); 652 | float percentageIncrease = (100 * (newArea - initialArea)) / initialArea; 653 | Debug.WriteLine($"Node { n.nodeId} split. New area increased by {percentageIncrease}%"); 654 | #endif 655 | 656 | return newNode; 657 | } 658 | 659 | /// 660 | /// Pick the seeds used to split a node. 661 | /// Select two entries to be the first elements of the groups 662 | /// 663 | /// 664 | /// 665 | /// 666 | /// 667 | private void pickSeeds(Node n, Rectangle newRect, int newId, Node newNode) 668 | { 669 | // Find extreme rectangles along all dimension. Along each dimension, 670 | // find the entry whose rectangle has the highest low side, and the one 671 | // with the lowest high side. Record the separation. 672 | float maxNormalizedSeparation = 0; 673 | int highestLowIndex = 0; 674 | int lowestHighIndex = 0; 675 | 676 | // for the purposes of picking seeds, take the MBR of the Node<T> to include 677 | // the new rectangle aswell. 678 | n.mbr.add(newRect); 679 | 680 | Debug.WriteLine($"pickSeeds(): NodeId = {n.nodeId}, newRect = {newRect}"); 681 | 682 | for (int d = 0; d < Rectangle.DIMENSIONS; d++) 683 | { 684 | float tempHighestLow = newRect.min[d]; 685 | int tempHighestLowIndex = -1; // -1 indicates the new rectangle is the seed 686 | 687 | float tempLowestHigh = newRect.max[d]; 688 | int tempLowestHighIndex = -1; 689 | 690 | for (int i = 0; i < n.entryCount; i++) 691 | { 692 | float tempLow = n.entries[i].min[d]; 693 | if (tempLow >= tempHighestLow) 694 | { 695 | tempHighestLow = tempLow; 696 | tempHighestLowIndex = i; 697 | } 698 | else 699 | { // ensure that the same index cannot be both lowestHigh and highestLow 700 | float tempHigh = n.entries[i].max[d]; 701 | if (tempHigh <= tempLowestHigh) 702 | { 703 | tempLowestHigh = tempHigh; 704 | tempLowestHighIndex = i; 705 | } 706 | } 707 | 708 | // PS2 [Adjust for shape of the rectangle cluster] Normalize the separations 709 | // by dividing by the widths of the entire set along the corresponding 710 | // dimension 711 | float normalizedSeparation = (tempHighestLow - tempLowestHigh) / (n.mbr.max[d] - n.mbr.min[d]); 712 | 713 | if (normalizedSeparation > 1 || normalizedSeparation < -1) 714 | { 715 | Debug.WriteLine("Invalid normalized separation"); 716 | } 717 | 718 | Debug.WriteLine($"Entry {i}, dimension {d}: HighestLow = {tempHighestLow} (index {tempHighestLowIndex})" + ", LowestHigh = " + 719 | tempLowestHigh + $" (index {tempLowestHighIndex}, NormalizedSeparation = {normalizedSeparation}"); 720 | 721 | // PS3 [Select the most extreme pair] Choose the pair with the greatest 722 | // normalized separation along any dimension. 723 | if (normalizedSeparation > maxNormalizedSeparation) 724 | { 725 | maxNormalizedSeparation = normalizedSeparation; 726 | highestLowIndex = tempHighestLowIndex; 727 | lowestHighIndex = tempLowestHighIndex; 728 | } 729 | } 730 | } 731 | 732 | // highestLowIndex is the seed for the new node. 733 | if (highestLowIndex == -1) 734 | { 735 | newNode.addEntry(newRect, newId); 736 | } 737 | else 738 | { 739 | newNode.addEntryNoCopy(n.entries[highestLowIndex], n.ids[highestLowIndex]); 740 | n.entries[highestLowIndex] = null; 741 | 742 | // move the new rectangle into the space vacated by the seed for the new node 743 | n.entries[highestLowIndex] = newRect; 744 | n.ids[highestLowIndex] = newId; 745 | } 746 | 747 | // lowestHighIndex is the seed for the original node. 748 | if (lowestHighIndex == -1) 749 | { 750 | lowestHighIndex = highestLowIndex; 751 | } 752 | 753 | entryStatus[lowestHighIndex] = ENTRY_STATUS_ASSIGNED; 754 | n.entryCount = 1; 755 | n.mbr.set(n.entries[lowestHighIndex].min, n.entries[lowestHighIndex].max); 756 | } 757 | 758 | 759 | 760 | 761 | /// 762 | /// Pick the next entry to be assigned to a group during a Node<T> split. 763 | /// [Determine cost of putting each entry in each group] For each 764 | /// entry not yet in a group, calculate the area increase required 765 | /// in the covering rectangles of each group 766 | /// 767 | /// 768 | /// 769 | /// 770 | private int pickNext(Node n, Node newNode) 771 | { 772 | float maxDifference = float.NegativeInfinity; 773 | int next = 0; 774 | int nextGroup = 0; 775 | 776 | maxDifference = float.NegativeInfinity; 777 | 778 | Debug.WriteLine("pickNext()"); 779 | 780 | for (int i = 0; i < maxNodeEntries; i++) 781 | { 782 | if (entryStatus[i] == ENTRY_STATUS_UNASSIGNED) 783 | { 784 | 785 | if (n.entries[i] == null) 786 | { 787 | Debug.WriteLine($"Error: Node {n.nodeId}, entry {i} is null"); 788 | } 789 | 790 | float nIncrease = n.mbr.enlargement(n.entries[i]); 791 | float newNodeIncrease = newNode.mbr.enlargement(n.entries[i]); 792 | float difference = Math.Abs(nIncrease - newNodeIncrease); 793 | 794 | if (difference > maxDifference) 795 | { 796 | next = i; 797 | 798 | if (nIncrease < newNodeIncrease) 799 | { 800 | nextGroup = 0; 801 | } 802 | else if (newNodeIncrease < nIncrease) 803 | { 804 | nextGroup = 1; 805 | } 806 | else if (n.mbr.area() < newNode.mbr.area()) 807 | { 808 | nextGroup = 0; 809 | } 810 | else if (newNode.mbr.area() < n.mbr.area()) 811 | { 812 | nextGroup = 1; 813 | } 814 | else if (newNode.entryCount < maxNodeEntries / 2) 815 | { 816 | nextGroup = 0; 817 | } 818 | else 819 | { 820 | nextGroup = 1; 821 | } 822 | maxDifference = difference; 823 | } 824 | 825 | Debug.WriteLine($"Entry {i} group0 increase = {nIncrease}, group1 increase = {newNodeIncrease}, diff = " + 826 | difference + $", MaxDiff = {maxDifference} (entry {next})"); 827 | } 828 | } 829 | 830 | entryStatus[next] = ENTRY_STATUS_ASSIGNED; 831 | 832 | if (nextGroup == 0) 833 | { 834 | n.mbr.add(n.entries[next]); 835 | n.entryCount++; 836 | } 837 | else 838 | { 839 | // move to new node. 840 | newNode.addEntryNoCopy(n.entries[next], n.ids[next]); 841 | n.entries[next] = null; 842 | } 843 | 844 | return next; 845 | } 846 | 847 | 848 | /// 849 | /// Recursively searches the tree for the nearest entry. Other queries 850 | /// call execute() on an IntProcedure when a matching entry is found; 851 | /// however nearest() must store the entry Ids as it searches the tree, 852 | /// in case a nearer entry is found. 853 | /// Uses the member variable nearestIds to store the nearest 854 | /// entry IDs. 855 | /// 856 | /// TODO rewrite this to be non-recursive? 857 | /// 858 | /// 859 | /// 860 | /// 861 | private float nearest(Point p, Node n, List nearestIds, float nearestDistance) 862 | { 863 | for (int i = 0; i < n.entryCount; i++) 864 | { 865 | float tempDistance = n.entries[i].distance(p); 866 | if (n.isLeaf()) 867 | { // for leaves, the distance is an actual nearest distance 868 | if (tempDistance < nearestDistance) 869 | { 870 | nearestDistance = tempDistance; 871 | nearestIds.Clear(); 872 | } 873 | if (tempDistance <= nearestDistance) 874 | { 875 | nearestIds.Add(n.ids[i]); 876 | } 877 | } 878 | else 879 | { // for index nodes, only go into them if they potentially could have 880 | // a rectangle nearer than actualNearest 881 | if (tempDistance <= nearestDistance) 882 | { 883 | // search the child node 884 | nearestDistance = nearest(p, getNode(n.ids[i]), nearestIds, nearestDistance); 885 | } 886 | } 887 | } 888 | return nearestDistance; 889 | } 890 | 891 | 892 | /// 893 | /// Recursively searches the tree for all intersecting entries. 894 | /// Immediately calls execute() on the passed IntProcedure when 895 | /// a matching entry is found. 896 | /// [x] TODO rewrite this to be non-recursive? Make sure it 897 | /// doesn't slow it down. 898 | /// 899 | /// 900 | /// 901 | /// 902 | private void intersects(Rectangle r, Action v, Node n) 903 | { 904 | for (int i = 0; i < n.entryCount; i++) 905 | { 906 | if (r.intersects(n.entries[i])) 907 | { 908 | if (n.isLeaf()) 909 | { 910 | v(n.ids[i]); 911 | } 912 | else 913 | { 914 | Node childNode = getNode(n.ids[i]); 915 | intersects(r, v, childNode); 916 | } 917 | } 918 | } 919 | } 920 | 921 | private Rectangle oldRectangle = new Rectangle(0, 0, 0, 0, 0, 0); 922 | 923 | /// 924 | /// Used by delete(). Ensures that all nodes from the passed node 925 | /// up to the root have the minimum number of entries. 926 | /// 927 | /// Note that the parent and parentEntry stacks are expected to 928 | /// contain the nodeIds of all parents up to the root. 929 | /// 930 | /// 931 | private void condenseTree(Node l) 932 | { 933 | // CT1 [Initialize] Set n=l. Set the list of eliminated 934 | // nodes to be empty. 935 | Node n = l; 936 | Node parent = null; 937 | int parentEntry = 0; 938 | 939 | //TIntStack eliminatedNodeIds = new TIntStack(); 940 | Stack eliminatedNodeIds = new Stack(); 941 | 942 | // CT2 [Find parent entry] If N is the root, go to CT6. Otherwise 943 | // let P be the parent of N, and let En be N's entry in P 944 | while (n.level != treeHeight) 945 | { 946 | parent = getNode(parents.Pop()); 947 | parentEntry = parentsEntry.Pop(); 948 | 949 | // CT3 [Eliminiate under-full node] If N has too few entries, 950 | // delete En from P and add N to the list of eliminated nodes 951 | if (n.entryCount < minNodeEntries) 952 | { 953 | parent.deleteEntry(parentEntry, minNodeEntries); 954 | eliminatedNodeIds.Push(n.nodeId); 955 | } 956 | else 957 | { 958 | // CT4 [Adjust covering rectangle] If N has not been eliminated, 959 | // adjust EnI to tightly contain all entries in N 960 | if (!n.mbr.Equals(parent.entries[parentEntry])) 961 | { 962 | oldRectangle.set(parent.entries[parentEntry].min, parent.entries[parentEntry].max); 963 | parent.entries[parentEntry].set(n.mbr.min, n.mbr.max); 964 | parent.recalculateMBR(oldRectangle); 965 | } 966 | } 967 | // CT5 [Move up one level in tree] Set N=P and repeat from CT2 968 | n = parent; 969 | } 970 | 971 | // CT6 [Reinsert orphaned entries] Reinsert all entries of nodes in set Q. 972 | // Entries from eliminated leaf nodes are reinserted in tree leaves as in 973 | // Insert(), but entries from higher level nodes must be placed higher in 974 | // the tree, so that leaves of their dependent subtrees will be on the same 975 | // level as leaves of the main tree 976 | while (eliminatedNodeIds.Count > 0) 977 | { 978 | Node e = getNode(eliminatedNodeIds.Pop()); 979 | for (int j = 0; j < e.entryCount; j++) 980 | { 981 | add(e.entries[j], e.ids[j], e.level); 982 | e.entries[j] = null; 983 | } 984 | e.entryCount = 0; 985 | deletedNodeIds.Push(e.nodeId); 986 | nodeMap.Remove(e.nodeId); 987 | } 988 | } 989 | 990 | /// 991 | /// Used by add(). Chooses a leaf to add the rectangle to. 992 | /// 993 | private Node chooseNode(Rectangle r, int level) 994 | { 995 | // CL1 [Initialize] Set N to be the root node 996 | Node n = getNode(rootNodeId); 997 | parents.Clear(); 998 | parentsEntry.Clear(); 999 | 1000 | // CL2 [Leaf check] If N is a leaf, return N 1001 | while (true) 1002 | { 1003 | if (n == null) 1004 | { 1005 | Debug.WriteLine($"Could not get root Node ({rootNodeId})"); 1006 | } 1007 | 1008 | if (n.level == level) 1009 | { 1010 | return n; 1011 | } 1012 | 1013 | // CL3 [Choose subtree] If N is not at the desired level, let F be the entry in N 1014 | // whose rectangle FI needs least enlargement to include EI. Resolve 1015 | // ties by choosing the entry with the rectangle of smaller area. 1016 | float leastEnlargement = n.getEntry(0).enlargement(r); 1017 | int index = 0; // index of rectangle in subtree 1018 | for (int i = 1; i < n.entryCount; i++) 1019 | { 1020 | Rectangle tempRectangle = n.getEntry(i); 1021 | float tempEnlargement = tempRectangle.enlargement(r); 1022 | if ((tempEnlargement < leastEnlargement) || 1023 | ((tempEnlargement == leastEnlargement) && 1024 | (tempRectangle.area() < n.getEntry(index).area()))) 1025 | { 1026 | index = i; 1027 | leastEnlargement = tempEnlargement; 1028 | } 1029 | } 1030 | 1031 | parents.Push(n.nodeId); 1032 | parentsEntry.Push(index); 1033 | 1034 | // CL4 [Descend until a leaf is reached] Set N to be the child Node<T> 1035 | // pointed to by Fp and repeat from CL2 1036 | n = getNode(n.ids[index]); 1037 | } 1038 | } 1039 | 1040 | /// 1041 | /// Ascend from a leaf Node<T> L to the root, adjusting covering rectangles and 1042 | /// propagating Node<T> splits as necessary. 1043 | /// 1044 | private Node adjustTree(Node n, Node nn) 1045 | { 1046 | // AT1 [Initialize] Set N=L. If L was split previously, set NN to be 1047 | // the resulting second node. 1048 | 1049 | // AT2 [Check if done] If N is the root, stop 1050 | while (n.level != treeHeight) 1051 | { 1052 | 1053 | // AT3 [Adjust covering rectangle in parent entry] Let P be the parent 1054 | // Node of N, and let En be N's entry in P. Adjust EnI so that it tightly 1055 | // encloses all entry rectangles in N. 1056 | Node parent = getNode(parents.Pop()); 1057 | int entry = parentsEntry.Pop(); 1058 | 1059 | if (parent.ids[entry] != n.nodeId) 1060 | { 1061 | Debug.WriteLine($"Error: entry {entry} in Node {parent.nodeId} should point to Node {n.nodeId}; actually points to Node {parent.ids[entry]}"); 1062 | } 1063 | 1064 | if (!parent.entries[entry].Equals(n.mbr)) 1065 | { 1066 | parent.entries[entry].set(n.mbr.min, n.mbr.max); 1067 | parent.mbr.set(parent.entries[0].min, parent.entries[0].max); 1068 | for (int i = 1; i < parent.entryCount; i++) 1069 | { 1070 | parent.mbr.add(parent.entries[i]); 1071 | } 1072 | } 1073 | 1074 | // AT4 [Propagate Node split upward] If N has a partner NN resulting from 1075 | // an earlier split, create a new entry Enn with Ennp pointing to NN and 1076 | // Enni enclosing all rectangles in NN. Add Enn to P if there is room. 1077 | // Otherwise, invoke splitNode to produce P and PP containing Enn and 1078 | // all P's old entries. 1079 | Node newNode = null; 1080 | if (nn != null) 1081 | { 1082 | if (parent.entryCount < maxNodeEntries) 1083 | { 1084 | parent.addEntry(nn.mbr, nn.nodeId); 1085 | } 1086 | else 1087 | { 1088 | newNode = splitNode(parent, nn.mbr.copy(), nn.nodeId); 1089 | } 1090 | } 1091 | 1092 | // AT5 [Move up to next level] Set N = P and set NN = PP if a split 1093 | // occurred. Repeat from AT2 1094 | n = parent; 1095 | nn = newNode; 1096 | 1097 | parent = null; 1098 | newNode = null; 1099 | } 1100 | 1101 | return nn; 1102 | } 1103 | 1104 | /// 1105 | /// Check the consistency of the tree. 1106 | /// 1107 | private void checkConsistency(int nodeId, int expectedLevel, Rectangle expectedMBR) 1108 | { 1109 | // go through the tree, and check that the internal data structures of 1110 | // the tree are not corrupted. 1111 | Node n = getNode(nodeId); 1112 | 1113 | if (n == null) 1114 | { 1115 | Debug.WriteLine($"Error: Could not read Node {nodeId}"); 1116 | } 1117 | 1118 | if (n.level != expectedLevel) 1119 | { 1120 | Debug.WriteLine($"Error: Node {nodeId}, expected level {expectedLevel}, actual level {n.level}"); 1121 | } 1122 | 1123 | Rectangle calculatedMBR = calculateMBR(n); 1124 | 1125 | if (!n.mbr.Equals(calculatedMBR)) 1126 | { 1127 | Debug.WriteLine($"Error: Node {nodeId}, calculated MBR does not equal stored MBR"); 1128 | } 1129 | 1130 | if (expectedMBR != null && !n.mbr.Equals(expectedMBR)) 1131 | { 1132 | Debug.WriteLine($"Error: Node {nodeId}, expected MBR (from parent) does not equal stored MBR"); 1133 | } 1134 | 1135 | // Check for corruption where a parent entry is the same object as the child MBR 1136 | if (expectedMBR != null && n.mbr.sameObject(expectedMBR)) 1137 | { 1138 | Debug.WriteLine($"Error: Node {nodeId} MBR using same rectangle object as parent's entry"); 1139 | } 1140 | 1141 | for (int i = 0; i < n.entryCount; i++) 1142 | { 1143 | if (n.entries[i] == null) 1144 | { 1145 | Debug.WriteLine($"Error: Node {nodeId}, Entry {i} is null"); 1146 | } 1147 | 1148 | if (n.level > 1) 1149 | { // if not a leaf 1150 | checkConsistency(n.ids[i], n.level - 1, n.entries[i]); 1151 | } 1152 | } 1153 | } 1154 | 1155 | /// 1156 | /// Given a Node object, calculate the Node MBR from it's entries. 1157 | /// Used in consistency checking 1158 | /// 1159 | private Rectangle calculateMBR(Node n) 1160 | { 1161 | Rectangle mbr = new Rectangle(n.entries[0].min, n.entries[0].max); 1162 | 1163 | for (int i = 1; i < n.entryCount; i++) 1164 | { 1165 | mbr.add(n.entries[i]); 1166 | } 1167 | return mbr; 1168 | } 1169 | 1170 | 1171 | public int Count 1172 | { 1173 | get 1174 | { 1175 | locker.AcquireReaderLock(locking_timeout); 1176 | 1177 | var size = this.msize; 1178 | 1179 | locker.ReleaseReaderLock(); 1180 | 1181 | return size; 1182 | } 1183 | } 1184 | 1185 | } 1186 | } -------------------------------------------------------------------------------- /RTree/RTree.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 1.1.0 6 | Infomatiq Limited, Aled Morris 7 | Dror Gluska 8 | 9 | RTree implementation 10 | https://github.com/drorgl/cspatialindexrt 11 | https://github.com/drorgl/cspatialindexrt 12 | true 13 | rtree, spatial index 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /RTree/Rectangle.cs: -------------------------------------------------------------------------------- 1 | // Rectangle.java version 1.0b2p1 2 | // Java Spatial Index Library 3 | // Copyright (C) 2002 Infomatiq Limited 4 | // Copyright (C) 2008 Aled Morris aled@sourceforge.net 5 | // 6 | // This library is free software; you can redistribute it and/or 7 | // modify it under the terms of the GNU Lesser General Public 8 | // License as published by the Free Software Foundation; either 9 | // version 2.1 of the License, or (at your option) any later version. 10 | // 11 | // This library is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | // Lesser General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU Lesser General Public 17 | // License along with this library; if not, write to the Free Software 18 | // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | 20 | // Ported to C# By Dror Gluska, April 9th, 2009 21 | 22 | using System; 23 | using System.Text; 24 | 25 | namespace RTree 26 | { 27 | public struct dimension 28 | { 29 | public float max; 30 | public float min; 31 | } 32 | 33 | 34 | /// 35 | /// Currently hardcoded to 3 dimensions, but could be extended. 36 | /// 37 | public class Rectangle 38 | { 39 | /// 40 | /// Number of dimensions in a rectangle. In theory this 41 | /// could be exended to three or more dimensions. 42 | /// 43 | internal const int DIMENSIONS = 3; 44 | 45 | /// 46 | /// array containing the minimum value for each dimension; ie { min(x), min(y) } 47 | /// 48 | internal float[] max; 49 | 50 | /// 51 | /// array containing the maximum value for each dimension; ie { max(x), max(y) } 52 | /// 53 | internal float[] min; 54 | 55 | /// 56 | /// ctor 57 | /// 58 | /// coordinate of any corner of the rectangle 59 | /// coordinate of any corner of the rectangle 60 | /// coordinate of the opposite corner 61 | /// coordinate of the opposite corner 62 | /// coordinate of any corner of the rectangle 63 | /// coordinate of the opposite corner 64 | public Rectangle(float x1, float y1, float x2, float y2, float z1, float z2) 65 | { 66 | min = new float[DIMENSIONS]; 67 | max = new float[DIMENSIONS]; 68 | set(x1, y1, x2, y2, z1, z2); 69 | } 70 | 71 | /// 72 | /// ctor 73 | /// 74 | /// min array containing the minimum value for each dimension; ie { min(x), min(y) } 75 | /// max array containing the maximum value for each dimension; ie { max(x), max(y) } 76 | public Rectangle(float[] min, float[] max) 77 | { 78 | if (min.Length != DIMENSIONS || max.Length != DIMENSIONS) 79 | { 80 | throw new Exception("Error in Rectangle constructor: " + 81 | "min and max arrays must be of length " + DIMENSIONS); 82 | } 83 | 84 | this.min = new float[DIMENSIONS]; 85 | this.max = new float[DIMENSIONS]; 86 | 87 | set(min, max); 88 | } 89 | 90 | /// 91 | /// Sets the size of the rectangle. 92 | /// 93 | /// coordinate of any corner of the rectangle 94 | /// coordinate of any corner of the rectangle 95 | /// coordinate of the opposite corner 96 | /// coordinate of the opposite corner 97 | /// coordinate of any corner of the rectangle 98 | /// coordinate of the opposite corner 99 | internal void set(float x1, float y1, float x2, float y2, float z1, float z2) 100 | { 101 | min[0] = Math.Min(x1, x2); 102 | min[1] = Math.Min(y1, y2); 103 | min[2] = Math.Min(z1, z2); 104 | max[0] = Math.Max(x1, x2); 105 | max[1] = Math.Max(y1, y2); 106 | max[2] = Math.Max(z1, z2); 107 | } 108 | 109 | /// 110 | /// Retrieves dimensions from rectangle 111 | /// probable dimensions: 112 | /// X = 0, Y = 1, Z = 2 113 | /// 114 | public dimension? get(int dimension) 115 | { 116 | if ((min.Length >= dimension) && (max.Length >= dimension)) 117 | { 118 | dimension retval = new dimension(); 119 | retval.min = min[dimension]; 120 | retval.max = max[dimension]; 121 | return retval; 122 | } 123 | return null; 124 | } 125 | 126 | /// 127 | /// Sets the size of the rectangle. 128 | /// 129 | /// min array containing the minimum value for each dimension; ie { min(x), min(y) } 130 | /// max array containing the maximum value for each dimension; ie { max(x), max(y) } 131 | internal void set(float[] min, float[] max) 132 | { 133 | System.Array.Copy(min, 0, this.min, 0, DIMENSIONS); 134 | System.Array.Copy(max, 0, this.max, 0, DIMENSIONS); 135 | } 136 | 137 | 138 | /// 139 | /// Make a copy of this rectangle 140 | /// 141 | /// copy of this rectangle 142 | internal Rectangle copy() 143 | { 144 | return new Rectangle(min, max); 145 | } 146 | 147 | /// 148 | /// Determine whether an edge of this rectangle overlies the equivalent 149 | /// edge of the passed rectangle 150 | /// 151 | internal bool edgeOverlaps(Rectangle r) 152 | { 153 | for (int i = 0; i < DIMENSIONS; i++) 154 | { 155 | if (min[i] == r.min[i] || max[i] == r.max[i]) 156 | { 157 | return true; 158 | } 159 | } 160 | return false; 161 | } 162 | 163 | /// 164 | /// Determine whether this rectangle intersects the passed rectangle 165 | /// 166 | /// The rectangle that might intersect this rectangle 167 | /// true if the rectangles intersect, false if they do not intersect 168 | internal bool intersects(Rectangle r) 169 | { 170 | // Every dimension must intersect. If any dimension 171 | // does not intersect, return false immediately. 172 | for (int i = 0; i < DIMENSIONS; i++) 173 | { 174 | if (max[i] < r.min[i] || min[i] > r.max[i]) 175 | { 176 | return false; 177 | } 178 | } 179 | return true; 180 | } 181 | 182 | /// 183 | /// Determine whether this rectangle contains the passed rectangle 184 | /// 185 | /// The rectangle that might be contained by this rectangle 186 | /// true if this rectangle contains the passed rectangle, false if it does not 187 | internal bool contains(Rectangle r) 188 | { 189 | for (int i = 0; i < DIMENSIONS; i++) 190 | { 191 | if (max[i] < r.max[i] || min[i] > r.min[i]) 192 | { 193 | return false; 194 | } 195 | } 196 | return true; 197 | } 198 | 199 | /// 200 | /// Determine whether this rectangle is contained by the passed rectangle 201 | /// 202 | /// The rectangle that might contain this rectangle 203 | /// true if the passed rectangle contains this rectangle, false if it does not 204 | internal bool containedBy(Rectangle r) 205 | { 206 | for (int i = 0; i < DIMENSIONS; i++) 207 | { 208 | if (max[i] > r.max[i] || min[i] < r.min[i]) 209 | { 210 | return false; 211 | } 212 | } 213 | return true; 214 | } 215 | 216 | 217 | /// 218 | /// Return the distance between this rectangle and the passed point. 219 | /// If the rectangle contains the point, the distance is zero. 220 | /// 221 | /// Point to find the distance to 222 | /// distance beween this rectangle and the passed point. 223 | internal float distance(Point p) 224 | { 225 | float distanceSquared = 0; 226 | for (int i = 0; i < DIMENSIONS; i++) 227 | { 228 | float greatestMin = Math.Max(min[i], p.coordinates[i]); 229 | float leastMax = Math.Min(max[i], p.coordinates[i]); 230 | if (greatestMin > leastMax) 231 | { 232 | distanceSquared += ((greatestMin - leastMax) * (greatestMin - leastMax)); 233 | } 234 | } 235 | return (float)Math.Sqrt(distanceSquared); 236 | } 237 | 238 | /// 239 | /// Return the distance between this rectangle and the passed rectangle. 240 | /// If the rectangles overlap, the distance is zero. 241 | /// 242 | /// Rectangle to find the distance to 243 | /// distance between this rectangle and the passed rectangle 244 | internal float distance(Rectangle r) 245 | { 246 | float distanceSquared = 0; 247 | for (int i = 0; i < DIMENSIONS; i++) 248 | { 249 | float greatestMin = Math.Max(min[i], r.min[i]); 250 | float leastMax = Math.Min(max[i], r.max[i]); 251 | if (greatestMin > leastMax) 252 | { 253 | distanceSquared += ((greatestMin - leastMax) * (greatestMin - leastMax)); 254 | } 255 | } 256 | return (float)Math.Sqrt(distanceSquared); 257 | } 258 | 259 | /// 260 | /// Return the squared distance from this rectangle to the passed point 261 | /// 262 | internal float distanceSquared(int dimension, float point) 263 | { 264 | float distanceSquared = 0; 265 | float tempDistance = point - max[dimension]; 266 | for (int i = 0; i < 2; i++) 267 | { 268 | if (tempDistance > 0) 269 | { 270 | distanceSquared = (tempDistance * tempDistance); 271 | break; 272 | } 273 | tempDistance = min[dimension] - point; 274 | } 275 | return distanceSquared; 276 | } 277 | 278 | /// 279 | /// Return the furthst possible distance between this rectangle and 280 | /// the passed rectangle. 281 | /// 282 | internal float furthestDistance(Rectangle r) 283 | { 284 | //Find the distance between this rectangle and each corner of the 285 | //passed rectangle, and use the maximum. 286 | 287 | float distanceSquared = 0; 288 | 289 | for (int i = 0; i < DIMENSIONS; i++) 290 | { 291 | distanceSquared += Math.Max(r.min[i], r.max[i]); 292 | #warning possible didn't convert properly 293 | //distanceSquared += Math.Max(distanceSquared(i, r.min[i]), distanceSquared(i, r.max[i])); 294 | } 295 | 296 | return (float)Math.Sqrt(distanceSquared); 297 | } 298 | 299 | /// 300 | /// Calculate the area by which this rectangle would be enlarged if 301 | /// added to the passed rectangle. Neither rectangle is altered. 302 | /// 303 | /// 304 | /// Rectangle to union with this rectangle, in order to 305 | /// compute the difference in area of the union and the 306 | /// original rectangle 307 | /// 308 | internal float enlargement(Rectangle r) 309 | { 310 | float enlargedArea = (Math.Max(max[0], r.max[0]) - Math.Min(min[0], r.min[0])) * 311 | (Math.Max(max[1], r.max[1]) - Math.Min(min[1], r.min[1])); 312 | 313 | return enlargedArea - area(); 314 | } 315 | 316 | /// 317 | /// Compute the area of this rectangle. 318 | /// 319 | /// The area of this rectangle 320 | internal float area() 321 | { 322 | return (max[0] - min[0]) * (max[1] - min[1]); 323 | } 324 | 325 | 326 | /// 327 | /// Computes the union of this rectangle and the passed rectangle, storing 328 | /// the result in this rectangle. 329 | /// 330 | /// Rectangle to add to this rectangle 331 | internal void add(Rectangle r) 332 | { 333 | for (int i = 0; i < DIMENSIONS; i++) 334 | { 335 | if (r.min[i] < min[i]) 336 | { 337 | min[i] = r.min[i]; 338 | } 339 | if (r.max[i] > max[i]) 340 | { 341 | max[i] = r.max[i]; 342 | } 343 | } 344 | } 345 | 346 | /// 347 | /// Find the the union of this rectangle and the passed rectangle. 348 | /// Neither rectangle is altered 349 | /// 350 | /// The rectangle to union with this rectangle 351 | internal Rectangle union(Rectangle r) 352 | { 353 | Rectangle union = this.copy(); 354 | union.add(r); 355 | return union; 356 | } 357 | 358 | internal bool CompareArrays(float[] a1, float[] a2) 359 | { 360 | if ((a1 == null) || (a2 == null)) 361 | return false; 362 | if (a1.Length != a2.Length) 363 | return false; 364 | 365 | for (int i = 0; i < a1.Length; i++) 366 | if (a1[i] != a2[i]) 367 | return false; 368 | return true; 369 | } 370 | 371 | /// 372 | /// Determine whether this rectangle is equal to a given object. 373 | /// Equality is determined by the bounds of the rectangle. 374 | /// 375 | /// The object to compare with this rectangle 376 | /// 377 | public override bool Equals(object obj) 378 | { 379 | bool equals = false; 380 | if (obj.GetType() == typeof(Rectangle)) 381 | { 382 | Rectangle r = (Rectangle)obj; 383 | #warning possible didn't convert properly 384 | if (CompareArrays(r.min, min) && CompareArrays(r.max, max)) 385 | { 386 | equals = true; 387 | } 388 | } 389 | return equals; 390 | } 391 | 392 | 393 | public override int GetHashCode() 394 | { 395 | return this.ToString().GetHashCode(); 396 | } 397 | 398 | 399 | /// 400 | /// Determine whether this rectangle is the same as another object 401 | /// 402 | /// Note that two rectangles can be equal but not the same object, 403 | /// if they both have the same bounds. 404 | /// 405 | /// 406 | /// 407 | /// Note that two rectangles can be equal but not the same object, 408 | /// if they both have the same bounds. 409 | /// 410 | /// 411 | internal bool sameObject(object o) 412 | { 413 | return base.Equals(o); 414 | } 415 | 416 | /// 417 | /// Return a string representation of this rectangle, in the form 418 | /// (1.2,3.4,5.6), (7.8, 9.10,11.12) 419 | /// 420 | public override string ToString() 421 | { 422 | StringBuilder sb = new StringBuilder(); 423 | 424 | // min coordinates 425 | sb.Append('('); 426 | for (int i = 0; i < DIMENSIONS; i++) 427 | { 428 | if (i > 0) 429 | { 430 | sb.Append(", "); 431 | } 432 | sb.Append(min[i]); 433 | } 434 | sb.Append("), ("); 435 | 436 | // max coordinates 437 | for (int i = 0; i < DIMENSIONS; i++) 438 | { 439 | if (i > 0) 440 | { 441 | sb.Append(", "); 442 | } 443 | sb.Append(max[i]); 444 | } 445 | sb.Append(')'); 446 | return sb.ToString(); 447 | } 448 | } 449 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | C# Porting from https://sourceforge.net/projects/jsi/ 2 | 3 | Basic usage 4 | 5 | 6 | Create a new instance: 7 | RTree.RTree tree = new RTree.RTree(); 8 | 9 | Create a rectangle: 10 | RTree.Rectangle rect = new RTree.Rectangle(1, 2, 3, 4, 5, 6); 11 | 12 | 13 | Add a new rectangle to the RTree: 14 | tree.Add(rect, object); 15 | 16 | 17 | Check which objects are inside the rectangle: 18 | var objects = tree.Contains(rect); 19 | 20 | 21 | Count how many items in the RTree: 22 | var i = tree.Count; 23 | 24 | 25 | Check which objects intersect with the rectangle: 26 | var objects = tree.Intersects(rect); 27 | 28 | 29 | Create a point: 30 | RTree.Point point = new RTree.Point(1, 2, 3); 31 | 32 | 33 | Get a list of rectangles close to the point with maximum distance: 34 | var objects = tree.Nearest(point, 10); 35 | --------------------------------------------------------------------------------