Adding a large number of graphics in a MVVM application

1462
5
02-25-2020 04:51 AM
BenShort
New Contributor

I am currently implementing ArcGIS 100.2.1 as we have a requirement to support UWP on Windows 10 LTSB (1607) and have a couple of issues with the application locking up while initialising the map and perhaps more crucially, an issue whereby we can't seem to add a large number of graphics to the map.

I've tried a handful of different approaches such as calling the RenderGraphics() method synchronously from the constructor and on a background thread from the MapLoaded event.

Currently I have the following implementation (abridged for readability):

View Model
public class ExampleMapViewModel : Screen
{
    private readonly PictureMarkerSymbol _markerStyleA;
    private readonly PictureMarkerSymbol _markerStyleB;
    private readonly PictureMarkerSymbol _markerStyleC;

    // List of custom markers (generated by a service) with status and locations
    private IList<MarkerRef> _markers;

    public Map Map { get; set; }
    public GraphicsOverlayCollection Overlays { get; set; }    

    public ExampleMapViewModel()
    {
        Map = new Map(Basemap.CreateLightGrayCanvasVector());
        Overlays = new GraphicsOverlayCollection();

        Map.Loaded += Map_Loaded;

        GraphicsOverlay customOverlay = new GraphicsOverlay() { Id = "CustomOverlay", MinScale = 36000, SelectionColor = Color.FromArgb(77, 0, 0, 0) };
        Overlays.Add(customOverlay);

        _markerStyleA = new PictureMarkerSymbol(new Uri("ms-appx:///Assets/MarkerStyleA.png", UriKind.RelativeOrAbsolute)) { Width = 24, Height = 24 };
        _markerStyleB = new PictureMarkerSymbol(new Uri("ms-appx:///Assets/MarkerStyleB.png", UriKind.RelativeOrAbsolute)) { Width = 24, Height = 24 };
        _markerStyleC = new PictureMarkerSymbol(new Uri("ms-appx:///Assets/MarkerStyleC.png", UriKind.RelativeOrAbsolute)) { Width = 24, Height = 24 };

        // Returns a collection of 2000 markers with ids, labels, coordinates and status
        _markers = _markerService.GetMarkers();
    }

    public async Task RenderGraphics()
    {
        await Task.Run(() =>
        {
            var overlay = Overlays.FirstOrDefault(x => x.Id == "CustomOverlay");

            if (overlay != null)
            {
                for (var i = 0; i < _markers.Count; i++)
                {
                    var graphic = CreateGraphic(_marker[i]);
                    overlay.Graphics.Add(graphic);
                    Debug.WriteLine($"Added graphic {i + 1}");
                }
            }
        });
    }

    private Graphic CreateGraphic(MarkerRef marker)
    {
        var loc = new MapPoint(marker.Longitude, marker.Latitude, SaptialReferences.Wgs84);
        var symbol = marker.Status == MarkerStatus.A ? _markerStyleA : marker.Status == MarkerStatus.B ? _markerStyleB : _markerStyleC;
        var attributes = new List<KeyValuePair<string, object>> { new KeyValuePair<string, object>("Reference", marker.ref) };
        var graphic = new Graphic(loc, attributes, symbol);

        return graphic;
    }

    private async void Map_Loaded(object sender, EventArgs e)
    {
        await RenderGraphics();
    }
}

View
<Page
  ...
  xmlns:esriui="using:Esri.ArcGISRuntime.UI.Controls">

  <SplitView>
    <SplitView.Pane>
    ...
    </SplitView.Pane>
    <SplitView.Content>
      <Grid>
        <esriui:MapView x:Name="MapView" 
                        Map="{Binding Map}"
                        GraphicsOverlays={Binding Overlays}"
                        Loaded="MapView_Loaded" />
      </Grid>
    </SplitView.Content>
  </SplitView>

</Page>‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

View (Code Behind)
public sealed partial class ExampleMapView : Page
{
    public ExampleMapView()
    {
        this.InitializeComponent();
    }

    private void MapView_Loaded(object sender, RoutedEventArgs e)
    {
        MapView.LocationDisplay.AutoPanMode = Esri.ArcGISRuntime.UI.LocationDisplayAutoPanMode.Recenter;
        MapView.LocationDisplay.IsEnabled = true;
    }
}

When the view model is loaded, I can see the output starts to show the 'Added graphic' lines that we defined on 43, but when running in the above code it only gets as far as 29 (the count of _markers is 2000) before the output stops and the application or map control freezes (in this case before the base layer can load). If I move the execution of the RenderGraphics() method into the constructor of the view model then I can get to approximately 120 graphics before it falls over but ultimately the result is the same. Commenting out line 42 where we add the graphic the overlay fixes the problem in so far as all the graphics are generated but it seems to be adding them to the overlay that is causing the issue.

I tried a slightly different approach whereby we only added graphics to the overlay that were within the coordinates of the current visible map area which worked slightly better but would ultimately crash after a certain number of graphics had been rendered, even if we cleared the overlay graphics collection every time.

Has anyone experienced something similar in the past?

Tags (3)
0 Kudos
5 Replies
dotMorten_esri
Esri Notable Contributor

Don't assign a symbol to every single graphics. That's extremely expensive - especially with PictureMarkerSymbols.

Instead use a UniqueValueRenderer and an attribute field on each graphic to pick the right symbol. That should help you.

I would say you're hitting a limit a lot sooner than I would have expected so there could be a lot more going on.

BenShort
New Contributor

So I've had a bit of play around with UniqueValueRenderer but not much luck so far, not sure if I'm misunderstanding how it maps the graphics attributes.

I'm adding graphics to the overlay now with both reference and status attributes:

MapPoint loc = new MapPoint(...);

List<KeyValuePair<string, object>> attributes = new List<KeyValuePair<string, object>>

{

    new KeyValuePair<string, object>("Reference", x.Reference),

    new KeyValuePair<string, object>("Status", x.Status.ToString()) // x.Status is an enum so must be converted to a string

}

return new Graphic(loc, attributes);

I'm then creating the following renderer:

var renderer = new UniqueValueRenderer();

renderer.FieldNames.Add("Status");

renderer.UniqueValues.Add(new UniqueValue("Operational Status", "Operational", _operationalMarkerSymbol, "Operational"));

renderer.UniqueValues.Add(new UniqueValue("Defective Status", "Defective", _defectiveMarkerSymbol, "Defective"));

...

_customOverlay.Renderer = renderer;

Overlays = new GraphicsOverlayCollection() { _customOverlay };

Currently I am not seeing any symbols on the map whatsoever, not sure if it is struggling to map the Status attribute from the graphic to the renderer field? The reference attribute isn't used to select the symbol but will be used when identifying what graphic was selected.

0 Kudos
dotMorten_esri
Esri Notable Contributor

we have a requirement to support UWP on Windows 10 LTSB (1607)

Side question, if I may, just so we can better understand this limitation when planning for future system requirement updates: Can you share some details about what sort of system you're deploying on? (here or as private message/email).

Considering LTSC is mainly meant for single-purpose devices, like for instance ATMs or medical equipment, I'm curious about the scenarios here, so we can better support that in future. I'm wondering if there's a group of apps we could do better to support.

There's some good info here on what LTSC is mainly for:

https://docs.microsoft.com/en-us/windows/whats-new/ltsc/#the-long-term-servicing-channel-ltsc

https://techcommunity.microsoft.com/t5/windows-it-pro-blog/ltsc-what-is-it-and-when-should-it-be-use... 

0 Kudos
BenShort
New Contributor

Thanks Morten, I'll take a look into the UniqueValueRenderer and see if that helps.

I've tried sending you a PM regarding the project but I believe you'll need to follow me first? Happy to share the details of what we're looking to achieve though.

0 Kudos
dotMorten_esri
Esri Notable Contributor

Aaah right. Feel free to email me at mnielsen (at) esri

0 Kudos