diff --git a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Receive/HostObjectBuilder.cs b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Receive/HostObjectBuilder.cs index 953a6b2abb..7e29d65ee6 100644 --- a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Receive/HostObjectBuilder.cs +++ b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Receive/HostObjectBuilder.cs @@ -45,6 +45,22 @@ public HostObjectBuilderResult Build( CancellationToken cancellationToken ) { + // get active CRS & offsets on Receive + SpatialReference activeSpatialRef = _contextStack.Current.Document.Map.SpatialReference; + // Browse for any trace of geolocation in non-GIS apps (e.g. Revit: implemented, Blender: todo on Blender side, Civil3d: ?) + // ATM, GIS commit CRS is stored per layer (in FeatureClass converter), but should be moved to the Root level too + CRSorigin? dataOrigin = null; // e.g. CRSorigin.FromRevitData(rootObject); + if (dataOrigin is CRSorigin crsOrigin) + { + activeSpatialRef = crsOrigin.CreateCustomCRS(); + } + double trueNorthRadians = 0; // example = CRSoffsetRotation.RotationFromRevitData(rootObject); + double latOffset = 0; + double lonOffset = 0; + CRSoffsetRotation crsOffsetRotation = new(activeSpatialRef, latOffset, lonOffset, trueNorthRadians); + // set active CRS & offsets on Receive + _contextStack.Current.Document.ActiveCRSoffsetRotation = crsOffsetRotation; + // Prompt the UI conversion started. Progress bar will swoosh. onOperationProgressed?.Invoke("Converting", null); @@ -211,7 +227,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/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs index 9b371194e0..7822498493 100644 --- a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs +++ b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs @@ -1,10 +1,13 @@ using System.Diagnostics; +using ArcGIS.Core.Geometry; using ArcGIS.Desktop.Mapping; 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; @@ -18,11 +21,17 @@ public class ArcGISRootObjectBuilder : IRootObjectBuilder { private readonly IUnitOfWorkFactory _unitOfWorkFactory; private readonly ISendConversionCache _sendConversionCache; + private readonly IConversionContextStack _contextStack; - public ArcGISRootObjectBuilder(IUnitOfWorkFactory unitOfWorkFactory, ISendConversionCache sendConversionCache) + public ArcGISRootObjectBuilder( + IUnitOfWorkFactory unitOfWorkFactory, + ISendConversionCache sendConversionCache, + IConversionContextStack contextStack + ) { _unitOfWorkFactory = unitOfWorkFactory; _sendConversionCache = sendConversionCache; + _contextStack = contextStack; } public RootObjectBuilderResult Build( @@ -32,6 +41,14 @@ public RootObjectBuilderResult Build( CancellationToken ct = default ) { + // set active CRS & offsets on Send, add offsets if we find a way to set them up + double trueNorth = 0; + double latOffset = 0; + double lonOffset = 0; + CRSoffsetRotation crsOffsetRotation = + new(_contextStack.Current.Document.Map.SpatialReference, latOffset, lonOffset, trueNorth); + _contextStack.Current.Document.ActiveCRSoffsetRotation = crsOffsetRotation; + // 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(); diff --git a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISConversionContextStack.cs b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISConversionContextStack.cs index 1649d205dc..ed7258eded 100644 --- a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISConversionContextStack.cs +++ b/DUI3-DX/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.SpatialReference); } private const string FGDB_NAME = "Speckle.gdb"; diff --git a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/Raw/FeatureClassToHostConverter.cs b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/Raw/FeatureClassToHostConverter.cs index d491ca2a1e..c87f27314c 100644 --- a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/Raw/FeatureClassToHostConverter.cs +++ b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/Raw/FeatureClassToHostConverter.cs @@ -87,8 +87,25 @@ public FeatureClass Convert(VectorLayer target) { wktString = target.crs.wkt.ToString(); } + // 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/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/Raw/PointSingleToHostConverter.cs b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/Raw/PointSingleToHostConverter.cs index 75bb57b476..ba87056b43 100644 --- a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/Raw/PointSingleToHostConverter.cs +++ b/DUI3-DX/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,13 @@ 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 ).ToGeometry(); } } diff --git a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/PointToSpeckleConverter.cs b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/PointToSpeckleConverter.cs index 46ffb8f937..38dce80d84 100644 --- a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/PointToSpeckleConverter.cs +++ b/DUI3-DX/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/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/PointcloudLayerToSpeckleConverter.cs b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/PointcloudLayerToSpeckleConverter.cs index 2ad672dea9..4cd7b4caa9 100644 --- a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/PointcloudLayerToSpeckleConverter.cs +++ b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/PointcloudLayerToSpeckleConverter.cs @@ -81,6 +81,9 @@ public SGIS.VectorLayer Convert(LasDatasetLayer target) { 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 = spatialRef.Unit.ToString(), }; diff --git a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/RasterLayerToSpeckleConverter.cs b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/RasterLayerToSpeckleConverter.cs index 22a93c52a9..5b0c8bb4dd 100644 --- a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/RasterLayerToSpeckleConverter.cs +++ b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/RasterLayerToSpeckleConverter.cs @@ -38,6 +38,9 @@ public SGIS.RasterLayer Convert(RasterLayer target) { 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 = spatialRef.Unit.ToString(), }; diff --git a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/VectorLayerToSpeckleConverter.cs b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/VectorLayerToSpeckleConverter.cs index 8d2a36e33f..387f2a297f 100644 --- a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/VectorLayerToSpeckleConverter.cs +++ b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/VectorLayerToSpeckleConverter.cs @@ -54,18 +54,21 @@ public VectorLayer Convert(FeatureLayer target) { VectorLayer speckleLayer = new(); - // get document CRS (for writing geometry coords) - var spatialRef = _contextStack.Current.Document.Map.SpatialReference; + // get Active CRS (for writing geometry coords) + var spatialRef = _contextStack.Current.Document.ActiveCRSoffsetRotation.SpatialReference; speckleLayer.crs = new CRS { wkt = spatialRef.Wkt, name = spatialRef.Name, - units_native = spatialRef.Unit.ToString(), + 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 speckleLayer.name = target.Name; - speckleLayer.units = _contextStack.Current.SpeckleUnits; + speckleLayer.units = _contextStack.Current.Document.ActiveCRSoffsetRotation.SpeckleUnitString; // get feature class fields var allLayerAttributes = new Base(); diff --git a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CRSoffsetRotation.cs b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CRSoffsetRotation.cs new file mode 100644 index 0000000000..741fc6aa78 --- /dev/null +++ b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CRSoffsetRotation.cs @@ -0,0 +1,153 @@ +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. + /// Angle to True North in radians. + public CRSoffsetRotation(ACG.SpatialReference spatialReference, double trueNorthRadians) + { + SpatialReference = spatialReference; + SpeckleUnitString = GetSpeckleUnit(spatialReference); + LatOffset = 0; + LonOffset = 0; + TrueNorthRadians = trueNorthRadians; + } + + /// + /// 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/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CRSorigin.cs b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CRSorigin.cs new file mode 100644 index 0000000000..0ceb2954c3 --- /dev/null +++ b/DUI3-DX/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/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/NonNativeFeaturesUtils.cs b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/NonNativeFeaturesUtils.cs index 3a17ff8796..9f95c740e8 100644 --- a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/NonNativeFeaturesUtils.cs +++ b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/NonNativeFeaturesUtils.cs @@ -117,8 +117,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(