From dd0a8b381c93c5af18eafdc9d20eb7158f06f4cb Mon Sep 17 00:00:00 2001 From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com> Date: Sat, 8 Jun 2024 14:01:05 +0100 Subject: [PATCH] arc gis traversal cleanup (#3482) * Changes to traversal for arcgis * Changed filed type casting to use ConvertTo * Simplified GetAcendents * xml comment * Shuffled some functions around * More tweaks * More changes * dumbdumb net versioning --- .../TraversalContextExtensions.cs | 30 +++-- .../TraversalContextExtensionsTests.cs | 10 ++ .../Operations/Receive/HostObjectBuilder.cs | 103 ++++++++---------- .../Receive/RhinoHostObjectBuilder.cs | 4 +- .../Utils/GISAttributeFieldType.cs | 10 +- .../Utils/INonNativeFeaturesUtils.cs | 4 +- .../Utils/NonNativeFeaturesUtils.cs | 36 +++--- 7 files changed, 102 insertions(+), 95 deletions(-) diff --git a/Core/Core/Models/GraphTraversal/TraversalContextExtensions.cs b/Core/Core/Models/GraphTraversal/TraversalContextExtensions.cs index b987cb1b45..dab65ed446 100644 --- a/Core/Core/Models/GraphTraversal/TraversalContextExtensions.cs +++ b/Core/Core/Models/GraphTraversal/TraversalContextExtensions.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; namespace Speckle.Core.Models.GraphTraversal; @@ -10,6 +12,7 @@ public static class TraversalContextExtensions /// /// /// + [Pure] public static IEnumerable GetPropertyPath(this TraversalContext context) { TraversalContext? head = context; @@ -26,22 +29,31 @@ public static IEnumerable GetPropertyPath(this TraversalContext context) } /// - /// Walks up the tree, returning all typed ascendant, starting the closest , - /// walking up nodes + /// Walks up the tree, returning all ascendant, including /// /// - /// - public static IEnumerable GetAscendantOfType(this TraversalContext context) - where T : Base + /// and all its ascendants + [Pure] + public static IEnumerable GetAscendants(this TraversalContext context) { TraversalContext? head = context; do { - if (head.Current is T c) - { - yield return c; - } + yield return head.Current; head = head.Parent; } while (head != null); } + + /// + /// Walks up the tree, returning all typed ascendant, starting the closest , + /// walking up nodes + /// + /// + /// and all its ascendants of type + [Pure] + public static IEnumerable GetAscendantOfType(this TraversalContext context) + where T : Base + { + return context.GetAscendants().OfType(); + } } diff --git a/Core/Tests/Speckle.Core.Tests.Unit/Models/GraphTraversal/TraversalContextExtensionsTests.cs b/Core/Tests/Speckle.Core.Tests.Unit/Models/GraphTraversal/TraversalContextExtensionsTests.cs index 9686e289bb..e8a38cf800 100644 --- a/Core/Tests/Speckle.Core.Tests.Unit/Models/GraphTraversal/TraversalContextExtensionsTests.cs +++ b/Core/Tests/Speckle.Core.Tests.Unit/Models/GraphTraversal/TraversalContextExtensionsTests.cs @@ -28,6 +28,16 @@ public void GetPropertyPath_ReturnsSequentialPath(int depth) Assert.That(path, Is.EquivalentTo(expected)); } + [TestCaseSource(nameof(TestDepths))] + public void GetAscendant(int depth) + { + var testData = CreateLinkedList(depth, i => new()); + + var all = TraversalContextExtensions.GetAscendants(testData).ToArray(); + + Assert.That(all, Has.Length.EqualTo(depth)); + } + [TestCaseSource(nameof(TestDepths))] public void GetAscendantOfType_AllBase(int depth) { 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 ce31b4775c..2b770782ef 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 @@ -1,3 +1,4 @@ +using System.Diagnostics.Contracts; using ArcGIS.Desktop.Mapping; using Speckle.Connectors.Utils.Builders; using Speckle.Converters.Common; @@ -9,6 +10,7 @@ using Speckle.Connectors.Utils.Conversion; using Speckle.Core.Models.GraphTraversal; using Speckle.Converters.ArcGIS3; +using RasterLayer = Objects.GIS.RasterLayer; namespace Speckle.Connectors.ArcGIS.Operations.Receive; @@ -34,77 +36,40 @@ GraphTraversal traverseFunction _traverseFunction = traverseFunction; } - public (string path, Geometry converted, string? parentId) ConvertNonNativeGeometries( - Base obj, - string[] path, - string? parentId, - List objectIds - ) + private (string path, Geometry converted) ConvertNonNativeGeometries(Base obj, string[] path) { Geometry converted = (Geometry)_converter.Convert(obj); - objectIds.Add(obj.id); List objPath = path.ToList(); objPath.Add(obj.speckle_type.Split(".")[^1]); - return ($"{string.Join("\\", objPath)}", converted, parentId); + return (string.Join("\\", objPath), converted); } - public (string path, string converted) ConvertNativeLayers(Collection obj, string[] path, List objectIds) + private (string path, string converted) ConvertNativeLayers(Collection obj, string[] path) { string converted = (string)_converter.Convert(obj); - objectIds.Add(obj.id); string objPath = $"{string.Join("\\", path)}\\{obj.name}"; return (objPath, converted); } - public string AddDatasetsToMap((string, string) databaseObj) + private string AddDatasetsToMap((string nestedLayerName, string datasetId) databaseObj) { + Uri uri = + new( + $"{_contextStack.Current.Document.SpeckleDatabasePath.AbsolutePath.Replace('/', '\\')}\\{databaseObj.datasetId}" + ); + Map map = _contextStack.Current.Document.Map; try { - return LayerFactory.Instance - .CreateLayer( - new Uri( - $"{_contextStack.Current.Document.SpeckleDatabasePath.AbsolutePath.Replace('/', '\\')}\\{databaseObj.Item2}" - ), - _contextStack.Current.Document.Map, - layerName: databaseObj.Item1 - ) - .URI; + return LayerFactory.Instance.CreateLayer(uri, map, layerName: databaseObj.nestedLayerName).URI; } catch (ArgumentException) { return StandaloneTableFactory.Instance - .CreateStandaloneTable( - new Uri( - $"{_contextStack.Current.Document.SpeckleDatabasePath.AbsolutePath.Replace('/', '\\')}\\{databaseObj.Item2}" - ), - _contextStack.Current.Document.Map, - tableName: databaseObj.Item1 - ) + .CreateStandaloneTable(uri, map, tableName: databaseObj.nestedLayerName) .URI; } } - private string[] GetLayerPath(TraversalContext context) - { - string[] collectionBasedPath = context.GetAscendantOfType().Select(c => c.name).ToArray(); - string[] reverseOrderPath = - collectionBasedPath.Length != 0 ? collectionBasedPath : context.GetPropertyPath().ToArray(); - return reverseOrderPath.Reverse().ToArray(); - } - - private bool HasGISParent(TraversalContext context) - { - List vectorLayers = context - .GetAscendantOfType() - .Where(obj => obj != context.Current) - .ToList(); - List rasterLayers = context - .GetAscendantOfType() - .Where(obj => obj != context.Current) - .ToList(); - return vectorLayers.Count + rasterLayers.Count > 0; - } - public HostObjectBuilderResult Build( Base rootObject, string projectName, @@ -116,31 +81,31 @@ CancellationToken cancellationToken // Prompt the UI conversion started. Progress bar will swoosh. onOperationProgressed?.Invoke("Converting", null); - // POC: This is where we will define our receive strategy, or maybe later somewhere else according to some setting pass from UI? var objectsToConvert = _traverseFunction .Traverse(rootObject) + .Where(ctx => ctx.Current is not Collection || IsGISType(ctx.Current)) .Where(ctx => HasGISParent(ctx) is false) - .Select(ctx => (GetLayerPath(ctx), ctx.Current, ctx.Parent?.Current.id)) .ToList(); int allCount = objectsToConvert.Count; int count = 0; - Dictionary convertedGeometries = new(); - List objectIds = new(); + Dictionary convertedGeometries = new(); List<(string path, string converted)> convertedGISObjects = new(); // 1. convert everything List results = new(objectsToConvert.Count); List bakedObjectIds = new(); - foreach (var item in objectsToConvert) + foreach (TraversalContext ctx in objectsToConvert) { - (string[] path, Base obj, string? parentId) = item; + string[] path = GetLayerPath(ctx); + Base obj = ctx.Current; + cancellationToken.ThrowIfCancellationRequested(); try { - if (obj is VectorLayer or Objects.GIS.RasterLayer) + if (IsGISType(obj)) { - var result = ConvertNativeLayers((Collection)obj, path, objectIds); + var result = ConvertNativeLayers((Collection)obj, path); convertedGISObjects.Add(result); // NOTE: Dim doesn't really know what is what - is the result.path the id of the obj? // TODO: is the type in here basically a GIS Layer? @@ -148,8 +113,8 @@ CancellationToken cancellationToken } else { - var result = ConvertNonNativeGeometries(obj, path, parentId, objectIds); - convertedGeometries[obj.id] = result; + var result = ConvertNonNativeGeometries(obj, path); + convertedGeometries[ctx] = result; // NOTE: Dim doesn't really know what is what - is the result.path the id of the obj? results.Add(new(Status.SUCCESS, obj, result.path, result.converted.GetType().ToString())); //POC: what native id?, path may not be unique @@ -184,4 +149,26 @@ CancellationToken cancellationToken // TODO: validated a correct set regarding bakedobject ids return new(bakedObjectIds, results); } + + [Pure] + private static string[] GetLayerPath(TraversalContext context) + { + string[] collectionBasedPath = context.GetAscendantOfType().Select(c => c.name).ToArray(); + string[] reverseOrderPath = + collectionBasedPath.Length != 0 ? collectionBasedPath : context.GetPropertyPath().ToArray(); + return reverseOrderPath.Reverse().ToArray(); + } + + [Pure] + private static bool HasGISParent(TraversalContext context) + { + List gisLayers = context.GetAscendants().Where(IsGISType).Where(obj => obj != context.Current).ToList(); + return gisLayers.Count > 0; + } + + [Pure] + private static bool IsGISType(Base obj) + { + return obj is RasterLayer or VectorLayer; + } } diff --git a/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Operations/Receive/RhinoHostObjectBuilder.cs b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Operations/Receive/RhinoHostObjectBuilder.cs index 26215d4464..02f653f08a 100644 --- a/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Operations/Receive/RhinoHostObjectBuilder.cs +++ b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Operations/Receive/RhinoHostObjectBuilder.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.Contracts; using Rhino; using Rhino.DocObjects; using Rhino.Geometry; @@ -175,7 +176,8 @@ private int GetAndCreateLayerFromPath(string[] path, string baseLayerName, Dicti return previousLayer.Index; } - private string[] GetLayerPath(TraversalContext context) + [Pure] + private static string[] GetLayerPath(TraversalContext context) { string[] collectionBasedPath = context.GetAscendantOfType().Select(c => c.name).ToArray(); string[] reverseOrderPath = diff --git a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/GISAttributeFieldType.cs b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/GISAttributeFieldType.cs index 64cb937987..332a6d077a 100644 --- a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/GISAttributeFieldType.cs +++ b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/GISAttributeFieldType.cs @@ -97,11 +97,11 @@ public static FieldType FieldTypeToNative(object fieldType) return fieldType switch { FieldType.String => (string)value, - FieldType.Single => (float)(double)value, - FieldType.Integer => (int)(long)value, // need this step because sent "ints" seem to be received as "longs" - FieldType.BigInteger => (long)value, - FieldType.SmallInteger => (short)(long)value, - FieldType.Double => (double)value, + FieldType.Single => Convert.ToSingle(value), + FieldType.Integer => Convert.ToInt32(value), // need this step because sent "ints" seem to be received as "longs" + FieldType.BigInteger => Convert.ToInt64(value), + FieldType.SmallInteger => Convert.ToInt16(value), + FieldType.Double => Convert.ToDouble(value), FieldType.Date => DateTime.Parse((string)value, null), FieldType.DateOnly => DateOnly.Parse((string)value), FieldType.TimeOnly => TimeOnly.Parse((string)value), diff --git a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/INonNativeFeaturesUtils.cs b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/INonNativeFeaturesUtils.cs index 6ab9c32603..1f4295b457 100644 --- a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/INonNativeFeaturesUtils.cs +++ b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/INonNativeFeaturesUtils.cs @@ -1,8 +1,10 @@ +using Speckle.Core.Models.GraphTraversal; + namespace Speckle.Converters.ArcGIS3.Utils; public interface INonNativeFeaturesUtils { public List<(string parentPath, string converted)> WriteGeometriesToDatasets( - Dictionary convertedObjs + Dictionary convertedObjs ); } 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 cdb5226076..621ad260ca 100644 --- a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/NonNativeFeaturesUtils.cs +++ b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/NonNativeFeaturesUtils.cs @@ -5,6 +5,7 @@ using Speckle.Converters.Common; using FieldDescription = ArcGIS.Core.Data.DDL.FieldDescription; using Speckle.Core.Logging; +using Speckle.Core.Models.GraphTraversal; namespace Speckle.Converters.ArcGIS3.Utils; @@ -23,24 +24,25 @@ public NonNativeFeaturesUtils( } public List<(string parentPath, string converted)> WriteGeometriesToDatasets( - Dictionary convertedObjs + Dictionary convertedObjs ) { - List<(string, string)> result = new(); + List<(string parentPath, string converted)> result = new(); // 1. Sort features into groups by path and geom type Dictionary geometries, string? parentId)> geometryGroups = new(); foreach (var item in convertedObjs) { try { - string objId = item.Key; - (string parentPath, ACG.Geometry geom, string? parentId) = item.Value; + TraversalContext context = item.Key; + (string parentPath, ACG.Geometry geom) = item.Value; + string? parentId = context.Parent?.Current.id; // add dictionnary item if doesn't exist yet // Key must be unique per parent and speckle_type // Key is composed of parentId and parentPath (that contains speckle_type) string uniqueKey = $"{parentId}_{parentPath}"; - if (!geometryGroups.TryGetValue(uniqueKey, out (List geometries, string? parentId) value)) + if (!geometryGroups.TryGetValue(uniqueKey, out _)) { geometryGroups[uniqueKey] = (new List(), parentId); } @@ -57,26 +59,18 @@ public NonNativeFeaturesUtils( // 2. for each group create a Dataset and add geometries there as Features foreach (var item in geometryGroups) { + string uniqueKey = item.Key; // parentId_parentPath + string parentPath = uniqueKey.Split('_', 2)[^1]; + string speckle_type = parentPath.Split('\\')[^1]; + (List geomList, string? parentId) = item.Value; try { - string uniqueKey = item.Key; // parentId_parentPath - string parentPath = uniqueKey.Split('_', 2)[^1]; - string speckle_type = parentPath.Split("\\")[^1]; - (List geomList, string? parentId) = item.Value; - try - { - string converted = CreateDatasetInDatabase(speckle_type, geomList, parentId); - result.Add((parentPath, converted)); - } - catch (GeodatabaseGeometryException) - { - // do nothing if conversion of some geometry groups fails - } + string converted = CreateDatasetInDatabase(speckle_type, geomList, parentId); + result.Add((parentPath, converted)); } - catch (Exception e) when (!e.IsFatal()) + catch (GeodatabaseGeometryException) { - // POC: report, etc. - Debug.WriteLine("conversion error happened."); + // do nothing if writing of some geometry groups fails } } return result;