Skip to content

Clustering

Jaak Laineste edited this page Apr 12, 2018 · 1 revision

Clustering

CARTO Mobile SDK can dynamically cluster point data if large amounts of points need to be shown without cluttering the MapView. Clusters are formed based on the map zoom level and spatially close points are placed into the same cluster.

Clusters are usually markers which display a location of several objects, and typically indicate the number of markers within each object.

Cluster sample

CARTO Mobile SDK has built-in cluster feature, which is highly customizable. You can define the following options in your app code:

  • Styling the cluster objects
  • Dynamically generate cluster object styles. For example, automatically display the number of objects in each cluster
  • Define the minimum zoom level for clusters
  • Set the minimum distance between objects, before it becomes a cluster
  • Indicate the action when clicking on marker. For example, zoom in, or expand cluster (i.e. open the points without zooming). The expand feature is useful for small clusters (containing up to four objects inside)

Depending on the device, the Mobile SDK can cluster 100,000 points in less than a second.

Implementing clustering

Clusters are generated dynamically, based on VectorDataSource data that loads the map layer. If using an API, it works as a unique layer with the ClusteredVectorLayer method, and includes the following parameters in the a hierarchal order:

  1. Select the layer DataSource In most cases, the LocalVectorDataSource function contains all the elements to request the data. It is important that the DataSource displays all elements in a layer, and does not limit it to the current map visualization bbox (bounding box)
  2. ClusterElementBuilder defines a single method buildClusterElement
    // 1. Initialize a local vector data source
    LocalVectorDataSource vectorDataSource1 = new LocalVectorDataSource(mapView.getOptions().getBaseProjection());

    // 2. Create Marker objects and add them to vectorDataSource
    // **Note:** This depends on the _app type_ of your mobile app settings. See AdvancedMap for samples with JSON loading and random point generation

    // 3. Initialize a vector layer with the previous data source
    ClusteredVectorLayer vectorLayer1 = new ClusteredVectorLayer(vectorDataSource1, new MyClusterElementBuilder(this.getApplication()));
    vectorLayer1.setMinimumClusterDistance(20);

    // 4. Add the previous vector layer to the map
    mapView.getLayers().add(vectorLayer1);

Define ClusterElementBuilder

The Cluster Element Builder takes set of original markers (map objects) as input, and returns one object (or another VectorElement, such as a Point or BalloonPopup) which dynamically replaces the original marker.

Note: It is highly recommended to reuse and cache styles to reduce memory usage. For example, a marker style with a specific number is only created once. Android and iOS samples use platform-specific graphic APIs to generate the bitmap for the marker. .NET example only uses BalloonPopup, which is slower but works the same across all platforms.

    private class MyClusterElementBuilder extends ClusterElementBuilder {

        @SuppressLint("UseSparseArrays")
        private Map< Integer, MarkerStyle > markerStyles = new HashMap< Integer, MarkerStyle >();
        private android.graphics.Bitmap markerBitmap;

        MyClusterElementBuilder(Application context) {
            markerBitmap = android.graphics.Bitmap.createBitmap(BitmapFactory.decodeResource(context.getResources(), R.drawable.marker_black));
        }

        @Override
        public VectorElement buildClusterElement(MapPos pos, VectorElementVector elements) {
            // 1. Reuse existing marker styles
            MarkerStyle style = markerStyles.get((int) elements.size());
            
            if (elements.size() == 1) {
                style = ((Marker) elements.get(0)).getStyle();
            }

            if (style == null) {
                android.graphics.Bitmap canvasBitmap = markerBitmap.copy(android.graphics.Bitmap.Config.ARGB_8888, true);
                android.graphics.Canvas canvas = new android.graphics.Canvas(canvasBitmap);
                android.graphics.Paint paint = new android.graphics.Paint(android.graphics.Paint.ANTI_ALIAS_FLAG);
                
                paint.setTextAlign(Paint.Align.CENTER);
                paint.setTextSize(12);
                paint.setColor(android.graphics.Color.argb(255, 0, 0, 0));
                
                canvas.drawText(Integer.toString((int) elements.size()), markerBitmap.getWidth() / 2, markerBitmap.getHeight() / 2 - 5, paint);
                
                MarkerStyleBuilder styleBuilder = new MarkerStyleBuilder();
                styleBuilder.setBitmap(BitmapUtils.createBitmapFromAndroidBitmap(canvasBitmap));
                styleBuilder.setSize(30);
                styleBuilder.setPlacementPriority((int)-elements.size());
                
                style = styleBuilder.buildStyle();
                
                markerStyles.put((int) elements.size(), style);
            }

            // 2. Create marker for the cluster
            Marker marker = new Marker(pos, style);
            return marker;
        }
    }