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

arc gis traversal cleanup #3482

Merged
merged 9 commits into from
Jun 8, 2024
Merged
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
30 changes: 21 additions & 9 deletions Core/Core/Models/GraphTraversal/TraversalContextExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;

namespace Speckle.Core.Models.GraphTraversal;

Expand All @@ -10,6 +12,7 @@ public static class TraversalContextExtensions
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
[Pure]
public static IEnumerable<string> GetPropertyPath(this TraversalContext context)
{
TraversalContext? head = context;
Expand All @@ -26,22 +29,31 @@ public static IEnumerable<string> GetPropertyPath(this TraversalContext context)
}

/// <summary>
/// Walks up the tree, returning all <typeparamref name="T"/> typed ascendant, starting the <typeparamref name="T"/> closest <paramref name="context"/>,
/// walking up <see cref="TraversalContext.Parent"/> nodes
/// Walks up the tree, returning all ascendant, including <paramref name="context"/>
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static IEnumerable<T> GetAscendantOfType<T>(this TraversalContext context)
where T : Base
/// <returns><paramref name="context"/> and all its ascendants</returns>
[Pure]
public static IEnumerable<Base> 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);
}

/// <summary>
/// Walks up the tree, returning all <typeparamref name="T"/> typed ascendant, starting the <typeparamref name="T"/> closest <paramref name="context"/>,
/// walking up <see cref="TraversalContext.Parent"/> nodes
/// </summary>
/// <param name="context"></param>
/// <returns><paramref name="context"/> and all its ascendants of type <typeparamref name="T"/></returns>
[Pure]
public static IEnumerable<T> GetAscendantOfType<T>(this TraversalContext context)
where T : Base
{
return context.GetAscendants().OfType<T>();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics.Contracts;
using ArcGIS.Desktop.Mapping;
using Speckle.Connectors.Utils.Builders;
using Speckle.Converters.Common;
Expand All @@ -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;

Expand All @@ -34,77 +36,40 @@ GraphTraversal traverseFunction
_traverseFunction = traverseFunction;
}

public (string path, Geometry converted, string? parentId) ConvertNonNativeGeometries(
Base obj,
string[] path,
string? parentId,
List<string> objectIds
)
private (string path, Geometry converted) ConvertNonNativeGeometries(Base obj, string[] path)
{
Geometry converted = (Geometry)_converter.Convert(obj);
objectIds.Add(obj.id);
List<string> 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<string> 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<Collection>().Select(c => c.name).ToArray();
string[] reverseOrderPath =
collectionBasedPath.Length != 0 ? collectionBasedPath : context.GetPropertyPath().ToArray();
return reverseOrderPath.Reverse().ToArray();
}

private bool HasGISParent(TraversalContext context)
{
List<VectorLayer> vectorLayers = context
.GetAscendantOfType<VectorLayer>()
.Where(obj => obj != context.Current)
.ToList();
List<Objects.GIS.RasterLayer> rasterLayers = context
.GetAscendantOfType<Objects.GIS.RasterLayer>()
.Where(obj => obj != context.Current)
.ToList();
return vectorLayers.Count + rasterLayers.Count > 0;
}

public HostObjectBuilderResult Build(
Base rootObject,
string projectName,
Expand All @@ -116,40 +81,40 @@ 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<string, (string path, Geometry converted, string? parentId)> convertedGeometries = new();
List<string> objectIds = new();
Dictionary<TraversalContext, (string path, Geometry converted)> convertedGeometries = new();
List<(string path, string converted)> convertedGISObjects = new();

// 1. convert everything
List<ReceiveConversionResult> results = new(objectsToConvert.Count);
List<string> 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?
results.Add(new(Status.SUCCESS, obj, result.path, "GIS Layer"));
}
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
Expand Down Expand Up @@ -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<Collection>().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<Base> 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;
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics.Contracts;
using Rhino;
using Rhino.DocObjects;
using Rhino.Geometry;
Expand Down Expand Up @@ -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<Collection>().Select(c => c.name).ToArray();
string[] reverseOrderPath =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
@@ -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<string, (string parentPath, ACG.Geometry geom, string? parentId)> convertedObjs
Dictionary<TraversalContext, (string parentPath, ACG.Geometry geom)> convertedObjs
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -23,24 +24,25 @@ public NonNativeFeaturesUtils(
}

public List<(string parentPath, string converted)> WriteGeometriesToDatasets(
Dictionary<string, (string parentPath, ACG.Geometry geom, string? parentId)> convertedObjs
Dictionary<TraversalContext, (string parentPath, ACG.Geometry geom)> convertedObjs
)
{
List<(string, string)> result = new();
List<(string parentPath, string converted)> result = new();
// 1. Sort features into groups by path and geom type
Dictionary<string, (List<ACG.Geometry> 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<ACG.Geometry> geometries, string? parentId) value))
if (!geometryGroups.TryGetValue(uniqueKey, out _))
{
geometryGroups[uniqueKey] = (new List<ACG.Geometry>(), parentId);
}
Expand All @@ -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<ACG.Geometry> 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<ACG.Geometry> 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;
Expand Down
Loading