Search code examples
androidopenstreetmaposmdroid

Changing cluster color based on cluster size using osmdroid


I am trying to have clusters of different color on my map according to the cluster size.

For this purpose I subclassed MarkerClusterer and changed the code according to my requirement.

Below is the code

public class CustomMarkerClusterer extends MarkerClusterer {

protected int mMaxClusteringZoomLevel = 17;
protected int mRadiusInPixels = 100;
protected double mRadiusInMeters;
protected Paint mTextPaint;
private ArrayList<Marker> mClonedMarkers;
private Context context;

/**
 * cluster icon anchor
 */
public float mAnchorU = Marker.ANCHOR_CENTER, mAnchorV = Marker.ANCHOR_CENTER;
/**
 * anchor point to draw the number of markers inside the cluster icon
 */
public float mTextAnchorU = Marker.ANCHOR_CENTER, mTextAnchorV = Marker.ANCHOR_CENTER;

public CustomMarkerClusterer(Context ctx) {
    super();
    mTextPaint = new Paint();
    mTextPaint.setColor(Color.BLACK);
    mTextPaint.setTextSize(15 * ctx.getResources().getDisplayMetrics().density);
    mTextPaint.setFakeBoldText(true);
    mTextPaint.setTextAlign(Paint.Align.CENTER);
    mTextPaint.setAntiAlias(true);
    this.context = ctx;
    Drawable clusterIconD = ctx.getResources().getDrawable(R.drawable.cluster2);
    Bitmap clusterIcon = ((BitmapDrawable) clusterIconD).getBitmap();
    setIcon(clusterIcon);
}

/**
 * If you want to change the default text paint (color, size, font)
 */
public Paint getTextPaint() {
    return mTextPaint;
}

/**
 * Set the radius of clustering in pixels. Default is 100px.
 */
public void setRadius(int radius) {
    mRadiusInPixels = radius;
}

/**
 * Set max zoom level with clustering. When zoom is higher or equal to this level, clustering is disabled.
 * You can put a high value to disable this feature.
 */
public void setMaxClusteringZoomLevel(int zoom) {
    mMaxClusteringZoomLevel = zoom;
}

/**
 * Radius-Based clustering algorithm
 */
@Override
public ArrayList<StaticCluster> clusterer(MapView mapView) {

    ArrayList<StaticCluster> clusters = new ArrayList<StaticCluster>();
    convertRadiusToMeters(mapView);

    mClonedMarkers = new ArrayList<Marker>(mItems); //shallow copy
    while (!mClonedMarkers.isEmpty()) {
        Marker m = mClonedMarkers.get(0);
        StaticCluster cluster = createCluster(m, mapView);
        clusters.add(cluster);
    }
    return clusters;
}

private StaticCluster createCluster(Marker m, MapView mapView) {
    GeoPoint clusterPosition = m.getPosition();

    StaticCluster cluster = new StaticCluster(clusterPosition);
    cluster.add(m);

    mClonedMarkers.remove(m);

    if (mapView.getZoomLevel() > mMaxClusteringZoomLevel) {
        //above max level => block clustering:
        return cluster;
    }

    Iterator<Marker> it = mClonedMarkers.iterator();
    while (it.hasNext()) {
        Marker neighbour = it.next();
        double distance = clusterPosition.distanceToAsDouble(neighbour.getPosition());
        if (distance <= mRadiusInMeters) {
            cluster.add(neighbour);
            it.remove();
        }
    }

    return cluster;
}

@Override
public Marker buildClusterMarker(StaticCluster cluster, MapView mapView) {
    Marker m = new Marker(mapView);
    m.setPosition(cluster.getPosition());
    m.setInfoWindow(null);
    m.setAnchor(mAnchorU, mAnchorV);
    Bitmap mutableBitmap=null;

    if (cluster.getSize() < 10) {
        Bitmap icon = Bitmap.createBitmap(((BitmapDrawable) context.getResources().getDrawable(R.drawable.cluster1)).getBitmap());
        mutableBitmap = icon.copy(Bitmap.Config.ARGB_8888, true);

        Canvas iconCanvas = new Canvas(mutableBitmap);
        iconCanvas.drawBitmap(mClusterIcon, 0, 0, null);
        String text = "" + cluster.getSize();
        int textHeight = (int) (mTextPaint.descent() + mTextPaint.ascent());
        iconCanvas.drawText(text,
                mTextAnchorU * icon.getWidth(),
                mTextAnchorV * icon.getHeight() - textHeight / 2,
                mTextPaint);
       // m.setIcon(new BitmapDrawable(mapView.getContext().getResources(), mutableBitmap));
    } else if (cluster.getSize() > 10 && cluster.getSize() < 100) {
        Bitmap icon = Bitmap.createBitmap(((BitmapDrawable) context.getResources().getDrawable(R.drawable.cluster3)).getBitmap());
         mutableBitmap = icon.copy(Bitmap.Config.ARGB_8888, true);
        Canvas iconCanvas = new Canvas(mutableBitmap);
        iconCanvas.drawBitmap(mClusterIcon, 0, 0, null);
        String text = "" + cluster.getSize();
        int textHeight = (int) (mTextPaint.descent() + mTextPaint.ascent());
        iconCanvas.drawText(text,
                mTextAnchorU * icon.getWidth(),
                mTextAnchorV * icon.getHeight() - textHeight / 2,
                mTextPaint);
       // m.setIcon(new BitmapDrawable(mapView.getContext().getResources(), mutableBitmap));
    } else if (cluster.getSize() > 100) {
        Bitmap icon = Bitmap.createBitmap(((BitmapDrawable) context.getResources().getDrawable(R.drawable.cluster2)).getBitmap());
        mutableBitmap = icon.copy(Bitmap.Config.ARGB_8888, true);
        Canvas iconCanvas = new Canvas(mutableBitmap);
        iconCanvas.drawBitmap(mClusterIcon, 0, 0, null);
        String text = "" + cluster.getSize();
        int textHeight = (int) (mTextPaint.descent() + mTextPaint.ascent());
        iconCanvas.drawText(text,
                mTextAnchorU * icon.getWidth(),
                mTextAnchorV * icon.getHeight() - textHeight / 2,
                mTextPaint);
      //  m.setIcon(new BitmapDrawable(mapView.getContext().getResources(), mutableBitmap));
    }
    m.setIcon(new BitmapDrawable(mapView.getContext().getResources(), mutableBitmap));

    return m;
}

@Override
public void renderer(ArrayList<StaticCluster> clusters, Canvas canvas, MapView mapView) {
    for (StaticCluster cluster : clusters) {
        if (cluster.getSize() == 1) {
            //cluster has only 1 marker => use it as it is:
            cluster.setMarker(cluster.getItem(0));
        } else {
            //only draw 1 Marker at Cluster center, displaying number of Markers contained
            Marker m = buildClusterMarker(cluster, mapView);
            cluster.setMarker(m);
        }
    }
}

private void convertRadiusToMeters(MapView mapView) {

    Rect mScreenRect = mapView.getIntrinsicScreenRect(null);

    int screenWidth = mScreenRect.right - mScreenRect.left;
    int screenHeight = mScreenRect.bottom - mScreenRect.top;

    BoundingBox bb = mapView.getBoundingBox();

    double diagonalInMeters = bb.getDiagonalLengthInMeters();
    double diagonalInPixels = Math.sqrt(screenWidth * screenWidth + screenHeight * screenHeight);
    double metersInPixel = diagonalInMeters / diagonalInPixels;

    mRadiusInMeters = mRadiusInPixels * metersInPixel;
}}

buildClusterMarker() is the method where I am trying to assign a different image to the cluster based on the size of the cluster.

When I debug the code it works fine but it only set single color cluster that is defined in the constructor of the class. If I change the image there, it gets changed but for all clusters.


Solution

  • There are three similar branches if code in the buildClusterMakerMethod(), here's the first one:

        Bitmap icon = Bitmap.createBitmap(((BitmapDrawable) context.getResources().getDrawable(R.drawable.cluster1)).getBitmap());
        mutableBitmap = icon.copy(Bitmap.Config.ARGB_8888, true);
    
        Canvas iconCanvas = new Canvas(mutableBitmap);
        iconCanvas.drawBitmap(mClusterIcon, 0, 0, null); //<=== HERE
        ...
    

    Let's go through the code line by line.

    1) There is a bitmap created from drawable R.drawable.cluster1 and stored in the icon variable.

    2) A copy of the icon is stored into the mutableBitmap variable.

    3) mutableBitmap is used to create a canvas. That means, that all operations executed on the canvas will be drawn into this bitmap.

    4) The original icon (stored in the mClusterIcon filed) is drawn on the canvas over the desired bitmap and thus covering it with the icon/bitmap set in the constructor.

    This happens for all 3 branches.

    You can delete all occurrences of the line (iconCanvas.drawBitmap(mClusterIcon, 0, 0, null); to solve the problem.