From abe5a07e6a364bff0466492f540d8744a4ebfb00 Mon Sep 17 00:00:00 2001 From: KatKatKateryna <89912278+KatKatKateryna@users.noreply.github.com> Date: Mon, 15 Jul 2024 07:55:12 +0100 Subject: [PATCH] DUI3-426 PR moved from https://github.com/specklesystems/speckle-sharp/pull/3545 (#7) * PR moved from https://github.com/specklesystems/speckle-sharp/pull/3545 * ArcGISDocument to Singleton * read offsets from metadata * move crs assignment on send to RootObjBuilder * move all conversions to activeCRS * safer read from metadata * default degrees to meters (like QGIS) * csharpier * scoped * remove UoW from root builders as send/recieve bindings already use it (#21) * remove POC comment * remove unnecessary assignments; add offsets to incoming dataset name * limit dataset name length * tilt ellipse on receive * fix substrings * remove Rhino uow comment * add SR to the received dataset name * format --------- Co-authored-by: Adam Hathcock --- .../Operations/Receive/HostObjectBuilder.cs | 5 +- .../Send/ArcGISRootObjectBuilder.cs | 40 +++- .../Operations/Send/RhinoRootObjectBuilder.cs | 15 +- .../ArcGISConversionContextStack.cs | 5 + .../ArcGISToSpeckleUnitConverter.cs | 6 + .../ToHost/Raw/FeatureClassToHostConverter.cs | 17 ++ .../ToHost/Raw/MeshListToHostConverter.cs | 3 +- .../ToHost/Raw/PointSingleToHostConverter.cs | 13 +- .../ToHost/TopLevel/ArcToHostConverter.cs | 4 +- .../ToHost/TopLevel/CircleToHostConverter.cs | 4 +- .../ToHost/TopLevel/EllipseToHostConverter.cs | 8 +- .../ToHost/TopLevel/LineToHostConverter.cs | 2 +- .../TopLevel/PolycurveToHostConverter.cs | 2 +- .../TopLevel/PolylineToHostConverter.cs | 2 +- .../Raw/EnvelopBoxToSpeckleConverter.cs | 4 +- .../ToSpeckle/Raw/PointToSpeckleConverter.cs | 21 +- .../Raw/PolylineFeatureToSpeckleConverter.cs | 10 +- .../SegmentCollectionToSpeckleConverter.cs | 12 +- .../PointcloudLayerToSpeckleConverter.cs | 15 +- .../TopLevel/RasterLayerToSpeckleConverter.cs | 15 +- .../TopLevel/TableToSpeckleConverter.cs | 2 +- .../TopLevel/VectorLayerToSpeckleConverter.cs | 13 -- .../Utils/CRSoffsetRotation.cs | 194 ++++++++++++++++++ .../Utils/CRSorigin.cs | 68 ++++++ .../Utils/NonNativeFeaturesUtils.cs | 31 ++- 25 files changed, 416 insertions(+), 95 deletions(-) create mode 100644 Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CRSoffsetRotation.cs create mode 100644 Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CRSorigin.cs diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Receive/HostObjectBuilder.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Receive/HostObjectBuilder.cs index aef95b809..c795e6913 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Receive/HostObjectBuilder.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Receive/HostObjectBuilder.cs @@ -45,6 +45,9 @@ public HostObjectBuilderResult Build( CancellationToken cancellationToken ) { + // TODO get spatialRef and offsets & rotation from ProjectInfo in CommitObject + // ATM, GIS commit CRS is stored per layer (in FeatureClass converter), but should be moved to the Root level too + // Prompt the UI conversion started. Progress bar will swoosh. onOperationProgressed?.Invoke("Converting", null); @@ -211,7 +214,7 @@ Dictionary createdLayerGroups ) { // get layer details - string? datasetId = trackerItem.DatasetId; // should not ne null here + string? datasetId = trackerItem.DatasetId; // should not be null here Uri uri = new($"{_contextStack.Current.Document.SpeckleDatabasePath.AbsolutePath.Replace('/', '\\')}\\{datasetId}"); string nestedLayerName = trackerItem.NestedLayerName; diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs index 9b371194e..9b9007bb1 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs @@ -1,10 +1,14 @@ using System.Diagnostics; +using ArcGIS.Core.Geometry; using ArcGIS.Desktop.Mapping; +using Objects.GIS; using Speckle.Autofac.DependencyInjection; using Speckle.Connectors.Utils.Builders; using Speckle.Connectors.Utils.Caching; using Speckle.Connectors.Utils.Conversion; using Speckle.Connectors.Utils.Operations; +using Speckle.Converters.ArcGIS3; +using Speckle.Converters.ArcGIS3.Utils; using Speckle.Converters.Common; using Speckle.Core.Logging; using Speckle.Core.Models; @@ -16,13 +20,19 @@ namespace Speckle.Connectors.ArcGis.Operations.Send; /// public class ArcGISRootObjectBuilder : IRootObjectBuilder { - private readonly IUnitOfWorkFactory _unitOfWorkFactory; + private readonly IRootToSpeckleConverter _rootToSpeckleConverter; private readonly ISendConversionCache _sendConversionCache; + private readonly IConversionContextStack _contextStack; - public ArcGISRootObjectBuilder(IUnitOfWorkFactory unitOfWorkFactory, ISendConversionCache sendConversionCache) + public ArcGISRootObjectBuilder( + ISendConversionCache sendConversionCache, + IConversionContextStack contextStack, + IRootToSpeckleConverter rootToSpeckleConverter + ) { - _unitOfWorkFactory = unitOfWorkFactory; _sendConversionCache = sendConversionCache; + _contextStack = contextStack; + _rootToSpeckleConverter = rootToSpeckleConverter; } public RootObjectBuilderResult Build( @@ -32,10 +42,8 @@ public RootObjectBuilderResult Build( CancellationToken ct = default ) { - // POC: does this feel like the right place? I am wondering if this should be called from within send/rcv? - // begin the unit of work - using var uow = _unitOfWorkFactory.Resolve(); - var converter = uow.Service; + // TODO: add a warning if Geographic CRS is set + // "Data has been sent in the units 'degrees'. It is advisable to set the project CRS to Projected type (e.g. EPSG:32631) to be able to receive geometry correctly in CAD/BIM software" int count = 0; @@ -60,7 +68,23 @@ public RootObjectBuilderResult Build( } else { - converted = converter.Convert(mapMember); + converted = _rootToSpeckleConverter.Convert(mapMember); + + // get Active CRS (for writing geometry coords) + var spatialRef = _contextStack.Current.Document.ActiveCRSoffsetRotation.SpatialReference; + converted["crs"] = new CRS + { + wkt = spatialRef.Wkt, + name = spatialRef.Name, + offset_y = System.Convert.ToSingle(_contextStack.Current.Document.ActiveCRSoffsetRotation.LatOffset), + offset_x = System.Convert.ToSingle(_contextStack.Current.Document.ActiveCRSoffsetRotation.LonOffset), + rotation = System.Convert.ToSingle(_contextStack.Current.Document.ActiveCRSoffsetRotation.TrueNorthRadians), + units_native = _contextStack.Current.Document.ActiveCRSoffsetRotation.SpeckleUnitString, + }; + + // other properties + converted["name"] = mapMember.Name; + converted["units"] = _contextStack.Current.Document.ActiveCRSoffsetRotation.SpeckleUnitString; converted.applicationId = applicationId; } diff --git a/Connectors/Rhino/Speckle.Connectors.Rhino7/Operations/Send/RhinoRootObjectBuilder.cs b/Connectors/Rhino/Speckle.Connectors.Rhino7/Operations/Send/RhinoRootObjectBuilder.cs index a1320ee94..845cabc98 100644 --- a/Connectors/Rhino/Speckle.Connectors.Rhino7/Operations/Send/RhinoRootObjectBuilder.cs +++ b/Connectors/Rhino/Speckle.Connectors.Rhino7/Operations/Send/RhinoRootObjectBuilder.cs @@ -19,25 +19,25 @@ namespace Speckle.Connectors.Rhino7.Operations.Send; /// public class RhinoRootObjectBuilder : IRootObjectBuilder { - private readonly IUnitOfWorkFactory _unitOfWorkFactory; + private readonly IRootToSpeckleConverter _rootToSpeckleConverter; private readonly ISendConversionCache _sendConversionCache; private readonly RhinoInstanceObjectsManager _instanceObjectsManager; private readonly IConversionContextStack _contextStack; private readonly RhinoLayerManager _layerManager; public RhinoRootObjectBuilder( - IUnitOfWorkFactory unitOfWorkFactory, ISendConversionCache sendConversionCache, IConversionContextStack contextStack, RhinoLayerManager layerManager, - RhinoInstanceObjectsManager instanceObjectsManager + RhinoInstanceObjectsManager instanceObjectsManager, + IRootToSpeckleConverter rootToSpeckleConverter ) { - _unitOfWorkFactory = unitOfWorkFactory; _sendConversionCache = sendConversionCache; _contextStack = contextStack; _layerManager = layerManager; _instanceObjectsManager = instanceObjectsManager; + _rootToSpeckleConverter = rootToSpeckleConverter; } public RootObjectBuilderResult Build( @@ -54,11 +54,6 @@ private RootObjectBuilderResult ConvertObjects( CancellationToken cancellationToken = default ) { - // POC: does this feel like the right place? I am wondering if this should be called from within send/rcv? - // begin the unit of work - using var uow = _unitOfWorkFactory.Resolve(); - var converter = uow.Service; - var rootObjectCollection = new Collection { name = _contextStack.Current.Document.Name ?? "Unnamed document" }; int count = 0; @@ -100,7 +95,7 @@ private RootObjectBuilderResult ConvertObjects( } else { - converted = converter.Convert(rhinoObject); + converted = _rootToSpeckleConverter.Convert(rhinoObject); converted.applicationId = applicationId; } diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISConversionContextStack.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISConversionContextStack.cs index d0b3148b4..40fa5c30e 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISConversionContextStack.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISConversionContextStack.cs @@ -4,6 +4,7 @@ using ArcGIS.Desktop.Core; using ArcGIS.Desktop.Framework.Threading.Tasks; using ArcGIS.Desktop.Mapping; +using Speckle.Converters.ArcGIS3.Utils; using Speckle.Converters.Common; namespace Speckle.Converters.ArcGIS3; @@ -13,12 +14,16 @@ public class ArcGISDocument public Project Project { get; } public Map Map { get; } public Uri SpeckleDatabasePath { get; } + public CRSoffsetRotation ActiveCRSoffsetRotation { get; set; } public ArcGISDocument() { Project = Project.Current; Map = MapView.Active.Map; SpeckleDatabasePath = EnsureOrAddSpeckleDatabase(); + // CRS of either: incoming commit to be applied to all received objects, or CRS to convert all objects to, before sending + // created per Send/Receive operation, will be the same for all objects in the operation + ActiveCRSoffsetRotation = new CRSoffsetRotation(MapView.Active.Map); } private const string FGDB_NAME = "Speckle.gdb"; diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISToSpeckleUnitConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISToSpeckleUnitConverter.cs index 44d9786b8..2d26c34af 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISToSpeckleUnitConverter.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISToSpeckleUnitConverter.cs @@ -28,6 +28,12 @@ private static IReadOnlyDictionary Create() public string ConvertOrThrow(Unit hostUnit) { + // allow to send data in degrees (RootObjBuilder will send a warning) + if (hostUnit.UnitType == UnitType.Angular && hostUnit.FactoryCode == 9102) + { + return Units.Meters; + } + int linearUnit = LinearUnit.CreateLinearUnit(hostUnit.Wkt).FactoryCode; if (s_unitMapping.TryGetValue(linearUnit, out string? value)) diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/Raw/FeatureClassToHostConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/Raw/FeatureClassToHostConverter.cs index 8d753461c..1ef3aea74 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/Raw/FeatureClassToHostConverter.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/Raw/FeatureClassToHostConverter.cs @@ -87,8 +87,25 @@ public FeatureClass Convert(VectorLayer target) { wktString = target.crs.wkt; } + // ATM, GIS commit CRS is stored per layer, but should be moved to the Root level too, and created once per Receive ACG.SpatialReference spatialRef = ACG.SpatialReferenceBuilder.CreateSpatialReference(wktString); + double trueNorthRadians = System.Convert.ToDouble( + (target.crs == null || target.crs?.rotation == null) ? 0 : target.crs.rotation + ); + double latOffset = System.Convert.ToDouble( + (target.crs == null || target.crs?.offset_y == null) ? 0 : target.crs.offset_y + ); + double lonOffset = System.Convert.ToDouble( + (target.crs == null || target.crs?.offset_x == null) ? 0 : target.crs.offset_x + ); + _contextStack.Current.Document.ActiveCRSoffsetRotation = new CRSoffsetRotation( + spatialRef, + latOffset, + lonOffset, + trueNorthRadians + ); + // create Fields List fields = _fieldsUtils.GetFieldsFromSpeckleLayer(target); diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/Raw/MeshListToHostConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/Raw/MeshListToHostConverter.cs index 941f6ac70..04b3ee086 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/Raw/MeshListToHostConverter.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/Raw/MeshListToHostConverter.cs @@ -24,7 +24,8 @@ public ACG.Multipatch Convert(List target) { throw new SpeckleConversionException("Feature contains no geometries"); } - ACG.MultipatchBuilderEx multipatchPart = new(_contextStack.Current.Document.Map.SpatialReference); + ACG.MultipatchBuilderEx multipatchPart = + new(_contextStack.Current.Document.ActiveCRSoffsetRotation.SpatialReference); foreach (SOG.Mesh part in target) { part.TriangulateMesh(); diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/Raw/PointSingleToHostConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/Raw/PointSingleToHostConverter.cs index 75bb57b47..e458ee273 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/Raw/PointSingleToHostConverter.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/Raw/PointSingleToHostConverter.cs @@ -1,6 +1,5 @@ using Speckle.Converters.Common; using Speckle.Converters.Common.Objects; -using Speckle.Core.Kits; using Speckle.Core.Models; namespace Speckle.Converters.ArcGIS3.ToHost.Raw; @@ -18,12 +17,14 @@ public PointToHostConverter(IConversionContextStack co public ACG.MapPoint Convert(SOG.Point target) { - double scaleFactor = Units.GetConversionFactor(target.units, _contextStack.Current.SpeckleUnits); + SOG.Point scaledMovedRotatedPoint = _contextStack.Current.Document.ActiveCRSoffsetRotation.OffsetRotateOnReceive( + target + ); return new ACG.MapPointBuilderEx( - target.x * scaleFactor, - target.y * scaleFactor, - target.z * scaleFactor, - _contextStack.Current.Document.Map.SpatialReference + scaledMovedRotatedPoint.x, + scaledMovedRotatedPoint.y, + scaledMovedRotatedPoint.z, + _contextStack.Current.Document.ActiveCRSoffsetRotation.SpatialReference ).ToGeometry(); } } diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/TopLevel/ArcToHostConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/TopLevel/ArcToHostConverter.cs index f33471361..0fff887f5 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/TopLevel/ArcToHostConverter.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/TopLevel/ArcToHostConverter.cs @@ -35,13 +35,13 @@ public ACG.Polyline Convert(SOG.Arc target) fromPt, toPt, new ACG.Coordinate2D(midPt), - _contextStack.Current.Document.Map.SpatialReference + _contextStack.Current.Document.ActiveCRSoffsetRotation.SpatialReference ); return new ACG.PolylineBuilderEx( segment, ACG.AttributeFlags.HasZ, - _contextStack.Current.Document.Map.SpatialReference + _contextStack.Current.Document.ActiveCRSoffsetRotation.SpatialReference ).ToGeometry(); } } diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/TopLevel/CircleToHostConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/TopLevel/CircleToHostConverter.cs index ed55e5dfe..6d6084888 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/TopLevel/CircleToHostConverter.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/TopLevel/CircleToHostConverter.cs @@ -46,13 +46,13 @@ public ACG.Polyline Convert(SOG.Circle target) new ACG.Coordinate2D(centerPt.X, centerPt.Y), (double)target.radius * scaleFactor, ACG.ArcOrientation.ArcClockwise, - _contextStack.Current.Document.Map.SpatialReference + _contextStack.Current.Document.ActiveCRSoffsetRotation.SpatialReference ); return new ACG.PolylineBuilderEx( circleSegment, ACG.AttributeFlags.HasZ, - _contextStack.Current.Document.Map.SpatialReference + _contextStack.Current.Document.ActiveCRSoffsetRotation.SpatialReference ).ToGeometry(); } } diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/TopLevel/EllipseToHostConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/TopLevel/EllipseToHostConverter.cs index 3a1a8583a..85680f0c1 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/TopLevel/EllipseToHostConverter.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/TopLevel/EllipseToHostConverter.cs @@ -43,7 +43,9 @@ public ACG.Polyline Convert(SOG.Ellipse target) double scaleFactor = Units.GetConversionFactor(target.units, _contextStack.Current.SpeckleUnits); // set default values - double angle = Math.Atan2(target.plane.xdir.y, target.plane.xdir.x); + double angle = + Math.Atan2(target.plane.xdir.y, target.plane.xdir.x) + + _contextStack.Current.Document.ActiveCRSoffsetRotation.TrueNorthRadians; double majorAxisRadius = (double)target.firstRadius; double minorAxisRatio = (double)target.secondRadius / majorAxisRadius; @@ -61,13 +63,13 @@ public ACG.Polyline Convert(SOG.Ellipse target) majorAxisRadius * scaleFactor, minorAxisRatio, ACG.ArcOrientation.ArcCounterClockwise, - _contextStack.Current.Document.Map.SpatialReference + _contextStack.Current.Document.ActiveCRSoffsetRotation.SpatialReference ); return new ACG.PolylineBuilderEx( segment, ACG.AttributeFlags.HasZ, - _contextStack.Current.Document.Map.SpatialReference + _contextStack.Current.Document.ActiveCRSoffsetRotation.SpatialReference ).ToGeometry(); } } diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/TopLevel/LineToHostConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/TopLevel/LineToHostConverter.cs index ab562f239..aa82c41ce 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/TopLevel/LineToHostConverter.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/TopLevel/LineToHostConverter.cs @@ -28,7 +28,7 @@ public ACG.Polyline Convert(SOG.Line target) return new ACG.PolylineBuilderEx( points, ACG.AttributeFlags.HasZ, - _contextStack.Current.Document.Map.SpatialReference + _contextStack.Current.Document.ActiveCRSoffsetRotation.SpatialReference ).ToGeometry(); } } diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/TopLevel/PolycurveToHostConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/TopLevel/PolycurveToHostConverter.cs index ba57e347f..a53a7669c 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/TopLevel/PolycurveToHostConverter.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/TopLevel/PolycurveToHostConverter.cs @@ -54,7 +54,7 @@ public ACG.Polyline Convert(SOG.Polycurve target) return new ACG.PolylineBuilderEx( segments, ACG.AttributeFlags.HasZ, - _contextStack.Current.Document.Map.SpatialReference + _contextStack.Current.Document.ActiveCRSoffsetRotation.SpatialReference ).ToGeometry(); } } diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/TopLevel/PolylineToHostConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/TopLevel/PolylineToHostConverter.cs index d32e32d02..35bfe78a9 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/TopLevel/PolylineToHostConverter.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/TopLevel/PolylineToHostConverter.cs @@ -32,7 +32,7 @@ public ACG.Polyline Convert(SOG.Polyline target) return new ACG.PolylineBuilderEx( points, ACG.AttributeFlags.HasZ, - _contextStack.Current.Document.Map.SpatialReference + _contextStack.Current.Document.ActiveCRSoffsetRotation.SpatialReference ).ToGeometry(); } } diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/EnvelopBoxToSpeckleConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/EnvelopBoxToSpeckleConverter.cs index 0296c5d32..bd617838d 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/EnvelopBoxToSpeckleConverter.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/EnvelopBoxToSpeckleConverter.cs @@ -25,13 +25,13 @@ public SOG.Box Convert(Envelope target) target.XMin, target.YMin, target.ZMin, - _contextStack.Current.Document.Map.SpatialReference + _contextStack.Current.Document.ActiveCRSoffsetRotation.SpatialReference ).ToGeometry(); MapPoint pointMax = new MapPointBuilderEx( target.XMax, target.YMax, target.ZMax, - _contextStack.Current.Document.Map.SpatialReference + _contextStack.Current.Document.ActiveCRSoffsetRotation.SpatialReference ).ToGeometry(); SOG.Point minPtSpeckle = _pointConverter.Convert(pointMin); SOG.Point maxPtSpeckle = _pointConverter.Convert(pointMax); diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/PointToSpeckleConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/PointToSpeckleConverter.cs index 46ffb8f93..38dce80d8 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/PointToSpeckleConverter.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/PointToSpeckleConverter.cs @@ -17,21 +17,34 @@ public SOG.Point Convert(MapPoint target) { try { + // reproject to Active CRS if ( - GeometryEngine.Instance.Project(target, _contextStack.Current.Document.Map.SpatialReference) + GeometryEngine.Instance.Project(target, _contextStack.Current.Document.ActiveCRSoffsetRotation.SpatialReference) is not MapPoint reprojectedPt ) { throw new SpeckleConversionException( - $"Conversion to Spatial Reference {_contextStack.Current.Document.Map.SpatialReference.Name} failed" + $"Conversion to Spatial Reference {_contextStack.Current.Document.ActiveCRSoffsetRotation.SpatialReference.Name} failed" ); } - return new(reprojectedPt.X, reprojectedPt.Y, reprojectedPt.Z, _contextStack.Current.SpeckleUnits); + + // convert to Speckle Pt + SOG.Point reprojectedSpecklePt = + new( + reprojectedPt.X, + reprojectedPt.Y, + reprojectedPt.Z, + _contextStack.Current.Document.ActiveCRSoffsetRotation.SpeckleUnitString + ); + SOG.Point scaledMovedRotatedPoint = _contextStack.Current.Document.ActiveCRSoffsetRotation.OffsetRotateOnSend( + reprojectedSpecklePt + ); + return scaledMovedRotatedPoint; } catch (ArgumentException ex) { throw new SpeckleConversionException( - $"Conversion to Spatial Reference {_contextStack.Current.Document.Map.SpatialReference} failed", + $"Conversion to Spatial Reference {_contextStack.Current.Document.ActiveCRSoffsetRotation.SpatialReference.Name} failed", ex ); } diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/PolylineFeatureToSpeckleConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/PolylineFeatureToSpeckleConverter.cs index a8cc1a99d..73c9dc188 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/PolylineFeatureToSpeckleConverter.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/PolylineFeatureToSpeckleConverter.cs @@ -26,8 +26,14 @@ public PolyineFeatureToSpeckleConverter( // densify the polylines with curves using precision value of the Map's Spatial Reference if (target.HasCurves is true) { - double tolerance = _contextStack.Current.Document.Map.SpatialReference.XYTolerance; - double conversionFactorToMeter = _contextStack.Current.Document.Map.SpatialReference.Unit.ConversionFactor; + double tolerance = _contextStack.Current.Document.ActiveCRSoffsetRotation.SpatialReference.XYTolerance; + double conversionFactorToMeter = _contextStack + .Current + .Document + .ActiveCRSoffsetRotation + .SpatialReference + .Unit + .ConversionFactor; var densifiedPolyline = ACG.GeometryEngine.Instance.DensifyByDeviation( target, tolerance * conversionFactorToMeter diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/SegmentCollectionToSpeckleConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/SegmentCollectionToSpeckleConverter.cs index f0b71ef47..38b3bc439 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/SegmentCollectionToSpeckleConverter.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/SegmentCollectionToSpeckleConverter.cs @@ -33,11 +33,17 @@ public SOG.Polyline Convert(ACG.ReadOnlySegmentCollection target) ACG.Polyline polylineFromSegment = new ACG.PolylineBuilderEx( segment, ACG.AttributeFlags.HasZ, - _contextStack.Current.Document.Map.SpatialReference + _contextStack.Current.Document.ActiveCRSoffsetRotation.SpatialReference ).ToGeometry(); - double tolerance = _contextStack.Current.Document.Map.SpatialReference.XYTolerance; - double conversionFactorToMeter = _contextStack.Current.Document.Map.SpatialReference.Unit.ConversionFactor; + double tolerance = _contextStack.Current.Document.ActiveCRSoffsetRotation.SpatialReference.XYTolerance; + double conversionFactorToMeter = _contextStack + .Current + .Document + .ActiveCRSoffsetRotation + .SpatialReference + .Unit + .ConversionFactor; var densifiedPolyline = ACG.GeometryEngine.Instance.DensifyByDeviation( polylineFromSegment, tolerance * conversionFactorToMeter diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/PointcloudLayerToSpeckleConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/PointcloudLayerToSpeckleConverter.cs index 2ad672dea..f6324ff77 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/PointcloudLayerToSpeckleConverter.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/PointcloudLayerToSpeckleConverter.cs @@ -74,19 +74,6 @@ public Base Convert(object target) public SGIS.VectorLayer Convert(LasDatasetLayer target) { SGIS.VectorLayer speckleLayer = new(); - - // get document CRS (for writing geometry coords) - var spatialRef = _contextStack.Current.Document.Map.SpatialReference; - speckleLayer.crs = new SGIS.CRS - { - wkt = spatialRef.Wkt, - name = spatialRef.Name, - units_native = spatialRef.Unit.ToString(), - }; - - // other properties - speckleLayer.name = target.Name; - speckleLayer.units = _contextStack.Current.SpeckleUnits; speckleLayer.nativeGeomType = target.MapLayerType.ToString(); speckleLayer.geomType = GISLayerGeometryType.POINTCLOUD; @@ -117,7 +104,7 @@ public SGIS.VectorLayer Convert(LasDatasetLayer target) colors = speckleColors, sizes = values, bbox = _boxConverter.Convert(target.QueryExtent()), - units = _contextStack.Current.SpeckleUnits + units = _contextStack.Current.Document.ActiveCRSoffsetRotation.SpeckleUnitString }; speckleLayer.elements.Add(cloud); diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/RasterLayerToSpeckleConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/RasterLayerToSpeckleConverter.cs index 22a93c52a..92afe6d22 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/RasterLayerToSpeckleConverter.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/RasterLayerToSpeckleConverter.cs @@ -32,21 +32,12 @@ public SGIS.RasterLayer Convert(RasterLayer target) { var speckleLayer = new SGIS.RasterLayer(); - // get document CRS (for writing geometry coords) - var spatialRef = _contextStack.Current.Document.Map.SpatialReference; - speckleLayer.crs = new CRS - { - wkt = spatialRef.Wkt, - name = spatialRef.Name, - units_native = spatialRef.Unit.ToString(), - }; - // layer native crs (for writing properties e.g. resolution, origin etc.) var spatialRefRaster = target.GetSpatialReference(); // get active map CRS if layer CRS is empty if (spatialRefRaster.Unit is null) { - spatialRefRaster = _contextStack.Current.Document.Map.SpatialReference; + spatialRefRaster = _contextStack.Current.Document.ActiveCRSoffsetRotation.SpatialReference; } speckleLayer.rasterCrs = new CRS { @@ -55,10 +46,6 @@ public SGIS.RasterLayer Convert(RasterLayer target) units_native = spatialRefRaster.Unit.ToString(), }; - // other properties - speckleLayer.name = target.Name; - speckleLayer.units = _contextStack.Current.SpeckleUnits; - // write details about the Raster RasterElement element = _gisRasterConverter.Convert(target.GetRaster()); speckleLayer.elements.Add(element); diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/TableToSpeckleConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/TableToSpeckleConverter.cs index 7649211f1..bb1e3018c 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/TableToSpeckleConverter.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/TableToSpeckleConverter.cs @@ -26,7 +26,7 @@ public Base Convert(object target) public VectorLayer Convert(StandaloneTable target) { - VectorLayer speckleLayer = new() { name = target.Name, }; + VectorLayer speckleLayer = new() { }; // get feature class fields var attributes = new Base(); diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/VectorLayerToSpeckleConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/VectorLayerToSpeckleConverter.cs index 58de84272..3b72e04d5 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/VectorLayerToSpeckleConverter.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/VectorLayerToSpeckleConverter.cs @@ -34,19 +34,6 @@ public VectorLayer Convert(FeatureLayer target) { VectorLayer speckleLayer = new(); - // get document CRS (for writing geometry coords) - var spatialRef = _contextStack.Current.Document.Map.SpatialReference; - speckleLayer.crs = new CRS - { - wkt = spatialRef.Wkt, - name = spatialRef.Name, - units_native = spatialRef.Unit.ToString(), - }; - - // other properties - speckleLayer.name = target.Name; - speckleLayer.units = _contextStack.Current.SpeckleUnits; - // get feature class fields var allLayerAttributes = new Base(); var dispayTable = target as IDisplayTable; diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CRSoffsetRotation.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CRSoffsetRotation.cs new file mode 100644 index 000000000..54b5817e6 --- /dev/null +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CRSoffsetRotation.cs @@ -0,0 +1,194 @@ +using System.Xml.Linq; +using ArcGIS.Desktop.Mapping; +using Objects.BuiltElements.Revit; +using Speckle.Core.Kits; +using Speckle.Core.Models; + +namespace Speckle.Converters.ArcGIS3.Utils; + +/// +/// Container with origin offsets and rotation angle for the specified SpatialReference +/// Offsets and rotation will modify geometry on Send, so non-GIS apps can receive it correctly +/// Receiving GIS geometry in GIS hostApp will "undo" the geometry modifications according to the offsets and rotation applied before +/// In the future, CAD/BIM objects will contain ProjectInfo data with CRS and offsets, so this object can be generated on Recieve +/// TODO: consider how to generate this object to receive non-GIS data already now, without it having ProjectInfo object +/// +public struct CRSoffsetRotation +{ + public ACG.SpatialReference SpatialReference { get; } + public string SpeckleUnitString { get; set; } + public double LatOffset { get; set; } + public double LonOffset { get; set; } + public double TrueNorthRadians { get; set; } + + public SOG.Point OffsetRotateOnReceive(SOG.Point pointOriginal) + { + // scale point to match units of the SpatialReference + string originalUnits = pointOriginal.units; + SOG.Point point = ScalePoint(pointOriginal, originalUnits, SpeckleUnitString); + + // 1. rotate coordinates + NormalizeAngle(); + double x2 = point.x * Math.Cos(TrueNorthRadians) - point.y * Math.Sin(TrueNorthRadians); + double y2 = point.x * Math.Sin(TrueNorthRadians) + point.y * Math.Cos(TrueNorthRadians); + // 2. offset coordinates + x2 += LonOffset; + y2 += LatOffset; + SOG.Point movedPoint = new(x2, y2, point.z, SpeckleUnitString); + + return movedPoint; + } + + public SOG.Point OffsetRotateOnSend(SOG.Point point) + { + // scale point to match units of the SpatialReference + string originalUnits = point.units; + point = ScalePoint(point, originalUnits, SpeckleUnitString); + + // 1. offset coordinates + NormalizeAngle(); + double x2 = point.x - LonOffset; + double y2 = point.y - LatOffset; + // 2. rotate coordinates + x2 = x2 * Math.Cos(TrueNorthRadians) + y2 * Math.Sin(TrueNorthRadians); + y2 = -x2 * Math.Sin(TrueNorthRadians) + y2 * Math.Cos(TrueNorthRadians); + SOG.Point movedPoint = new(x2, y2, point.z, SpeckleUnitString); + + return movedPoint; + } + + private readonly SOG.Point ScalePoint(SOG.Point point, string fromUnit, string toUnit) + { + double scaleFactor = Units.GetConversionFactor(fromUnit, toUnit); + return new SOG.Point(point.x * scaleFactor, point.y * scaleFactor, point.z * scaleFactor, toUnit); + } + + private readonly string GetSpeckleUnit(ACG.SpatialReference spatialReference) + { + return new ArcGISToSpeckleUnitConverter().ConvertOrThrow(spatialReference.Unit); + } + + private void NormalizeAngle() + { + if (TrueNorthRadians < -2 * Math.PI || TrueNorthRadians > 2 * Math.PI) + { + TrueNorthRadians = TrueNorthRadians % 2 * Math.PI; + } + } + + public static double? RotationFromRevitData(Base rootObject) + { + // rewrite function to take into account Local reference point in Revit, and Transformation matrix + foreach (KeyValuePair prop in rootObject.GetMembers(DynamicBaseMemberType.Dynamic)) + { + if (prop.Key == "info") + { + ProjectInfo? revitProjInfo = (ProjectInfo?)rootObject[prop.Key]; + if (revitProjInfo != null) + { + try + { + if (revitProjInfo["locations"] is List locationList && locationList.Count > 0) + { + Base location = locationList[0]; + return Convert.ToDouble(location["trueNorth"]); + } + } + catch (Exception ex) when (ex is FormatException || ex is InvalidCastException || ex is OverflowException) + { + // origin not found, do nothing + } + break; + } + } + } + return null; + } + + /// + /// Initializes a new instance of . + /// + /// SpatialReference to apply offsets and rotation to. + public CRSoffsetRotation(ACG.SpatialReference spatialReference) + { + SpatialReference = spatialReference; + SpeckleUnitString = GetSpeckleUnit(spatialReference); + LatOffset = 0; + LonOffset = 0; + TrueNorthRadians = 0; + } + + /// + /// Initializes a new instance of . + /// + /// SpatialReference to apply offsets and rotation to. + /// Map to read metadata from. + public CRSoffsetRotation(Map map) + { + ACG.SpatialReference spatialReference = map.SpatialReference; + + SpatialReference = spatialReference; + SpeckleUnitString = GetSpeckleUnit(spatialReference); + + // read from metadata + string metadata = map.GetMetadata(); + var root = XDocument.Parse(metadata).Root; + string? textData = root?.Element("dataIdInfo")?.Element("resConst")?.Element("Consts")?.Element("useLimit")?.Value; + textData = textData?.Replace("", "").Replace("", ""); + + // set offsets and rotation from metadata if available + // format to write to Metadata "Use Limitations" field: + // _specklexoffset=100_speckleyoffset=200_specklenorth=0_ + + if ( + textData != null + && textData.ToLower().Contains("_specklexoffset=") + && textData.ToLower().Contains("_speckleyoffset=") + && textData.ToLower().Contains("_specklenorth=") + ) + { + string? latElement = textData.ToLower().Split("_speckleyoffset=")[^1].Split("_")[0]; + string? lonElement = textData.ToLower().Split("_specklexoffset=")[^1].Split("_")[0]; + string? northElement = textData.ToLower().Split("_specklenorth=")[^1].Split("_")[0]; + try + { + LatOffset = latElement is null ? 0 : Convert.ToDouble(latElement); + LonOffset = lonElement is null ? 0 : Convert.ToDouble(lonElement); + TrueNorthRadians = northElement is null ? 0 : Convert.ToDouble(northElement); + } + catch (Exception ex) when (ex is FormatException or OverflowException) + { + LatOffset = 0; + LonOffset = 0; + TrueNorthRadians = 0; + } + } + else + { + LatOffset = 0; + LonOffset = 0; + TrueNorthRadians = 0; + } + } + + /// + /// Initializes a new instance of . + /// + /// SpatialReference to apply offsets and rotation to. + /// Latitude (Y) ofsset in the current SpatialReference units. + /// Longitude (X) ofsset in the current SpatialReference units. + /// Angle to True North in radians. + public CRSoffsetRotation( + ACG.SpatialReference spatialReference, + double latOffset, + double lonOffset, + double trueNorthRadians + ) + { + SpatialReference = spatialReference; + SpeckleUnitString = GetSpeckleUnit(spatialReference); + LatOffset = latOffset; + LonOffset = lonOffset; + TrueNorthRadians = trueNorthRadians; + } +} diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CRSorigin.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CRSorigin.cs new file mode 100644 index 000000000..0ceb2954c --- /dev/null +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CRSorigin.cs @@ -0,0 +1,68 @@ +using ArcGIS.Core.Geometry; +using Objects.BuiltElements.Revit; +using Speckle.Core.Models; + +namespace Speckle.Converters.ArcGIS3.Utils; + +/// +/// Container with origin coordinates and rotation angle +/// +public readonly struct CRSorigin +{ + public double LatDegrees { get; } + public double LonDegrees { get; } + + /// + /// Initializes a new instance of . + /// + /// Latitude (Y) in degrees. + /// Longitude (X) in degrees. + public CRSorigin(double latDegrees, double lonDegrees) + { + LatDegrees = latDegrees; + LonDegrees = lonDegrees; + } + + public static CRSorigin? FromRevitData(Base rootObject) + { + // rewrite function to take into account Local reference point in Revit, and Transformation matrix + foreach (KeyValuePair prop in rootObject.GetMembers(DynamicBaseMemberType.Dynamic)) + { + if (prop.Key == "info") + { + ProjectInfo? revitProjInfo = (ProjectInfo?)rootObject[prop.Key]; + if (revitProjInfo != null) + { + try + { + double lat = Convert.ToDouble(revitProjInfo["latitude"]); + double lon = Convert.ToDouble(revitProjInfo["longitude"]); + double trueNorth; + if (revitProjInfo["locations"] is List locationList && locationList.Count > 0) + { + Base location = locationList[0]; + trueNorth = Convert.ToDouble(location["trueNorth"]); + } + return new CRSorigin(lat * 180 / Math.PI, lon * 180 / Math.PI); + } + catch (Exception ex) when (ex is FormatException || ex is InvalidCastException || ex is OverflowException) + { + // origin not found, do nothing + } + break; + } + } + } + return null; + } + + public SpatialReference CreateCustomCRS() + { + string wktString = + // QGIS example: $"PROJCS[\"unknown\", GEOGCS[\"unknown\", DATUM[\"WGS_1984\", SPHEROID[\"WGS 84\", 6378137, 298.257223563], AUTHORITY[\"EPSG\", \"6326\"]], PRIMEM[\"Greenwich\", 0, AUTHORITY[\"EPSG\", \"8901\"]], UNIT[\"degree\", 0.0174532925199433]], PROJECTION[\"Transverse_Mercator\"], PARAMETER[\"latitude_of_origin\", {LatDegrees}], PARAMETER[\"central_meridian\", {LonDegrees}], PARAMETER[\"scale_factor\", 1], PARAMETER[\"false_easting\", 0], PARAMETER[\"false_northing\", 0], UNIT[\"metre\", 1, AUTHORITY[\"EPSG\", \"9001\"]], AXIS[\"Easting\", EAST], AXIS[\"Northing\", NORTH]]"; + // replicating ArcGIS created custom WKT: + $"PROJCS[\"SpeckleSpatialReference_latlon_{LatDegrees}_{LonDegrees}\", GEOGCS[\"GCS_WGS_1984\", DATUM[\"D_WGS_1984\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\", 0.0], UNIT[\"Degree\", 0.0174532925199433]], PROJECTION[\"Transverse_Mercator\"], PARAMETER[\"False_Easting\", 0.0], PARAMETER[\"False_Northing\", 0.0], PARAMETER[\"Central_Meridian\", {LonDegrees}], PARAMETER[\"Scale_Factor\", 1.0], PARAMETER[\"Latitude_Of_Origin\", {LatDegrees}], UNIT[\"Meter\", 1.0]]"; + + return SpatialReferenceBuilder.CreateSpatialReference(wktString); + } +} diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/NonNativeFeaturesUtils.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/NonNativeFeaturesUtils.cs index b2bf3f769..57aa8d67b 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/NonNativeFeaturesUtils.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/NonNativeFeaturesUtils.cs @@ -44,12 +44,31 @@ Dictionary conversionTracker string? datasetId = trackerItem.DatasetId; if (geom != null && datasetId == null) // only non-native geomerties, not written into a dataset yet { - string speckle_type = trackerItem.Base.speckle_type.Split(".")[^1]; + // add dictionnary item if doesn't exist yet + // Key must be unique per parent and speckleType + // Adding Offsets/rotation to Unique key, so the modified CAD geometry doesn't overwrite non-modified one + // or, same commit received with different Offsets are saved to separate datasets + + // Also, keep char limit for dataset name under 128: https://pro.arcgis.com/en/pro-app/latest/help/data/geodatabases/manage-saphana/enterprise-geodatabase-limits.htm + + string speckleType = trackerItem.Base.speckle_type.Split(".")[^1]; + speckleType = speckleType.Substring(0, Math.Max(10, speckleType.Length)); string? parentId = context.Parent?.Current.id; - // add dictionnary item if doesn't exist yet - // Key must be unique per parent and speckle_type - string uniqueKey = $"speckleTYPE_{speckle_type}_speckleID_{parentId}"; + CRSoffsetRotation activeSR = _contextStack.Current.Document.ActiveCRSoffsetRotation; + string XOffset = Convert.ToString(activeSR.LonOffset).Replace(".", "_"); + XOffset = XOffset.Length > 15 ? XOffset.Substring(0, 15) : XOffset; + + string YOffset = Convert.ToString(activeSR.LatOffset).Replace(".", "_"); + YOffset = YOffset.Length > 15 ? YOffset.Substring(0, 15) : YOffset; + + string TrueNorth = Convert.ToString(activeSR.TrueNorthRadians).Replace(".", "_"); + TrueNorth = TrueNorth.Length > 10 ? TrueNorth.Substring(0, 10) : TrueNorth; + + // text: 36 symbols, speckleTYPE: 10, sr: 10, offsets: 40, id: 32 = 128 + string uniqueKey = + $"speckle_{speckleType}_SR_{activeSR.SpatialReference.Name.Substring(0, Math.Max(15, activeSR.SpatialReference.Name.Length))}_X_{XOffset}_Y_{YOffset}_North_{TrueNorth}_speckleID_{parentId}"; + if (!geometryGroups.TryGetValue(uniqueKey, out _)) { geometryGroups[uniqueKey] = new(); @@ -117,8 +136,8 @@ private void CreateDatasetInDatabase( Geodatabase geodatabase = new(fileGeodatabaseConnectionPath); SchemaBuilder schemaBuilder = new(geodatabase); - // get Spatial Reference from the document - ACG.SpatialReference spatialRef = _contextStack.Current.Document.Map.SpatialReference; + // get Spatial Reference from the Active CRS for Receive + ACG.SpatialReference spatialRef = _contextStack.Current.Document.ActiveCRSoffsetRotation.SpatialReference; // create Fields List<(FieldDescription, Func)> fieldsAndFunctions = _fieldUtils.CreateFieldsFromListOfBase(