From 8cc400c994131c21a16681dd63ca65d3b206c697 Mon Sep 17 00:00:00 2001 From: KatKatKateryna Date: Mon, 24 Jun 2024 19:58:17 +0100 Subject: [PATCH 1/3] create custom CRS; use RevitProjectInfo on Receive --- .../Operations/Receive/HostObjectBuilder.cs | 11 ++++ .../ArcGISConversionContextStack.cs | 3 + .../Geometry/PointSingleToHostConverter.cs | 5 +- .../Utils/CRSorigin.cs | 64 +++++++++++++++++++ .../Utils/NonNativeFeaturesUtils.cs | 2 +- 5 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CRSorigin.cs 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 3e22f2d85d..125270ed6e 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 @@ -47,6 +47,17 @@ CancellationToken cancellationToken // Prompt the UI conversion started. Progress bar will swoosh. onOperationProgressed?.Invoke("Converting", null); + // Receive geo-located data in the correct place + // 1. From Revit + CRSorigin? dataOrigin = CRSorigin.FromRevitData(rootObject); + + // save SpatialReference, use it for dataset writing + if (dataOrigin is CRSorigin crsOrigin) + { + SpatialReference incomingSpatialRef = crsOrigin.CreateCustomCRS(); + _contextStack.Current.Document.IncomingSpatialReference = incomingSpatialRef; + } + var objectsToConvert = _traverseFunction .Traverse(rootObject) .Where(ctx => ctx.Current is not Collection || IsGISType(ctx.Current)) diff --git a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISConversionContextStack.cs b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISConversionContextStack.cs index 1649d205dc..1e9cb5e8cc 100644 --- a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISConversionContextStack.cs +++ b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISConversionContextStack.cs @@ -5,6 +5,7 @@ using ArcGIS.Desktop.Framework.Threading.Tasks; using ArcGIS.Desktop.Mapping; using Speckle.Converters.Common; +using ArcGIS.Core.Geometry; namespace Speckle.Converters.ArcGIS3; @@ -13,12 +14,14 @@ public class ArcGISDocument public Project Project { get; } public Map Map { get; } public Uri SpeckleDatabasePath { get; } + public SpatialReference IncomingSpatialReference { get; set; } public ArcGISDocument() { Project = Project.Current; Map = MapView.Active.Map; SpeckleDatabasePath = EnsureOrAddSpeckleDatabase(); + IncomingSpatialReference = MapView.Active.Map.SpatialReference; } private const string FGDB_NAME = "Speckle.gdb"; diff --git a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Geometry/PointSingleToHostConverter.cs b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Geometry/PointSingleToHostConverter.cs index e30ec589e5..f42d2f291c 100644 --- a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Geometry/PointSingleToHostConverter.cs +++ b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Geometry/PointSingleToHostConverter.cs @@ -18,7 +18,10 @@ public PointToHostConverter(IConversionContextStack co public ACG.MapPoint Convert(SOG.Point target) { - double scaleFactor = Units.GetConversionFactor(target.units, _contextStack.Current.SpeckleUnits); + double scaleFactor = Units.GetConversionFactor( + target.units, + _contextStack.Current.Document.IncomingSpatialReference.Unit.ToString() + ); return new ACG.MapPointBuilderEx( target.x * scaleFactor, target.y * scaleFactor, 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..9118b59b86 --- /dev/null +++ b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CRSorigin.cs @@ -0,0 +1,64 @@ +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) + { + 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 = + $"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]]"; + 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..3a848d4329 100644 --- a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/NonNativeFeaturesUtils.cs +++ b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/NonNativeFeaturesUtils.cs @@ -118,7 +118,7 @@ private void CreateDatasetInDatabase( SchemaBuilder schemaBuilder = new(geodatabase); // get Spatial Reference from the document - ACG.SpatialReference spatialRef = _contextStack.Current.Document.Map.SpatialReference; + ACG.SpatialReference spatialRef = _contextStack.Current.Document.IncomingSpatialReference; // create Fields List<(FieldDescription, Func)> fieldsAndFunctions = _fieldUtils.CreateFieldsFromListOfBase( From c110112608e6bf9a2a535034aef067c586cad6f7 Mon Sep 17 00:00:00 2001 From: KatKatKateryna Date: Tue, 25 Jun 2024 13:48:20 +0100 Subject: [PATCH 2/3] save GIS incoming SpatialReference to ContextStack; delete Revit origin implementation for now --- .../Operations/Receive/HostObjectBuilder.cs | 7 +++---- .../Layers/FeatureClassToHostConverter.cs | 1 + .../ArcGIS/Speckle.Converters.ArcGIS3/Utils/CRSorigin.cs | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) 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 125270ed6e..70f314d925 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 @@ -47,11 +47,10 @@ CancellationToken cancellationToken // Prompt the UI conversion started. Progress bar will swoosh. onOperationProgressed?.Invoke("Converting", null); - // Receive geo-located data in the correct place - // 1. From Revit - CRSorigin? dataOrigin = CRSorigin.FromRevitData(rootObject); + // Browse for any trace of geolocation in non-GIS apps (e.g. Revit: implemented, Blender: todo on Blender side, Civil3d: ?) + CRSorigin? dataOrigin = null; // e.g. CRSorigin.FromRevitData(rootObject); - // save SpatialReference, use it for dataset writing + // save SpatialReference (overwrite default Map.SpatialRef), use it for dataset writing if (dataOrigin is CRSorigin crsOrigin) { SpatialReference incomingSpatialRef = crsOrigin.CreateCustomCRS(); diff --git a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Layers/FeatureClassToHostConverter.cs b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Layers/FeatureClassToHostConverter.cs index 0a084ffee8..57befa464d 100644 --- a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Layers/FeatureClassToHostConverter.cs +++ b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Layers/FeatureClassToHostConverter.cs @@ -88,6 +88,7 @@ public FeatureClass Convert(VectorLayer target) wktString = target.crs.wkt.ToString(); } ACG.SpatialReference spatialRef = ACG.SpatialReferenceBuilder.CreateSpatialReference(wktString); + _contextStack.Current.Document.IncomingSpatialReference = spatialRef; // create Fields List fields = _fieldsUtils.GetFieldsFromSpeckleLayer(target); diff --git a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CRSorigin.cs b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CRSorigin.cs index 9118b59b86..dfeccf5f54 100644 --- a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CRSorigin.cs +++ b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CRSorigin.cs @@ -25,6 +25,7 @@ public CRSorigin(double latDegrees, double 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") From 9bdfa99e7a15aaa1a11a18c88da44074763abe62 Mon Sep 17 00:00:00 2001 From: KatKatKateryna Date: Wed, 26 Jun 2024 00:55:11 +0100 Subject: [PATCH 3/3] update WKT format --- .../ArcGIS/Speckle.Converters.ArcGIS3/Utils/CRSorigin.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CRSorigin.cs b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CRSorigin.cs index dfeccf5f54..0ceb2954c3 100644 --- a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CRSorigin.cs +++ b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CRSorigin.cs @@ -59,7 +59,10 @@ public CRSorigin(double latDegrees, double lonDegrees) public SpatialReference CreateCustomCRS() { string wktString = - $"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]]"; + // 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); } }