using System;
using GeoAPI.Geometries;
using GisSharpBlog.NetTopologySuite.Geometries;
namespace GisSharpBlog.NetTopologySuite.Algorithm
{
///
/// Computes the minimum diameter of a Geometry.
/// The minimum diameter is defined to be the
/// width of the smallest band that contains the point,
/// where a band is a strip of the plane defined
/// by two parallel lines.
/// This can be thought of as the smallest hole that the point can be
/// moved through, with a single rotation.
/// The first step in the algorithm is computing the convex hull of the Geometry.
/// If the input Geometry is known to be convex, a hint can be supplied to
/// avoid this computation.
///
public class MinimumDiameter
{
private readonly IGeometry inputGeom;
private readonly bool isConvex;
private LineSegment minBaseSeg = new LineSegment();
private ICoordinate minWidthPt = null;
private int minPtIndex;
private double minWidth = 0.0;
///
/// Compute a minimum diameter for a giver Geometry.
///
/// a Geometry.
public MinimumDiameter(IGeometry inputGeom)
: this(inputGeom, false) {}
///
/// Compute a minimum diameter for a giver Geometry,
/// with a hint if
/// the Geometry is convex
/// (e.g. a convex Polygon or LinearRing,
/// or a two-point LineString, or a Point).
///
/// a Geometry which is convex.
/// true if the input point is convex.
public MinimumDiameter(IGeometry inputGeom, bool isConvex)
{
this.inputGeom = inputGeom;
this.isConvex = isConvex;
}
///
/// Gets the length of the minimum diameter of the input Geometry.
///
/// The length of the minimum diameter.
public double Length
{
get
{
ComputeMinimumDiameter();
return minWidth;
}
}
///
/// Gets the Coordinate forming one end of the minimum diameter.
///
/// A coordinate forming one end of the minimum diameter.
public ICoordinate WidthCoordinate
{
get
{
ComputeMinimumDiameter();
return minWidthPt;
}
}
///
/// Gets the segment forming the base of the minimum diameter.
///
/// The segment forming the base of the minimum diameter.
public ILineString SupportingSegment
{
get
{
ComputeMinimumDiameter();
return inputGeom.Factory.CreateLineString(new ICoordinate[]
{
minBaseSeg.P0,
minBaseSeg.P1
});
}
}
///
/// Gets a LineString which is a minimum diameter.
///
/// A LineString which is a minimum diameter.
public ILineString Diameter
{
get
{
ComputeMinimumDiameter();
// return empty linearRing if no minimum width calculated
if (minWidthPt == null)
{
ICoordinate[] nullCoords = null;
return inputGeom.Factory.CreateLineString(nullCoords);
}
ICoordinate basePt = minBaseSeg.Project(minWidthPt);
return inputGeom.Factory.CreateLineString(new ICoordinate[]
{
basePt,
minWidthPt
});
}
}
///
///
///
private void ComputeMinimumDiameter()
{
// check if computation is cached
if (minWidthPt != null)
{
return;
}
if (isConvex)
{
ComputeWidthConvex(inputGeom);
}
else
{
IGeometry convexGeom = (new ConvexHull(inputGeom)).GetConvexHull();
ComputeWidthConvex(convexGeom);
}
}
///
///
///
///
private void ComputeWidthConvex(IGeometry geom)
{
ICoordinate[] pts = null;
if (geom is IPolygon)
{
pts = ((IPolygon) geom).ExteriorRing.Coordinates;
}
else
{
pts = geom.Coordinates;
}
// special cases for lines or points or degenerate rings
if (pts.Length == 0)
{
minWidth = 0.0;
minWidthPt = null;
minBaseSeg = null;
}
else if (pts.Length == 1)
{
minWidth = 0.0;
minWidthPt = pts[0];
minBaseSeg.P0 = pts[0];
minBaseSeg.P1 = pts[0];
}
else if (pts.Length == 2 || pts.Length == 3)
{
minWidth = 0.0;
minWidthPt = pts[0];
minBaseSeg.P0 = pts[0];
minBaseSeg.P1 = pts[1];
}
else
{
ComputeConvexRingMinDiameter(pts);
}
}
///
/// Compute the width information for a ring of Coordinates.
/// Leaves the width information in the instance variables.
///
///
private void ComputeConvexRingMinDiameter(ICoordinate[] pts)
{
// for each segment in the ring
minWidth = Double.MaxValue;
int currMaxIndex = 1;
LineSegment seg = new LineSegment();
// compute the max distance for all segments in the ring, and pick the minimum
for (int i = 0; i < pts.Length - 1; i++)
{
seg.P0 = pts[i];
seg.P1 = pts[i + 1];
currMaxIndex = FindMaxPerpDistance(pts, seg, currMaxIndex);
}
}
///
///
///
///
///
///
///
private int FindMaxPerpDistance(ICoordinate[] pts, LineSegment seg, int startIndex)
{
double maxPerpDistance = seg.DistancePerpendicular(pts[startIndex]);
double nextPerpDistance = maxPerpDistance;
int maxIndex = startIndex;
int nextIndex = maxIndex;
while (nextPerpDistance >= maxPerpDistance)
{
maxPerpDistance = nextPerpDistance;
maxIndex = nextIndex;
nextIndex = NextIndex(pts, maxIndex);
nextPerpDistance = seg.DistancePerpendicular(pts[nextIndex]);
}
// found maximum width for this segment - update global min dist if appropriate
if (maxPerpDistance < minWidth)
{
minPtIndex = maxIndex;
minWidth = maxPerpDistance;
minWidthPt = pts[minPtIndex];
minBaseSeg = new LineSegment(seg);
}
return maxIndex;
}
///
///
///
///
///
///
private static int NextIndex(ICoordinate[] pts, int index)
{
index++;
if (index >= pts.Length)
{
index = 0;
}
return index;
}
}
}