Select to view content in your preferred language

Overiding

807
4
03-11-2013 06:30 AM
PhilipKnight1
Regular Contributor
This is semi-related to this thread:

How is ClusterGraphicsAsync suposed to be overridden? I have overridden OnCreateGraphic, but I'm a bit confused on overriding ClusterGraphicsAsync. From the API reference:
The clustering algorithm is expected to run on a seperate cancellable thread to prevent blocking the UI. On completion, raise ClusteringCompleted by invoking OnClusteringCompleted. The event should not be raised if clustering was cancelled.


I'm also guessing that ClusterGraphicsAsync calls OnCreateGraphic?, but in that case what does it do with the returned graphics?

So my questions are:
1) How do I 
run on a seperate cancellable thread to prevent blocking the UI
?

2) How do 
raise ClusteringCompleted by invoking OnClusteringCompleted
?

3) How does CancelAsync factor into all of this? Much they also be overridden, if so how?

4a) Does ClusterGraphicsAsync call OnCreateGraphic?

   4b) If so, what do you do with the returning graphics?

   4c) If you create clusters from the "graphics" parameters based on the "resolution" parameter, is there a smart way of doing this, as this seems like it can be very complicated and easily could be O(n^2)?



ANY thoughts are welcome
0 Kudos
4 Replies
DominiqueBroux
Esri Frequent Contributor


I'm also guessing that ClusterGraphicsAsync calls OnCreateGraphic?, but in that case what does it do with the returned graphics?


The built-in GraphicsClusterer.ClusterGraphicsAsync calls OnCreateGraphic so a developer can use OnCreateGraphic as an extension point without having to redevelop the cluster process.
If you are on the way to write your own cluster process, you should subclass Clusterer which doesn't define OnCreateGraphic.
The requirement is that, at the end of the process the clustered graphics, are returned as argument of the ClusteringCompleted event.

How do I

run on a seperate cancellable thread to prevent blocking the UI

Up to the developer.
BackgroundWorkers or Tasks executed on the thread pool are possible options.

How do

raise ClusteringCompleted by invoking OnClusteringCompleted

OnClusteringCompleted is protected so you can call it from a subclas of 'Clusterer'

How does CancelAsync factor into all of this? Much they also be overridden, if so how?

CancelAsync is called by the Framework when the cluster result is no more useful (for example after the user zoomed in or out).
A subclass of Clusterer can override it in order to cancel the custom cluster background process.

4a) Does ClusterGraphicsAsync call OnCreateGraphic?

4b) If so, what do you do with the returning graphics?

The graphics are returned as argument of the ClusteringCompleted event.
OnCreateGraphic is only useful to subclass GraphicsClusterer without having to develop the cluster process.

4c) If you create clusters from the "graphics" parameters based on the "resolution" parameter, is there a smart way of doing this, as this seems like it can be very complicated and easily could be O(n^2)?

For sure it's not that simple. It would be easier to use the built in process by subclassing GraphicsClusterer.
If your issue comes from a need to filter the graphics to cluster perhaps you might create another graphics layer with the graphics to cluster and apply the cluster process to this layer. The OnCreateGraphic override could be used to synchronize the 2 layers.
I never tried that though 🙂
0 Kudos
PhilipKnight1
Regular Contributor
So it surely wasn't easy. In fact I never did get everything to work, but I did learn some things and perhaps others will be able to build upon this:

My original reason for doing this original was that I didn't know what graphic I wanted to remove from being clustered until I saw what graphics were left over after clustering (by left-over I mean not clustered).

I was able to cluster the graphics, but I wasn't able to do it properly. Clusters were far too close together and just sitting on top of eachother, or else they were just all gobbled together. But it also seemed that some graphics were just not getting grouped together.

        public override void ClusterGraphicsAsync(IEnumerable<Graphic> graphics, double resolution)
        {
            int incomingTotal = graphics.Count();

            int total = 0;

            //TODO run on seperate cancellable thread
            //BackgroundWorkers or Tasks executed on the thread pool are possible options.
            //if (bw.IsBusy != true)
            //{
            //    bw.RunWorkerAsync();
            //}
            //else
            //{
                
            //}

            GraphicCollection returnGraphics = new GraphicCollection();


            //Group graphics into collections that will be turned into clusters
            IEnumerable<GraphicCollection> graphicGroups = groupGraphics(graphics, resolution);


            GraphicCollection gCollection;
            for (int i = 0; i < graphicGroups.Count(); i++ )
            {
                gCollection = graphicGroups.ElementAt(i);
                if (gCollection.Count > 2)
                {
                    Utility.removeTextSymbols(ref gCollection); //need to remove TextSymbols or else they will be counted

                    MapPoint mp = getCenter(gCollection);

                    Graphic cluster = OnCreateGraphic2(gCollection, mp, gCollection.Count);
                    returnGraphics.Add(cluster);
                    total += gCollection.Count;
                }
                else //should just add one TextSymbol and one GraphicSymbol
                {
                    foreach (Graphic g in gCollection)
                    {
                        returnGraphics.Add(g);
                    }
                    total += 1;
                }
                
            }

            //raise ClusteringCompleted by invoking OnClusteringCompleted
            OnClusteringCompleted(returnGraphics);
        }


        private IEnumerable<GraphicCollection> groupGraphics(IEnumerable<Graphic> graphics, double resolution)
        {
            Queue<Graphic> q = new Queue<Graphic>(graphics);            
            Collection<GraphicCollection> clusters = new Collection<GraphicCollection>();
            int totalCount = 0;
            double range = resolution * 50;

            while (q.Count > 0)
            {
                Graphic g = q.Dequeue();
                GraphicCollection gCollection = new GraphicCollection();
                Envelope searchEnvelope = g.Geometry.Extent;

                //expand the evelope to include the search resolution
                searchEnvelope.XMax += (60*resolution);
                searchEnvelope.YMax += (60*resolution); 
                searchEnvelope.XMin -= (60*resolution);
                searchEnvelope.YMin -= (60*resolution);



                gCollection.Add(g);

                //iterate through q, finding all graphics within extent

                if (q.Count > 0)
                {
                    Graphic searchG = null;
                    Graphic firstSearchG = null;
                    Graphic prevSearchG = null;

                    //remove graphics from queue that are within searchEvelope
                    while (q.Count > 0 && (searchG == null || searchG != firstSearchG || prevSearchG == null))
                    {
                        prevSearchG = searchG;
                        searchG = q.Dequeue(); //new search graphic
                        if (firstSearchG == null)
                        {
                            firstSearchG = searchG;
                        }

                        //if (searchEnvelope.Intersects(searchG.Geometry.Extent) == true)
                        //if (envelopesIntersect(searchEnvelope, searchG.Geometry.Extent) == true)
                        if (withinRange(g, searchG, range) == true)
                        {

                            if (searchG.Symbol.GetType() == typeof(ESRI.ArcGIS.Client.FeatureService.Symbols.PictureMarkerSymbol))
                            {
                                gCollection.Insert(0,searchG);
                            }
                            else 
                            {
                                gCollection.Add(searchG); //send textsymbol to end of colelction, so drawn last
                            }

                            totalCount++;
                            if (searchG == firstSearchG)
                            {
                                firstSearchG = null;
                            }
                        }
                        else
                        {
                            q.Enqueue(searchG);
                        }
                    }
                }
                clusters.Add(gCollection);
            }

            return clusters;
        }


        private bool withinRange(Graphic g1, Graphic g2, double range)
        {
            double a = g2.Geometry.Extent.YMax - g1.Geometry.Extent.YMax;
            double b = g2.Geometry.Extent.XMax - g1.Geometry.Extent.XMax;
            double c = Math.Sqrt(Math.Pow(a, 2) + Math.Pow(b, 2));

            if (c <= range)
            {
                return true;
            }
            return false;
        }
0 Kudos
AnkitaBhatia
Occasional Contributor
Hi Phil,
Thanks for sharing the code, it was indeed very helpful :)!!
I got my code working to a point where I can cluster according to an attribute of the graphic.
The problem I  am facing however is that I am not able to control the Zoom level.

I tried to play with the range property (Increasing the range) and that helps but I am not sure this is the right way to do it.
Can you help me on this.
I am trying to give user different views of data at different Zoom levels using cluster.
So at minimum Zoom and  -> On block appears (which clusters all the graphics of same type).
In between -> I want the block to appear (only the information I am displaying on the block increases)
Maximum Zoom I want to make visible all the graphics.
0 Kudos
PhilipKnight1
Regular Contributor
Hi Phil,
Thanks for sharing the code, it was indeed very helpful :)!!
I got my code working to a point where I can cluster according to an attribute of the graphic.
The problem I  am facing however is that I am not able to control the Zoom level.

I tried to play with the range property (Increasing the range) and that helps but I am not sure this is the right way to do it.
Can you help me on this.
I am trying to give user different views of data at different Zoom levels using cluster.
So at minimum Zoom and  -> On block appears (which clusters all the graphics of same type).
In between -> I want the block to appear (only the information I am displaying on the block increases)
Maximum Zoom I want to make visible all the graphics.


It's been a little while since I have worked with the API, but there is an event that gets fired when the zoom level changes (I'm sure you can find info on it in the forums somewhere), add an event listener to this event and then depending on the zoom level, configure how you want the cluster to work.

Keep in mind that there is no turn on/off clusterer, you either set the clusterer or you don't set it at all or set it to null. Goodluck!
0 Kudos