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

D ui3 305 agis implement highlight for individual features e.g. from receive report #3529

Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System.Reflection;
using ArcGIS.Core.Data;
using ArcGIS.Desktop.Framework.Threading.Tasks;
using ArcGIS.Desktop.Mapping;
using Speckle.Connectors.ArcGIS.HostApp;
using Speckle.Connectors.ArcGIS.Utils;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models;
Expand Down Expand Up @@ -58,7 +60,8 @@ public BasicConnectorBinding(DocumentModelStore store, ArcGISSettings settings,

public void RemoveModel(ModelCard model) => _store.RemoveModel(model);

public void HighlightObjects(List<string> objectIds) => HighlightObjectsOnView(objectIds);
public void HighlightObjects(List<string> objectIds) =>
HighlightObjectsOnView(objectIds.Select(x => new ObjectID(x)).ToList());

public void HighlightModel(string modelCardId)
{
Expand All @@ -69,16 +72,16 @@ public void HighlightModel(string modelCardId)
return;
}

var objectIds = new List<string>();
var objectIds = new List<ObjectID>();

if (model is SenderModelCard senderModelCard)
{
objectIds = senderModelCard.SendFilter.NotNull().GetObjectIds();
objectIds = senderModelCard.SendFilter.NotNull().GetObjectIds().Select(x => new ObjectID(x)).ToList();
}

if (model is ReceiverModelCard receiverModelCard)
{
objectIds = receiverModelCard.BakedObjectIds.NotNull();
objectIds = receiverModelCard.BakedObjectIds.NotNull().Select(x => new ObjectID(x)).ToList();
}

if (objectIds is null)
Expand All @@ -88,42 +91,42 @@ public void HighlightModel(string modelCardId)
HighlightObjectsOnView(objectIds);
}

private async void HighlightObjectsOnView(List<string> objectIds)
private async void HighlightObjectsOnView(List<ObjectID> objectIds)
{
MapView mapView = MapView.Active;

await QueuedTask
.Run(() =>
{
List<MapMember> mapMembers = GetMapMembers(objectIds, mapView);
List<MapMemberFeature> mapMembersFeatures = GetMapMembers(objectIds, mapView);
ClearSelectionInTOC();
ClearSelection();
SelectMapMembersInTOC(mapMembers);
SelectMapMembers(mapMembers);
SelectMapMembersInTOC(mapMembersFeatures);
SelectMapMembersAndFeatures(mapMembersFeatures);
mapView.ZoomToSelected();
})
.ConfigureAwait(false);
}

private List<MapMember> GetMapMembers(List<string> objectIds, MapView mapView)
private List<MapMemberFeature> GetMapMembers(List<ObjectID> objectIds, MapView mapView)
{
List<MapMember> mapMembers = new();
// find the layer on the map (from the objectID) and add the featureID is available
List<MapMemberFeature> mapMembersFeatures = new();

foreach (string objectId in objectIds)
foreach (ObjectID objectId in objectIds)
{
MapMember mapMember = mapView.Map.FindLayer(objectId);
MapMember mapMember = mapView.Map.FindLayer(objectId.MappedLayerURI, true);
if (mapMember is null)
{
mapMember = mapView.Map.FindStandaloneTable(objectId);
mapMember = mapView.Map.FindStandaloneTable(objectId.MappedLayerURI);
}
if (mapMember is null)
if (mapMember is not null)
{
continue;
MapMemberFeature mapMembersFeat = new(mapMember, objectId.FeatureId);
mapMembersFeatures.Add(mapMembersFeat);
}
mapMembers.Add(mapMember);
}

return mapMembers;
return mapMembersFeatures;
}

private void ClearSelection()
Expand All @@ -143,24 +146,39 @@ private void ClearSelectionInTOC()
MapView.Active.ClearTOCSelection();
}

private void SelectMapMembers(List<MapMember> mapMembers)
private void SelectMapMembersAndFeatures(List<MapMemberFeature> mapMembersFeatures)
{
foreach (var member in mapMembers)
foreach (MapMemberFeature mapMemberFeat in mapMembersFeatures)
{
MapMember member = mapMemberFeat.MapMember;
if (member is FeatureLayer layer)
{
layer.Select();
if (mapMemberFeat.FeatureId == null)
{
// select full layer if featureID not specified
layer.Select();
}
else
{
// query features by ID
var objectIDfield = layer.GetFeatureClass().GetDefinition().GetObjectIDField();

// FeatureID range starts from 0, but auto-assigned IDs in the layer start from 1
QueryFilter anotherQueryFilter = new() { WhereClause = $"{objectIDfield} = {mapMemberFeat.FeatureId + 1}" };
using (Selection onlyOneSelection = layer.Select(anotherQueryFilter, SelectionCombinationMethod.New)) { }
}
}
}
}

private void SelectMapMembersInTOC(List<MapMember> mapMembers)
private void SelectMapMembersInTOC(List<MapMemberFeature> mapMembersFeatures)
{
List<Layer> layers = new();
List<StandaloneTable> tables = new();

foreach (MapMember member in mapMembers)
foreach (MapMemberFeature mapMemberFeat in mapMembersFeatures)
{
MapMember member = mapMemberFeat.MapMember;
if (member is Layer layer)
{
if (member is not GroupLayer) // group layer selection clears other layers selection
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Speckle.Core.Models.GraphTraversal;
using Speckle.Converters.ArcGIS3;
using RasterLayer = Objects.GIS.RasterLayer;
using Speckle.Connectors.ArcGIS.Utils;

namespace Speckle.Connectors.ArcGIS.Operations.Receive;

Expand Down Expand Up @@ -116,21 +117,33 @@ CancellationToken cancellationToken
else if (trackerItem.DatasetId == null)
{
results.Add(
new(Status.ERROR, trackerItem.Base, null, null, new ArgumentException("Unknown error: Dataset not created"))
new(
Status.ERROR,
trackerItem.Base,
null,
null,
new ArgumentException($"Unknown error: Dataset not created for {trackerItem.Base.speckle_type}")
)
);
}
else if (bakedMapMembers.TryGetValue(trackerItem.DatasetId, out MapMember? value))
{
// add layer and layer URI to tracker
trackerItem.AddConvertedMapMember(value);
trackerItem.AddLayerURI(value.URI);
conversionTracker[item.Key] = trackerItem; // not necessary atm, but needed if we use conversionTracker further
// only add a report item
AddResultsFromTracker(trackerItem, results);
}
else
{
// add layer and layer URI to tracker
// add layer to Map
MapMember mapMember = AddDatasetsToMap(trackerItem, createdLayerGroups);

// add layer and layer URI to tracker
trackerItem.AddConvertedMapMember(mapMember);
trackerItem.AddLayerURI(mapMember.URI);
conversionTracker[item.Key] = trackerItem;
conversionTracker[item.Key] = trackerItem; // not necessary atm, but needed if we use conversionTracker further

// add layer URI to bakedIds
bakedObjectIds.Add(trackerItem.MappedLayerURI == null ? "" : trackerItem.MappedLayerURI);
Expand All @@ -151,24 +164,45 @@ CancellationToken cancellationToken

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
if (trackerItem.MappedLayerURI == null) // should not happen
{
results.Add(
new(
Status.SUCCESS,
Status.ERROR,
trackerItem.Base,
trackerItem.MappedLayerURI,
trackerItem.HostAppMapMember?.GetType().ToString()
null,
null,
new ArgumentException($"Created Layer URI not found for {trackerItem.Base.speckle_type}")
)
);
}
else
{
// encode layer ID and ID of its feature in 1 object represented as string
ObjectID objectId = new(trackerItem.MappedLayerURI, trackerItem.DatasetRow);
if (trackerItem.HostAppGeom != null) // individual hostAppGeometry
{
results.Add(
new(
Status.SUCCESS,
trackerItem.Base,
objectId.ObjectIdToString(),
trackerItem.HostAppGeom.GetType().ToString()
)
);
}
else // hostApp Layers
{
results.Add(
new(
Status.SUCCESS,
trackerItem.Base,
objectId.ObjectIdToString(),
trackerItem.HostAppMapMember?.GetType().ToString()
)
);
}
}
}

private MapMember AddDatasetsToMap(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using ArcGIS.Desktop.Mapping;

namespace Speckle.Connectors.ArcGIS.Utils;

// bind together a layer object on the map, and auto-assigned ID if the specific feature
public readonly struct MapMemberFeature
{
public int? FeatureId { get; } // unique feature id (start from 0) of a feature in the layer
public MapMember MapMember { get; } // layer object on the Map

public MapMemberFeature(MapMember mapMember, int? featureId)
{
MapMember = mapMember;
FeatureId = featureId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
namespace Speckle.Connectors.ArcGIS.Utils;

// this struct is needed to be able to parse single-string value into IDs of both a layer, and it's individual feature
public struct ObjectID
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would omit MapMember from this struct, it is confusing why objectID checks for MapMember as below.

if (objectId.MapMember == null)
{
   continue;
}

What I expect from ObjectID is just id, if you include MapMember it becomes something else. In this case, we might need another struct that has MapMember too instead considering its nullability on ObjectID struct.

{
private const string FEATURE_ID_SEPARATOR = "__speckleFeatureId__";
public string MappedLayerURI { get; } // unique ID of the layer on the map
public int? FeatureId { get; } // unique feature id (start from 0) of a feature in the layer

public ObjectID(string encodedId)
{
List<string> stringParts = encodedId.Split(FEATURE_ID_SEPARATOR).ToList();
MappedLayerURI = stringParts[0];
FeatureId = null;
if (stringParts.Count > 1)
{
FeatureId = Convert.ToInt32(stringParts[1]);
}
}

public ObjectID(string layerId, int? featureId)
{
MappedLayerURI = layerId;
FeatureId = featureId;
}

public readonly string ObjectIdToString()
{
if (FeatureId == null)
{
return $"{MappedLayerURI}";
}
else
{
return $"{MappedLayerURI}{FEATURE_ID_SEPARATOR}{FeatureId}";
}
}
}
Loading