From 9aa8249ebf95345979bd32fe44472567125efd65 Mon Sep 17 00:00:00 2001 From: KatKatKateryna <89912278+KatKatKateryna@users.noreply.github.com> Date: Thu, 30 May 2024 16:55:31 +0800 Subject: [PATCH] CNX-9445 arc gis precision check for acrs circles curves ellipse (#3428) * clean segment converters * send densified curved segments using Map CRS precision * receive circles without segmentation * convert precision points to meters; use factoryCode for unit conversion to Speckle; add USfeet unit * remove unused conversions * fix * remove line segment converter * merge conflicts --- .../Bindings/ArcGISSendBinding.cs | 2 +- .../ArcGISToSpeckleUnitConverter.cs | 31 ++++----- .../PolylineFeatureToSpeckleConverter.cs | 29 +++++++- .../BezierSegmentToSpeckleConverter.cs | 58 ---------------- .../EllipticArcSegmentToSpeckleConverter.cs | 67 ------------------- .../CircleToHostConverter.cs | 45 ++++++------- .../SegmentCollectionToSpeckleConverter.cs | 44 ++++++------ 7 files changed, 87 insertions(+), 189 deletions(-) delete mode 100644 DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Geometry/BezierSegmentToSpeckleConverter.cs delete mode 100644 DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Geometry/EllipticArcSegmentToSpeckleConverter.cs diff --git a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs index c5db590209..8e9fba23bd 100644 --- a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs +++ b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs @@ -261,7 +261,7 @@ public List GetSendSettings() public async Task Send(string modelCardId) { //poc: dupe code between connectors - using IUnitOfWork> unitOfWork = _unitOfWorkFactory.Resolve>(); + using var unitOfWork = _unitOfWorkFactory.Resolve>(); try { if (_store.GetModelById(modelCardId) is not SenderModelCard modelCard) diff --git a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISToSpeckleUnitConverter.cs b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISToSpeckleUnitConverter.cs index a40e3d15d7..d36f1f32a6 100644 --- a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISToSpeckleUnitConverter.cs +++ b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISToSpeckleUnitConverter.cs @@ -7,31 +7,28 @@ namespace Speckle.Converters.ArcGIS3; public class ArcGISToSpeckleUnitConverter : IHostToSpeckleUnitConverter { - private static readonly IReadOnlyDictionary s_unitMapping = Create(); + private readonly Dictionary _unitMapping = new(); - private static IReadOnlyDictionary Create() + private ArcGISToSpeckleUnitConverter() { - var dict = new Dictionary(); // POC: we should have a unit test to confirm these are as expected and don't change - //_unitMapping[LinearUnit.] = Units.Meters; - dict[LinearUnit.Millimeters.Name] = Units.Millimeters; - dict[LinearUnit.Centimeters.Name] = Units.Centimeters; - dict[LinearUnit.Meters.Name] = Units.Meters; - dict[LinearUnit.Kilometers.Name] = Units.Kilometers; - dict[LinearUnit.Inches.Name] = Units.Inches; - dict[LinearUnit.Feet.Name] = Units.Feet; - dict[LinearUnit.Yards.Name] = Units.Yards; - dict[LinearUnit.Miles.Name] = Units.Miles; - //_unitMapping[LinearUnit.Decimeters] = Units.; - //_unitMapping[LinearUnit.NauticalMiles] = Units.; - return dict; + // more units: https://pro.arcgis.com/en/pro-app/latest/sdk/api-reference/topic8349.html + _unitMapping[LinearUnit.Millimeters.FactoryCode] = Units.Millimeters; + _unitMapping[LinearUnit.Centimeters.FactoryCode] = Units.Centimeters; + _unitMapping[LinearUnit.Meters.FactoryCode] = Units.Meters; + _unitMapping[LinearUnit.Kilometers.FactoryCode] = Units.Kilometers; + _unitMapping[LinearUnit.Inches.FactoryCode] = Units.Inches; + _unitMapping[LinearUnit.Feet.FactoryCode] = Units.Feet; + _unitMapping[LinearUnit.Yards.FactoryCode] = Units.Yards; + _unitMapping[LinearUnit.Miles.FactoryCode] = Units.Miles; + _unitMapping[9003] = Units.USFeet; } public string ConvertOrThrow(Unit hostUnit) { - var linearUnit = LinearUnit.CreateLinearUnit(hostUnit.Wkt).Name; + int linearUnit = LinearUnit.CreateLinearUnit(hostUnit.Wkt).FactoryCode; - if (s_unitMapping.TryGetValue(linearUnit, out string? value)) + if (_unitMapping.TryGetValue(linearUnit, out string? value)) { return value; } diff --git a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Features/PolylineFeatureToSpeckleConverter.cs b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Features/PolylineFeatureToSpeckleConverter.cs index 2af833bed4..b28ad710c4 100644 --- a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Features/PolylineFeatureToSpeckleConverter.cs +++ b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Features/PolylineFeatureToSpeckleConverter.cs @@ -1,3 +1,5 @@ +using ArcGIS.Desktop.Mapping; +using Speckle.Converters.Common; using Speckle.Converters.Common.Objects; namespace Speckle.Converters.ArcGIS3.Features; @@ -5,17 +7,40 @@ namespace Speckle.Converters.ArcGIS3.Features; public class PolyineFeatureToSpeckleConverter : ITypedConverter> { private readonly ITypedConverter _segmentConverter; + private readonly IConversionContextStack _contextStack; - public PolyineFeatureToSpeckleConverter(ITypedConverter segmentConverter) + public PolyineFeatureToSpeckleConverter( + ITypedConverter segmentConverter, + IConversionContextStack contextStack + ) { _segmentConverter = segmentConverter; + _contextStack = contextStack; } public IReadOnlyList Convert(ACG.Polyline target) { // https://pro.arcgis.com/en/pro-app/latest/sdk/api-reference/topic8480.html List polylineList = new(); - foreach (var segmentCollection in target.Parts) + ACG.Polyline polylineToConvert = target; + + // segmentize the polylines with curves using precision value of the Map's Spatial Reference + if (target.HasCurves is true) + { + double tolerance = _contextStack.Current.Document.SpatialReference.XYTolerance; + double conversionFactorToMeter = _contextStack.Current.Document.SpatialReference.Unit.ConversionFactor; + var densifiedPolyline = ACG.GeometryEngine.Instance.DensifyByDeviation( + target, + tolerance * conversionFactorToMeter + ); + polylineToConvert = (ACG.Polyline)densifiedPolyline; + if (densifiedPolyline == null) + { + throw new ArgumentException("Polyline densification failed"); + } + } + + foreach (var segmentCollection in polylineToConvert.Parts) { polylineList.Add(_segmentConverter.Convert(segmentCollection)); } diff --git a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Geometry/BezierSegmentToSpeckleConverter.cs b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Geometry/BezierSegmentToSpeckleConverter.cs deleted file mode 100644 index 8c62df731d..0000000000 --- a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Geometry/BezierSegmentToSpeckleConverter.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Speckle.Converters.Common.Objects; -using Speckle.Converters.Common; -using ArcGIS.Desktop.Mapping; - -namespace Speckle.Converters.ArcGIS3.Geometry; - -public class BezierSegmentToSpeckleConverter : ITypedConverter -{ - private readonly IConversionContextStack _contextStack; - private readonly ITypedConverter _pointConverter; - - public BezierSegmentToSpeckleConverter( - IConversionContextStack contextStack, - ITypedConverter pointConverter - ) - { - _contextStack = contextStack; - _pointConverter = pointConverter; - } - - public SOG.Polyline Convert(ACG.CubicBezierSegment target) - { - // Determine the number of vertices to create along the arc - int numVertices = Math.Max((int)target.Length, 3); // Determine based on desired segment length or other criteria - List points = new(); - - // Calculate the vertices along the curve - for (int i = 0; i <= numVertices; i++) - { - double t = i / (double)numVertices; - - // Calculate the point using the cubic Bezier formula - double x = - (1 - t) * (1 - t) * (1 - t) * target.StartPoint.X - + 3 * (1 - t) * (1 - t) * t * target.ControlPoint1.X - + 3 * (1 - t) * t * t * target.ControlPoint2.X - + t * t * t * target.EndPoint.X; - - double y = - (1 - t) * (1 - t) * (1 - t) * target.StartPoint.Y - + 3 * (1 - t) * (1 - t) * t * target.ControlPoint1.Y - + 3 * (1 - t) * t * t * target.ControlPoint2.Y - + t * t * t * target.EndPoint.Y; - - ACG.MapPoint pointOnCurve = ACG.MapPointBuilderEx.CreateMapPoint(x, y, target.SpatialReference); - points.Add(_pointConverter.Convert(pointOnCurve)); - } - - // create Speckle Polyline - SOG.Polyline polyline = - new(points.SelectMany(pt => new[] { pt.x, pt.y, pt.z }).ToList(), _contextStack.Current.SpeckleUnits) - { - // bbox = box, - length = target.Length - }; - return polyline; - } -} diff --git a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Geometry/EllipticArcSegmentToSpeckleConverter.cs b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Geometry/EllipticArcSegmentToSpeckleConverter.cs deleted file mode 100644 index af4c3716c4..0000000000 --- a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Geometry/EllipticArcSegmentToSpeckleConverter.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Speckle.Converters.Common.Objects; -using Speckle.Converters.Common; -using ArcGIS.Desktop.Mapping; - -namespace Speckle.Converters.ArcGIS3.Geometry; - -public class EllipticArcToSpeckleConverter : ITypedConverter -{ - private readonly IConversionContextStack _contextStack; - private readonly ITypedConverter _pointConverter; - - public EllipticArcToSpeckleConverter( - IConversionContextStack contextStack, - ITypedConverter pointConverter - ) - { - _contextStack = contextStack; - _pointConverter = pointConverter; - } - - public SOG.Polyline Convert(ACG.EllipticArcSegment target) - { - // Determine the number of vertices to create along the arc - int numVertices = Math.Max((int)target.Length, 3); // Determine based on desired segment length or other criteria - List points = new(); - - // get correct direction - int coeff = 1; - double fullAngle = target.EndAngle - target.StartAngle; - double angleStart = target.StartAngle; - - // define the direction - if ( - !((target.IsCounterClockwise is false || fullAngle >= 0) && (target.IsCounterClockwise is true || fullAngle < 0)) - ) - { - fullAngle = Math.PI * 2 - Math.Abs(fullAngle); - if (target.IsCounterClockwise is false) - { - coeff = -1; - } - } - - // Calculate the vertices along the arc - for (int i = 0; i <= numVertices; i++) - { - // Calculate the point along the arc - double angle = angleStart + coeff * fullAngle * (i / (double)numVertices); - ACG.MapPoint pointOnArc = ACG.MapPointBuilderEx.CreateMapPoint( - target.CenterPoint.X + target.SemiMajorAxis * Math.Cos(angle), - target.CenterPoint.Y + target.SemiMinorAxis * Math.Sin(angle), - target.SpatialReference - ); - - points.Add(_pointConverter.Convert(pointOnArc)); - } - - // create Speckle Polyline - SOG.Polyline polyline = - new(points.SelectMany(pt => new[] { pt.x, pt.y, pt.z }).ToList(), _contextStack.Current.SpeckleUnits) - { - // bbox = box, - length = target.Length - }; - return polyline; - } -} diff --git a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Geometry/ISpeckleObjectToHost/CircleToHostConverter.cs b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Geometry/ISpeckleObjectToHost/CircleToHostConverter.cs index cff9770e7d..7e3ce179f2 100644 --- a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Geometry/ISpeckleObjectToHost/CircleToHostConverter.cs +++ b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Geometry/ISpeckleObjectToHost/CircleToHostConverter.cs @@ -1,5 +1,7 @@ +using ArcGIS.Desktop.Mapping; using Speckle.Converters.Common; using Speckle.Converters.Common.Objects; +using Speckle.Core.Kits; using Speckle.Core.Models; namespace Speckle.Converters.ArcGIS3.Geometry.ISpeckleObjectToHost; @@ -8,45 +10,38 @@ namespace Speckle.Converters.ArcGIS3.Geometry.ISpeckleObjectToHost; public class CircleToHostConverter : IToHostTopLevelConverter, ITypedConverter { private readonly ITypedConverter _pointConverter; + private readonly IConversionContextStack _contextStack; - public CircleToHostConverter(ITypedConverter pointConverter) + public CircleToHostConverter( + ITypedConverter pointConverter, + IConversionContextStack contextStack + ) { _pointConverter = pointConverter; + _contextStack = contextStack; } public object Convert(Base target) => Convert((SOG.Circle)target); public ACG.Polyline Convert(SOG.Circle target) { - // Determine the number of vertices to create along the cirlce - int numVertices = Math.Max((int)target.length, 100); // Determine based on desired segment length or other criteria - List pointsOriginal = new(); - if (target.radius == null) { throw new SpeckleConversionException("Conversion failed: Circle doesn't have a radius"); } - // Calculate the vertices along the arc - for (int i = 0; i <= numVertices; i++) - { - // Calculate the point along the arc - double angle = 2 * Math.PI * (i / (double)numVertices); - SOG.Point pointOnCircle = - new( - target.plane.origin.x + (double)target.radius * Math.Cos(angle), - target.plane.origin.y + (double)target.radius * Math.Sin(angle), - target.plane.origin.z - ); - - pointsOriginal.Add(pointOnCircle); - } - if (pointsOriginal[0] != pointsOriginal[^1]) - { - pointsOriginal.Add(pointsOriginal[0]); - } + // create a native ArcGIS circle segment + ACG.MapPoint centerPt = _pointConverter.Convert(target.plane.origin); + + double scaleFactor = Units.GetConversionFactor(target.units, _contextStack.Current.SpeckleUnits); + ACG.EllipticArcSegment circleSegment = ACG.EllipticArcBuilderEx.CreateCircle( + new ACG.Coordinate2D(centerPt.X, centerPt.Y), + (double)target.radius * scaleFactor, + ACG.ArcOrientation.ArcClockwise + ); + + var circlePolyline = new ACG.PolylineBuilderEx(circleSegment, ACG.AttributeFlags.HasZ).ToGeometry(); - var points = pointsOriginal.Select(x => _pointConverter.Convert(x)); - return new ACG.PolylineBuilderEx(points, ACG.AttributeFlags.HasZ).ToGeometry(); + return circlePolyline; } } diff --git a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Geometry/SegmentCollectionToSpeckleConverter.cs b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Geometry/SegmentCollectionToSpeckleConverter.cs index 0c9572c24e..0e13f55260 100644 --- a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Geometry/SegmentCollectionToSpeckleConverter.cs +++ b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Geometry/SegmentCollectionToSpeckleConverter.cs @@ -8,20 +8,14 @@ public class SegmentCollectionToSpeckleConverter : ITypedConverter _contextStack; private readonly ITypedConverter _pointConverter; - private readonly ITypedConverter _arcConverter; - private readonly ITypedConverter _bezierConverter; public SegmentCollectionToSpeckleConverter( IConversionContextStack contextStack, - ITypedConverter pointConverter, - ITypedConverter arcConverter, - ITypedConverter bezierConverter + ITypedConverter pointConverter ) { _contextStack = contextStack; _pointConverter = pointConverter; - _arcConverter = arcConverter; - _bezierConverter = bezierConverter; } public SOG.Polyline Convert(ACG.ReadOnlySegmentCollection target) @@ -34,27 +28,39 @@ public SOG.Polyline Convert(ACG.ReadOnlySegmentCollection target) { len += segment.Length; - // do something specific per segment type + // specific conversion per segment type switch (segment.SegmentType) { case ACG.SegmentType.Line: - points.Add(_pointConverter.Convert(segment.StartPoint)); - points.Add(_pointConverter.Convert(segment.EndPoint)); - break; - case ACG.SegmentType.Bezier: - var segmentBezier = (ACG.CubicBezierSegment)segment; - points.AddRange(_bezierConverter.Convert(segmentBezier).GetPoints()); - break; - case ACG.SegmentType.EllipticArc: - var segmentElliptic = (ACG.EllipticArcSegment)segment; - points.AddRange(_arcConverter.Convert(segmentElliptic).GetPoints()); + points = AddPtsToPolylinePts( + points, + new List() + { + _pointConverter.Convert(segment.StartPoint), + _pointConverter.Convert(segment.EndPoint) + } + ); break; + default: + throw new SpeckleConversionException($"Segment of type '{segment.SegmentType}' cannot be converted"); } } - // var box = _boxConverter.Convert(target.Extent); SOG.Polyline polyline = new(points.SelectMany(pt => new[] { pt.x, pt.y, pt.z }).ToList(), _contextStack.Current.SpeckleUnits) { }; return polyline; } + + private List AddPtsToPolylinePts(List points, List newSegmentPts) + { + if (points.Count == 0 || points[^1] != newSegmentPts[0]) + { + points.AddRange(newSegmentPts); + } + else + { + points.AddRange(newSegmentPts.GetRange(1, newSegmentPts.Count - 1)); + } + return points; + } }