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/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..4bd04a9 100644
--- a/Nsi.Geospatial/Spatial/RTreeNode.cs
+++ b/Nsi.Geospatial/Spatial/RTreeNode.cs
@@ -5,321 +5,313 @@
using System.Threading.Tasks;
using System.Xml;
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 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 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.MBRXMin).ToList(); }
- else { sortedChidrens = _children.OrderBy(c => c.MBRXMax).ToList(); }
- }
- else
- {
- if (min) { sortedChidrens = _children.OrderBy(c => c.MBRYMin).ToList(); }
- else { sortedChidrens = _children.OrderBy(c => c.MBRYMax).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.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 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.MBRXMax, feature.MBRXMin, feature.MBRYMax, feature.MBRYMin);
- 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.MBRXMax, feature.MBRXMin, feature.MBRYMax, feature.MBRYMin, 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);
- 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.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(_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??= _treeManager._root;
+ 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()
{
- MBRXMin = _children.Min(c => c.MBRXMin);
- MBRXMax = _children.Max(c => c.MBRXMax);
- MBRYMin = _children.Min(c => c.MBRYMin);
- MBRYMax = _children.Max(c => c.MBRYMax);
- 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(double XMax, double XMin, double YMax, double YMin, List nodeWalk)
- {
- if (getIsEndNode)
- {
- nodeWalk.Add(this);
- }
- else
- {
- foreach (RTreeNode node in _children)
- {
- if (node.getMBRoverlap(XMax, XMin, YMax, YMin) > 0)
- {
- node.getCandidateEndNodesByMBR(XMax, XMin, YMax, YMin, nodeWalk);
- }
- }
- }
- }
- public void getCandidateFeatNodesByMBR(double XMax, double XMin, double YMax, double YMin, List nodeWalk)
- {
- if (getIsEndNode)
- {
- foreach (RTreeNode node in _children)
- {
- if (node.getMBRoverlap(XMax, XMin, YMax, YMin) > 0)
- {
- nodeWalk.Add(this);
- break;
- }
- }
- }
- else
- {
- foreach (RTreeNode node in _children)
- {
- if (node.getMBRoverlap(XMax, XMin, YMax, YMin) > 0)
- {
- node.getCandidateFeatNodesByMBR(XMax, XMin, YMax, YMin, 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(double XMax, double XMin, double YMax, double YMin)
- {
- double overlap = 0;
- if ((XMax >= MBRXMin && XMax <= MBRXMax) || (XMin >= MBRXMin && XMin <= MBRXMax))
- {
- if ((YMax >= MBRYMin && YMax <= MBRYMax) || (YMin >= MBRYMin && YMin <= MBRYMax))
- {
- double xAxisOverlap = Math.Min(XMax, MBRXMax) - Math.Max(XMin, MBRXMin);
- double YAxisOverlap = Math.Min(YMax, MBRYMax) - Math.Max(YMin, MBRYMin);
- 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)
- {
- double featArea = (XMax - XMin) * (YMax - YMin);
- return getArea + featArea - getMBRoverlap(XMax, XMin, YMax, YMin);
- }
- 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 (MBRXMax < MBRXMin || MBRYMax < MBRYMin) { return 0; }
- return (MBRXMax - MBRXMin) * (MBRYMax - MBRYMin);
- }
- }
- public double getPerimeter
- {
- get { return 2 * ((MBRXMax - MBRXMin) + (MBRYMax - MBRYMin)); }
- }
- }
-}
+ 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/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)
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);