.NET implementations of common geometric entities such as triangles and line segments.
$ dotnet add package Themis.GeometryThis project encompasses the core geometriic object models and funcitonality that allow for efficient and reliable representation, manipulation, and analysis of common geospatial data types.
This namespace exposes the BoundingBox class which represents both 2D BoundingBox and 3D BoundingCube geometries within a single concrete class. In both dimensional cases, the BoundingBox is defined by its local Minima (X,Y,Z) and Maxima (X,Y,Z).
Note: When in 2D - the Minima/Maxima Z coordinates are left as double.NaN.
Further, the BoundingBox implementation is at the core of the spatial indexing functionality provided by the QuadTree.
While the current BoundingBox implementation only has a default constructor it also exposes a number of Factory methods as well as a Fluent interface to generate the desired resultant geometry.
Consider the following:
// Create a 2D BoundingBox centered at (1, 1) with minima (-1.5, -1.5) and maxima (3.5, 3.5)
var box = BoundingBox.FromPoint(1.0, 1.0, 5.0);
var box2 = BoundingBox.From(-1.5, -1.5, 3.5, 3.5);
// Resulting Minima & Maxima are equal - thus the two BoundingBoxes are equal
Assert.Equal(box, box2);
Assert.True(box.Contains(1.0, 1.0));
// Creates a new 3D BoundingBox with the specified elevation (Z) range
var cube = BoundingBox.From(box).WithZ(0, 100.0);
// The 3D BoundingBox can still perform both 2D & 3D containment checks
Assert.True(cube.Contains(1.0, 1.0));
Assert.True(cube.Contains(1.0, 1.0, 1.0));
We can also easily expand a given BoundingBox to include any other BoundingBox or simply alter the local Minima / Maxima as follows:
// Create two BoundingBoxes that overlap and then create a 3rd that include them both
var A = BoundingBox.From(-1.5, -1.5, 3.5, 3.5);
var B = BoundingBox.From(0, 0, 5.0, 5.0);
// Can check that the two BoundingBoxes intersect/overlap as well
Assert.True(A.Intersects(B));
// Lets create a new BoundingBox by expanding A to include B
var C = A.ExpandToInclude(B);
// Can now see the maxima & minima are expanded as expected
Assert.Equal(5.0, C.MaxX);
Assert.Equal(5.0, C.MaxY);
Beyond the above functionality - each BoundingBox also exposes the following fields:
This namespace exposes Themis' LineSegment and LineString implementations that are intended to be used to model 2/3D linear geometries. While both implementations will technically function within any dimension it's recommended consumers limit their dimensionality to 2/3D.
The LineSegment is composed of an ordered pair of vector vertices (named A & B) and encompasses both the infinite line between A->B but also the discrete LineSegment formed by A->B. Given those two components the LineSegment is able to efficiently do the following:
LineSegment at any station (distance along the line)LineSegment to any input positionLineSegment from any input positionThe LineString is composed of two or more vertices as an ordered, connected, set of linear geometries. In order to build the LineString we create a LineSegment between each vertex and the following vertex (excluding the final vertex). While in 2D this could represent the connectivity map of power poles, the 3D extension can be used to model wire geometries or other complex shapes composed of many lines. Given this, the LineString exposes the following functionality:
LineSegment geometries to any input positionLineSegment geometriesIn order to instantiate a LineSegment we'll need to first have two Vectors that represent the starting Vertex (A) and the terminating Vertex (B).
Note: We've also included some extension methods to easily convert a given IEnumerable<T> into a Vector<T> that simplifies this.
Here's an example:
// Need to instantiate the two vertices (0,0,0) and (5,5,5)
var A = new double[] { 0.0, 0.0, 0.0 }.ToVector();
var B = new double[] { 5.0, 5.0, 5.0 }.ToVector();
// Generate the LineSegment A->B from (0,0,0) to (5,5,5)
var line = new LineSegment(A, B);
// Get the 3D and 2D length of the LineSegment
double len = line.Length;
double len2D = line.Length2D;
Assert.NotEqual(len, len2D); // True
Another key example is when you need to find the nearest point (from a collection of points) to a given LineSegment:
// A collection of position vectors
var points = new List<Vector<double>>() { .. };
// Since this is ascending by default, can take the 'first' element as nearest
var nearest = points.OrderyBy(p => line.DistanceToPoint(p)).First();
Or the inverse - given a collection of LineSegments find the one nearest a given point of interest:
// A single query POI
var point = new double[] { .. }.ToVector();
// A collection of LineSegments
var segs = new List<LineSegment>() { .. };
// Get the LineSegment closest to the input POI
var nearestSeg = segs.OrderBy(s => s.DistanceToPoint(point)).First();
var nearestPoint = nearestSeg.GetClosestPoint(point);
This namespace exposes the Triangle class which is used to represent 2D/3D triangular geometries as defined by a set of three vector vertices. Once created a Triangle exposes the following key functionality & fields:
LineSegmentsBoundingBox envelope of the Triangle geometryTriangle geometryTriangle surface for a given (X,Y) positionAs mentioned above - in order to generate a Triangle we'll need to have a collection of vertices that define the Triangle geometry. Here's an example:
// Forming Triangle (0, 0, 0) -> (1, 0, 1) -> (0, 1, 0)
var A = new double[] { 0.0, 0.0, 0.0 }.ToVector();
var B = new double[] { 1.0, 0.0, 1.0 }.ToVector();
var C = new double[] { 0.0, 1.0, 0.0 }.ToVector();
// Generate the Triangle object
var Triangle = new Triangle(new() { A, B, C });
Now with the Triangle defined, we can check for containment of any given position and then sample its elevation on the Triangle surface as follows:
// Input POI's elevation doesn't matter for containment or Z-sampling
var pos = new double[] {0.25, 0.25, double.NaN}.ToVector();
// Checking containment & extract elevation (Z)
if(Triangle.Contains(pos))
{
double Z = Triangle.GetZ(pos); // 0.25
}