/// <summary> /// /// </summary> /// <param name="g0"></param> public GeometryGraphOperation(IGeometry g0) { ComputationPrecision = g0.PrecisionModel; arg = new GeometryGraph[1]; arg[0] = new GeometryGraph(0, g0);; }
/// <summary> /// /// </summary> /// <param name="g0"></param> /// <param name="g1"></param> public GeometryGraphOperation(IGeometry g0, IGeometry g1) { // use the most precise model for the result if (g0.PrecisionModel.CompareTo(g1.PrecisionModel) >= 0) ComputationPrecision = g0.PrecisionModel; else ComputationPrecision = g1.PrecisionModel; arg = new GeometryGraph[2]; arg[0] = new GeometryGraph(0, g0); arg[1] = new GeometryGraph(1, g1); }
/// <summary> /// /// </summary> /// <param name="geomGraph"></param> public void Build(GeometryGraph geomGraph) { // compute nodes for intersections between previously noded edges ComputeIntersectionNodes(geomGraph, 0); /* * Copy the labelling for the nodes in the parent Geometry. These override * any labels determined by intersections. */ CopyNodesAndLabels(geomGraph, 0); /* * Build EdgeEnds for all intersections. */ EdgeEndBuilder eeBuilder = new EdgeEndBuilder(); IList eeList = eeBuilder.ComputeEdgeEnds(geomGraph.GetEdgeEnumerator()); InsertEdgeEnds(eeList); }
/// <summary> /// Insert nodes for all intersections on the edges of a Geometry. /// Label the created nodes the same as the edge label if they do not already have a label. /// This allows nodes created by either self-intersections or /// mutual intersections to be labelled. /// Endpoint nodes will already be labelled from when they were inserted. /// Precondition: edge intersections have been computed. /// </summary> /// <param name="geomGraph"></param> /// <param name="argIndex"></param> public void ComputeIntersectionNodes(GeometryGraph geomGraph, int argIndex) { for (IEnumerator edgeIt = geomGraph.GetEdgeEnumerator(); edgeIt.MoveNext(); ) { Edge e = (Edge) edgeIt.Current; Locations eLoc = e.Label.GetLocation(argIndex); for (IEnumerator eiIt = e.EdgeIntersectionList.GetEnumerator(); eiIt.MoveNext(); ) { EdgeIntersection ei = (EdgeIntersection) eiIt.Current; RelateNode n = (RelateNode) nodes.AddNode(ei.Coordinate); if (eLoc == Locations.Boundary) n.SetLabelBoundary(argIndex); else if (n.Label.IsNull(argIndex)) n.SetLabel(argIndex, Locations.Interior); } } }
/// <summary> /// Compute the labelling for all dirEdges in this star, as well /// as the overall labelling. /// </summary> /// <param name="geom"></param> public override void ComputeLabelling(GeometryGraph[] geom) { base.ComputeLabelling(geom); // determine the overall labelling for this DirectedEdgeStar // (i.e. for the node it is based at) label = new Label(Locations.Null); IEnumerator it = GetEnumerator(); while(it.MoveNext()) { EdgeEnd ee = (EdgeEnd)it.Current; Edge e = ee.Edge; Label eLabel = e.Label; for (int i = 0; i < 2; i++) { Locations eLoc = eLabel.GetLocation(i); if (eLoc == Locations.Interior || eLoc == Locations.Boundary) label.SetLocation(i, Locations.Interior); } } }
/// <summary> /// For all edges, check if there are any intersections which are NOT at an endpoint. /// The Geometry is not simple if there are intersections not at endpoints. /// </summary> /// <param name="graph"></param> private bool HasNonEndpointIntersection(GeometryGraph graph) { for (IEnumerator i = graph.GetEdgeEnumerator(); i.MoveNext(); ) { Edge e = (Edge) i.Current; int maxSegmentIndex = e.MaximumSegmentIndex; for (IEnumerator eiIt = e.EdgeIntersectionList.GetEnumerator(); eiIt.MoveNext(); ) { EdgeIntersection ei = (EdgeIntersection) eiIt.Current; if (!ei.IsEndPoint(maxSegmentIndex)) return true; } } return false; }
/// <summary> /// /// </summary> /// <param name="graph"></param> public SweeplineNestedRingTester(GeometryGraph graph) { this.graph = graph; }
/// <summary> /// /// </summary> /// <param name="g"></param> private void CheckValid(IMultiPolygon g) { foreach(IPolygon p in g.Geometries) { CheckInvalidCoordinates(p); if (validErr != null) return; CheckClosedRings(p); if (validErr != null) return; } GeometryGraph graph = new GeometryGraph(0, g); CheckTooFewPoints(graph); if (validErr != null) return; CheckConsistentArea(graph); if (validErr != null) return; if (!IsSelfTouchingRingFormingHoleValid) { CheckNoSelfIntersectingRings(graph); if (validErr != null) return; } foreach(IPolygon p in g.Geometries) { CheckHolesInShell(p, graph); if (validErr != null) return; } foreach (IPolygon p in g.Geometries) { CheckHolesNotNested(p, graph); if (validErr != null) return; } CheckShellsNotNested(g, graph); if (validErr != null) return; CheckConnectedInteriors(graph); }
/// <summary> /// Checks validity of a LineString. /// Almost anything goes for lineStrings! /// </summary> /// <param name="g"></param> private void CheckValid(ILineString g) { CheckInvalidCoordinates(g.Coordinates); if (validErr != null) return; GeometryGraph graph = new GeometryGraph(0, g); CheckTooFewPoints(graph); }
/// <summary> /// Tests that no element polygon is wholly in the interior of another element polygon. /// Preconditions: /// Shells do not partially overlap. /// Shells do not touch along an edge. /// No duplicate rings exists. /// This routine relies on the fact that while polygon shells may touch at one or /// more vertices, they cannot touch at ALL vertices. /// </summary> private void CheckShellsNotNested(IMultiPolygon mp, GeometryGraph graph) { for (int i = 0; i < mp.NumGeometries; i++) { IPolygon p = (IPolygon) mp.GetGeometryN(i); ILinearRing shell = p.Shell; for (int j = 0; j < mp.NumGeometries; j++) { if (i == j) continue; IPolygon p2 = (IPolygon) mp.GetGeometryN(j); CheckShellNotNested(shell, p2, graph); if (validErr != null) return; } } }
/// <summary> /// This routine checks to see if a shell is properly contained in a hole. /// It assumes that the edges of the shell and hole do not /// properly intersect. /// </summary> /// <param name="shell"></param> /// <param name="hole"></param> /// <param name="graph"></param> /// <returns> /// <c>null</c> if the shell is properly contained, or /// a Coordinate which is not inside the hole if it is not. /// </returns> private ICoordinate CheckShellInsideHole(ILinearRing shell, ILinearRing hole, GeometryGraph graph) { ICoordinate[] shellPts = shell.Coordinates; ICoordinate[] holePts = hole.Coordinates; // TODO: improve performance of this - by sorting pointlists? ICoordinate shellPt = FindPointNotNode(shellPts, hole, graph); // if point is on shell but not hole, check that the shell is inside the hole if (shellPt != null) { bool insideHole = CGAlgorithms.IsPointInRing(shellPt, holePts); if (!insideHole) return shellPt; } ICoordinate holePt = FindPointNotNode(holePts, shell, graph); // if point is on hole but not shell, check that the hole is outside the shell if (holePt != null) { bool insideShell = CGAlgorithms.IsPointInRing(holePt, shellPts); if (insideShell) return holePt; return null; } Assert.ShouldNeverReachHere("points in shell and hole appear to be equal"); return null; }
/// <summary> /// /// </summary> /// <param name="graph"></param> public QuadtreeNestedRingTester(GeometryGraph graph) { this.graph = graph; }
/// <summary> /// Test that no edge intersection is the /// endpoint of a closed line. To check this we compute the /// degree of each endpoint. The degree of endpoints of closed lines /// must be exactly 2. /// </summary> /// <param name="graph"></param> private bool HasClosedEndpointIntersection(GeometryGraph graph) { IDictionary endPoints = new SortedList(); for (IEnumerator i = graph.GetEdgeEnumerator(); i.MoveNext(); ) { Edge e = (Edge) i.Current; bool isClosed = e.IsClosed; ICoordinate p0 = e.GetCoordinate(0); AddEndpoint(endPoints, p0, isClosed); ICoordinate p1 = e.GetCoordinate(e.NumPoints - 1); AddEndpoint(endPoints, p1, isClosed); } for (IEnumerator i = endPoints.Values.GetEnumerator(); i.MoveNext(); ) { EndpointInfo eiInfo = (EndpointInfo) i.Current; if (eiInfo.IsClosed && eiInfo.Degree != 2) return true; } return false; }
/// <summary> /// /// </summary> /// <param name="geom"></param> /// <returns></returns> private bool IsSimpleLinearGeometry(IGeometry geom) { if (geom.IsEmpty) return true; GeometryGraph graph = new GeometryGraph(0, geom); LineIntersector li = new RobustLineIntersector(); SegmentIntersector si = graph.ComputeSelfNodes(li, true); // if no self-intersection, must be simple if (!si.HasIntersection) return true; if (si.HasProperIntersection) return false; if (HasNonEndpointIntersection(graph)) return false; if (HasClosedEndpointIntersection(graph)) return false; return true; }
/// <summary> /// Tests that no hole is nested inside another hole. /// This routine assumes that the holes are disjoint. /// To ensure this, holes have previously been tested /// to ensure that: /// They do not partially overlap /// (checked by <c>checkRelateConsistency</c>). /// They are not identical /// (checked by <c>checkRelateConsistency</c>). /// </summary> private void CheckHolesNotNested(IPolygon p, GeometryGraph graph) { QuadtreeNestedRingTester nestedTester = new QuadtreeNestedRingTester(graph); foreach (ILinearRing innerHole in p.Holes) nestedTester.Add(innerHole); bool isNonNested = nestedTester.IsNonNested(); if (!isNonNested) validErr = new TopologyValidationError(TopologyValidationErrors.NestedHoles, nestedTester.NestedPoint); }
/// <summary> /// Check that there is no ring which self-intersects (except of course at its endpoints). /// This is required by OGC topology rules (but not by other models /// such as ESRI SDE, which allow inverted shells and exverted holes). /// </summary> /// <param name="graph"></param> private void CheckNoSelfIntersectingRings(GeometryGraph graph) { for (IEnumerator i = graph.GetEdgeEnumerator(); i.MoveNext(); ) { Edge e = (Edge) i.Current; CheckNoSelfIntersectingRing(e.EdgeIntersectionList); if (validErr != null) return; } }
/// <summary> /// /// </summary> /// <param name="arg"></param> public RelateComputer(GeometryGraph[] arg) { this.arg = arg; }
/// <summary> /// Check if a shell is incorrectly nested within a polygon. This is the case /// if the shell is inside the polygon shell, but not inside a polygon hole. /// (If the shell is inside a polygon hole, the nesting is valid.) /// The algorithm used relies on the fact that the rings must be properly contained. /// E.g. they cannot partially overlap (this has been previously checked by /// <c>CheckRelateConsistency</c>). /// </summary> private void CheckShellNotNested(ILinearRing shell, IPolygon p, GeometryGraph graph) { ICoordinate[] shellPts = shell.Coordinates; // test if shell is inside polygon shell ILinearRing polyShell = p.Shell; ICoordinate[] polyPts = polyShell.Coordinates; ICoordinate shellPt = FindPointNotNode(shellPts, polyShell, graph); // if no point could be found, we can assume that the shell is outside the polygon if (shellPt == null) return; bool insidePolyShell = CGAlgorithms.IsPointInRing(shellPt, polyPts); if (!insidePolyShell) return; // if no holes, this is an error! if (p.NumInteriorRings <= 0) { validErr = new TopologyValidationError(TopologyValidationErrors.NestedShells, shellPt); return; } /* * Check if the shell is inside one of the holes. * This is the case if one of the calls to checkShellInsideHole * returns a null coordinate. * Otherwise, the shell is not properly contained in a hole, which is an error. */ ICoordinate badNestedPt = null; for (int i = 0; i < p.NumInteriorRings; i++) { ILinearRing hole = p.Holes[i]; badNestedPt = CheckShellInsideHole(shell, hole, graph); if (badNestedPt == null) return; } validErr = new TopologyValidationError(TopologyValidationErrors.NestedShells, badNestedPt); }
/// <summary> /// /// </summary> /// <param name="geomGraph"></param> public ConsistentAreaTester(GeometryGraph geomGraph) { this.geomGraph = geomGraph; }
/// <summary> /// /// </summary> /// <param name="graph"></param> private void CheckTooFewPoints(GeometryGraph graph) { if (graph.HasTooFewPoints) { validErr = new TopologyValidationError(TopologyValidationErrors.TooFewPoints, graph.InvalidPoint); return; } }
/// <summary> /// /// </summary> /// <param name="geomGraph"></param> public ConnectedInteriorTester(GeometryGraph geomGraph) { this.geomGraph = geomGraph; }
/// <summary> /// Checks validity of a LinearRing. /// </summary> /// <param name="g"></param> private void CheckValid(ILinearRing g) { CheckInvalidCoordinates(g.Coordinates); if (validErr != null) return; CheckClosedRing(g); if (validErr != null) return; GeometryGraph graph = new GeometryGraph(0, g); CheckTooFewPoints(graph); if (validErr != null) return; LineIntersector li = new RobustLineIntersector(); graph.ComputeSelfNodes(li, true); CheckNoSelfIntersectingRings(graph); }
/// <summary> /// Find a point from the list of testCoords /// that is NOT a node in the edge for the list of searchCoords. /// </summary> /// <param name="testCoords"></param> /// <param name="searchRing"></param> /// <param name="graph"></param> /// <returns>The point found, or <c>null</c> if none found.</returns> public static ICoordinate FindPointNotNode(ICoordinate[] testCoords, ILinearRing searchRing, GeometryGraph graph) { // find edge corresponding to searchRing. Edge searchEdge = graph.FindEdge(searchRing); // find a point in the testCoords which is not a node of the searchRing EdgeIntersectionList eiList = searchEdge.EdgeIntersectionList; // somewhat inefficient - is there a better way? (Use a node map, for instance?) foreach(ICoordinate pt in testCoords) if(!eiList.IsIntersection(pt)) return pt; return null; }
/// <summary> /// /// </summary> /// <param name="graph"></param> public SimpleNestedRingTester(GeometryGraph graph) { this.graph = graph; }
/// <summary> /// /// </summary> /// <param name="graph"></param> private void CheckConnectedInteriors(GeometryGraph graph) { ConnectedInteriorTester cit = new ConnectedInteriorTester(graph); if (!cit.IsInteriorsConnected()) validErr = new TopologyValidationError(TopologyValidationErrors.DisconnectedInteriors, cit.Coordinate); }
/// <summary> /// Copy all nodes from an arg point into this graph. /// The node label in the arg point overrides any previously computed /// label for that argIndex. /// (E.g. a node may be an intersection node with /// a computed label of Boundary, /// but in the original arg Geometry it is actually /// in the interior due to the Boundary Determination Rule). /// </summary> /// <param name="geomGraph"></param> /// <param name="argIndex"></param> public void CopyNodesAndLabels(GeometryGraph geomGraph, int argIndex) { for (IEnumerator nodeIt = geomGraph.GetNodeEnumerator(); nodeIt.MoveNext(); ) { Node graphNode = (Node) nodeIt.Current; Node newNode = nodes.AddNode(graphNode.Coordinate); newNode.SetLabel(argIndex, graphNode.Label.GetLocation(argIndex)); } }
/// <summary> /// /// </summary> /// <param name="graph"></param> private void CheckConsistentArea(GeometryGraph graph) { ConsistentAreaTester cat = new ConsistentAreaTester(graph); bool isValidArea = cat.IsNodeConsistentArea; if (!isValidArea) { validErr = new TopologyValidationError(TopologyValidationErrors.SelfIntersection, cat.InvalidPoint); return; } if (cat.HasDuplicateRings) { validErr = new TopologyValidationError(TopologyValidationErrors.DuplicateRings, cat.InvalidPoint); return; } }
/// <summary> /// Tests that each hole is inside the polygon shell. /// This routine assumes that the holes have previously been tested /// to ensure that all vertices lie on the shell or inside it. /// A simple test of a single point in the hole can be used, /// provide the point is chosen such that it does not lie on the /// boundary of the shell. /// </summary> /// <param name="p">The polygon to be tested for hole inclusion.</param> /// <param name="graph">A GeometryGraph incorporating the polygon.</param> private void CheckHolesInShell(IPolygon p, GeometryGraph graph) { ILinearRing shell = p.Shell; IPointInRing pir = new MCPointInRing(shell); for (int i = 0; i < p.NumInteriorRings; i++) { ILinearRing hole = p.Holes[i]; ICoordinate holePt = FindPointNotNode(hole.Coordinates, shell, graph); /* * If no non-node hole vertex can be found, the hole must * split the polygon into disconnected interiors. * This will be caught by a subsequent check. */ if (holePt == null) return; bool outside = !pir.IsInside((Coordinate) holePt); if(outside) { validErr = new TopologyValidationError(TopologyValidationErrors.HoleOutsideShell, holePt); return; } } }
/// <summary> /// /// </summary> /// <param name="g"></param> /// <param name="li"></param> /// <param name="includeProper"></param> /// <returns></returns> 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; }