From a6f3def5f8c53fb42297987db677be27c9448558 Mon Sep 17 00:00:00 2001 From: Will Lehman Date: Wed, 2 Sep 2026 14:06:58 +0000 Subject: [PATCH 1/3] updating to boundingbox throughout --- Nsi.Geospatial/Geometry/BoundingBox.cs | 10 +-- Nsi.Geospatial/Geometry/Feature.cs | 12 ++-- Nsi.Geospatial/Geometry/Part.cs | 12 ++-- Nsi.Geospatial/Spatial/RTreeManager.cs | 88 +++++++++++++------------- Nsi.Geospatial/Spatial/RTreeNode.cs | 76 +++++++++++----------- Nsi.Geospatial/Spatial/SpatialJoins.cs | 6 +- 6 files changed, 100 insertions(+), 104 deletions(-) diff --git a/Nsi.Geospatial/Geometry/BoundingBox.cs b/Nsi.Geospatial/Geometry/BoundingBox.cs index 65e7d5c..f805759 100644 --- a/Nsi.Geospatial/Geometry/BoundingBox.cs +++ b/Nsi.Geospatial/Geometry/BoundingBox.cs @@ -1,12 +1,12 @@ namespace Nsi.Geospatial.Geometry; /// Axis-aligned 2D bounding box. Replaces the old double[4] MBR arrays. -public readonly struct BoundingBox +public struct BoundingBox { - public double MinX { get; } - public double MinY { get; } - public double MaxX { get; } - public double MaxY { get; } + public double MinX { get; set;} + public double MinY { get; set;} + public double MaxX { get; set;} + public double MaxY { get; set;} public BoundingBox(double minX, double minY, double maxX, double maxY) { diff --git a/Nsi.Geospatial/Geometry/Feature.cs b/Nsi.Geospatial/Geometry/Feature.cs index 3bafef2..a5c1696 100644 --- a/Nsi.Geospatial/Geometry/Feature.cs +++ b/Nsi.Geospatial/Geometry/Feature.cs @@ -16,7 +16,7 @@ public sealed class Feature public string? Name { get; set; } public List Parts { get; } = new(); - public BoundingBox Mbr { get; private set; } = BoundingBox.Empty; + public BoundingBox BoundingBox { get; private set; } = BoundingBox.Empty; /// This feature's attribute values, keyed by column name. Null-safe. public Dictionary Attributes { get; } = new(StringComparer.OrdinalIgnoreCase); @@ -26,15 +26,15 @@ public sealed class Feature public void AddPart(Part part) { Parts.Add(part); - Mbr = Mbr.Union(part.Mbr); + BoundingBox = BoundingBox.Union(part.BoundingBox); } - public BoundingBox ComputeMbr() + public BoundingBox ComputeBoundingBox() { - Mbr = BoundingBox.Empty; + BoundingBox = BoundingBox.Empty; foreach (var p in Parts) - Mbr = Mbr.Union(p.Mbr); - return Mbr; + BoundingBox = BoundingBox.Union(p.BoundingBox); + return BoundingBox; } public T? GetAttribute(string name) diff --git a/Nsi.Geospatial/Geometry/Part.cs b/Nsi.Geospatial/Geometry/Part.cs index 00fd17c..0414088 100644 --- a/Nsi.Geospatial/Geometry/Part.cs +++ b/Nsi.Geospatial/Geometry/Part.cs @@ -6,7 +6,7 @@ namespace Nsi.Geospatial.Geometry; public sealed class Part { public List Vertices { get; } = new(); - public BoundingBox Mbr { get; private set; } = BoundingBox.Empty; + public BoundingBox BoundingBox { get; private set; } = BoundingBox.Empty; public bool IsHole { get; set; } public bool Direction { get; set; } public int BeginIndex { get; set; } @@ -20,7 +20,7 @@ public sealed class Part public Part(string? wkt = null) => Wkt = wkt; - public void AddVertex(Vertex vertex, bool updateMbr = true) + public void AddVertex(Vertex vertex, bool updateBoundingBox = true) { if (Vertices.Count > 0) { @@ -38,11 +38,11 @@ public void AddVertex(Vertex vertex, bool updateMbr = true) Vertices.Add(vertex); - if (updateMbr) + if (updateBoundingBox) { - Mbr = Vertices.Count == 1 + BoundingBox = Vertices.Count == 1 ? BoundingBox.Point(vertex.X, vertex.Y) - : Mbr.Union(BoundingBox.Point(vertex.X, vertex.Y)); + : BoundingBox.Union(BoundingBox.Point(vertex.X, vertex.Y)); } } @@ -52,7 +52,7 @@ public void CloseRing() if (Vertices.Count > 0) { var first = Vertices[0]; - AddVertex(new Vertex(first.X, first.Y), updateMbr: false); + AddVertex(new Vertex(first.X, first.Y), updateBoundingBox: false); (CentroidX, CentroidY) = GeometryMath.Centroid(Vertices.Select(v => (v.X, v.Y))); Area = GeometryMath.Area(Vertices.Select(v => (v.X, v.Y))); } diff --git a/Nsi.Geospatial/Spatial/RTreeManager.cs b/Nsi.Geospatial/Spatial/RTreeManager.cs index e9a2e1b..09cbef1 100644 --- a/Nsi.Geospatial/Spatial/RTreeManager.cs +++ b/Nsi.Geospatial/Spatial/RTreeManager.cs @@ -3,50 +3,48 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using Nsi.Geospatial.Geometry; -namespace Nsi.Geospatial.Spatial -{ - public class RTreeManager - { - public RTreeNode _root; - private int _minChildren; - private int _maxChildren; - public RTreeManager(int minChilds = 4, int maxChilds = 10) - { - _minChildren = minChilds; - _maxChildren = maxChilds; - _root = new RTreeNode(this, _maxChildren, _minChildren); - } +namespace Nsi.Geospatial.Spatial; - public void addFeature(int[] featInd, double mbrXmax, double mbrXmin, double mbrYmax, double mbrYmin) - { - RTreeNode featNode = new RTreeNode(this, _maxChildren, _minChildren, featInd); - featNode.MBRXMin = mbrXmin; - featNode.MBRXMax = mbrXmax; - featNode.MBRYMin = mbrYmin; - featNode.MBRYMax = mbrYmax; - _root.addFeatureChildEnforceIntersect(featNode); - } - public List findByXY(double x, double y) - { - List nodePAth = new List(); - _root.getCandidateFeatNodesByMBR(x, x, y, y, nodePAth); - return nodePAth; - } - public List findByInd(int ind) - { - List nodePAth = new List(); - _root.getChildrenContainingInd(ind, nodePAth); - return nodePAth; - } - public List getEndNodes - { - get - { - List endNodes = new List(); - _root.getEndNodes(endNodes); - return endNodes; - } - } - } -} \ No newline at end of file + public class RTreeManager + { + public RTreeNode _root; + private int _minChildren; + private int _maxChildren; + public RTreeManager(int minChilds = 4, int maxChilds = 10) + { + _minChildren = minChilds; + _maxChildren = maxChilds; + _root = new RTreeNode(this, _maxChildren, _minChildren); + } + + public void addFeature(int[] featInd, BoundingBox bbox) + { + RTreeNode featNode = new RTreeNode(this, _maxChildren, _minChildren, featInd); + featNode.BoundingBox = bbox; + _root.addFeatureChildEnforceIntersect(featNode); + } + public List findByXY(double x, double y) + { + List nodePAth = new List(); + BoundingBox bbox = new BoundingBox(x,y,x,y); + _root.getCandidateFeatNodesByMBR(bbox, nodePAth); + return nodePAth; + } + public List findByInd(int ind) + { + List nodePAth = new List(); + _root.getChildrenContainingInd(ind, nodePAth); + return nodePAth; + } + public List getEndNodes + { + get + { + List endNodes = new List(); + _root.getEndNodes(endNodes); + return endNodes; + } + } + } \ No newline at end of file diff --git a/Nsi.Geospatial/Spatial/RTreeNode.cs b/Nsi.Geospatial/Spatial/RTreeNode.cs index 474634f..eb94554 100644 --- a/Nsi.Geospatial/Spatial/RTreeNode.cs +++ b/Nsi.Geospatial/Spatial/RTreeNode.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using System.Xml; using System.Xml.Linq; +using Nsi.Geospatial.Geometry; namespace Nsi.Geospatial.Spatial { @@ -17,10 +18,7 @@ public class RTreeNode public int maxChidrens = 0; public int minChidrens = 0; - public double MBRXMin { get; set; } = double.MaxValue; - public double MBRXMax { get; set; } = double.MinValue; - public double MBRYMin { get; set; } = double.MaxValue; - public double MBRYMax { get; set; } = double.MinValue; + public BoundingBox BoundingBox {get;set;} = BoundingBox.Empty; public double cumulativeOverlap { get; set; } public double siblingOverlap { get; set; } @@ -75,13 +73,13 @@ private void buildChildOptions(List<(RTreeNode[], double[])> options, bool xAxis List sortedChidrens = null; if(xAxis) { - if (min) { sortedChidrens = _children.OrderBy(c => c.MBRXMin).ToList(); } - else { sortedChidrens = _children.OrderBy(c => c.MBRXMax).ToList(); } + if (min) { sortedChidrens = _children.OrderBy(c => c.BoundingBox.MinX).ToList(); } + else { sortedChidrens = _children.OrderBy(c => c.BoundingBox.MaxX).ToList(); } } else { - if (min) { sortedChidrens = _children.OrderBy(c => c.MBRYMin).ToList(); } - else { sortedChidrens = _children.OrderBy(c => c.MBRYMax).ToList(); } + if (min) { sortedChidrens = _children.OrderBy(c => c.BoundingBox.MinY).ToList(); } + else { sortedChidrens = _children.OrderBy(c => c.BoundingBox.MaxY).ToList(); } } for(int split = minChidrens; split <= _children.Count() - minChidrens; split++) @@ -100,8 +98,8 @@ private void buildChildOptions(List<(RTreeNode[], double[])> options, bool xAxis node2.addChild(Child, false, false); } } - double overlapWidth = Math.Max(0, Math.Min(node1.MBRXMax, node2.MBRXMax) - Math.Max(node1.MBRXMin, node2.MBRXMin)); - double overlapHeight = Math.Max(0, Math.Min(node1.MBRYMax, node2.MBRYMax) - Math.Max(node1.MBRYMin, node2.MBRYMin)); + double overlapWidth = Math.Max(0, Math.Min(node1.BoundingBox.MaxX, node2.BoundingBox.MaxX) - Math.Max(node1.BoundingBox.MinX, node2.BoundingBox.MinX)); + double overlapHeight = Math.Max(0, Math.Min(node1.BoundingBox.MaxY, node2.BoundingBox.MaxY) - Math.Max(node1.BoundingBox.MinY, node2.BoundingBox.MinY)); double overlap = overlapWidth * overlapHeight; double totalArea = node1.getArea + node2.getArea; double perimeterTotal = node1.getPerimeter + node2.getPerimeter; @@ -126,7 +124,7 @@ public void addFeatureChild(RTreeNode feature) double minExtension = double.MaxValue; foreach (RTreeNode childnode in _children) { - double extensionReq = childnode.getAddedSizeToAccomodate(feature.MBRXMax, feature.MBRXMin, feature.MBRYMax, feature.MBRYMin); + double extensionReq = childnode.getAddedSizeToAccomodate(feature.BoundingBox); if (extensionReq < minExtension) { bestCandidate = childnode; @@ -144,13 +142,13 @@ public void addFeatureChildEnforceIntersect(RTreeNode feature) { //Find the lowest level child node that would least expand to accept the feature geometry List candidateKids = new List(); - getCandidateEndNodesByMBR(feature.MBRXMax, feature.MBRXMin, feature.MBRYMax, feature.MBRYMin, candidateKids); + getCandidateEndNodesByMBR(feature.BoundingBox, candidateKids); if (candidateKids.Count == 0) { candidateKids = _treeManager.getEndNodes; } RTreeNode bestCandidate = null; double minExtension = double.MaxValue; foreach (RTreeNode candidate in candidateKids) { - double extensionReq = candidate.getAddedSizeToAccomodate(feature.MBRXMax, feature.MBRXMin, feature.MBRYMax, feature.MBRYMin); + double extensionReq = candidate.getAddedSizeToAccomodate(feature.BoundingBox); if (extensionReq < minExtension) { bestCandidate = candidate; @@ -163,10 +161,10 @@ public void addChild(RTreeNode child, bool canSplit, bool canPropagateMBRup) { _children.Add(child); child._parent = this; - if(child.MBRXMin < MBRXMin) { MBRXMin = child.MBRXMin; } - if(child.MBRXMax > MBRXMax) { MBRXMax = child.MBRXMax; } - if(child.MBRYMin < MBRYMin) { MBRYMin = child.MBRYMin; } - if(child.MBRYMax > MBRYMax) { MBRYMax = child.MBRYMax; } + if(child.BoundingBox.MinX < this.BoundingBox.MinX) { this.BoundingBox.MinX = child.BoundingBox.MinX; } + if(child.BoundingBox.MaxX > this.BoundingBox.MaxX) { this.BoundingBox.MaxX = child.BoundingBox.MaxX; } + if(child.BoundingBox.MinY < this.BoundingBox.MinY) { this.BoundingBox.MinY = child.BoundingBox.MinY; } + if(child.BoundingBox.MaxY > this.BoundingBox.MaxY) { this.BoundingBox.MaxY = child.BoundingBox.MaxY; } if(_children.Count > maxChidrens && canSplit) { split(); @@ -178,10 +176,10 @@ public void addChild(RTreeNode child, bool canSplit, bool canPropagateMBRup) } public void RecomputeMBR() { - MBRXMin = _children.Min(c => c.MBRXMin); - MBRXMax = _children.Max(c => c.MBRXMax); - MBRYMin = _children.Min(c => c.MBRYMin); - MBRYMax = _children.Max(c => c.MBRYMax); + BoundingBox.MinX = _children.Min(c => c.BoundingBox.MinX); + BoundingBox.MaxX = _children.Max(c => c.BoundingBox.MaxX); + BoundingBox.MinY = _children.Min(c => c.BoundingBox.MinY); + BoundingBox.MaxY = _children.Max(c => c.BoundingBox.MaxY); if(_parent != null) { _parent.RecomputeMBR(); } } public void UpdateParents(RTreeNode newParent) @@ -206,7 +204,7 @@ public void getEndNodes(List nodeWalk) } } } - public void getCandidateEndNodesByMBR(double XMax, double XMin, double YMax, double YMin, List nodeWalk) + public void getCandidateEndNodesByMBR(BoundingBox bbox, List nodeWalk) { if (getIsEndNode) { @@ -216,20 +214,20 @@ public void getCandidateEndNodesByMBR(double XMax, double XMin, double YMax, dou { foreach (RTreeNode node in _children) { - if (node.getMBRoverlap(XMax, XMin, YMax, YMin) > 0) + if (node.getMBRoverlap(bbox) > 0) { - node.getCandidateEndNodesByMBR(XMax, XMin, YMax, YMin, nodeWalk); + node.getCandidateEndNodesByMBR(bbox, nodeWalk); } } } } - public void getCandidateFeatNodesByMBR(double XMax, double XMin, double YMax, double YMin, List nodeWalk) + public void getCandidateFeatNodesByMBR(BoundingBox bbox, List nodeWalk) { if (getIsEndNode) { foreach (RTreeNode node in _children) { - if (node.getMBRoverlap(XMax, XMin, YMax, YMin) > 0) + if (node.getMBRoverlap(bbox) > 0) { nodeWalk.Add(this); break; @@ -240,9 +238,9 @@ public void getCandidateFeatNodesByMBR(double XMax, double XMin, double YMax, do { foreach (RTreeNode node in _children) { - if (node.getMBRoverlap(XMax, XMin, YMax, YMin) > 0) + if (node.getMBRoverlap(bbox) > 0) { - node.getCandidateFeatNodesByMBR(XMax, XMin, YMax, YMin, nodeWalk); + node.getCandidateFeatNodesByMBR(bbox, nodeWalk); } } } @@ -276,24 +274,24 @@ public void getPathReverse(List nodeWalk) _parent.getPathReverse(nodeWalk); } } - public double getMBRoverlap(double XMax, double XMin, double YMax, double YMin) + public double getMBRoverlap(BoundingBox bbox) { double overlap = 0; - if ((XMax >= MBRXMin && XMax <= MBRXMax) || (XMin >= MBRXMin && XMin <= MBRXMax)) + if ((bbox.MaxX >= BoundingBox.MinX && bbox.MaxX <= BoundingBox.MaxX) || (bbox.MinX >= BoundingBox.MinX && bbox.MinX <= BoundingBox.MaxX)) { - if ((YMax >= MBRYMin && YMax <= MBRYMax) || (YMin >= MBRYMin && YMin <= MBRYMax)) + if ((bbox.MaxY >= BoundingBox.MinY && bbox.MaxY <= BoundingBox.MaxY) || (bbox.MinY >= BoundingBox.MinY && bbox.MinY <= BoundingBox.MaxY)) { - double xAxisOverlap = Math.Min(XMax, MBRXMax) - Math.Max(XMin, MBRXMin); - double YAxisOverlap = Math.Min(YMax, MBRYMax) - Math.Max(YMin, MBRYMin); + double xAxisOverlap = Math.Min(bbox.MaxX, BoundingBox.MaxX) - Math.Max(bbox.MinX, BoundingBox.MinX); + double YAxisOverlap = Math.Min(bbox.MaxY, BoundingBox.MaxY) - Math.Max(bbox.MinY, BoundingBox.MinY); overlap = Math.Max(xAxisOverlap * YAxisOverlap, 1); //Always return at least one, if top two conditions are met to avoid ignoring point shape overlap } } return overlap; } - public double getAddedSizeToAccomodate(double XMax, double XMin, double YMax, double YMin) + public double getAddedSizeToAccomodate(BoundingBox bbox) { - double featArea = (XMax - XMin) * (YMax - YMin); - return getArea + featArea - getMBRoverlap(XMax, XMin, YMax, YMin); + double featArea = (bbox.MaxX - bbox.MinX) * (bbox.MaxY - bbox.MinY); + return getArea + featArea - getMBRoverlap(bbox); } public bool getIsEndNode { @@ -313,13 +311,13 @@ public double getArea { get { - if (MBRXMax < MBRXMin || MBRYMax < MBRYMin) { return 0; } - return (MBRXMax - MBRXMin) * (MBRYMax - MBRYMin); + if (BoundingBox.MaxX < BoundingBox.MinX || BoundingBox.MaxY < BoundingBox.MinY) { return 0; } + return (BoundingBox.MaxX - BoundingBox.MinX) * (BoundingBox.MaxY - BoundingBox.MinY); } } public double getPerimeter { - get { return 2 * ((MBRXMax - MBRXMin) + (MBRYMax - MBRYMin)); } + get { return 2 * ((BoundingBox.MaxX - BoundingBox.MinX) + (BoundingBox.MaxY - BoundingBox.MinY)); } } } } diff --git a/Nsi.Geospatial/Spatial/SpatialJoins.cs b/Nsi.Geospatial/Spatial/SpatialJoins.cs index 04f38d2..f3163bc 100644 --- a/Nsi.Geospatial/Spatial/SpatialJoins.cs +++ b/Nsi.Geospatial/Spatial/SpatialJoins.cs @@ -128,8 +128,8 @@ public static RTreeManager BuildTree(FeatureCollection fc) for (int i = 0; i < fc.Count; i++) { var f = fc[i]; - if (f.Mbr != BoundingBox.Empty) - tree.addFeature(new[] { f.Id, 0 }, f.Mbr.MaxX, f.Mbr.MinX, f.Mbr.MaxY, f.Mbr.MinY); + if (f.BoundingBox != BoundingBox.Empty) + tree.addFeature(new[] { f.Id, 0 }, f.BoundingBox); } return tree; } @@ -138,7 +138,7 @@ private static double DistanceFeatureToFeature(Feature point, Feature polygon) { if (polygon.Parts.Count == 0) return double.MaxValue; double best = double.MaxValue; - double px = point.Mbr.MinX, py = point.Mbr.MinY; + double px = point.BoundingBox.MinX, py = point.BoundingBox.MinY; foreach (var part in polygon.Parts) { if (part.Vertices.Count < 2) From f465d26c918cf4abc37cb591bab1b517cb78a3e9 Mon Sep 17 00:00:00 2001 From: Will Lehman Date: Wed, 2 Sep 2026 14:27:35 +0000 Subject: [PATCH 2/3] fixing bounding box across tests and other features --- Nsi.Geospatial.Io/SpatialReader.cs | 2 +- Nsi.Geospatial/Spatial/RTreeNode.cs | 595 +++++++++--------- .../Nsi.Geospatial.Io.Tests/SpatialIoTests.cs | 12 +- tests/Nsi.Geospatial.Tests/RTreeTests.cs | 23 +- .../Nsi.Geospatial.Tests/SpatialJoinTests.cs | 4 +- 5 files changed, 315 insertions(+), 321 deletions(-) diff --git a/Nsi.Geospatial.Io/SpatialReader.cs b/Nsi.Geospatial.Io/SpatialReader.cs index 4b730df..09f8efb 100644 --- a/Nsi.Geospatial.Io/SpatialReader.cs +++ b/Nsi.Geospatial.Io/SpatialReader.cs @@ -47,7 +47,7 @@ public FeatureCollection Read(string path) { foreach (var part in ProcessGeometry(geom, wkt)) f.AddPart(part); - f.ComputeMbr(); + f.ComputeBoundingBox(); } fc.AddFeature(f); diff --git a/Nsi.Geospatial/Spatial/RTreeNode.cs b/Nsi.Geospatial/Spatial/RTreeNode.cs index eb94554..d76dd91 100644 --- a/Nsi.Geospatial/Spatial/RTreeNode.cs +++ b/Nsi.Geospatial/Spatial/RTreeNode.cs @@ -7,317 +7,310 @@ using System.Xml.Linq; using Nsi.Geospatial.Geometry; -namespace Nsi.Geospatial.Spatial -{ - public class RTreeNode - { - public RTreeNode _parent; - public RTreeManager _treeManager; - public List _children = new List(); - public int[] _featureIndex; // { Feature Index, Sub-Part Index (for holes, etc) } - public int maxChidrens = 0; - public int minChidrens = 0; +namespace Nsi.Geospatial.Spatial; - public BoundingBox BoundingBox {get;set;} = BoundingBox.Empty; + public class RTreeNode + { + public RTreeNode _parent; + public RTreeManager _treeManager; + public List _children = new List(); + public int[] _featureIndex; // { Feature Index, Sub-Part Index (for holes, etc) } + public int maxChidrens = 0; + public int minChidrens = 0; - public double cumulativeOverlap { get; set; } - public double siblingOverlap { get; set; } + public BoundingBox BoundingBox {get;set;} = BoundingBox.Empty; - public RTreeNode(RTreeManager treemanager, int maxChildren, int minChildren, int[] featInd = null) - { - _treeManager = treemanager; - maxChidrens = maxChildren; - minChidrens = minChildren; - _featureIndex = featInd; - } - public void split() - { - List<(RTreeNode[] nodes, double[] metrics)> Options = new List<(RTreeNode[], double[])>(); + public double cumulativeOverlap { get; set; } + public double siblingOverlap { get; set; } - //First X axis split by min - buildChildOptions(Options, true, true); + public RTreeNode(RTreeManager treemanager, int maxChildren, int minChildren, int[] featInd = null) + { + _treeManager = treemanager; + maxChidrens = maxChildren; + minChidrens = minChildren; + _featureIndex = featInd; + } + public void split() + { + List<(RTreeNode[] nodes, double[] metrics)> Options = new List<(RTreeNode[], double[])>(); - //Then X axis split by max - buildChildOptions(Options, true, false); + //First X axis split by min + buildChildOptions(Options, true, true); - //Then Y axis split by min - buildChildOptions(Options, false, true); + //Then X axis split by max + buildChildOptions(Options, true, false); - //Then Y axis split by max - buildChildOptions(Options, false, false); + //Then Y axis split by min + buildChildOptions(Options, false, true); - List newKidsOntheBlock; - - newKidsOntheBlock = Options.OrderBy(x => x.metrics[0]).ThenBy(x => x.metrics[1]).ThenBy(x => x.metrics[2]).First().nodes.ToList(); - - if(_parent != null) - { - _parent._children.Remove(this); - newKidsOntheBlock[0].UpdateParents(_parent); - newKidsOntheBlock[1].UpdateParents(_parent); - _parent.addChild(newKidsOntheBlock[0], false, true); - _parent.addChild(newKidsOntheBlock[1], true, true); - } - else - { - RTreeNode newRoot = new RTreeNode(_treeManager, maxChidrens, minChidrens); - newKidsOntheBlock[0].UpdateParents(newRoot); - newKidsOntheBlock[1].UpdateParents(newRoot); - newRoot.addChild(newKidsOntheBlock[0], false, true); - newRoot.addChild(newKidsOntheBlock[1], true, true); - _treeManager._root = newRoot; - } - } - private void buildChildOptions(List<(RTreeNode[], double[])> options, bool xAxis, bool min) - { - List sortedChidrens = null; - if(xAxis) - { - if (min) { sortedChidrens = _children.OrderBy(c => c.BoundingBox.MinX).ToList(); } - else { sortedChidrens = _children.OrderBy(c => c.BoundingBox.MaxX).ToList(); } - } - else - { - if (min) { sortedChidrens = _children.OrderBy(c => c.BoundingBox.MinY).ToList(); } - else { sortedChidrens = _children.OrderBy(c => c.BoundingBox.MaxY).ToList(); } - } - - for(int split = minChidrens; split <= _children.Count() - minChidrens; split++) - { - RTreeNode node1 = new RTreeNode(_treeManager, maxChidrens, minChidrens); - RTreeNode node2 = new RTreeNode(_treeManager, maxChidrens, minChidrens); - for (int i = 0; i < sortedChidrens.Count; i++) - { - var Child = sortedChidrens[i]; - if (i < split) - { - node1.addChild(Child, false, false); - } - else - { - node2.addChild(Child, false, false); - } - } - double overlapWidth = Math.Max(0, Math.Min(node1.BoundingBox.MaxX, node2.BoundingBox.MaxX) - Math.Max(node1.BoundingBox.MinX, node2.BoundingBox.MinX)); - double overlapHeight = Math.Max(0, Math.Min(node1.BoundingBox.MaxY, node2.BoundingBox.MaxY) - Math.Max(node1.BoundingBox.MinY, node2.BoundingBox.MinY)); - double overlap = overlapWidth * overlapHeight; - double totalArea = node1.getArea + node2.getArea; - double perimeterTotal = node1.getPerimeter + node2.getPerimeter; + //Then Y axis split by max + buildChildOptions(Options, false, false); - node1.siblingOverlap = overlap; - node2.siblingOverlap = overlap; - node1.cumulativeOverlap = overlap + siblingOverlap; - node2.cumulativeOverlap = overlap + siblingOverlap; + List newKidsOntheBlock; + + newKidsOntheBlock = Options.OrderBy(x => x.metrics[0]).ThenBy(x => x.metrics[1]).ThenBy(x => x.metrics[2]).First().nodes.ToList(); + + if(_parent != null) + { + _parent._children.Remove(this); + newKidsOntheBlock[0].UpdateParents(_parent); + newKidsOntheBlock[1].UpdateParents(_parent); + _parent.addChild(newKidsOntheBlock[0], false, true); + _parent.addChild(newKidsOntheBlock[1], true, true); + } + else + { + RTreeNode newRoot = new RTreeNode(_treeManager, maxChidrens, minChidrens); + newKidsOntheBlock[0].UpdateParents(newRoot); + newKidsOntheBlock[1].UpdateParents(newRoot); + newRoot.addChild(newKidsOntheBlock[0], false, true); + newRoot.addChild(newKidsOntheBlock[1], true, true); + _treeManager._root = newRoot; + } + } + private void buildChildOptions(List<(RTreeNode[], double[])> options, bool xAxis, bool min) + { + List sortedChidrens = null; + if(xAxis) + { + if (min) { sortedChidrens = _children.OrderBy(c => c.BoundingBox.MinX).ToList(); } + else { sortedChidrens = _children.OrderBy(c => c.BoundingBox.MaxX).ToList(); } + } + else + { + if (min) { sortedChidrens = _children.OrderBy(c => c.BoundingBox.MinY).ToList(); } + else { sortedChidrens = _children.OrderBy(c => c.BoundingBox.MaxY).ToList(); } + } + + for(int split = minChidrens; split <= _children.Count() - minChidrens; split++) + { + RTreeNode node1 = new RTreeNode(_treeManager, maxChidrens, minChidrens); + RTreeNode node2 = new RTreeNode(_treeManager, maxChidrens, minChidrens); + for (int i = 0; i < sortedChidrens.Count; i++) + { + var Child = sortedChidrens[i]; + if (i < split) + { + node1.addChild(Child, false, false); + } + else + { + node2.addChild(Child, false, false); + } + } + double overlapWidth = Math.Max(0, Math.Min(node1.BoundingBox.MaxX, node2.BoundingBox.MaxX) - Math.Max(node1.BoundingBox.MinX, node2.BoundingBox.MinX)); + double overlapHeight = Math.Max(0, Math.Min(node1.BoundingBox.MaxY, node2.BoundingBox.MaxY) - Math.Max(node1.BoundingBox.MinY, node2.BoundingBox.MinY)); + double overlap = overlapWidth * overlapHeight; + double totalArea = node1.getArea + node2.getArea; + double perimeterTotal = node1.getPerimeter + node2.getPerimeter; - options.Add((new RTreeNode[] { node1, node2 }, new double[] { overlap, totalArea, perimeterTotal })); - } - } - public void addFeatureChild(RTreeNode feature) - { - if(getIsEndNode) - { - addChild(feature, true, true); - } - else - { - RTreeNode bestCandidate = null; - double minExtension = double.MaxValue; - foreach (RTreeNode childnode in _children) - { - double extensionReq = childnode.getAddedSizeToAccomodate(feature.BoundingBox); - if (extensionReq < minExtension) - { - bestCandidate = childnode; - minExtension = extensionReq; - } - else if (extensionReq == minExtension && childnode.getArea < bestCandidate.getArea) - { - bestCandidate = childnode; - } - } - bestCandidate.addFeatureChild(feature); - } - } - public void addFeatureChildEnforceIntersect(RTreeNode feature) - { - //Find the lowest level child node that would least expand to accept the feature geometry - List candidateKids = new List(); - getCandidateEndNodesByMBR(feature.BoundingBox, candidateKids); - if (candidateKids.Count == 0) { candidateKids = _treeManager.getEndNodes; } - RTreeNode bestCandidate = null; - double minExtension = double.MaxValue; - foreach (RTreeNode candidate in candidateKids) - { - double extensionReq = candidate.getAddedSizeToAccomodate(feature.BoundingBox); - if (extensionReq < minExtension) - { - bestCandidate = candidate; - minExtension = extensionReq; - } - } - bestCandidate.addChild(feature, true, true); - } - public void addChild(RTreeNode child, bool canSplit, bool canPropagateMBRup) - { - _children.Add(child); - child._parent = this; - if(child.BoundingBox.MinX < this.BoundingBox.MinX) { this.BoundingBox.MinX = child.BoundingBox.MinX; } - if(child.BoundingBox.MaxX > this.BoundingBox.MaxX) { this.BoundingBox.MaxX = child.BoundingBox.MaxX; } - if(child.BoundingBox.MinY < this.BoundingBox.MinY) { this.BoundingBox.MinY = child.BoundingBox.MinY; } - if(child.BoundingBox.MaxY > this.BoundingBox.MaxY) { this.BoundingBox.MaxY = child.BoundingBox.MaxY; } - if(_children.Count > maxChidrens && canSplit) - { - split(); - } - else if(canPropagateMBRup) - { - RecomputeMBR(); - } - } + node1.siblingOverlap = overlap; + node2.siblingOverlap = overlap; + node1.cumulativeOverlap = overlap + siblingOverlap; + node2.cumulativeOverlap = overlap + siblingOverlap; + + options.Add((new RTreeNode[] { node1, node2 }, new double[] { overlap, totalArea, perimeterTotal })); + } + } + public void addFeatureChild(RTreeNode feature) + { + if(getIsEndNode) + { + addChild(feature, true, true); + } + else + { + RTreeNode bestCandidate = null; + double minExtension = double.MaxValue; + foreach (RTreeNode childnode in _children) + { + double extensionReq = childnode.getAddedSizeToAccomodate(feature.BoundingBox); + if (extensionReq < minExtension) + { + bestCandidate = childnode; + minExtension = extensionReq; + } + else if (extensionReq == minExtension && childnode.getArea < bestCandidate.getArea) + { + bestCandidate = childnode; + } + } + bestCandidate.addFeatureChild(feature); + } + } + public void addFeatureChildEnforceIntersect(RTreeNode feature) + { + //Find the lowest level child node that would least expand to accept the feature geometry + List candidateKids = new List(); + getCandidateEndNodesByMBR(feature.BoundingBox, candidateKids); + if (candidateKids.Count == 0) { candidateKids = _treeManager.getEndNodes; } + RTreeNode bestCandidate = null; + double minExtension = double.MaxValue; + foreach (RTreeNode candidate in candidateKids) + { + double extensionReq = candidate.getAddedSizeToAccomodate(feature.BoundingBox); + if (extensionReq < minExtension) + { + bestCandidate = candidate; + minExtension = extensionReq; + } + } + bestCandidate.addChild(feature, true, true); + } + public void addChild(RTreeNode child, bool canSplit, bool canPropagateMBRup) + { + _children.Add(child); + child._parent = this; + BoundingBox = BoundingBox.Union(child.BoundingBox); + if (_children.Count > maxChidrens && canSplit) + split(); + else if (canPropagateMBRup) + RecomputeMBR(); + } public void RecomputeMBR() { - BoundingBox.MinX = _children.Min(c => c.BoundingBox.MinX); - BoundingBox.MaxX = _children.Max(c => c.BoundingBox.MaxX); - BoundingBox.MinY = _children.Min(c => c.BoundingBox.MinY); - BoundingBox.MaxY = _children.Max(c => c.BoundingBox.MaxY); - if(_parent != null) { _parent.RecomputeMBR(); } - } - public void UpdateParents(RTreeNode newParent) - { - _parent = newParent; - foreach (var child in _children) - { - child.UpdateParents(this); - } - } - public void getEndNodes(List nodeWalk) - { - if(getIsEndNode) - { - nodeWalk.Add(this); - } - else - { - foreach (RTreeNode node in _children) - { - node.getEndNodes(nodeWalk); - } - } + BoundingBox = new BoundingBox( + _children.Min(c => c.BoundingBox.MinX), + _children.Min(c => c.BoundingBox.MinY), + _children.Max(c => c.BoundingBox.MaxX), + _children.Max(c => c.BoundingBox.MaxY)); + if (_parent != null) _parent.RecomputeMBR(); } - public void getCandidateEndNodesByMBR(BoundingBox bbox, List nodeWalk) - { - if (getIsEndNode) - { - nodeWalk.Add(this); - } - else - { - foreach (RTreeNode node in _children) - { - if (node.getMBRoverlap(bbox) > 0) - { - node.getCandidateEndNodesByMBR(bbox, nodeWalk); - } - } - } - } - public void getCandidateFeatNodesByMBR(BoundingBox bbox, List nodeWalk) - { - if (getIsEndNode) - { - foreach (RTreeNode node in _children) - { - if (node.getMBRoverlap(bbox) > 0) - { - nodeWalk.Add(this); - break; - } - } - } - else - { - foreach (RTreeNode node in _children) - { - if (node.getMBRoverlap(bbox) > 0) - { - node.getCandidateFeatNodesByMBR(bbox, nodeWalk); - } - } - } - } - public void getChildrenContainingInd(int ind, List nodeWalk) - { - if (getIsEndNode) - { - foreach (RTreeNode node in _children) - { - if (node._featureIndex[0] == ind) - { - getPathReverse(nodeWalk); - break; - } - } - } - else - { - foreach (RTreeNode node in _children) - { - node.getChildrenContainingInd(ind, nodeWalk); - } - } - } - public void getPathReverse(List nodeWalk) - { - nodeWalk.Add(this); - if (_parent != null) - { - _parent.getPathReverse(nodeWalk); - } - } - public double getMBRoverlap(BoundingBox bbox) - { - double overlap = 0; - if ((bbox.MaxX >= BoundingBox.MinX && bbox.MaxX <= BoundingBox.MaxX) || (bbox.MinX >= BoundingBox.MinX && bbox.MinX <= BoundingBox.MaxX)) - { - if ((bbox.MaxY >= BoundingBox.MinY && bbox.MaxY <= BoundingBox.MaxY) || (bbox.MinY >= BoundingBox.MinY && bbox.MinY <= BoundingBox.MaxY)) - { - double xAxisOverlap = Math.Min(bbox.MaxX, BoundingBox.MaxX) - Math.Max(bbox.MinX, BoundingBox.MinX); - double YAxisOverlap = Math.Min(bbox.MaxY, BoundingBox.MaxY) - Math.Max(bbox.MinY, BoundingBox.MinY); - overlap = Math.Max(xAxisOverlap * YAxisOverlap, 1); //Always return at least one, if top two conditions are met to avoid ignoring point shape overlap - } - } - return overlap; - } - public double getAddedSizeToAccomodate(BoundingBox bbox) - { - double featArea = (bbox.MaxX - bbox.MinX) * (bbox.MaxY - bbox.MinY); - return getArea + featArea - getMBRoverlap(bbox); - } - public bool getIsEndNode - { - get - { - if ((_children.Count == 0 && _featureIndex == null) || (_children.Count > 0 && _children[0]._featureIndex != null)) - { - return true; - } - else - { - return false; - } - } - } - public double getArea - { - get - { - if (BoundingBox.MaxX < BoundingBox.MinX || BoundingBox.MaxY < BoundingBox.MinY) { return 0; } - return (BoundingBox.MaxX - BoundingBox.MinX) * (BoundingBox.MaxY - BoundingBox.MinY); - } - } - public double getPerimeter - { - get { return 2 * ((BoundingBox.MaxX - BoundingBox.MinX) + (BoundingBox.MaxY - BoundingBox.MinY)); } - } - } -} + public void UpdateParents(RTreeNode newParent) + { + _parent = newParent; + foreach (var child in _children) + { + child.UpdateParents(this); + } + } + public void getEndNodes(List nodeWalk) + { + if(getIsEndNode) + { + nodeWalk.Add(this); + } + else + { + foreach (RTreeNode node in _children) + { + node.getEndNodes(nodeWalk); + } + } + } + public void getCandidateEndNodesByMBR(BoundingBox bbox, List nodeWalk) + { + if (getIsEndNode) + { + nodeWalk.Add(this); + } + else + { + foreach (RTreeNode node in _children) + { + if (node.getMBRoverlap(bbox) > 0) + { + node.getCandidateEndNodesByMBR(bbox, nodeWalk); + } + } + } + } + public void getCandidateFeatNodesByMBR(BoundingBox bbox, List nodeWalk) + { + if (getIsEndNode) + { + foreach (RTreeNode node in _children) + { + if (node.getMBRoverlap(bbox) > 0) + { + nodeWalk.Add(this); + break; + } + } + } + else + { + foreach (RTreeNode node in _children) + { + if (node.getMBRoverlap(bbox) > 0) + { + node.getCandidateFeatNodesByMBR(bbox, nodeWalk); + } + } + } + } + public void getChildrenContainingInd(int ind, List nodeWalk) + { + if (getIsEndNode) + { + foreach (RTreeNode node in _children) + { + if (node._featureIndex[0] == ind) + { + getPathReverse(nodeWalk); + break; + } + } + } + else + { + foreach (RTreeNode node in _children) + { + node.getChildrenContainingInd(ind, nodeWalk); + } + } + } + public void getPathReverse(List nodeWalk) + { + nodeWalk.Add(this); + if (_parent != null) + { + _parent.getPathReverse(nodeWalk); + } + } + public double getMBRoverlap(BoundingBox bbox) + { + double overlap = 0; + if ((bbox.MaxX >= BoundingBox.MinX && bbox.MaxX <= BoundingBox.MaxX) || (bbox.MinX >= BoundingBox.MinX && bbox.MinX <= BoundingBox.MaxX)) + { + if ((bbox.MaxY >= BoundingBox.MinY && bbox.MaxY <= BoundingBox.MaxY) || (bbox.MinY >= BoundingBox.MinY && bbox.MinY <= BoundingBox.MaxY)) + { + double xAxisOverlap = Math.Min(bbox.MaxX, BoundingBox.MaxX) - Math.Max(bbox.MinX, BoundingBox.MinX); + double YAxisOverlap = Math.Min(bbox.MaxY, BoundingBox.MaxY) - Math.Max(bbox.MinY, BoundingBox.MinY); + overlap = Math.Max(xAxisOverlap * YAxisOverlap, 1); //Always return at least one, if top two conditions are met to avoid ignoring point shape overlap + } + } + return overlap; + } + public double getAddedSizeToAccomodate(BoundingBox bbox) + { + double featArea = (bbox.MaxX - bbox.MinX) * (bbox.MaxY - bbox.MinY); + return getArea + featArea - getMBRoverlap(bbox); + } + public bool getIsEndNode + { + get + { + if ((_children.Count == 0 && _featureIndex == null) || (_children.Count > 0 && _children[0]._featureIndex != null)) + { + return true; + } + else + { + return false; + } + } + } + public double getArea + { + get + { + if (BoundingBox.MaxX < BoundingBox.MinX || BoundingBox.MaxY < BoundingBox.MinY) { return 0; } + return (BoundingBox.MaxX - BoundingBox.MinX) * (BoundingBox.MaxY - BoundingBox.MinY); + } + } + public double getPerimeter + { + get { return 2 * ((BoundingBox.MaxX - BoundingBox.MinX) + (BoundingBox.MaxY - BoundingBox.MinY)); } + } + } diff --git a/tests/Nsi.Geospatial.Io.Tests/SpatialIoTests.cs b/tests/Nsi.Geospatial.Io.Tests/SpatialIoTests.cs index c4b7dd5..3cc8306 100644 --- a/tests/Nsi.Geospatial.Io.Tests/SpatialIoTests.cs +++ b/tests/Nsi.Geospatial.Io.Tests/SpatialIoTests.cs @@ -115,10 +115,10 @@ public void PolygonsShapefileRoundTrip() Assert.Equal(orig.GetAttribute("id"), back.GetAttribute("id")); // MBR must survive the round-trip. - Assert.Equal(orig.Mbr.MinX, back.Mbr.MinX, Tol); - Assert.Equal(orig.Mbr.MinY, back.Mbr.MinY, Tol); - Assert.Equal(orig.Mbr.MaxX, back.Mbr.MaxX, Tol); - Assert.Equal(orig.Mbr.MaxY, back.Mbr.MaxY, Tol); + Assert.Equal(orig.BoundingBox.MinX, back.BoundingBox.MinX, Tol); + Assert.Equal(orig.BoundingBox.MinY, back.BoundingBox.MinY, Tol); + Assert.Equal(orig.BoundingBox.MaxX, back.BoundingBox.MaxX, Tol); + Assert.Equal(orig.BoundingBox.MaxY, back.BoundingBox.MaxY, Tol); // And the geometry must still contain the same points. var ring = back.Parts[0].Vertices; @@ -182,8 +182,8 @@ public void PolygonsGeoJsonRoundTrip() Assert.Equal(fc.Count, read.Count); var back = read[0]; - Assert.Equal(fc[0].Mbr.MinX, back.Mbr.MinX, Tol); - Assert.Equal(fc[0].Mbr.MaxX, back.Mbr.MaxX, Tol); + Assert.Equal(fc[0].BoundingBox.MinX, back.BoundingBox.MinX, Tol); + Assert.Equal(fc[0].BoundingBox.MaxX, back.BoundingBox.MaxX, Tol); Assert.True(PointInPolygon((5, 5), back.Parts[0].Vertices)); } finally diff --git a/tests/Nsi.Geospatial.Tests/RTreeTests.cs b/tests/Nsi.Geospatial.Tests/RTreeTests.cs index 755e0be..74cf0bf 100644 --- a/tests/Nsi.Geospatial.Tests/RTreeTests.cs +++ b/tests/Nsi.Geospatial.Tests/RTreeTests.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Nsi.Geospatial.Geometry; using Nsi.Geospatial.Spatial; using Xunit; @@ -16,8 +17,8 @@ public class RTreeTests public void FindByXY_FindsContainingFeature() { var tree = new RTreeManager(); - tree.addFeature(new[] { 0, 0 }, 10, 0, 10, 0); // Xmax, Xmin, Ymax, Ymin - tree.addFeature(new[] { 1, 0 }, 60, 50, 60, 50); + tree.addFeature(new[] { 0, 0 }, new BoundingBox(0, 0, 10, 10)); // Xmax=10, Xmin=0, Ymax=10, Ymin=0 + tree.addFeature(new[] { 1, 0 }, new BoundingBox(50, 50, 60, 60)); // Xmax=60, Xmin=50, Ymax=60, Ymin=50 var hits = FeatureIndicesAt(tree, 5, 5); Assert.Contains(0, hits); @@ -28,8 +29,8 @@ public void FindByXY_FindsContainingFeature() public void FindByXY_PointOutsideFeatureMBR_NotReturned() { var tree = new RTreeManager(); - tree.addFeature(new[] { 0, 0 }, 10, 0, 10, 0); - tree.addFeature(new[] { 1, 0 }, 60, 50, 60, 50); + tree.addFeature(new[] { 0, 0 }, new BoundingBox(0, 0, 10, 10)); + tree.addFeature(new[] { 1, 0 }, new BoundingBox(50, 50, 60, 60)); var hits = FeatureIndicesAt(tree, 55, 55); Assert.Contains(1, hits); @@ -41,7 +42,7 @@ public void FindByInd_ReturnsLeafToRootPath() { var tree = new RTreeManager(); for (int i = 0; i < 100; i++) - tree.addFeature(new[] { i, 0 }, i * 10 + 5, i * 10, i * 10 + 5, i * 10); + tree.addFeature(new[] { i, 0 }, new BoundingBox(i * 10, i * 10, i * 10 + 5, i * 10 + 5)); var path = tree.findByInd(42); Assert.NotEmpty(path); @@ -60,14 +61,14 @@ public void BulkInsert_AllFeaturesFindableByPoint() { var tree = new RTreeManager(minChilds: 3, maxChilds: 6); for (int i = 0; i < 500; i++) - tree.addFeature(new[] { i, 0 }, i * 10 + 5, i * 10, i * 10 + 5, i * 10); + tree.addFeature(new[] { i, 0 }, new BoundingBox(i * 10, i * 10, i * 10 + 5, i * 10 + 5)); for (int i = 0; i < 500; i++) { var hits = FeatureIndicesAt(tree, i * 10 + 2.5, i * 10 + 2.5); if (!hits.Contains(i)) { - string test = "WTF"; + string test = "WTF"; } Assert.Contains(i, hits); } @@ -78,7 +79,7 @@ public void GetEndNodes_LeavesHoldAllFeatureIndices() { var tree = new RTreeManager(minChilds: 3, maxChilds: 6); for (int i = 0; i < 50; i++) - tree.addFeature(new[] { i, 0 }, i * 10 + 5, i * 10, i * 10 + 5, i * 10); + tree.addFeature(new[] { i, 0 }, new BoundingBox(i * 10, i * 10, i * 10 + 5, i * 10 + 5)); var leaves = tree.getEndNodes; Assert.NotEmpty(leaves); @@ -96,7 +97,7 @@ public void GetEndNodes_LeavesHoldAllFeatureIndices() Assert.Equal(50, allIndices.Distinct().Count()); } - /// Collect the feature indices of end nodes returned by findByXY that actually contain the point. + /// Collect the feature indices of end nodes returned by findByXY that actually contain the point. private static List FeatureIndicesAt(RTreeManager tree, double x, double y) { var indices = new List(); @@ -105,10 +106,10 @@ private static List FeatureIndicesAt(RTreeManager tree, double x, double y) foreach (var child in leaf._children) { var ind = child._featureIndex; - if (ind is not null && child.getMBRoverlap(x, x, y, y) > 0) + if (ind is not null && child.getMBRoverlap(new BoundingBox(x, y, x, y)) > 0) indices.Add(ind[0]); } } return indices; } -} +} \ No newline at end of file diff --git a/tests/Nsi.Geospatial.Tests/SpatialJoinTests.cs b/tests/Nsi.Geospatial.Tests/SpatialJoinTests.cs index ae17f43..db9cdf2 100644 --- a/tests/Nsi.Geospatial.Tests/SpatialJoinTests.cs +++ b/tests/Nsi.Geospatial.Tests/SpatialJoinTests.cs @@ -19,7 +19,7 @@ public void NearestPointsToPolygons_FirstJoin_CopiesValue() p1.Parts[0].AddVertex(new Vertex(0, 10)); p1.Parts[0].AddVertex(new Vertex(0, 0)); p1.Parts[0].CloseRing(); - p1.ComputeMbr(); + p1.ComputeBoundingBox(); polys.AddFeature(p1); polys.Schema.AddField("VALUE", FieldType.Double, 12, 2); @@ -27,7 +27,7 @@ public void NearestPointsToPolygons_FirstJoin_CopiesValue() var pp = new Feature(); pp.Parts.Add(new Part()); pp.Parts[0].AddVertex(new Vertex(1, 1)); - pp.ComputeMbr(); + pp.ComputeBoundingBox(); pp.Attributes["VALUE"] = 42.0; pnts.AddFeature(pp); pnts.Schema.AddField("VALUE", FieldType.Double, 12, 2); From 328b3f855023c31d9a0c169237f991dc81727703 Mon Sep 17 00:00:00 2001 From: Will Lehman Date: Wed, 2 Sep 2026 14:34:37 +0000 Subject: [PATCH 3/3] fixing bug found by tests --- Nsi.Geospatial/Spatial/RTreeNode.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Nsi.Geospatial/Spatial/RTreeNode.cs b/Nsi.Geospatial/Spatial/RTreeNode.cs index d76dd91..4bd04a9 100644 --- a/Nsi.Geospatial/Spatial/RTreeNode.cs +++ b/Nsi.Geospatial/Spatial/RTreeNode.cs @@ -155,6 +155,7 @@ public void addFeatureChildEnforceIntersect(RTreeNode feature) minExtension = extensionReq; } } + bestCandidate??= _treeManager._root; bestCandidate.addChild(feature, true, true); } public void addChild(RTreeNode child, bool canSplit, bool canPropagateMBRup)