Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DUI3-426 add offset rotation to the currently applied crs to send receive data #3545

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -211,7 +227,7 @@ Dictionary<string, GroupLayer> 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;

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -18,11 +21,17 @@ public class ArcGISRootObjectBuilder : IRootObjectBuilder<MapMember>
{
private readonly IUnitOfWorkFactory _unitOfWorkFactory;
private readonly ISendConversionCache _sendConversionCache;
private readonly IConversionContextStack<ArcGISDocument, Unit> _contextStack;

public ArcGISRootObjectBuilder(IUnitOfWorkFactory unitOfWorkFactory, ISendConversionCache sendConversionCache)
public ArcGISRootObjectBuilder(
IUnitOfWorkFactory unitOfWorkFactory,
ISendConversionCache sendConversionCache,
IConversionContextStack<ArcGISDocument, Unit> contextStack
)
{
_unitOfWorkFactory = unitOfWorkFactory;
_sendConversionCache = sendConversionCache;
_contextStack = contextStack;
}

public RootObjectBuilderResult Build(
Expand All @@ -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<IRootToSpeckleConverter>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<FieldDescription> fields = _fieldsUtils.GetFieldsFromSpeckleLayer(target);

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -18,12 +17,13 @@ public PointToHostConverter(IConversionContextStack<ArcGISDocument, ACG.Unit> 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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
using Objects.BuiltElements.Revit;
using Speckle.Core.Kits;
using Speckle.Core.Models;

namespace Speckle.Converters.ArcGIS3.Utils;

/// <summary>
/// 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
/// </summary>
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<string, object?> 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<Base> 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;
}

/// <summary>
/// Initializes a new instance of <see cref="CRSoffsetRotation"/>.
/// </summary>
/// <param name="spatialReference">SpatialReference to apply offsets and rotation to.</param>
public CRSoffsetRotation(ACG.SpatialReference spatialReference)
{
SpatialReference = spatialReference;
SpeckleUnitString = GetSpeckleUnit(spatialReference);
LatOffset = 0;
LonOffset = 0;
TrueNorthRadians = 0;
}

/// <summary>
/// Initializes a new instance of <see cref="CRSoffsetRotation"/>.
/// </summary>
/// <param name="spatialReference">SpatialReference to apply offsets and rotation to.</param>
/// <param name="trueNorthRadians">Angle to True North in radians.</param>
public CRSoffsetRotation(ACG.SpatialReference spatialReference, double trueNorthRadians)
{
SpatialReference = spatialReference;
SpeckleUnitString = GetSpeckleUnit(spatialReference);
LatOffset = 0;
LonOffset = 0;
TrueNorthRadians = trueNorthRadians;
}

/// <summary>
/// Initializes a new instance of <see cref="CRSoffsetRotation"/>.
/// </summary>
/// <param name="spatialReference">SpatialReference to apply offsets and rotation to.</param>
/// <param name="latOffset">Latitude (Y) ofsset in the current SpatialReference units.</param>
/// <param name="lonOffset">Longitude (X) ofsset in the current SpatialReference units.</param>
/// <param name="trueNorthRadians">Angle to True North in radians.</param>
public CRSoffsetRotation(
ACG.SpatialReference spatialReference,
double latOffset,
double lonOffset,
double trueNorthRadians
)
{
SpatialReference = spatialReference;
SpeckleUnitString = GetSpeckleUnit(spatialReference);
LatOffset = latOffset;
LonOffset = lonOffset;
TrueNorthRadians = trueNorthRadians;
}
}
Loading
Loading