├── .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 |
--------------------------------------------------------------------------------