How can I preset the symbol that 'analysis.Buffer' uses to render?

2355
8
07-28-2016 08:18 AM
HoriaTudosie
Occasional Contributor II

I'm using this work around:

var v1 = await QueuedTask.Run(() => Geoprocessing.MakeValueArray(l1, null, units));
await Geoprocessing.ExecuteToolAsync("analysis.Buffer", v1).ContinueWith(async t =>
{
     var fillBuffLayer = DeltaLayer.Map.Layers.Where(l =>
     {
          var b = l as FeatureLayer;
          return (b != null) && (b.LabelClasses[0].WhereClause == "[BUFF_DIST]");
     }).FirstOrDefault();

     var buffLayer1 = (FeatureLayer)fillBuffLayer;
     if (buffLayer1 != null)
     {
          var simpleValueRenderer =
              (CIMSimpleRenderer)buffLayer1.CreateRenderer(BufferFillRendererDefinition));
          buffLayer1.SetRenderer(simpleValueRenderer);
     }

However it takes long time to execute and flickers ugly...

The second issue I face: the above code continue like this:

    if (l2 != null)
    {
        var v2 = await QueuedTask.Run(() => Geoprocessing.MakeValueArray(l2, null, units));
        await Geoprocessing.ExecuteToolAsync("analysis.Buffer", v2);

        var emptyBuffLayer = DeltaLayer.Map.Layers.Where(l =>
        {
            var b = l as FeatureLayer;
            return (b != null) && !Equals(b, buffLayer1) && (b.LabelClasses[0].WhereClause == "[BUFF_DIST]");
        }).FirstOrDefault();

        var buffLayer2 = (FeatureLayer)emptyBuffLayer;
        if (buffLayer2 != null)
        {
            var simpleValueRenderer = 
                (CIMSimpleRenderer)(buffLayer2.CreateRenderer(BufferEmptyRendererDefinition));
            buffLayer2.SetRenderer(simpleValueRenderer);
        }

and it is called after a Select and a Zoom to a feature which contains the points from the arrays l1 and l2.

With or without this code, after Select/Zoom, the map remains busy for a long lime (like in 5-10 seconds - why?!)

...Most of the times it fails either the points in l1 or l2 (IReadOnlyCollection<object>), or both, or fails to change the renter. However, some times (quite seldom) it works well...

Would it be because of the busy time on the mapView?

(And - third: Intellisesnse reports closures for l1, l2 and this on both ContinueWith...)

0 Kudos
8 Replies
CharlesMacleod
Esri Regular Contributor

Can you provide more information please: How many points in the collections and what is the size of the buffer? I will try and reproduce.

I am assuming that there are no exceptions? That the code executes without failing (GP succeeds, Renderer definition is good, renderer is set with no errors)?

Also: it is not possible to change the buffer symbol.

0 Kudos
HoriaTudosie
Occasional Contributor II

This is what the extension does:

Basically, given a road segment, it tries to match another one with complementary operation INSERT/DELETE.

The test is done in three points - two at segment ends and one somewhere in the middle.

The calculation is always correct and displayed on the bottom of the grid.

The display is done only for the user convenience and repeats the buffering with the tool mentioned before.

The buffer is about 1/20 of the segment road, and the segment road length is consistent with small streets in an average city.

(The units are in Degrees - I would have prefer them in meters to be able to put a cap, but since it is not a real requirement, I can operate also in degrees. The value is something 10^-5, so not very comprehensive...)

The picture above is one that succeeds after about 8 trials. I can repeat the attempt clicking the button "Automatic Pairing" on the Delete header.

It gets a few seconds to get the disks/circle in some random colors and a few more to change in these standard colors depicted there.

But all the previous trials came with no circles, sometimes only with the two filled ones, some other times only with the empty one.

When I have three match points - there is only one call to ExecToolAsinc (l2 in the second code block above, is null.) In that situation I can get the picture ready most of the times, but there are also situations when I have to repeat.

(Before doing all that, I'm also deleting all previous disks, but I have commented that procedure (line 13) during tests, and it has no effect:

public async Task<bool> ClearBufferLayers()
{
    AutomaticMessage = null;
    AutomaticPossiblePair = null;
    await QueuedTask.Run(() =>
    {
        return DeltaLayer.Map.Layers.Where(l =>
        {
            var b = l as FeatureLayer;
            return (b != null) && (b.LabelClasses[0].WhereClause == "[BUFF_DIST]");
        });
    }).ContinueWith(async t => await QueuedTask.Run(() => DeltaLayer.Map.RemoveLayers(t.Result)));
    //await QueuedTask.Run(() => DeltaLayer.Map.RemoveLayers(buffLayers));
    return true;
}

)

I'm using ExecToolAsync because I cannot draw directly on the map.

ArcGis Pro does not support GraphicsLayers (or does it?) and I'm not able to create a FeatureLayer on the fly.

This is the code I attempt to run a new feature layer in the default geodatabase:

private async Task AddSketchLayer(string layerName, bool fill)
{
    var uri = new Uri($@"{Project.Current.DefaultGeodatabasePath}\{layerName}", UriKind.Absolute);
    try
    {
        var f = await QueuedTask.Run(() => LayerFactory.CreateLayer(uri, DeltaMap.Map));
    }
    catch
    {
    }
}

But it fails! The error is System.ArgumentException, the Exception message is

{"Failed to create layer from C:\\Users\\htudosie\\Documents\\ArcGIS\\Projects\\MyProject1\\MyProject1.gdb\\PointMatchLayer."}

There is no other detail or tip on how I can fix it.

Being able to build that feature layer and adding a render, I would get rid of ExecToolAsinc. I already have the buffer shapes...

0 Kudos
CharlesMacleod
Esri Regular Contributor

So I tried two scenarios. I could not reproduce your issues. I am using a File GDB, all local data. Projection is WebMercator.

Scenario1:

I created a point layer that included the end points of an underlying line feature class as well as mid points. There are a total of 9 points (I had 3 lines originally). I used the Buffer GP Tool to buffer the points and add the result to the map. I changed the symbol using a SimpleRenderer. There is a noticeable pause on the first GP call. Say 4 or 5 seconds. Subsequent calls take about 1 second. No flickering.

Scenario2:

I extracted all the Geometries from the point layer. I made a MultiPoint. I buffered the Multipoint using GeometryEngine.Buffer. I added the output polygon (from the buffer) to the Graphic Overlay. This was almost instantaneous. You could improve the performance by caching the geometry and not querying the layer each time (if the point layer was not changing in your session).

//This is Scenario 1 using the GP

internal class ButtonScenario1 : Button {

        internal static int _count = 1;

        protected async override void OnClick() {

            var pointsLayer = MapView.Active.Map.GetLayersAsFlattenedList().First(

                l => l.Name == "Points_For_Lines");

            var layerName = $@"Points_Buffer{_count++}";

           

            var valueArray =

                Geoprocessing.MakeValueArray(pointsLayer,

                    $@"E:\Scratch\TestBuffer\TestBuffer.gdb\{layerName}",

                    "50 meters");

            await Geoprocessing.ExecuteToolAsync("analysis.Buffer", valueArray);

            var buffer_layer = MapView.Active.Map.GetLayersAsFlattenedList().First(

                l => l.Name == layerName) as FeatureLayer;

            //set the renderer to something different than the default

            await QueuedTask.Run(() => {

                var outline = SymbolFactory.ConstructStroke(ColorFactory.BlueRGB, 2.0, SimpleLineStyle.Solid);

                var circleSymbol = SymbolFactory.ConstructPolygonSymbol(ColorFactory.GreyRGB, SimpleFillStyle.Solid,

                    outline);

                var srdef = buffer_layer.GetRenderer() as CIMSimpleRenderer;

                srdef.Symbol = circleSymbol.MakeSymbolReference();

                buffer_layer.SetRenderer(srdef);

            });

        }

    }

//This is Scenario 2 using the Overlay

internal class ButtonScenario2 : Button {

        private IDisposable _graphic = null;

        private CIMPolygonSymbol _polySymbol = null;

        private Geometry _bufferResult = null;

        protected async override void OnClick() {

            if (_graphic != null) {

                _graphic.Dispose();//Clear out the old overlay

                _graphic = null;

            }

            if (_polySymbol == null) {

                _polySymbol = await CreatePolygonSymbolAsync();

            }

            //we will use a Geometry in this example

            var pointsLayer = MapView.Active.Map.GetLayersAsFlattenedList().First(

                l => l.Name == "Points_For_Lines") as FeatureLayer;

            await QueuedTask.Run(() => {

                var mpBuilder = new MultipointBuilder(pointsLayer.GetSpatialReference());

                var cursor = pointsLayer.GetFeatureClass().Search();

                while (cursor.MoveNext()) {

                    mpBuilder.Add(((Feature) cursor.Current).GetShape() as MapPoint);

                }

               //Can be cached safely if the Geometry is not changing

                var mp = mpBuilder.ToGeometry();

                var buff = GeometryEngine.Buffer(mp, 50.0);

                _graphic = MapView.Active.AddOverlay(buff, _polySymbol.MakeSymbolReference());

            });

           

        }

        internal static Task<CIMPolygonSymbol> CreatePolygonSymbolAsync() {

            return QueuedTask.Run(() => {

                var outline = SymbolFactory.ConstructStroke(ColorFactory.BlueRGB, 2.0, SimpleLineStyle.Solid);

                return SymbolFactory.ConstructPolygonSymbol(ColorFactory.GreyRGB, SimpleFillStyle.Solid,

                    outline);

            });

        }

    }

HoriaTudosie
Occasional Contributor II

Thank you,

However I got so many issues that I've consider a complete different approach: I've created features layers in the project's gdb, added symbollogy of each type (I need two types - so two feature layers, each with its own symbollogy - the easiest approach,) and I'm adding my buffers as geometries (with an EditOperation.)

Before that, I've tried everything and got something working (sorry, I don't have the code anymore.)

My extension explores a collection of features (roads) that can be paired (Delete segment with Insert segment) automatically - testing the end points and the middle point in a buffer. I'm showing the buffers for visual feedback.

When the solution using ExecToolAsync was ready (with lot of delays, flickering, dudes, etc.) I was able to explore max 12-20 features, after them ArcGIS Pro was blocking in a dead-lock (no error, no message, nothing - just waiting forever.) It was not the first issue with ExecToolAsync, so I've decided to stop wasting time and did the research for the above solution.

Even so, I'm still using Geoprocessing.ExecuteToolAsync("management.CreateFeatureClass" once I'm starting a new project and is not quite working as expected:

private async void AddSketchLayers()
{
    PointMatchLayer = await AddSketchLayer(Model.LayerPointMatch, true);
    PointEmptyLayer = await AddSketchLayer(Model.LayerPointEmpty, false);
}


private async Task<FeatureLayer> AddSketchLayer(string layerName, bool fill)
{
    var layerNames = DeltaMap.Map.Layers.Select(l => l.Name).ToArray();
    if (layerNames.Contains(layerName))
    {
        return DeltaMap.Map.Layers.FirstOrDefault(l => l.Name == layerName) as FeatureLayer;
    }


    SetBusy($"Create {layerName}...");
    try
    {
        var outPath = Project.Current.DefaultGeodatabasePath;
        var result = await QueuedTask.Run(async () =>
        {
            //var workspaceName = $@"in_memory\{layerName}";


            var parameters = Geoprocessing.MakeValueArray(
                outPath, layerName, "Polygon", null, "No","No", DeltaLayer.Map.SpatialReference.Wkid);
            //var env = Geoprocessing.MakeEnvironmentArray(workspace: workspaceName);
            var cts = new CancellationTokenSource();
            await Geoprocessing.ExecuteToolAsync("management.CreateFeatureClass",
                parameters, null, cts.Token, (eventName, o) =>
                {
                    switch (eventName)
                    {
                        case "OnValidate":
                            if (((IGPMessage[])o).Any(it => it.Type == GPMessageType.Warning ||
                                                            it.Type == GPMessageType.Error))
                            {
                                {
                                    var errorsOrWarnings =
                                        ((IGPMessage[])o).Where(it => it.Type == GPMessageType.Error).ToArray();
                                    if (errorsOrWarnings.Any())
                                    {
                                        var e = errorsOrWarnings.Select(err => err.Text).ToArray();
                                        MessageBox.Show(string.Join(@"
", e));
                                        cts.Cancel();
                                    }
                                }
                            }
                            break;
                    }
                });


            var layer = DeltaMap.Map.Layers.FirstOrDefault(l => l.Name == layerName) as FeatureLayer;

            if (layer == null)
            {
                layer = LayerFactory.CreateFeatureLayer(
                    new Uri($@"{outPath}\{layerName}", UriKind.Relative),
                    DeltaMap.Map);
            }


            if (layer != null)
            {
                layer.SetDisplayCacheType(DisplayCacheType.None);
                layer.SetSnappable(false);
                layer.SetEditable(false);
                layer.SetSelectable(false);
                layer.SetShowPopups(false);


                var rendererDefinition = new SimpleRendererDefinition
                {
                    SymbolTemplate = SymbolFactory.ConstructPolygonSymbol(
                        fill
                        ? ColorFactory.CreateRGBColor(162, 80, 255, 90)
                        : ColorFactory.CreateRGBColor(255, 88, 100, 90),
                        fill
                        ? SimpleFillStyle.Solid
                        : SimpleFillStyle.Null,
                        new CIMSolidStroke
                        {
                            Color = fill
                            ? ColorFactory.CreateRGBColor(162, 80, 255, 127)
                            : ColorFactory.CreateRGBColor(255, 88, 100, 127),
                            Width = 1,
                            Enable = true
                        }).MakeSymbolReference()
                };


                var simpleValueRenderer = (CIMSimpleRenderer)(layer.CreateRenderer(rendererDefinition));
                layer.SetRenderer(simpleValueRenderer);
            }
            return layer;
        });
        return result;
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.ToString());
    }
    return null;
}

See lines 56 to 64. Note that I need two Feature Layer, so I call the method AddSketchLayer twice in lines 3 and 4.

The test in line 58 is for the first call, the second call adds the layer to the map directly (line(56,) totally unexpected!

If I'll have more issue with this code, I'll try to replace the call to ExecToolAsync from line 30 with something more natively...

ExecuteToolAsync

0 Kudos
CharlesMacleod
Esri Regular Contributor

I suggest moving this bit outside of the QueuedTask.Run lambda:

  1. var parameters = Geoprocessing.MakeValueArray( 
  2.                 outPath, layerName, "Polygon", null, "No","No", DeltaLayer.Map.SpatialReference.Wkid); 
  3.             //var env = Geoprocessing.MakeEnvironmentArray(workspace: workspaceName); 
  4.             var cts = new CancellationTokenSource(); 
  5.             await Geoprocessing.ExecuteToolAsync("management.CreateFeatureClass"
  6.                 parameters, null, cts.Token, (eventName, o) => 
  7.                 { 
  8.                     switch (eventName) 
  9.                     { 
  10.                         case "OnValidate"
  11.                             if (((IGPMessage[])o).Any(it => it.Type == GPMessageType.Warning || 
  12.                                                             it.Type == GPMessageType.Error)) 
  13.                             { 
  14.                                 { 
  15.                                     var errorsOrWarnings = 
  16.                                         ((IGPMessage[])o).Where(it => it.Type == GPMessageType.Error).ToArray(); 
  17.                                     if (errorsOrWarnings.Any()) 
  18.                                     { 
  19.                                         var e = errorsOrWarnings.Select(err => err.Text).ToArray(); 
  20.                                         MessageBox.Show(string.Join(@" 
  21. ", e)); 
  22.                                         cts.Cancel(); 
  23.                                     } 
  24.                                 } 
  25.                             } 
  26.                             break
  27.                     } 
  28.                 }); 
0 Kudos
HoriaTudosie
Occasional Contributor II

Trying your solution now, which is cleaner - I did not know about AddOverlay method!

(How do you remove the geometries added like that!?)

Working from home today, - looks a lot slowser because the map remains busy after each zomm for a couple seconds. I had the feg that the previous method (using FeatureLayers) was rendering my symbols before clearing the busy flag... I have to come back to check, or to go to the office to have faster connection...

(I also have to stop now for a couple hours...)

0 Kudos
CharlesMacleod
Esri Regular Contributor

You will have to maintain a list of the graphics you add. Each call to AddOverlay returns an IDisposable. When you dispose that disposable the graphics associated with it are cleared. A convenient place to cache the graphics is your Module (something like internal static List<IDisposable> MyGraphics { ..... }).

See https://github.com/Esri/arcgis-pro-sdk-community-samples/tree/master/Map-Exploration/OverlayExamples

HoriaTudosie
Occasional Contributor II

Unfortunately, the solution with AddOverlay is not a good fit: AddOverlay waits for the map to be not busy (long time) while the previous solution was putting everything on the map before finishing updating the base map!

I have reverted everything and used your previous suggestion...

0 Kudos