/// <summary> /// /// </summary> /// <param name="g0"></param> public GeometryGraphOperation(IGeometry g0) { ComputationPrecision = g0.PrecisionModel; arg = new GeometryGraph[1]; arg[0] = new GeometryGraph(0, g0);; }
public GeometryGraphOperation(IGeometry g0, IGeometry g1, IBoundaryNodeRule boundaryNodeRule) { // 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, boundaryNodeRule); arg[1] = new GeometryGraph(1, g1, boundaryNodeRule); }
/// <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 Coordinate FindPointNotNode(Coordinate[] 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(Coordinate pt in testCoords) if(!eiList.IsIntersection(pt)) return pt; return null; }
/// <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) { foreach (Edge e in geomGraph.Edges) { Location eLoc = e.Label.GetLocation(argIndex); foreach (EdgeIntersection ei in e.EdgeIntersectionList) { RelateNode n = (RelateNode) _nodes.AddNode(ei.Coordinate); if (eLoc == Location.Boundary) n.SetLabelBoundary(argIndex); else if (n.Label.IsNull(argIndex)) n.SetLabel(argIndex, Location.Interior); } } }
/// <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<EdgeEnd> eeList = eeBuilder.ComputeEdgeEnds(geomGraph.Edges); InsertEdgeEnds(eeList); }
/// <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(Location.Null); IEnumerator<EdgeEnd> it = GetEnumerator(); while(it.MoveNext()) { EdgeEnd ee = it.Current; Edge e = ee.Edge; Label eLabel = e.Label; for (int i = 0; i < 2; i++) { Location eLoc = eLabel.GetLocation(i); if (eLoc == Location.Interior || eLoc == Location.Boundary) _label.SetLocation(i, Location.Interior); } } }
/// <summary> /// /// </summary> /// <param name="arg"></param> public RelateComputer(GeometryGraph[] arg) { _arg = arg; }
/// <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) { foreach (Edge e in graph.Edges) { int maxSegmentIndex = e.MaximumSegmentIndex; foreach (EdgeIntersection ei in e.EdgeIntersectionList) { if (!ei.IsEndPoint(maxSegmentIndex)) { _nonSimpleLocation = ei.Coordinate; return true; } } } return false; }
/// <summary> /// /// </summary> /// <param name="graph"></param> public SweeplineNestedRingTester(GeometryGraph graph) { this.graph = graph; }
/// <summary> /// /// </summary> /// <param name="geomIndex"></param> /// <param name="p"></param> /// <param name="geom"></param> /// <returns></returns> private Location GetLocation(int geomIndex, Coordinate p, GeometryGraph[] geom) { // compute location only on demand if (_ptInAreaLocation[geomIndex] == Location.Null) _ptInAreaLocation[geomIndex] = SimplePointInAreaLocator.Locate(p, geom[geomIndex].Geometry); return _ptInAreaLocation[geomIndex]; }
/// <summary> /// /// </summary> /// <param name="geomGraph"></param> public ConsistentAreaTester(GeometryGraph geomGraph) { this.geomGraph = geomGraph; }
/// <summary> /// /// </summary> /// <param name="graph"></param> public QuadtreeNestedRingTester(GeometryGraph graph) { _graph = graph; }
/// <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 Coordinate CheckShellInsideHole(ILinearRing shell, ILinearRing hole, GeometryGraph graph) { Coordinate[] shellPts = shell.Coordinates; Coordinate[] holePts = hole.Coordinates; // TODO: improve performance of this - by sorting pointlists? Coordinate 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; } Coordinate 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> /// 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> /// 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> /// 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) { Coordinate[] shellPts = shell.Coordinates; // test if shell is inside polygon shell ILinearRing polyShell = p.Shell; Coordinate[] polyPts = polyShell.Coordinates; Coordinate 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. */ Coordinate 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="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> /// /// </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> /// /// </summary> /// <param name="graph"></param> private void CheckTooFewPoints(GeometryGraph graph) { if (graph.HasTooFewPoints) { validErr = new TopologyValidationError(TopologyValidationErrors.TooFewPoints, graph.InvalidPoint); return; } }
public IndexedNestedRingTester(GeometryGraph graph) { _graph = graph; }
/// <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> /// /// </summary> /// <param name="geomGraph"></param> public virtual void ComputeLabelling(GeometryGraph[] geomGraph) { ComputeEdgeEndLabels(geomGraph[0].BoundaryNodeRule); // Propagate side labels around the edges in the star // for each parent Geometry PropagateSideLabels(0); PropagateSideLabels(1); /* * If there are edges that still have null labels for a point * this must be because there are no area edges for that point incident on this node. * In this case, to label the edge for that point we must test whether the * edge is in the interior of the point. * To do this it suffices to determine whether the node for the edge is in the interior of an area. * If so, the edge has location Interior for the point. * In all other cases (e.g. the node is on a line, on a point, or not on the point at all) the edge * has the location Exterior for the point. * * Note that the edge cannot be on the Boundary of the point, since then * there would have been a parallel edge from the Geometry at this node also labelled Boundary * and this edge would have been labelled in the previous step. * * This code causes a problem when dimensional collapses are present, since it may try and * determine the location of a node where a dimensional collapse has occurred. * The point should be considered to be on the Exterior * of the polygon, but locate() will return Interior, since it is passed * the original Geometry, not the collapsed version. * * If there are incident edges which are Line edges labelled Boundary, * then they must be edges resulting from dimensional collapses. * In this case the other edges can be labelled Exterior for this Geometry. * * MD 8/11/01 - NOT True! The collapsed edges may in fact be in the interior of the Geometry, * which means the other edges should be labelled Interior for this Geometry. * Not sure how solve this... Possibly labelling needs to be split into several phases: * area label propagation, symLabel merging, then finally null label resolution. */ bool[] hasDimensionalCollapseEdge = { false, false }; foreach (var e in Edges) { Label label = e.Label; for (int geomi = 0; geomi < 2; geomi++) if (label.IsLine(geomi) && label.GetLocation(geomi) == Location.Boundary) hasDimensionalCollapseEdge[geomi] = true; } foreach (var e in Edges) { Label label = e.Label; for (int geomi = 0; geomi < 2; geomi++) { if (label.IsAnyNull(geomi)) { Location loc; if (hasDimensionalCollapseEdge[geomi]) loc = Location.Exterior; else { Coordinate p = e.Coordinate; loc = GetLocation(geomi, p, geomGraph); } label.SetAllLocationsIfNull(geomi, loc); } } } }
/// <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> public bool IsAreaLabelsConsistent(GeometryGraph geometryGraph) { ComputeEdgeEndLabels(geometryGraph.BoundaryNodeRule); return CheckAreaLabelsConsistent(0); }
/// <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]; Coordinate 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(holePt); if(outside) { validErr = new TopologyValidationError(TopologyValidationErrors.HoleOutsideShell, holePt); return; } } }
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) { _nonSimpleLocation = si.ProperIntersectionPoint; return false; } if (HasNonEndpointIntersection(graph)) return false; if (_isClosedEndpointsInInterior) { 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) { var nestedTester = new IndexedNestedRingTester(graph); foreach (ILinearRing innerHole in p.Holes) nestedTester.Add(innerHole); bool isNonNested = nestedTester.IsNonNested(); if (!isNonNested) validErr = new TopologyValidationError(TopologyValidationErrors.NestedHoles, nestedTester.NestedPoint); }
/// <summary> /// Tests that no edge intersection is the endpoint of a closed line. /// This ensures that closed lines are not touched at their endpoint, /// which is an interior point according to the Mod-2 rule /// To check this we compute the degree of each endpoint. /// The degree of endpoints of closed lines /// must be exactly 2. /// </summary> private bool HasClosedEndpointIntersection(GeometryGraph graph) { IDictionary<Coordinate, EndpointInfo> endPoints = new SortedDictionary<Coordinate, EndpointInfo>(); foreach (Edge e in graph.Edges) { //int maxSegmentIndex = e.MaximumSegmentIndex; bool isClosed = e.IsClosed; Coordinate p0 = e.GetCoordinate(0); AddEndpoint(endPoints, p0, isClosed); Coordinate p1 = e.GetCoordinate(e.NumPoints - 1); AddEndpoint(endPoints, p1, isClosed); } foreach (EndpointInfo eiInfo in endPoints.Values) { if (eiInfo.IsClosed && eiInfo.Degree != 2) { _nonSimpleLocation = eiInfo.Point; return true; } } return false; }
/// <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> /// /// </summary> /// <param name="geomGraph"></param> public ConnectedInteriorTester(GeometryGraph geomGraph) { _geomGraph = geomGraph; }
/// <summary> /// /// </summary> public bool IsAreaLabelsConsistent(GeometryGraph geometryGraph) { ComputeEdgeEndLabels(geometryGraph.BoundaryNodeRule); return(CheckAreaLabelsConsistent(0)); }