Skip to content

Commit

Permalink
arc gis traversal cleanup (#3482)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
JR-Morgan authored Jun 8, 2024
1 parent 61f5b20 commit dd0a8b3
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 95 deletions.
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

0 comments on commit dd0a8b3

Please sign in to comment.