using System;
using System.Collections;
using GeoAPI.Geometries;
using GisSharpBlog.NetTopologySuite.Algorithm;
using GisSharpBlog.NetTopologySuite.Geometries;
using GisSharpBlog.NetTopologySuite.GeometriesGraph.Index;
using GisSharpBlog.NetTopologySuite.Utilities;
namespace GisSharpBlog.NetTopologySuite.GeometriesGraph
{
///
/// A GeometryGraph is a graph that models a given Geometry.
///
public class GeometryGraph : PlanarGraph
{
///
/// The lineEdgeMap is a map of the linestring components of the
/// parentGeometry to the edges which are derived from them.
/// This is used to efficiently perform findEdge queries
///
private readonly IDictionary lineEdgeMap = new Hashtable();
///
/// If this flag is true, the Boundary Determination Rule will used when deciding
/// whether nodes are in the boundary or not
///
private bool useBoundaryDeterminationRule = false;
private readonly int argIndex; // the index of this point as an argument to a spatial function (used for labelling)
private ICollection boundaryNodes;
///
///
///
///
///
public GeometryGraph(int argIndex, IGeometry parentGeom)
{
InvalidPoint = null;
HasTooFewPoints = false;
this.argIndex = argIndex;
Geometry = parentGeom;
if (parentGeom != null)
{
Add(parentGeom);
}
}
///
///
///
public bool HasTooFewPoints { get; private set; }
///
///
///
public ICoordinate InvalidPoint { get; private set; }
///
///
///
public IGeometry Geometry { get; private set; }
///
///
///
public ICollection BoundaryNodes
{
get
{
if (boundaryNodes == null)
{
boundaryNodes = nodes.GetBoundaryNodes(argIndex);
}
return boundaryNodes;
}
}
///
/// This method implements the Boundary Determination Rule
/// for determining whether
/// a component (node or edge) that appears multiple times in elements
/// of a MultiGeometry is in the boundary or the interior of the Geometry.
/// The SFS uses the "Mod-2 Rule", which this function implements.
/// An alternative (and possibly more intuitive) rule would be
/// the "At Most One Rule":
/// isInBoundary = (componentCount == 1)
///
public static bool IsInBoundary(int boundaryCount)
{
// the "Mod-2 Rule"
return boundaryCount%2 == 1;
}
///
///
///
///
///
public static Locations DetermineBoundary(int boundaryCount)
{
return IsInBoundary(boundaryCount) ? Locations.Boundary : Locations.Interior;
}
///
///
///
///
public ICoordinate[] GetBoundaryPoints()
{
ICollection coll = BoundaryNodes;
ICoordinate[] pts = new ICoordinate[coll.Count];
int i = 0;
for (IEnumerator it = coll.GetEnumerator(); it.MoveNext();)
{
Node node = (Node) it.Current;
pts[i++] = (ICoordinate) node.Coordinate.Clone();
}
return pts;
}
///
///
///
///
///
public Edge FindEdge(ILineString line)
{
return (Edge) lineEdgeMap[line];
}
///
///
///
///
public void ComputeSplitEdges(IList edgelist)
{
for (IEnumerator i = edges.GetEnumerator(); i.MoveNext();)
{
Edge e = (Edge) i.Current;
e.EdgeIntersectionList.AddSplitEdges(edgelist);
}
}
///
/// Add an Edge computed externally. The label on the Edge is assumed
/// to be correct.
///
///
public void AddEdge(Edge e)
{
InsertEdge(e);
ICoordinate[] coord = e.Coordinates;
// insert the endpoint as a node, to mark that it is on the boundary
InsertPoint(argIndex, coord[0], Locations.Boundary);
InsertPoint(argIndex, coord[coord.Length - 1], Locations.Boundary);
}
///
/// Add a point computed externally. The point is assumed to be a
/// Point Geometry part, which has a location of INTERIOR.
///
///
public void AddPoint(ICoordinate pt)
{
InsertPoint(argIndex, pt, Locations.Interior);
}
///
/// Compute self-nodes, taking advantage of the Geometry type to
/// minimize the number of intersection tests. (E.g. rings are
/// not tested for self-intersection, since they are assumed to be valid).
///
/// The LineIntersector to use.
/// If false, intersection checks are optimized to not test rings for self-intersection.
/// The SegmentIntersector used, containing information about the intersections found.
public SegmentIntersector ComputeSelfNodes(LineIntersector li, bool computeRingSelfNodes)
{
SegmentIntersector si = new SegmentIntersector(li, true, false);
EdgeSetIntersector esi = CreateEdgeSetIntersector();
// optimized test for Polygons and Rings
if (!computeRingSelfNodes &&
(Geometry is ILinearRing || Geometry is IPolygon || Geometry is IMultiPolygon))
{
esi.ComputeIntersections(edges, si, false);
}
else
{
esi.ComputeIntersections(edges, si, true);
}
AddSelfIntersectionNodes(argIndex);
return si;
}
///
///
///
///
///
///
///
public SegmentIntersector ComputeEdgeIntersections(GeometryGraph g,
LineIntersector li, bool includeProper)
{
SegmentIntersector si = new SegmentIntersector(li, includeProper, true);
si.SetBoundaryNodes(BoundaryNodes, g.BoundaryNodes);
EdgeSetIntersector esi = CreateEdgeSetIntersector();
esi.ComputeIntersections(edges, g.edges, si);
return si;
}
///
///
///
///
private EdgeSetIntersector CreateEdgeSetIntersector()
{
// various options for computing intersections, from slowest to fastest
return new SimpleMCSweepLineIntersector();
}
///
///
///
///
private void Add(IGeometry g)
{
if (g.IsEmpty)
{
return;
}
// check if this Geometry should obey the Boundary Determination Rule
// all collections except MultiPolygons obey the rule
if (g is IGeometryCollection && !(g is IMultiPolygon))
{
useBoundaryDeterminationRule = true;
}
if (g is IPolygon)
{
AddPolygon((IPolygon) g);
}
// LineString also handles LinearRings
else if (g is ILineString)
{
AddLineString((ILineString) g);
}
else if (g is IPoint)
{
AddPoint((IPoint) g);
}
else if (g is IMultiPoint)
{
AddCollection((IMultiPoint) g);
}
else if (g is IMultiLineString)
{
AddCollection((IMultiLineString) g);
}
else if (g is IMultiPolygon)
{
AddCollection((IMultiPolygon) g);
}
else if (g is IGeometryCollection)
{
AddCollection((IGeometryCollection) g);
}
else
{
throw new NotSupportedException(g.GetType().FullName);
}
}
///
///
///
///
private void AddCollection(IGeometryCollection gc)
{
for (int i = 0; i < gc.NumGeometries; i++)
{
IGeometry g = gc.GetGeometryN(i);
Add(g);
}
}
///
/// Add a Point to the graph.
///
///
private void AddPoint(IPoint p)
{
ICoordinate coord = p.Coordinate;
InsertPoint(argIndex, coord, Locations.Interior);
}
///
/// The left and right topological location arguments assume that the ring is oriented CW.
/// If the ring is in the opposite orientation,
/// the left and right locations must be interchanged.
///
///
///
///
private void AddPolygonRing(ILinearRing lr, Locations cwLeft, Locations cwRight)
{
ICoordinate[] coord = CoordinateArrays.RemoveRepeatedPoints(lr.Coordinates);
if (coord.Length < 4)
{
HasTooFewPoints = true;
InvalidPoint = coord[0];
return;
}
Locations left = cwLeft;
Locations right = cwRight;
if (CGAlgorithms.IsCCW(coord))
{
left = cwRight;
right = cwLeft;
}
Edge e = new Edge(coord, new Label(argIndex, Locations.Boundary, left, right));
lineEdgeMap[lr] = e;
InsertEdge(e);
// insert the endpoint as a node, to mark that it is on the boundary
InsertPoint(argIndex, coord[0], Locations.Boundary);
}
///
///
///
///
private void AddPolygon(IPolygon p)
{
AddPolygonRing(p.Shell, Locations.Exterior, Locations.Interior);
for (int i = 0; i < p.NumInteriorRings; i++)
{
// Holes are topologically labelled opposite to the shell, since
// the interior of the polygon lies on their opposite side
// (on the left, if the hole is oriented CW)
AddPolygonRing(p.Holes[i], Locations.Interior, Locations.Exterior);
}
}
///
///
///
///
private void AddLineString(ILineString line)
{
ICoordinate[] coord = CoordinateArrays.RemoveRepeatedPoints(line.Coordinates);
if (coord.Length < 2)
{
HasTooFewPoints = true;
InvalidPoint = coord[0];
return;
}
// add the edge for the LineString
// line edges do not have locations for their left and right sides
Edge e = new Edge(coord, new Label(argIndex, Locations.Interior));
lineEdgeMap[line] = e;
InsertEdge(e);
/*
* Add the boundary points of the LineString, if any.
* Even if the LineString is closed, add both points as if they were endpoints.
* This allows for the case that the node already exists and is a boundary point.
*/
Assert.IsTrue(coord.Length >= 2, "found LineString with single point");
InsertBoundaryPoint(argIndex, coord[0]);
InsertBoundaryPoint(argIndex, coord[coord.Length - 1]);
}
///
///
///
///
///
///
private void InsertPoint(int argIndex, ICoordinate coord, Locations onLocation)
{
Node n = nodes.AddNode(coord);
Label lbl = n.Label;
if (lbl == null)
{
n.Label = new Label(argIndex, onLocation);
}
else
{
lbl.SetLocation(argIndex, onLocation);
}
}
///
/// Adds points using the mod-2 rule of SFS. This is used to add the boundary
/// points of dim-1 geometries (Curves/MultiCurves). According to the SFS,
/// an endpoint of a Curve is on the boundary
/// if it is in the boundaries of an odd number of Geometries.
///
///
///
private void InsertBoundaryPoint(int argIndex, ICoordinate coord)
{
Node n = nodes.AddNode(coord);
Label lbl = n.Label;
// the new point to insert is on a boundary
int boundaryCount = 1;
// determine the current location for the point (if any)
Locations loc = Locations.Null;
if (lbl != null)
{
loc = lbl.GetLocation(argIndex, Positions.On);
}
if (loc == Locations.Boundary)
{
boundaryCount++;
}
// determine the boundary status of the point according to the Boundary Determination Rule
Locations newLoc = DetermineBoundary(boundaryCount);
lbl.SetLocation(argIndex, newLoc);
}
///
///
///
///
private void AddSelfIntersectionNodes(int argIndex)
{
for (IEnumerator i = edges.GetEnumerator(); i.MoveNext();)
{
Edge e = (Edge) i.Current;
Locations eLoc = e.Label.GetLocation(argIndex);
for (IEnumerator eiIt = e.EdgeIntersectionList.GetEnumerator(); eiIt.MoveNext();)
{
EdgeIntersection ei = (EdgeIntersection) eiIt.Current;
AddSelfIntersectionNode(argIndex, ei.Coordinate, eLoc);
}
}
}
///
/// Add a node for a self-intersection.
/// If the node is a potential boundary node (e.g. came from an edge which
/// is a boundary) then insert it as a potential boundary node.
/// Otherwise, just add it as a regular node.
///
///
///
///
private void AddSelfIntersectionNode(int argIndex, ICoordinate coord, Locations loc)
{
// if this node is already a boundary node, don't change it
if (IsBoundaryNode(argIndex, coord))
{
return;
}
if (loc == Locations.Boundary && useBoundaryDeterminationRule)
{
InsertBoundaryPoint(argIndex, coord);
}
else
{
InsertPoint(argIndex, coord, loc);
}
}
}
}