Modifying Geometry in EditCompleted Event Handler

911
3
Jump to solution
07-07-2020 04:00 AM
KrisCulin
Occasional Contributor

Hello,

Hopefully someone will be able to tell me what I am doing wrong or an alternative approach.

In our ArcGIS Pro configuration, we need to keep links "connected" to their end nodes.  So if you move a node, the end point geometry of the connected links need to be updated accordingly.

Here is an example (N= Node, L= Link)

N1 - L - N2

If you move N1, then the first geometry point of L will be updated to match.  If you move N2, the last point of L is updated.  If you move both nodes and the link, then the link's end points will be updated accordingly.  If you move only N1 and N2, then the end points of L are updated accordingly.  Any intermediate vertices on the link would be left to ArcGIS Pro to update.

I created a test add-in that uses the EditCompleted event handler and code very similar to what we use in production.  I was able to reproduce the refresh issue using this code. 

Here is a quick rundown of what it does:

  1. Loop thru the list of modified layers (arg.Members)
  2. Verify the member was modified and has modifications (arg.Modifies.ContainsKey(member) and arg.FeatureModified(member)
  3. Query that layer using the ObjectIDs that were modified.
  4. For each node moved, update the connected links.
    1. To do this, the Pipe layer is retrieved, queried and geometry updated to match the node's geometry. 
    2. In this sample, only the start point of the two polylines connected to the node are updated
    3. In production, we have a more sophisticated manner of determining which links need updating and query accordingly.

This code works.  You move the node in the map and if you debug the code everything is executed with no errors.  However, the polylines in the map don't update. If you zoom in or out, they "might" update to their correct geometry.  The only time they "stick" to their correct geometry is if you select the links interactively.  In production, if there are 3 links connected to a node that was moved, 2 of the 3 links show as "updated" but the last link shows the old geometry.  Only until you select that link does it refresh in the map.  Using the refresh button in the bottom-right corner of the status bar does not do anything.  The refresh issue occurs both in our production code and this test add-in.  The test add-in does not use any of our custom API.

I've tried using the EditOperation.Callback approach.  The context parameter in that case has an Invalidate method which appears to force the map to update.  However, this also creates an additional item on the undo stack which is something we are trying to avoid.  I could find no other place that offered an Invalidate method.

Calling ClearDisplayCache on the layer does not appear to help.

The code I use here is very similar to the code that we used with ArcMap (via ArcObjects).  In ArcMap, there is just the one action on the undo stack, "Modify Feature".  Undoing this will move back both the node and the connected link.

I also tried watching the RowChangedEvent.  However, in production we have 31 total layers that we maintain and 26 of them are point layers which means they can be connected to links (2 of the 31 are polyline layers, 2 do not connect to links and 1 is a polygon layer).  If we watched this event, we'd also have to watch the ActiveViewChangedEvent as well to know when to subscribe and unsubscribe the event.

            await QueuedTask.Run(() =>
            {
                if (arg.Modifies != null)
                {
                    if (arg.Modifies.Count > 0)
                    {
                        foreach (FeatureLayer featureLayer in arg.Members)
                        {
                            if (arg.Modifies.ContainsKey(featureLayer) && arg.FeaturesModified(featureLayer))
                            {
                                var oids = arg.Modifies[featureLayer];

                                // A necessary check as a map member can be in the list of modified layers but
                                // no actual features were modified.  Don't query the feature layer if tehre are
                                // no modified features
                                if (oids.Count > 0)
                                {
                                    // In our real-world case, the use can select multiple nodes and move them
                                    // We need to update any "connected" links accordingly which is what this
                                    // code mimics.
                                    QueryFilter qf = new QueryFilter
                                    {
                                        ObjectIDs = new List<long>(oids)
                                    };

                                    var featureClass = featureLayer.GetFeatureClass();
                                    using (var featureCursor = featureClass.Search(qf, false))
                                    {
                                        // Loop through the modified features on this layer.
                                        while (featureCursor.MoveNext())
                                        {
                                            Feature feature = featureCursor.Current as Feature;
                                            MapPoint point = feature.GetShape() as MapPoint;

                                            // In this case we are hard-coding the layer as there are just two in the project
                                            // In our real-world code, we know the "ElementType" and get the appropriate layer
                                            // based on that thru our own API calls.  In this simplified case we are simply getting
                                            // the layer that is NOT the current feature layer.
                                            Layer layer = MapView.Active.Map.GetLayersAsFlattenedList().First(l => l.Name != featureLayer.Name);

                                            FeatureLayer pipeLayer = layer as FeatureLayer;
                                            if (pipeLayer != null)
                                            {
                                                FeatureClass pipeFeatureClass = pipeLayer.GetFeatureClass();

                                                // Get all links in the layer - should just be two of them.
                                                // For each link, update the start point of its geometry to that of the node.
                                                // This keeps the link "connected" to the node on the start side of the link.
                                                using (var linkFeatureCursor = pipeFeatureClass.Search(null, false))
                                                {
                                                    while (linkFeatureCursor.MoveNext())
                                                    {
                                                        Feature link = linkFeatureCursor.Current as Feature;
                                                        Polyline polyline = link.GetShape() as Polyline;

                                                        List<MapPoint> points = new List<MapPoint>(polyline.PointCount);
                                                        points.Add(point);

                                                        for (int i = 1; i < polyline.PointCount; ++i)
                                                            points.Add(polyline.Points[i]);

                                                        Polyline updatedGeometry = PolylineBuilder.CreatePolyline(points, point.SpatialReference);
                                                        link.SetShape(updatedGeometry);
                                                        link.Store();
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            });

If you need the full add-in project, I can attach that.

TiA.

Kris

0 Kudos
1 Solution

Accepted Solutions
KrisCulin
Occasional Contributor

HI Sean,

My manager found the MapView.Invalidate method.  The method takes either the layer and an extent or a dictionary of layer to a list of object ids (for that layer).  I found that if I keep track of the links that were modified and use this method, the map updates correctly.  Then the object ids I provide for the two polyline layers we have will refresh the map accordingly by invalidating those features.

This approach is working and appears to be performant enough for our needs.

Thank you for your suggestion.

Kris

View solution in original post

0 Kudos
3 Replies
by Anonymous User
Not applicable

Hi Kris,

Have you tried the the ArcGIS.Desktop.Mapping.MapView.Redraw(bool) method?

In terms of workflow, going through the rowevents is probably your best option. The running editoperation is passed through to make additional edits, you'll get one item on the undo stack, and the display caches will be in sync.

0 Kudos
KrisCulin
Occasional Contributor

Hi Sean,

Yes, I've tried calling Redraw and RedrawAsync. I've also tried calling ClearDisplayCache for every feature later. The refresh button in the status bar also doesn't do it.

None of those work.  The only way it seems to fix itself is if you select the link that needs refreshing.

It seems a cache is not updating when it should.

Kris

0 Kudos
KrisCulin
Occasional Contributor

HI Sean,

My manager found the MapView.Invalidate method.  The method takes either the layer and an extent or a dictionary of layer to a list of object ids (for that layer).  I found that if I keep track of the links that were modified and use this method, the map updates correctly.  Then the object ids I provide for the two polyline layers we have will refresh the map accordingly by invalidating those features.

This approach is working and appears to be performant enough for our needs.

Thank you for your suggestion.

Kris

0 Kudos