private static FeatureCutter CreateFeatureCutter([NotNull] ApplyCutLinesRequest request, out IList <IFeature> targetFeatures) { GetFeatures(request.CalculationRequest.SourceFeatures, request.CalculationRequest.TargetFeatures, request.CalculationRequest.ClassDefinitions, out IList <IFeature> sourceFeatures, out targetFeatures); ChangeAlongZSource zSource = (ChangeAlongZSource)request.ChangedVerticesZSource; DatasetSpecificSettingProvider <ChangeAlongZSource> zSourceProvider = new DatasetSpecificSettingProvider <ChangeAlongZSource>( "Z values for changed vertices", zSource); var cutter = new FeatureCutter(sourceFeatures) { ZSourceProvider = zSourceProvider }; if (request.InsertVerticesInTarget) { cutter.TargetFeatures = targetFeatures; } return(cutter); }
private static IList <IGeometry> TryCutRingGroups( 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 var existingFeature = new List <IPolygon>(); var newFeatures = new List <IPolygon>(); var cutResultsPerConnectedComponent = TryCutConnectedComponents(inputPolygon, cutPolyline, zSource); foreach (IList <RingGroup> cutRingGroups in cutResultsPerConnectedComponent) { IRing ringTemplate = GeometryFactory.CreateEmptyRing(inputPolygon); var cutResults = cutRingGroups.Select( rg => GeometryConversionUtils.CreatePolygon( inputPolygon, ringTemplate, rg)) .ToList(); var largest = GeometryUtils.GetLargestGeometry(cutResults); foreach (IPolygon cutComponent in cutResults) { if (cutComponent == largest) { existingFeature.Add(cutComponent); } else { newFeatures.Add(cutComponent); } } } if (newFeatures.Count == 0) { // no cut happened: return(null); } var result = new List <IGeometry> { GeometryUtils.Union(existingFeature) }; result.AddRange(newFeatures.Cast <IGeometry>()); return(result); }
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 void CannotCutMultipartPolygonWithCutLineWithinTolerance() { ISpatialReference lv95 = SpatialReferenceUtils.CreateSpatialReference( WellKnownHorizontalCS.LV95); IPolygon originalPoly = GeometryFactory.CreatePolygon( GeometryFactory.CreateEnvelope(2600000, 1200000, 500, 100, 100, lv95)); IPolygon innerRingPoly = GeometryFactory.CreatePolygon( GeometryFactory.CreateEnvelope(2600000, 1200000, 500, 10, 10, lv95)); IPolygon secondPart = GeometryFactory.CreatePolygon( GeometryFactory.CreateEnvelope(2700000, 1200000, 500, 100, 100, lv95)); ((IGeometryCollection)originalPoly).AddGeometryCollection( (IGeometryCollection)innerRingPoly); ((IGeometryCollection)originalPoly).AddGeometryCollection( (IGeometryCollection)secondPart); GeometryUtils.Simplify(originalPoly); // The cut line runs along the second ring (and diverts off to the outside) IPolyline cutLine = GeometryFactory.CreateLine( GeometryFactory.CreatePoint(2700000 + 50, 1200000 - 30), GeometryFactory.CreatePoint(2700000 + 50, 1200000), GeometryFactory.CreatePoint(2700000 + 130, 1200000 + 50)); cutLine.SpatialReference = lv95; bool customIntersectOrig = IntersectionUtils.UseCustomIntersect; try { IntersectionUtils.UseCustomIntersect = false; const ChangeAlongZSource zSource = ChangeAlongZSource.InterpolatedSource; var resultsAo = CutGeometryUtils.TryCut(originalPoly, cutLine, zSource); IntersectionUtils.UseCustomIntersect = true; var resultsGeom = CutGeometryUtils.TryCut(originalPoly, cutLine, zSource); Assert.Null(resultsAo); Assert.Null(resultsGeom); } finally { IntersectionUtils.UseCustomIntersect = customIntersectOrig; } }
private ChangeAlongZSource DetermineZSource(IFeature feature) { string note = null; ChangeAlongZSource zSource = ZSourceProvider?.GetValue(feature, out note) ?? ChangeAlongZSource.Target; if (note != null) { _msg.Info(note); } return(zSource); }
public static string GetDisplayText(ChangeAlongZSource zSource) { switch (zSource) { case ChangeAlongZSource.Target: return("Target"); case ChangeAlongZSource.InterpolatedSource: return("Interpolated source"); case ChangeAlongZSource.SourcePlane: return("Source plane"); default: throw new ArgumentOutOfRangeException( $"Unknown ChangeAlongZSource: {zSource}"); } }
private void ProcessFeature([NotNull] IFeature feature, [NotNull] IPolycurve overlappingGeometry) { IGeometry featureShape = feature.Shape; if (GeometryUtils.Disjoint(featureShape, overlappingGeometry)) { return; } IList <IGeometry> overlappingResults; string note = null; ChangeAlongZSource zSource = ZSourceProvider?.GetValue(feature, out note) ?? ChangeAlongZSource.Target; if (note != null) { _msg.Info(note); } var sourceMultipatch = featureShape as IMultiPatch; IList <IGeometry> modifiedGeometries = sourceMultipatch != null ? RemoveOverlaps(sourceMultipatch, (IPolygon)overlappingGeometry, zSource, out overlappingResults) : RemoveOverlap((IPolycurve)featureShape, overlappingGeometry, zSource, out overlappingResults); // additional check for undefined z values - this happens if the target has no Zs or UseSourceZs is active // -> currently the undefined Zs are interpolated before storing if (HasAnyGeometryUndefinedZs(modifiedGeometries)) { _msg.DebugFormat( "The result geometry of {0} has undefined z values.", GdbObjectUtils.ToString(feature)); } OverlapResultGeometries singleFeatureResult = new OverlapResultGeometries(feature, modifiedGeometries, overlappingResults); Result.ResultsByFeature.Add(singleFeatureResult); }
public static T PrepareCutPolylineZs <T>([NotNull] T cutPolycurve, ChangeAlongZSource zSource) where T : IPolycurve { // Copy from CutGeometryUtils if (zSource == ChangeAlongZSource.Target || !GeometryUtils.IsZAware(cutPolycurve)) { return(cutPolycurve); } T result = GeometryFactory.Clone(cutPolycurve); // Do not make z-unaware to avoid the Z values to be restored in SegmentReplacementUtils.EnsureZs() ((IZAware)result).DropZs(); return(result); }
private static void CutAndAssignToFootprintParts( [NotNull] GeometryPart multipatchPart, [NotNull] IPolyline cutLine, [NotNull] IDictionary <RingGroup, List <RingGroup> > splitPartsByFootprintPart, ChangeAlongZSource zSource) { double tolerance = GeometryUtils.GetXyTolerance(multipatchPart.FirstGeometry); double zTolerance = GeometryUtils.GetZTolerance(multipatchPart.FirstGeometry); RingGroup ringGroup = GeometryConversionUtils.CreateRingGroup(multipatchPart); int pointId; if (GeometryUtils.HasUniqueVertexId( Assert.NotNull(multipatchPart.MainOuterRing), out pointId)) { ringGroup.Id = pointId; } bool inverted = ringGroup.ClockwiseOriented == false; if (inverted) { ringGroup.ReverseOrientation(); } IList <RingGroup> cutRingGroups = CutRingGroupPlanar(ringGroup, cutLine, tolerance, zSource, zTolerance); AssignResultsToFootprintParts(cutRingGroups, splitPartsByFootprintPart, tolerance); if (inverted) { foreach (RingGroup cutRingGroup in cutRingGroups) { cutRingGroup.ReverseOrientation(); } } }
public static IList <IGeometry> TryCut([NotNull] IPolygon inputPolygon, [NotNull] IPolyline cutPolyline, ChangeAlongZSource zSource) { IList <IGeometry> result; double originalArea = ((IArea)inputPolygon).Area; if (IntersectionUtils.UseCustomIntersect && !GeometryUtils.HasNonLinearSegments(inputPolygon) && !GeometryUtils.HasNonLinearSegments(cutPolyline) && !GeometryUtils.IsMAware(inputPolygon)) { result = TryCutXY(inputPolygon, cutPolyline, zSource); } else { result = TryCutArcObjects(inputPolygon, cutPolyline, zSource); } if (result != null) { double newArea = result.Sum(geometry => ((IArea)geometry).Area); double tolerance = originalArea * 0.001; if (!MathUtils.AreEqual(newArea, originalArea, tolerance)) { _msg.DebugFormat("Input Polygon:{0}{1}", Environment.NewLine, GeometryUtils.ToXmlString(inputPolygon)); _msg.DebugFormat("Cut polyline:{0}{1}", Environment.NewLine, GeometryUtils.ToXmlString(cutPolyline)); throw new AssertionException( "The cut operation has significantly changed the polygon area! Please report this polygon and the cut line."); } } return(result); }
private static IList <IList <RingGroup> > TryCutConnectedComponents( IPolygon inputPolygon, IPolyline cutPolyline, ChangeAlongZSource zSource) { double tolerance = GeometryUtils.GetXyTolerance(inputPolygon); double zTolerance = GeometryUtils.GetZTolerance(inputPolygon); var result = new List <IList <RingGroup> >(); foreach (IPolygon connectedComponent in GeometryUtils.GetConnectedComponents( inputPolygon)) { RingGroup ringGroup = GeometryConversionUtils.CreateRingGroup(connectedComponent); IList <RingGroup> cutRingGroups = CutRingGroupPlanar( ringGroup, cutPolyline, tolerance, zSource, zTolerance); result.Add(cutRingGroups); } return(result); }
public void CanCutPolygonWithZSourcePlane() { ISpatialReference lv95 = SpatialReferenceUtils.CreateSpatialReference( WellKnownHorizontalCS.LV95); IPolygon originalPoly = GeometryFactory.CreatePolygon( GeometryFactory.CreateEnvelope(2600000, 1200000, 500, 100, 100, lv95)); IPolygon innerRingPoly = GeometryFactory.CreatePolygon( GeometryFactory.CreateEnvelope(2600000, 1200000, 500, 10, 10, lv95)); Plane3D plane = Plane3D.FitPlane(new List <Pnt3D> { new Pnt3D(2600000, 1200000, 500), new Pnt3D(2600100, 1200100, 580), new Pnt3D(2600000, 1200100, 550) }); ((IGeometryCollection)originalPoly).AddGeometryCollection( (IGeometryCollection)innerRingPoly); GeometryUtils.Simplify(originalPoly); ChangeAlongZUtils.AssignZ((IPointCollection)originalPoly, plane); // The non-z-aware cut line cuts north of the inner ring -> the inner ring should be assigned to the southern result IPolyline cutLine = GeometryFactory.CreateLine( GeometryFactory.CreatePoint(2600000 - 100, 1200020), GeometryFactory.CreatePoint(2600000 - 40, 1200020), GeometryFactory.CreatePoint(2600000 - 40, 1200040), GeometryFactory.CreatePoint(2600000 + 40, 1200040), GeometryFactory.CreatePoint(2600000 + 40, 1200020), GeometryFactory.CreatePoint(2600000 + 100, 1200020)); cutLine.SpatialReference = lv95; bool customIntersectOrig = IntersectionUtils.UseCustomIntersect; try { IntersectionUtils.UseCustomIntersect = false; const ChangeAlongZSource zSource = ChangeAlongZSource.SourcePlane; var resultsAo = CutGeometryUtils.TryCut(originalPoly, cutLine, zSource); IntersectionUtils.UseCustomIntersect = true; var resultsGeom = CutGeometryUtils.TryCut(originalPoly, cutLine, zSource); Assert.NotNull(resultsAo); Assert.NotNull(resultsGeom); EnsureCutResult(resultsAo, originalPoly, plane, 3); EnsureCutResult(resultsGeom, originalPoly, plane, 3); // NOTE: The results have different start/end points, therefore GeometryUtils.AreEqual is false Assert.True(GeometryUtils.AreEqualInXY(resultsAo[0], resultsGeom[0])); Assert.True(GeometryUtils.AreEqualInXY(resultsAo[1], resultsGeom[1])); Assert.AreEqual(0, IntersectionUtils.GetZOnlyDifferenceLines( (IPolycurve)resultsAo[0], (IPolycurve)resultsGeom[0], 0.001) .Length); Assert.AreEqual(0, IntersectionUtils.GetZOnlyDifferenceLines( (IPolycurve)resultsAo[1], (IPolycurve)resultsGeom[1], 0.001) .Length); } finally { IntersectionUtils.UseCustomIntersect = customIntersectOrig; } }
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> /// Cuts the provided multipatch along the specified cutLine. /// </summary> /// <param name="multipatch"></param> /// <param name="cutLine"></param> /// <param name="zSource">The source of the Z values to be used for the new vertices of the result features.</param> /// <param name="usedCutLine">The cut result containing information about the cut operation.</param> /// <param name="nonSimpleFootprintAction">The action to be performed if one of the result multipatches /// has a degenerate footprint (because it is a sliver polygon with sub-tolerance self intersections).</param> /// <returns></returns> public static IDictionary <IPolygon, IMultiPatch> TryCut( [NotNull] IMultiPatch multipatch, [NotNull] IPolyline cutLine, ChangeAlongZSource zSource, [CanBeNull] CutPolyline usedCutLine = null, DegenerateMultipatchFootprintAction nonSimpleFootprintAction = DegenerateMultipatchFootprintAction.Throw) { Assert.ArgumentNotNull(multipatch, nameof(multipatch)); Assert.ArgumentNotNull(cutLine, nameof(cutLine)); bool allRings = GeometryUtils.IsRingBasedMultipatch(multipatch); Assert.True(allRings, "The multipatch geometry contains triangles, triangle fans or triangle strips, which are currently not supported"); // NOTE: ITopologicalOperator4 is not supported by multipatch, ITopologicalOperator3.Cut returns non-Z-aware polygons // -> Use GeomUtils.Cut implementation which could eventually classify the left/right parts and avoid footprint-cutting // only to find the correct assignment to result features. // TODO: Create footprint as RingGroups directly from multipatch rings IPolygon footprint = GeometryFactory.CreatePolygon(multipatch); IList <IGeometry> cutFootprintParts = TryCutRingGroups(footprint, cutLine, ChangeAlongZSource.Target); if (cutFootprintParts == null || cutFootprintParts.Count == 0) { _msg.DebugFormat( "Not even the footprint could be cut. No multipatch cutting performed."); if (usedCutLine != null) { usedCutLine.Polyline = cutLine; usedCutLine.SuccessfulCut = false; } return(new Dictionary <IPolygon, IMultiPatch>(0)); } // Get the 'connected components', i.e. outer ring with respective inner rings. IList <GeometryPart> multipatchParts = GeometryPart.FromGeometry(multipatch).ToList(); Dictionary <RingGroup, List <RingGroup> > splitPartsByFootprintPart = PrepareSplitPartsDictionary(cutFootprintParts); foreach (GeometryPart part in multipatchParts) { CutAndAssignToFootprintParts(part, cutLine, splitPartsByFootprintPart, zSource); } // make a separate multipatch per footprint-part with all respective ring-groups Dictionary <IPolygon, IMultiPatch> result = BuildResultMultipatches( multipatch, splitPartsByFootprintPart, nonSimpleFootprintAction); if (usedCutLine != null && splitPartsByFootprintPart.Values.Count > 1) { // TODO: Extract actual cutLine from inBound/outBound intersections usedCutLine.Polyline = cutLine; usedCutLine.SuccessfulCut = false; } return(result); }
private static List <IGeometry> TryCutArcObjects( [NotNull] IPolygon inputPolygon, [NotNull] IPolyline cutPolyline, ChangeAlongZSource zSource) { cutPolyline = ChangeAlongZUtils.PrepareCutPolylineZs(cutPolyline, zSource); var existingFeature = new List <IPolygon>(); var newFeatures = new List <IPolygon>(); foreach (IPolygon connectedComponent in GeometryUtils.GetConnectedComponents( inputPolygon)) { Plane3D plane = null; if (zSource == ChangeAlongZSource.SourcePlane) { double zTolerance = GeometryUtils.GetZTolerance(inputPolygon); plane = ChangeAlongZUtils.GetSourcePlane( GeometryConversionUtils.GetPntList(connectedComponent), zTolerance); } var cutComponents = TryCut(connectedComponent, cutPolyline); if (cutComponents == null) { existingFeature.Add(connectedComponent); } else { var largest = GeometryUtils.GetLargestGeometry(cutComponents); foreach (IPolygon cutComponent in cutComponents.Cast <IPolygon>()) { if (plane != null) { ChangeAlongZUtils.AssignZ((IPointCollection)cutComponent, plane); } else if (zSource == ChangeAlongZSource.InterpolatedSource && GeometryUtils.IsZAware(cutComponent)) { ((IZ)cutComponent).CalculateNonSimpleZs(); } if (cutComponent == largest) { existingFeature.Add(cutComponent); } else { newFeatures.Add(cutComponent); } } } } if (newFeatures.Count == 0) { return(null); } var result = new List <IGeometry> { GeometryUtils.Union(existingFeature), }; result.AddRange(newFeatures.Cast <IGeometry>()); return(result); }
private IList <IGeometry> RemoveOverlap( [NotNull] IPolycurve fromGeometry, [NotNull] IPolycurve overlaps, ChangeAlongZSource zSource, out IList <IGeometry> overlappingResults) { Assert.ArgumentNotNull(fromGeometry, nameof(fromGeometry)); Assert.ArgumentNotNull(overlaps, nameof(overlaps)); overlaps = ChangeAlongZUtils.PrepareCutPolylineZs(overlaps, zSource); Plane3D plane = null; if (zSource == ChangeAlongZSource.SourcePlane) { plane = ChangeAlongZUtils.GetSourcePlane( GeometryConversionUtils.GetPntList(fromGeometry), GeometryUtils.GetZTolerance(fromGeometry)); } IGeometry rawResult = GetDifference(fromGeometry, overlaps); if (plane != null) { ChangeAlongZUtils.AssignZ((IPointCollection)rawResult, plane); } int originalPartCount = GetRelevantPartCount(fromGeometry); int resultPartCount = GetRelevantPartCount(rawResult); // TODO: This works for simple cases. To be correct the difference operation and part comparison would have to be // done on a per-part basis, because in the same operation one part could be added and one removed. // Just comparing part counts before and after is misleading in such cases. if (resultPartCount > originalPartCount) { Result.ResultHasMultiparts = true; } IList <IGeometry> result = new List <IGeometry>(); // TODO explode only those input parts that where cut into more than one result part // --> preserve other input parts in original geometry if (rawResult is IPolycurve && _explodeMultipartResult && IsMultipart(rawResult)) { // Exploding all selected multipart geos // to explode geos only if not avoidable, // use GetPositivePartCount and compare counts before and after cut operation foreach (IGeometry geometry in GeometryUtils.Explode(rawResult)) { var part = (IPolycurve)geometry; result.Add(part); } if (((IGeometryCollection)fromGeometry).GeometryCount > 1) { // TODO: Fix this situation by checking which individual parts were split by the overlap and only turn them into a new feature _msg.Warn( "The selection included multi-part geometry. Storing this geometry generated several copies of the feature."); } } else { result.Add(rawResult); } overlappingResults = _storeOverlapsAsNewFeatures ? CalculateOverlappingGeometries(fromGeometry, overlaps) : null; return(result); }
private IList <IGeometry> RemoveOverlaps( [NotNull] IMultiPatch sourceMultipatch, [NotNull] IPolygon overlaps, ChangeAlongZSource zSource, out IList <IGeometry> overlappingMultiPatches) { // NOTE: // Difference / Intersect are useless for multipatches, they can only return polygons // -> Get the relevant boundary segments of the overlap and use the FeatureCutter to cut the multipatch. // Then select the correct (non-intersecting part) IPolyline interiorIntersection = GetCutLine(sourceMultipatch, overlaps); Assert.False(interiorIntersection.IsEmpty, "Cut line is empty."); IDictionary <IPolygon, IMultiPatch> cutResultByFootprintPart = CutGeometryUtils.TryCut(sourceMultipatch, interiorIntersection, zSource); IList <IGeometry> result = new List <IGeometry>(cutResultByFootprintPart.Count); overlappingMultiPatches = _storeOverlapsAsNewFeatures ? new List <IGeometry>( cutResultByFootprintPart.Count) : null; // now select the multipatch of the right side... foreach (KeyValuePair <IPolygon, IMultiPatch> footprintWithMultipatch in cutResultByFootprintPart) { IMultiPatch resultMultipatch = footprintWithMultipatch.Value; if (!GeometryUtils.InteriorIntersects(footprintWithMultipatch.Key, overlaps)) { result.Add(resultMultipatch); } else { overlappingMultiPatches?.Add(resultMultipatch); } } if (!_explodeMultipartResult) { // merge the different parts into one: if (result.Count > 1) { result = new List <IGeometry> { GeometryUtils.Union(result) }; } if (overlappingMultiPatches?.Count > 1) { overlappingMultiPatches = new List <IGeometry> { GeometryUtils.Union(overlappingMultiPatches) }; } } return(result); }