Clustering

When rendering a large number of points in a map, symbolizing each point individually is often counterproductive since symbols frequently overlap, making it difficult to distinguish between points. Symbolizing multiple points with a single symbol is often used to resolve this issue by providing a more aesthetic, efficient, and usable rendering solution. This process is called clustering.

Clustering identifies groups of points in a layer that are within a given cluster distance. The cluster distance is dependent on the scale at which the map is displayed; as you zoom in, fewer points are clustered and as you zoom out, more points are clustered.

NoteNote:

Clustering can be applied to a GraphicsLayer or FeatureLayer.

The two techniques for clustering graphics are as follows:

Both techniques can be used with a GraphicsLayer or a FeatureLayer.

Using the SimpleClusterer

A SimpleClusterer can be added to a GraphicsLayer or FeatureLayer using the following XAML:

<esri:GraphicsLayer ID="MyGraphicsLayer">
    <esri:GraphicsLayer.Clusterer>
        <esri:SimpleClusterer />
    </esri:GraphicsLayer.Clusterer>
</esri:GraphicsLayer>

Groups of points that form a cluster are symbolized using a large cluster symbol. The default large cluster symbol is rendered using a gradient color between red and yellow, and the feature count the cluster represents is drawn on top of the symbol. The large cluster symbol is not interactive by default.

You can modify the SimpleClusterer in XAML using the following properties (default values are listed):

SimpleClusterer property

Description

Radius

The radius within which features will be clustered. Defined in pixels. Default = 40.

Gradient

The LinearGradientBrush used to render large clusters. Default = LinearGradientBrush; MappingMode = RelativeToBoundingBox; GradientStop1: Offset = 0, Argb=127,255,255,0, GradientStop2: Offset = 1, Argb=127,255,0,0.

The following steps illustrates how to modify SimpleClusterer properties in XAML. The result will generate a blue gradient symbol for the large clusters. A green diamond symbol has been applied to single graphic features. See the Simple Clusterer sample in the Graphics section of the Interactive SDK for a functional example.

Screen shot of modified SimpleClusterer symbols.

NoteNote:

The steps assume that you are starting with an application that already contains a Map control. See Creating a map for an example of setting up a basic mapping application.

  1. Add a reference to the System.Runtime.Serialization assembly in your project.
  2. Add a namespace reference to the ESRI.ArcGIS.Client.Symbols namespace in the ESRI.ArcGIS.Client assembly by inserting the attribute shown in the following code as an attribute of the phone:PhoneApplicationPage element in the XAML:
    xmlns:esriSymbols="clr-namespace:ESRI.ArcGIS.Client.Symbols;assembly=ESRI.ArcGIS.Client"
    
  3. Add a SimpleRenderer and a LinearGradientBrush as a static resources. The declaration of the Grid.Resources will be inside the LayoutRoot grid element.
    <Grid.Resources>
        <esri:SimpleRenderer x:Key="MySimpleRenderer">
            <esri:SimpleRenderer.Symbol>
                <esriSymbols:SimpleMarkerSymbol Color="#FF00BB00" Size="26" Style="Diamond" />
            </esri:SimpleRenderer.Symbol>
        </esri:SimpleRenderer>
        <LinearGradientBrush x:Key="BlueGradient" MappingMode="RelativeToBoundingBox" >
            <GradientStop Color="#990011FF" Offset="0"/>
            <GradientStop Color="#990055FF" Offset="0.25"/>
            <GradientStop Color="#990099FF" Offset="0.5"/>
            <GradientStop Color="#9900CCFF" Offset="0.75"/>
            <GradientStop Color="#9900FFFF" Offset="1"/>
        </LinearGradientBrush>
    </Grid.Resources>
    
  4. Add the SimpleClusterer to your GraphicsLayer. Specify the Renderer and the Gradient of the SimpleClusterer.
    <esri:GraphicsLayer ID="MyGraphicsLayer" Renderer="{StaticResource MySimpleRenderer}">
        <esri:GraphicsLayer.Clusterer>
            <esri:SimpleClusterer Gradient="{StaticResource BlueGradient}" />
        </esri:GraphicsLayer.Clusterer>
    </esri:GraphicsLayer>
    
NoteNote:

This example focuses on setting up the symbology of the GraphicsLayer to make use of clustering. It does not demonstrate populating the GraphicsLayer with data that will use the clustering. An example of a query used to populate the GraphicsLayer and make use of the clustering can be found in the code-behind of the Simple Clusterer sample in the Graphics section of the Interactive SDK.

Displaying SimpleClusterer details

A user working with the map may want to get additional information about the features grouped within a large cluster. The ArcGIS API for Windows Phone Toolkit assembly (ESRI.ArcGIS.Client.Toolkit) includes an InfoWindow and a ChildPage that work together for this purpose. See Using the InfoWindow to create map tips for details.

Extending the GraphicsClusterer

To customize the look, feel, and behavior of clustering, you need to create a custom class that derives from ESRI.ArcGIS.Client.GraphicsClusterer and overrides the OnCreateGraphic() method to return a cluster graphic. The clustered features are passed to the OnCreateGraphic method in a graphic collection. Interrogate the collection and graphic features to determine how to create a clustered graphic with a symbol. The SimpleClusterer returns a large cluster symbol.

The following code generates a single symbol for any graphic collection that has more than one feature. The cluster symbol size and color are calculated, and values in an attribute column in the GraphicsLayer or FeatureLayer on which the custom clusterer is applied will be aggregated. The sum total of the values will be displayed on the cluster symbol. For brevity, only the custom cluster class and custom marker symbol class source code are provided here, along with an example of how to associate a feature layer with the custom clusterer. To view the complete example, see the Custom Clusterer sample in the Graphics section of the Interactive SDK.

public class SumClusterer : GraphicsClusterer
{
    public SumClusterer()
    {
        MinimumColor = Colors.Red;
        MaximumColor = Colors.Yellow;
        SymbolScale = 1;
        base.Radius = 50;
    }

    public string AggregateColumn { get; set; }
    public double SymbolScale { get; set; }
    public Color MinimumColor { get; set; }
    public Color MaximumColor { get; set; }

    protected override Graphic OnCreateGraphic(GraphicCollection cluster, 
                                               MapPoint point, int maxClusterCount)
    {
        if (cluster.Count == 1) return cluster[0];
        Graphic graphic = null;

        double sum = 0;

        foreach (Graphic g in cluster)
        {
            if (g.Attributes.ContainsKey(AggregateColumn))
            {
                try
                {
                    sum += Convert.ToDouble(g.Attributes[AggregateColumn]);
                }
                catch { }
            }
        }
        double size = (sum + 450) / 30; 
        size = (Math.Log(sum * SymbolScale / 10) * 10 + 20);
        if (size < 12) size = 12;
        graphic = new Graphic() { Symbol = new ClusterSymbol() { Size = size }, 
            Geometry = point };
        graphic.Attributes.Add("Count", sum);
        graphic.Attributes.Add("Size", size);
        graphic.Attributes.Add("Color", InterpolateColor(size - 12, 100));
        return graphic;
    }

    private static Brush InterpolateColor(double value, double max)
    {
        value = (int)Math.Round(value * 255.0 / max);
        if (value > 255) value = 255; 
        else if (value < 0) value = 0;
        return new SolidColorBrush(Color.FromArgb(127, 255, (byte)value, 0));
    }
}

internal class ClusterSymbol : ESRI.ArcGIS.Client.Symbols.MarkerSymbol
{
    public ClusterSymbol()
    {
        string template = @"
        <ControlTemplate xmlns=""http://schemas.microsoft.com/client/2007"" 
                         xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"" 
                         xmlns:esriConverters=""clr-namespace:ESRI.ArcGIS.Client.ValueConverters;assembly=ESRI.ArcGIS.Client"">
            <Grid IsHitTestVisible=""False"">
                <Grid.Resources>
                    <esriConverters:DictionaryConverter x:Key=""MyDictionaryConverter"" />
                </Grid.Resources>
                <Ellipse
                    Fill=""{Binding Attributes, Converter={StaticResource MyDictionaryConverter}, ConverterParameter=Color}"" 
                    Width=""{Binding Attributes, Converter={StaticResource MyDictionaryConverter}, ConverterParameter=Size}""
                    Height=""{Binding Attributes, Converter={StaticResource MyDictionaryConverter}, ConverterParameter=Size}"" />
                <Grid HorizontalAlignment=""Center"" VerticalAlignment=""Center"">
                    <TextBlock 
                        Text=""{Binding Attributes, Converter={StaticResource MyDictionaryConverter}, ConverterParameter=Count}"" 
                        FontSize=""19"" Margin=""1,1,0,0"" FontWeight=""Bold""
                        Foreground=""#99000000"" />
                    <TextBlock
                        Text=""{Binding Attributes, Converter={StaticResource MyDictionaryConverter}, ConverterParameter=Count}"" 
                        FontSize=""19"" Margin=""0,0,1,1"" FontWeight=""Bold""
                        Foreground=""White"" />
                </Grid>
            </Grid>
        </ControlTemplate>";

        this.ControlTemplate = System.Windows.Markup.XamlReader.Load(template) as ControlTemplate;
    }

    public double Size { get; set; }
    public override double OffsetX
    {
        get
        {
            return Size / 2;
        }
        set
        {
            throw new NotSupportedException();
        }
    }
    public override double OffsetY
    {
        get
        {
            return Size / 2;
        }
        set
        {
            throw new NotSupportedException();
        }
    }
}
NoteNote:

Although the Custom Clusterer class is the same for both Windows Phone and Silverlight, the code for the ClusterSymbol is not. Since the ArcGIS API for Silverlight is built on Silverlight 4, the ClusterSymbol control template can bind directly to attributes. In the ArcGIS API for Windows Phone, attribute binding is not supported, so a DictionaryConverter is used.

A custom clusterer can be applied to a layer in XAML. First, provide an XML namespace reference for your custom clusterer.

xmlns:samples="clr-namespace:CustomClustererSample"
Then associate your layer with the custom clusterer.
<esri:FeatureLayer ID="MyFeatureLayer"
    Url="http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Specialty/ESRI_StatesCitiesRivers_USA/MapServer/0" 
    OutFields="CITY_NAME,POP1990"
    Mode="OnDemand">
    <esri:FeatureLayer.Clusterer>
        <samples:SumClusterer AggregateColumn="POP1990" SymbolScale="0.001" />
    </esri:FeatureLayer.Clusterer>
</esri:FeatureLayer>

1/23/2012