/// <summary> /// Determines whether the specified closed polycurve contains (including the boundary) the /// specified test geometry. This method ignores the orientation. /// </summary> /// <param name="closedPolycurve"></param> /// <param name="targetSegments"></param> /// <param name="tolerance"></param> /// <param name="knownIntersections"></param> /// <returns></returns> public static bool PolycurveContainsXY( [NotNull] ISegmentList closedPolycurve, [NotNull] Linestring targetSegments, double tolerance, IEnumerable <SegmentIntersection> knownIntersections = null) { if (AreBoundsDisjoint(closedPolycurve, targetSegments, tolerance)) { return(false); } if (knownIntersections == null) { knownIntersections = SegmentIntersectionUtils.GetSegmentIntersectionsXY( closedPolycurve, targetSegments, tolerance); } // TODO: GeomUtils.IEnumerable<IntersectionPoint3D> GetIntersectionPointsWithDeviation() for better performance var intersectionPoints = GeomTopoOpUtils.GetIntersectionPoints(closedPolycurve, targetSegments, tolerance, knownIntersections, false); Pnt3D nonIntersectingTargetPnt = GetNonIntersectingTargetPoint(targetSegments, intersectionPoints); IEnumerable <Pnt3D> checkPoints = nonIntersectingTargetPnt != null ? new[] { nonIntersectingTargetPnt } : targetSegments.GetPoints(); return(checkPoints.All(p => PolycurveContainsXY(closedPolycurve, p, tolerance))); // The check points are assumed not to be on the boundary! }
public byte[] WriteMultipolygon(MultiPolycurve multipolygon, Ordinates ordinates = Ordinates.Xyz) { return(WriteMultipolygon( GeomTopoOpUtils.GetConnectedComponents(multipolygon, double.Epsilon).ToList(), ordinates)); }
/// <summary> /// Determines whether this linestring has linear self-intersections, e.g. because it is /// partially vertical. /// </summary> /// <param name="tolerance"></param> /// <returns></returns> public bool HasLinearSelfIntersections(double tolerance) { // Some segment must be covered by other segments. for (var i = 0; i < _segments.Count; i++) { Line3D segment = _segments[i]; if (segment.StartPoint.EqualsXY(segment.EndPoint, tolerance)) { // vertical continue; } var segmentIntersectingLines = GeomTopoOpUtils.GetLinearSelfIntersectionsXY(this, i, tolerance); if (segmentIntersectingLines.Count == 0) { continue; } return(true); } return(false); }
/// <summary> /// Returns the subcurve consisting of the points between the specified start location /// along the linestring and the end. If this linestring is closed and /// <paramref name="fullRing"/> is true, the resulting linestring will be a closed /// linestring that crosses the null point and ends at the start location. /// </summary> /// <param name="startSegmentIdx"></param> /// <param name="startRatioAlong"></param> /// <param name="clonePoints"></param> /// <param name="fullRing"></param> /// <returns></returns> public Linestring GetSubcurve(int startSegmentIdx, double startRatioAlong, bool clonePoints, bool fullRing = false) { if (startSegmentIdx < SegmentCount - 1 || startRatioAlong < 1) { Linestring toEndSubcurve = GetSubcurve(startSegmentIdx, startRatioAlong, SegmentCount - 1, 1, clonePoints); if (IsClosed && fullRing) { double epsilon = MathUtils.GetDoubleSignificanceEpsilon(XMax, YMax); Linestring fromStartSubcurve = GetSubcurve(0, 0, startSegmentIdx, startRatioAlong, clonePoints); return(GeomTopoOpUtils.MergeConnectedLinestrings( new List <Linestring> { toEndSubcurve, fromStartSubcurve }, null, epsilon)); } return(toEndSubcurve); } // The specified start is at the end point if (IsClosed) { return(new Linestring(GetPoints(0, null, clonePoints))); } throw new ArgumentOutOfRangeException(nameof(startSegmentIdx), "Start must be before the last point for un-closed linestring."); }
private static Linestring CreateWithBoundaryLoop(Linestring containingSourceRing, Linestring touchingInteriorRing, IntersectionPoint3D touchIntersection, double tolerance) { double sourceTouchPointRatio; int sourceTouchSegmentIdx = touchIntersection.GetLocalSourceIntersectionSegmentIdx( containingSourceRing, out sourceTouchPointRatio); List <Linestring> subcurves = new List <Linestring>(); subcurves.Add(containingSourceRing.GetSubcurve( 0, 0, sourceTouchSegmentIdx, sourceTouchPointRatio, false)); double targetTouchPointRatio; int targetTouchSegmentIdx = touchIntersection.GetLocalTargetIntersectionSegmentIdx( touchingInteriorRing, out targetTouchPointRatio); subcurves.Add(touchingInteriorRing.GetSubcurve( targetTouchSegmentIdx, targetTouchPointRatio, false, true)); subcurves.Add(containingSourceRing.GetSubcurve( sourceTouchSegmentIdx, sourceTouchPointRatio, containingSourceRing.SegmentCount - 1, 1, false)); Linestring withBoundaryLoop = GeomTopoOpUtils.MergeConnectedLinestrings(subcurves, null, tolerance); return(withBoundaryLoop); }
private static IEnumerable <Pnt3DIntersectionPoint> GetIntersections( Linestring lines1, Linestring lines2, ISpatialReference spatialReference, double xyTolerance) { // Increase tolerance by epsilon to find intersections that differ exactly by tolerance xyTolerance += MathUtils.GetDoubleSignificanceEpsilon(lines1.XMax, lines1.YMax); var intersectionPoints = GeomTopoOpUtils.GetIntersectionPoints(lines1, lines2, xyTolerance); var nanTargetVertices = intersectionPoints.Where(p => double.IsNaN(p.VirtualTargetVertex)); foreach (var nanVertex in nanTargetVertices) { _msg.Warn( $"Target vertex is NaN: {nanVertex}; linestring1: {lines1}; linestring2: {lines2}"); } return(intersectionPoints .Where(p => !double.IsNaN(p.VirtualTargetVertex)) .Where(p => p.Type == IntersectionPointType.Crossing) .Select(p => new Pnt3DIntersectionPoint(p.Point, spatialReference, GetZ(p, lines2)))); }
private static IList <RingGroup> CutRingGroupPlanar( [NotNull] RingGroup ringGroup, [NotNull] IPolyline cutLine, double tolerance, ChangeAlongZSource zSource, double zTolerance) { cutLine = GeometryFactory.Clone(cutLine); if (GeometryUtils.IsZAware(cutLine) && zSource != ChangeAlongZSource.Target) { ((IZAware)cutLine).DropZs(); } Plane3D plane = null; if (zSource == ChangeAlongZSource.SourcePlane) { plane = ChangeAlongZUtils.GetSourcePlane( ringGroup.ExteriorRing.GetPoints().ToList(), zTolerance); } GeometryUtils.Simplify(cutLine, true, true); MultiPolycurve cutLinestrings = new MultiPolycurve( GeometryUtils.GetPaths(cutLine).Select( cutPath => GeometryConversionUtils.CreateLinestring(cutPath))); IList <RingGroup> resultGroups = GeomTopoOpUtils.CutPlanar(ringGroup, cutLinestrings, tolerance); foreach (RingGroup resultPoly in resultGroups) { resultPoly.Id = ringGroup.Id; if (plane != null) { resultPoly.AssignUndefinedZs(plane); } else { resultPoly.InterpolateUndefinedZs(); } } Marshal.ReleaseComObject(cutLine); if (resultGroups.Count == 0) { // Return uncut original resultGroups.Add(ringGroup); } return(resultGroups); }
public bool IsOnTheRightSide([NotNull] ISegmentList source, [NotNull] Pnt3D testPoint, bool disregardOrientation = false) { Assert.True(source.IsClosed, "Source must be closed ring(s)"); Linestring sourceRing = source.GetPart(SourcePartIndex); double sourceRatio; int sourceSegmentIdx = GetLocalSourceIntersectionSegmentIdx(sourceRing, out sourceRatio); Line3D sourceSegment = sourceRing[sourceSegmentIdx]; if (sourceRatio > 0 && sourceRatio < 1) { // The intersection is on the source segment's interior return(sourceSegment.IsLeftXY(testPoint) < 0); } Line3D previousSegment, nextSegment; // Intersection at source vertex 0 or 1 -> get the 2 adjacent segments // ReSharper disable once CompareOfFloatsByEqualityOperator if (sourceRatio == 0) { previousSegment = sourceRing.PreviousSegmentInRing(sourceSegmentIdx, true); nextSegment = SegmentIntersection.IsSourceZeroLength2D ? sourceRing.NextSegmentInRing(sourceSegmentIdx, true) : sourceSegment; } else // sourceRatio == 1 { previousSegment = SegmentIntersection.IsSourceZeroLength2D ? sourceRing.PreviousSegmentInRing( sourceSegmentIdx, true) : sourceSegment; nextSegment = sourceRing.NextSegmentInRing(sourceSegmentIdx, true); } bool result = GeomTopoOpUtils.IsOnTheRightSide(previousSegment.StartPoint, Point, nextSegment.EndPoint, testPoint); if (!disregardOrientation && sourceRing.ClockwiseOriented == false) { result = !result; } return(result); }
public bool IsVerticalRing(double tolerance) { // NOTE: Just returning ClockwiseOriented == null reports incorrect results if the bottom // right has a cut-back. if (!IsClosed) { return(false); } // Optimization using 2D area double area2D = Math.Abs(GetArea2D()); double areaTolerance = GetLength2D() * tolerance; if (area2D > areaTolerance) { return(false); } // Each segment must be vertical or completely covered by other segments. for (var i = 0; i < _segments.Count; i++) { Line3D segment = _segments[i]; if (segment.StartPoint.EqualsXY(segment.EndPoint, tolerance)) { // vertical continue; } var segmentIntersectingLines = GeomTopoOpUtils.GetLinearSelfIntersectionsXY(this, i, tolerance); if (segmentIntersectingLines.Count != 1) { return(false); } Linestring segmentIntersectingLine = segmentIntersectingLines[0]; if (segmentIntersectingLine.StartPoint.EqualsXY(segment.StartPoint, tolerance) && segmentIntersectingLine.EndPoint.EqualsXY(segment.EndPoint, tolerance)) { continue; } return(false); } return(true); }
public bool Equals(Linestring x, Linestring y) { if (x == null && y == null) { return(true); } if (x == null || y == null) { return(false); } return(GeomTopoOpUtils.AreEqualXY(x, y, _tolerance)); }
/// <summary> /// Determines whether the provided rings touch in xy. If available, the spatial index of the second ring is used. /// </summary> /// <param name="ring1"></param> /// <param name="ring2"></param> /// <param name="tolerance"></param> /// <param name="ringsAreDisjoint">Whether the two rings are disjoint.</param> /// <param name="disregardRingOrientation">Whether the ring orientation can be used to determine /// the inside/outside. Use true if the rings are known not to be simple in terms of ring orientation.</param> /// <param name="ring2CanHaveLinearSelfIntersections">Whether the second ring can have self-intersections, /// for example because it is vertical. If true, it will be reported as touching if it does not intersect /// the interior of ring1, even if there are linear intersections in both directions. This does not exactly /// conform with Clementini logic.</param> /// <returns></returns> public static bool TouchesXY([NotNull] Linestring ring1, [NotNull] Linestring ring2, double tolerance, out bool ringsAreDisjoint, bool disregardRingOrientation = false, bool ring2CanHaveLinearSelfIntersections = false) { Assert.ArgumentCondition(ring1.IsClosed && ring2.IsClosed, "Both rings must be closed."); IEnumerable <SegmentIntersection> segmentIntersections = SegmentIntersectionUtils.GetSegmentIntersectionsXY( ring1, ring2, tolerance); ringsAreDisjoint = true; var allIntersections = new List <SegmentIntersection>(); // Quick checks and list collection bool?linearIntersectionsInverted = null; foreach (SegmentIntersection intersection in segmentIntersections) { ringsAreDisjoint = false; if (intersection.SingleInteriorIntersectionFactor != null) { return(false); } if (intersection.HasLinearIntersection) { //// TODO/Experimental: Test with more exception cases //if (intersection.IsPotentialPseudoLinearIntersection( // ring1[intersection.SourceIndex], ring2[intersection.TargetIndex], // tolerance)) //{ // continue; //} if (!intersection.IsSegmentZeroLength2D) { if (!disregardRingOrientation && !intersection.LinearIntersectionInOppositeDirection) { // Optimization if the ring orientation is known to be correct return(false); } if (linearIntersectionsInverted == null) { linearIntersectionsInverted = intersection.LinearIntersectionInOppositeDirection; } else if (linearIntersectionsInverted.Value != intersection.LinearIntersectionInOppositeDirection && !ring2CanHaveLinearSelfIntersections) { return(false); } } } allIntersections.Add(intersection); } IList <IntersectionPoint3D> intersectionPoints = GeomTopoOpUtils.GetIntersectionPoints( ring1, ring2, tolerance, allIntersections, false); if (HasSourceCrossingIntersections(ring1, ring2, intersectionPoints)) { return(false); } // No intersection or no deviation of target from source or all deviations to the same side bool contained = RingsContainEachOther(ring1, ring2, intersectionPoints, tolerance, disregardRingOrientation, ring2CanHaveLinearSelfIntersections); ringsAreDisjoint = ringsAreDisjoint && !contained; return(!contained); }
private static IList <IGeometry> TryCutXY( IPolygon inputPolygon, IPolyline cutPolyline, ChangeAlongZSource zSource) { // TODO: // In order to avoid the arbitrary grouping of multipart polygons, try to apply left/right logic // provided by GeomTopoOpUtils double tolerance = GeometryUtils.GetXyTolerance(inputPolygon); double zTolerance = GeometryUtils.GetZTolerance(inputPolygon); MultiPolycurve inputMultipoly = GeometryConversionUtils.CreateMultiPolycurve(inputPolygon); var cutLine = GeometryFactory.Clone(cutPolyline); if (GeometryUtils.IsZAware(cutLine) && zSource != ChangeAlongZSource.Target) { ((IZAware)cutLine).DropZs(); } Plane3D plane = null; if (zSource == ChangeAlongZSource.SourcePlane) { plane = ChangeAlongZUtils.GetSourcePlane( inputMultipoly.GetPoints().ToList(), zTolerance); } GeometryUtils.Simplify(cutLine, true, true); MultiPolycurve cutLinestrings = GeometryConversionUtils.CreateMultiPolycurve(cutLine); bool isMultipart = GeometryUtils.GetExteriorRingCount(inputPolygon) > 1; IList <MultiLinestring> resultGeoms = GeomTopoOpUtils.CutXY(inputMultipoly, cutLinestrings, tolerance, !isMultipart); var result = new List <IGeometry>(); foreach (MultiLinestring resultPoly in resultGeoms) { if (plane != null) { resultPoly.AssignUndefinedZs(plane); } else { resultPoly.InterpolateUndefinedZs(); } result.Add(GeometryConversionUtils.CreatePolygon(inputPolygon, resultPoly.GetLinestrings())); } Marshal.ReleaseComObject(cutLine); return(result.Count == 0 ? null : result); }
/// <summary> /// Moves from one intersection to the next by /// - first following the source /// - at each intersection taking the right-most (alternatively, the lef-most, depending /// on <see cref="PreferredTurnDirection"/>) turn until reaching the start again. /// </summary> /// <param name="startIntersections"></param> /// <returns></returns> public IList <Linestring> FollowSubcurvesClockwise( [NotNull] ICollection <IntersectionPoint3D> startIntersections) { IList <Linestring> result = new List <Linestring>(); var subcurveInfos = new List <IntersectionRun>(); while (startIntersections.Count > 0) { subcurveInfos.Clear(); bool onlyFollowingSource = true; IntersectionPoint3D startIntersection = startIntersections.First(); startIntersections.Remove(startIntersection); IntersectionPoint3D previousIntersection = startIntersection; Pnt3D ringStart = null; foreach (IntersectionRun next in NavigateSubcurves(startIntersection)) { IntersectionPoint3D nextIntersection = next.NextIntersection; subcurveInfos.Add(next); Pnt3D startPoint; if (next.ContainsSourceStart(out startPoint)) { ringStart = startPoint; } if (next.ContinuingOnSource) { if (startIntersections.Contains(previousIntersection)) { // Remove, if we follow the source through other start. This happens with vertical rings. //startIntersections.Remove(previousIntersection); } } else { onlyFollowingSource = false; } previousIntersection = nextIntersection; } // At some point the result must deviate from source otherwise the target does not cut it if (!onlyFollowingSource) { // Finish ring result.Add(GeomTopoOpUtils.MergeConnectedLinestrings( subcurveInfos.Select(i => i.Subcurve).ToList(), ringStart, Tolerance)); foreach (int sourceIdx in subcurveInfos.Select( i => i.NextIntersection.SourcePartIndex)) { IntersectedSourcePartIndexes.Add(sourceIdx); } foreach (int targetIdx in subcurveInfos.Select( i => i.NextIntersection.TargetPartIndex)) { IntersectedTargetPartIndexes.Add(targetIdx); } } } return(result); }
private static int GetContainingRingIndex([NotNull] MultiLinestring polygon, [NotNull] Linestring containedRing, double tolerance, out bool ringsAreEqual, out IntersectionPoint3D touchPoint) { IList <IntersectionPoint3D> intersectionPoints = GeomTopoOpUtils.GetIntersectionPoints(polygon, containedRing, tolerance); ringsAreEqual = false; touchPoint = null; // Equal to outer ring -> removes outer ring in original // or equal to inner ring -> ignore cookie cutter if (intersectionPoints.Count == 2 && intersectionPoints[0].SourcePartIndex == intersectionPoints[1].SourcePartIndex && intersectionPoints[0].Point.Equals(intersectionPoints[1].Point) && intersectionPoints[0].Type == IntersectionPointType.LinearIntersectionStart && intersectionPoints[1].Type == IntersectionPointType.LinearIntersectionEnd) { ringsAreEqual = true; return(intersectionPoints[0].SourcePartIndex); } var outerRingIntersections = intersectionPoints .Where(i => polygon.GetLinestring(i.SourcePartIndex).ClockwiseOriented == true) .ToList(); // Touching outer ring in one point -> boundary loop in original if (outerRingIntersections.Count > 0) { Assert.True(outerRingIntersections.Count < 2, "Unexpected number of touching points."); touchPoint = outerRingIntersections[0]; return(touchPoint.SourcePartIndex); } // Inside an inner ring -> ignore cookie cutter for (int i = 0; i < polygon.PartCount; i++) { Linestring ring = polygon.GetLinestring(i); if (ring.ClockwiseOriented == true) { continue; } int currentIdx = i; bool?areaContainsXY = GeomRelationUtils.AreaContainsXY( ring, containedRing, intersectionPoints.Where(ip => ip.SourcePartIndex == currentIdx), tolerance, true); if (areaContainsXY == true) { return(i); } } // Inside an outer ring but not an inner ring: Add as island for (int i = 0; i < polygon.PartCount; i++) { Linestring ring = polygon.GetLinestring(i); if (ring.ClockwiseOriented == false) { continue; } if (GeomRelationUtils.AreaContainsXY(ring, containedRing.StartPoint, tolerance) == true) { return(i); } } return(-1); }