using GeoAPI.Geometries;
using GisSharpBlog.NetTopologySuite.Geometries;
namespace GisSharpBlog.NetTopologySuite.Algorithm
{
///
/// Computes the centroid of an area point.
/// Algorithm:
/// Based on the usual algorithm for calculating
/// the centroid as a weighted sum of the centroids
/// of a decomposition of the area into (possibly overlapping) triangles.
/// The algorithm has been extended to handle holes and multi-polygons.
/// See
/// for further details of the basic approach.
///
public class CentroidArea
{
private ICoordinate basePt = null; // the point all triangles are based at
private ICoordinate triangleCent3 = new Coordinate(); // temporary variable to hold centroid of triangle
private double areasum2 = 0; // Partial area sum
private readonly ICoordinate cg3 = new Coordinate(); // partial centroid sum
///
///
///
public CentroidArea() {}
///
///
///
public ICoordinate Centroid
{
get
{
ICoordinate cent = new Coordinate();
cent.X = cg3.X/3/areasum2;
cent.Y = cg3.Y/3/areasum2;
return cent;
}
}
///
/// Adds the area defined by a Geometry to the centroid total.
/// If the point has no area it does not contribute to the centroid.
///
/// The point to add.
public void Add(IGeometry geom)
{
if (geom is IPolygon)
{
IPolygon poly = (IPolygon) geom;
BasePoint = poly.ExteriorRing.GetCoordinateN(0);
Add(poly);
}
else if (geom is IGeometryCollection)
{
IGeometryCollection gc = (IGeometryCollection) geom;
foreach (IGeometry geometry in gc.Geometries)
{
Add(geometry);
}
}
}
///
/// Adds the area defined by an array of
/// coordinates. The array must be a ring;
/// i.e. end with the same coordinate as it starts with.
///
/// An array of Coordinates.
public void Add(ICoordinate[] ring)
{
BasePoint = ring[0];
AddShell(ring);
}
///
///
///
private ICoordinate BasePoint
{
get
{
return basePt;
}
set
{
if (basePt == null)
{
basePt = value;
}
}
}
///
///
///
///
private void Add(IPolygon poly)
{
AddShell(poly.ExteriorRing.Coordinates);
foreach (ILineString ls in poly.InteriorRings)
{
AddHole(ls.Coordinates);
}
}
///
///
///
///
private void AddShell(ICoordinate[] pts)
{
bool isPositiveArea = !CGAlgorithms.IsCCW(pts);
for (int i = 0; i < pts.Length - 1; i++)
{
AddTriangle(basePt, pts[i], pts[i + 1], isPositiveArea);
}
}
///
///
///
///
private void AddHole(ICoordinate[] pts)
{
bool isPositiveArea = CGAlgorithms.IsCCW(pts);
for (int i = 0; i < pts.Length - 1; i++)
{
AddTriangle(basePt, pts[i], pts[i + 1], isPositiveArea);
}
}
///
///
///
///
///
///
///
private void AddTriangle(ICoordinate p0, ICoordinate p1, ICoordinate p2, bool isPositiveArea)
{
double sign = (isPositiveArea) ? 1.0 : -1.0;
Centroid3(p0, p1, p2, ref triangleCent3);
double area2 = Area2(p0, p1, p2);
cg3.X += sign*area2*triangleCent3.X;
cg3.Y += sign*area2*triangleCent3.Y;
areasum2 += sign*area2;
}
///
/// Returns three times the centroid of the triangle p1-p2-p3.
/// The factor of 3 is
/// left in to permit division to be avoided until later.
///
private static void Centroid3(ICoordinate p1, ICoordinate p2, ICoordinate p3, ref ICoordinate c)
{
c.X = p1.X + p2.X + p3.X;
c.Y = p1.Y + p2.Y + p3.Y;
return;
}
///
/// Returns twice the signed area of the triangle p1-p2-p3,
/// positive if a,b,c are oriented ccw, and negative if cw.
///
private static double Area2(ICoordinate p1, ICoordinate p2, ICoordinate p3)
{
return (p2.X - p1.X)*(p3.Y - p1.Y) - (p3.X - p1.X)*(p2.Y - p1.Y);
}
}
}