/// <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 = new PrecisionModel(g0.PrecisionModel); else ComputationPrecision = new PrecisionModel(g1.PrecisionModel); arg = new GeometryGraph[2]; arg[0] = new GeometryGraph(0, g0); arg[1] = new GeometryGraph(1, g1); }
/// <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(IList<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> /// /// </summary> /// <param name="geomGraph"></param> public virtual 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> /// Checks the validity of a polygon and sets the validErr flag. /// </summary> /// <param name="g"></param> private void CheckValidPolygon(IPolygon g) { CheckClosedRings(g); if (_validErr != null) return; GeometryGraph graph = new GeometryGraph(0, g); CheckConsistentArea(graph); if (_validErr != null) return; if (!IsSelfTouchingRingFormingHoleValid) { CheckNoSelfIntersectingRings(graph); if (_validErr != null) return; } CheckHolesInShell(g, graph); if (_validErr != null) return; CheckHolesNotNested(g, graph); if (_validErr != null) return; CheckConnectedInteriors(graph); }
/// <summary> /// /// </summary> /// <param name="g"></param> private void CheckValidMultipolygon(IMultiPolygon g) { foreach(Polygon 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(Polygon p in g.Geometries) { CheckHolesInShell(p, graph); if (_validErr != null) return; } foreach (Polygon 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="geomIndex"></param> /// <param name="p"></param> /// <param name="geom"></param> /// <returns></returns> public virtual Locations GetLocation(int geomIndex, Coordinate p, GeometryGraph[] geom) { // compute location only on demand if (_ptInAreaLocation[geomIndex] == Locations.Null) _ptInAreaLocation[geomIndex] = SimplePointInAreaLocator.Locate(p, geom[geomIndex].Geometry); return _ptInAreaLocation[geomIndex]; }
/// <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 (LinearRing innerHole in p.Holes) nestedTester.Add(innerHole); bool isNonNested = nestedTester.IsNonNested(); if (!isNonNested) _validErr = new TopologyValidationError(TopologyValidationErrors.NestedHoles, nestedTester.NestedPoint); }
/// <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(LinearRing shell, Polygon p, GeometryGraph graph) { IList<Coordinate> shellPts = shell.Coordinates; // test if shell is inside polygon shell LinearRing polyShell = (LinearRing)p.ExteriorRing; IList<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.NumHoles <= 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.NumHoles; i++) { LinearRing hole = (LinearRing)p.GetInteriorRingN(i); badNestedPt = CheckShellInsideHole(shell, hole, graph); if (badNestedPt == null) return; } _validErr = new TopologyValidationError(TopologyValidationErrors.NestedShells, badNestedPt); }
/// <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> /// 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="g0"></param> public GeometryGraphOperation(IGeometry g0) { ComputationPrecision = new PrecisionModel(g0.PrecisionModel); arg = new GeometryGraph[1]; arg[0] = new GeometryGraph(0, g0);; }
/// <summary> /// /// </summary> /// <param name="geomGraph"></param> public ConsistentAreaTester(GeometryGraph geomGraph) { this.geomGraph = geomGraph; }
/// <summary> /// /// </summary> /// <param name="graph"></param> public SimpleNestedRingTester(GeometryGraph graph) { _graph = graph; }
/// <summary> /// /// </summary> /// <param name="geomGraph"></param> public ConnectedInteriorTester(GeometryGraph geomGraph) { _geomGraph = geomGraph; }
/// <summary> /// /// </summary> /// <param name="g"></param> /// <param name="li"></param> /// <param name="includeProper"></param> /// <returns></returns> public virtual 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; }
/// <summary> /// /// </summary> /// <param name="graph"></param> private void CheckTooFewPoints(GeometryGraph graph) { if (graph.HasTooFewPoints) { _validErr = new TopologyValidationError(TopologyValidationErrors.TooFewPoints, graph.InvalidPoint); return; } }
/// <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; Coordinate p0 = e.GetCoordinate(0); AddEndpoint(endPoints, p0, isClosed); Coordinate 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="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="geom"></param> /// <returns></returns> private bool IsSimpleLinearGeometry(Geometry 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 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.NumHoles; i++) { LinearRing hole = (LinearRing)p.GetInteriorRingN(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; } } }
/// <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> /// 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(IGeometry mp, GeometryGraph graph) { for (int i = 0; i < mp.NumGeometries; i++) { Polygon p = (Polygon)mp.GetGeometryN(i); LinearRing shell = (LinearRing)p.ExteriorRing; for (int j = 0; j < mp.NumGeometries; j++) { if (i == j) continue; Polygon p2 = (Polygon)mp.GetGeometryN(j); CheckShellNotNested(shell, p2, graph); if (_validErr != null) return; } } }
/// <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 static Coordinate CheckShellInsideHole(LinearRing shell, LinearRing hole, GeometryGraph graph) { IList<Coordinate> shellPts = shell.Coordinates; IList<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> /// /// </summary> /// <param name="graph"></param> public SweeplineNestedRingTester(GeometryGraph graph) { _graph = graph; }
/// <summary> /// Checks validity of a LineString. /// Almost anything goes for lineStrings! /// </summary> /// <param name="g"></param> private void CheckValidLineString(IGeometry g) { GeometryGraph graph = new GeometryGraph(0, g); CheckTooFewPoints(graph); }
/// <summary> /// /// </summary> /// <param name="geom"></param> public virtual void ComputeLabelling(GeometryGraph[] geom) { ComputeEdgeEndLabels(); // 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. * * Notice 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 }; for (IEnumerator it = GetEnumerator(); it.MoveNext(); ) { EdgeEnd e = (EdgeEnd)it.Current; Label label = e.Label; for (int geomi = 0; geomi < 2; geomi++) if (label.IsLine(geomi) && label.GetLocation(geomi) == Locations.Boundary) hasDimensionalCollapseEdge[geomi] = true; } for (IEnumerator it = GetEnumerator(); it.MoveNext(); ) { EdgeEnd e = (EdgeEnd) it.Current; Label label = e.Label; for (int geomi = 0; geomi < 2; geomi++) { if (label.IsAnyNull(geomi)) { Locations loc; if (hasDimensionalCollapseEdge[geomi]) loc = Locations.Exterior; else { Coordinate p = e.Coordinate; loc = GetLocation(geomi, p, geom); } label.SetAllLocationsIfNull(geomi, loc); } } } }
/// <summary> /// /// </summary> /// <param name="arg"></param> public RelateComputer(GeometryGraph[] arg) { this.arg = arg; }
/// <summary> /// Checks validity of a LinearRing. /// </summary> /// <param name="g"></param> private void CheckValidRing(ILinearRing g) { CheckClosedRing(g); if (_validErr != null) return; GeometryGraph graph = new GeometryGraph(0, g); LineIntersector li = new RobustLineIntersector(); graph.ComputeSelfNodes(li, true); CheckNoSelfIntersectingRings(graph); }