Skip to content

Commit

Permalink
DUI3-295 track non native received objects for the full report (#3497)
Browse files Browse the repository at this point in the history
* introduce ConverionTracker; simplify variables

* track conversions up to writing groups into dataset

* throw exceptions for failed datasets. TODO: write to results

* correct results

* correct result order

* properly updating Trackers

* more comments

* remove fake Exception from GIS layers

* Move BUILD function to the top
  • Loading branch information
KatKatKateryna authored Jun 13, 2024
1 parent cc70758 commit f6a23eb
Show file tree
Hide file tree
Showing 6 changed files with 237 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,12 @@ private void SelectMapMembersInTOC(List<MapMember> mapMembers)
}
}
MapView.Active.SelectLayers(layers);
// MapView.Active.SelectStandaloneTables(tables); // clears previous selection, not clear how to ADD selection instead

// this step clears previous selection, not clear how to ADD selection instead
// this is why, activating it only if no layers are selected
if (layers.Count == 0)
{
MapView.Active.SelectStandaloneTables(tables);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,40 +36,6 @@ GraphTraversal traverseFunction
_traverseFunction = traverseFunction;
}

private (string path, Geometry converted) ConvertNonNativeGeometries(Base obj, string[] path)
{
Geometry converted = (Geometry)_converter.Convert(obj);
List<string> objPath = path.ToList();
objPath.Add(obj.speckle_type.Split(".")[^1]);
return (string.Join("\\", objPath), converted);
}

private (string path, string converted) ConvertNativeLayers(Collection obj, string[] path)
{
string converted = (string)_converter.Convert(obj);
string objPath = $"{string.Join("\\", path)}\\{obj.name}";
return (objPath, converted);
}

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(uri, map, layerName: databaseObj.nestedLayerName).URI;
}
catch (ArgumentException)
{
return StandaloneTableFactory.Instance
.CreateStandaloneTable(uri, map, tableName: databaseObj.nestedLayerName)
.URI;
}
}

public HostObjectBuilderResult Build(
Base rootObject,
string projectName,
Expand All @@ -89,8 +55,7 @@ CancellationToken cancellationToken

int allCount = objectsToConvert.Count;
int count = 0;
Dictionary<TraversalContext, (string path, Geometry converted)> convertedGeometries = new();
List<(string path, string converted)> convertedGISObjects = new();
Dictionary<TraversalContext, ObjectConversionTracker> conversionTracker = new();

// 1. convert everything
List<ReceiveConversionResult> results = new(objectsToConvert.Count);
Expand All @@ -105,21 +70,15 @@ CancellationToken cancellationToken
{
if (IsGISType(obj))
{
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"));
string nestedLayerPath = $"{string.Join("\\", path)}\\{((Collection)obj).name}";
string datasetId = (string)_converter.Convert(obj);
conversionTracker[ctx] = new ObjectConversionTracker(obj, nestedLayerPath, datasetId);
}
else
{
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
// TODO: Do we need this here? I remember oguzhan saying something that selection/object highlighting is weird in arcgis (weird is subjective)
// bakedObjectIds.Add(result.path);
string nestedLayerPath = $"{string.Join("\\", path)}\\{obj.speckle_type.Split(".")[^1]}";
Geometry converted = (Geometry)_converter.Convert(obj);
conversionTracker[ctx] = new ObjectConversionTracker(obj, nestedLayerPath, converted);
}
}
catch (Exception ex) when (!ex.IsFatal()) // DO NOT CATCH SPECIFIC STUFF, conversion errors should be recoverable
Expand All @@ -130,26 +89,101 @@ CancellationToken cancellationToken
}

// 2. convert Database entries with non-GIS geometry datasets

onOperationProgressed?.Invoke("Writing to Database", null);
convertedGISObjects.AddRange(_nonGisFeaturesUtils.WriteGeometriesToDatasets(convertedGeometries));
_nonGisFeaturesUtils.WriteGeometriesToDatasets(conversionTracker);

// 3. add layer and tables to the Table Of Content
int bakeCount = 0;
Dictionary<string, MapMember> bakedMapMembers = new();
onOperationProgressed?.Invoke("Adding to Map", bakeCount);
// 3. add layer and tables to the Table Of Content
foreach (var databaseObj in convertedGISObjects)
foreach (var item in conversionTracker)
{
cancellationToken.ThrowIfCancellationRequested();
var trackerItem = conversionTracker[item.Key]; // updated tracker object

// BAKE OBJECTS HERE
bakedObjectIds.Add(AddDatasetsToMap(databaseObj));
onOperationProgressed?.Invoke("Adding to Map", (double)++bakeCount / convertedGISObjects.Count);
if (trackerItem.Exception != null)
{
results.Add(new(Status.ERROR, trackerItem.Base, null, null, trackerItem.Exception));
}
else if (trackerItem.DatasetId == null)
{
results.Add(
new(Status.ERROR, trackerItem.Base, null, null, new ArgumentException("Unknown error: Dataset not created"))
);
}
else if (bakedMapMembers.TryGetValue(trackerItem.DatasetId, out MapMember? value))
{
// only add a report item
AddResultsFromTracker(trackerItem, results);
}
else
{
// add layer and layer URI to tracker
MapMember mapMember = AddDatasetsToMap(trackerItem);
trackerItem.AddConvertedMapMember(mapMember);
trackerItem.AddLayerURI(mapMember.URI);
conversionTracker[item.Key] = trackerItem;

// add layer URI to bakedIds
bakedObjectIds.Add(trackerItem.MappedLayerURI == null ? "" : trackerItem.MappedLayerURI);

// add report item
AddResultsFromTracker(trackerItem, results);
}
onOperationProgressed?.Invoke("Adding to Map", (double)++bakeCount / conversionTracker.Count);
}

// TODO: validated a correct set regarding bakedobject ids
return new(bakedObjectIds, results);
}

private void AddResultsFromTracker(ObjectConversionTracker trackerItem, List<ReceiveConversionResult> results)
{
// prioritize individual hostAppGeometry type, if available:
if (trackerItem.HostAppGeom != null)
{
results.Add(
new(Status.SUCCESS, trackerItem.Base, trackerItem.MappedLayerURI, trackerItem.HostAppGeom.GetType().ToString())
);
}
else
{
results.Add(
new(
Status.SUCCESS,
trackerItem.Base,
trackerItem.MappedLayerURI,
trackerItem.HostAppMapMember?.GetType().ToString()
)
);
}
}

private MapMember AddDatasetsToMap(ObjectConversionTracker trackerItem)
{
string? datasetId = trackerItem.DatasetId; // should not ne null here
string nestedLayerName = trackerItem.NestedLayerName;

Uri uri = new($"{_contextStack.Current.Document.SpeckleDatabasePath.AbsolutePath.Replace('/', '\\')}\\{datasetId}");
Map map = _contextStack.Current.Document.Map;

// Most of the Speckle-written datasets will be containing geometry and added as Layers
// although, some datasets might be just tables (e.g. native GIS Tables, in the future maybe Revit schedules etc.
// We can create a connection to the dataset in advance and determine its type, but this will be more
// expensive, than assuming by default that it's a layer with geometry (which in most cases it's expected to be)
try
{
var layer = LayerFactory.Instance.CreateLayer(uri, map, layerName: nestedLayerName);
return layer;
}
catch (ArgumentException)
{
var table = StandaloneTableFactory.Instance.CreateStandaloneTable(uri, map, tableName: nestedLayerName);
return table;
}
}

[Pure]
private static string[] GetLayerPath(TraversalContext context)
{
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using ArcGIS.Desktop.Mapping;
using Speckle.Core.Models;

namespace Speckle.Converters.ArcGIS3.Utils;

/// <summary>
/// Container connecting the received Base object, converted hostApp object, dataset it was written to,
/// URI of the layer mapped from the dataset and, if applicable, feature/row id.
/// </summary>
public struct ObjectConversionTracker
{
public Base Base { get; set; }
public string NestedLayerName { get; set; }
public ACG.Geometry? HostAppGeom { get; set; }
public MapMember? HostAppMapMember { get; set; }
public string? DatasetId { get; set; }
public int? DatasetRow { get; set; }
public string? MappedLayerURI { get; set; }
public Exception? Exception { get; set; }

public void AddException(Exception ex)
{
Exception = ex;
HostAppGeom = null;
DatasetId = null;
DatasetRow = null;
MappedLayerURI = null;
}

public void AddDatasetId(string datasetId)
{
DatasetId = datasetId;
}

public void AddDatasetRow(int datasetRow)
{
DatasetRow = datasetRow;
}

public void AddConvertedMapMember(MapMember mapMember)
{
HostAppMapMember = mapMember;
}

public void AddLayerURI(string layerURIstring)
{
MappedLayerURI = layerURIstring;
}

/// <summary>
/// Initializes a new instance of <see cref="ObjectConversionTracker"/>.
/// </summary>
/// <param name="baseObj">Original received Base object.</param>
/// <param name="nestedLayerName">String with the full traversed path to the object. Will be used to create nested layer structure in the TOC.</param>
public ObjectConversionTracker(Base baseObj, string nestedLayerName)
{
Base = baseObj;
NestedLayerName = nestedLayerName;
}

/// <summary>
/// Constructor for received non-GIS geometries.
/// Initializes a new instance of <see cref="ObjectConversionTracker"/>, accepting converted hostApp geometry.
/// </summary>
/// <param name="baseObj">Original received Base object.</param>
/// <param name="nestedLayerName">String with the full traversed path to the object. Will be used to create nested layer structure in the TOC.</param>
/// <param name="hostAppGeom">Converted ArcGIS.Core.Geometry.</param>
public ObjectConversionTracker(Base baseObj, string nestedLayerName, ACG.Geometry hostAppGeom)
{
Base = baseObj;
NestedLayerName = nestedLayerName;
HostAppGeom = hostAppGeom;
}

/// <summary>
/// Constructor for received native GIS layers.
/// Initializes a new instance of <see cref="ObjectConversionTracker"/>, accepting datasetID of a coverted Speckle layer.
/// </summary>
/// <param name="baseObj">Original received Base object.</param>
/// <param name="nestedLayerName">String with the full traversed path to the object. Will be used to create nested layer structure in the TOC.</param>
/// <param name="datasetId">ID of the locally written dataset, created from received Speckle layer.</param>
public ObjectConversionTracker(Base baseObj, string nestedLayerName, string datasetId)
{
Base = baseObj;
NestedLayerName = nestedLayerName;
DatasetId = datasetId;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,5 @@ namespace Speckle.Converters.ArcGIS3.Utils;

public interface INonNativeFeaturesUtils
{
public List<(string parentPath, string converted)> WriteGeometriesToDatasets(
Dictionary<TraversalContext, (string parentPath, ACG.Geometry geom)> convertedObjs
);
public void WriteGeometriesToDatasets(Dictionary<TraversalContext, ObjectConversionTracker> conversionTracker);
}
Loading

0 comments on commit f6a23eb

Please sign in to comment.