From 7d10c7fc89414290a7d5e36780d024f5b7ec1d82 Mon Sep 17 00:00:00 2001 From: KatKatKateryna <89912278+KatKatKateryna@users.noreply.github.com> Date: Mon, 1 Jul 2024 18:44:48 +0800 Subject: [PATCH] DUI3-305 agis implement highlight for individual features e.g. from receive report (#3529) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * introduce ObjectID constructor * adapt BasicBindings highlight to use ObjectId struct * select individual features * update missing tracker Items * query features by ID for selection * separate struct for MapMembers * add comments * formatting * formatting --------- Co-authored-by: Oğuzhan Koral <45078678+oguzhankoral@users.noreply.github.com> --- .../Bindings/BasicConnectorBinding.cs | 64 ++++++++++++------- .../Operations/Receive/HostObjectBuilder.cs | 62 ++++++++++++++---- .../Utils/MapMemberFeature.cs | 16 +++++ .../Utils/ObjectID.cs | 38 +++++++++++ 4 files changed, 143 insertions(+), 37 deletions(-) create mode 100644 DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/MapMemberFeature.cs create mode 100644 DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/ObjectID.cs diff --git a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/BasicConnectorBinding.cs b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/BasicConnectorBinding.cs index 649691a526..961ceb1576 100644 --- a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/BasicConnectorBinding.cs +++ b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/BasicConnectorBinding.cs @@ -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; @@ -58,7 +60,8 @@ public BasicConnectorBinding(DocumentModelStore store, ArcGISSettings settings, public void RemoveModel(ModelCard model) => _store.RemoveModel(model); - public void HighlightObjects(List objectIds) => HighlightObjectsOnView(objectIds); + public void HighlightObjects(List objectIds) => + HighlightObjectsOnView(objectIds.Select(x => new ObjectID(x)).ToList()); public void HighlightModel(string modelCardId) { @@ -69,16 +72,16 @@ public void HighlightModel(string modelCardId) return; } - var objectIds = new List(); + var objectIds = new List(); 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) @@ -88,42 +91,42 @@ public void HighlightModel(string modelCardId) HighlightObjectsOnView(objectIds); } - private async void HighlightObjectsOnView(List objectIds) + private async void HighlightObjectsOnView(List objectIds) { MapView mapView = MapView.Active; await QueuedTask .Run(() => { - List mapMembers = GetMapMembers(objectIds, mapView); + List mapMembersFeatures = GetMapMembers(objectIds, mapView); ClearSelectionInTOC(); ClearSelection(); - SelectMapMembersInTOC(mapMembers); - SelectMapMembers(mapMembers); + SelectMapMembersInTOC(mapMembersFeatures); + SelectMapMembersAndFeatures(mapMembersFeatures); mapView.ZoomToSelected(); }) .ConfigureAwait(false); } - private List GetMapMembers(List objectIds, MapView mapView) + private List GetMapMembers(List objectIds, MapView mapView) { - List mapMembers = new(); + // find the layer on the map (from the objectID) and add the featureID is available + List 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() @@ -143,24 +146,39 @@ private void ClearSelectionInTOC() MapView.Active.ClearTOCSelection(); } - private void SelectMapMembers(List mapMembers) + private void SelectMapMembersAndFeatures(List 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 mapMembers) + private void SelectMapMembersInTOC(List mapMembersFeatures) { List layers = new(); List 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 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 3e22f2d85d..953a6b2abb 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 @@ -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; @@ -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); @@ -151,24 +164,45 @@ CancellationToken cancellationToken private void AddResultsFromTracker(ObjectConversionTracker trackerItem, List 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( diff --git a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/MapMemberFeature.cs b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/MapMemberFeature.cs new file mode 100644 index 0000000000..026e5165dc --- /dev/null +++ b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/MapMemberFeature.cs @@ -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; + } +} diff --git a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/ObjectID.cs b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/ObjectID.cs new file mode 100644 index 0000000000..8374181c5b --- /dev/null +++ b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/ObjectID.cs @@ -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 +{ + 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 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}"; + } + } +}