Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Disable clustering at max zoom level with Googles android-maps-utils

I'm clustering markers on a Google map in Android using the clustering from android-maps-utils. I want to disable the clustering when the map is at it's max zoom level (ie always render individual ClusterItems if the map is at max zoom level, otherwise cluster as normal).

I'm able to almost get it working by doing a test for zoom level in shouldRenderAsCluster() in a custom class extending DefaultClusterRenderer. However my solution lags one step behind the actual zoom level. For instance if the max zoom for the map is level 21, and the user zooms in from level 20 to level 21, the check in shouldRenderAsCluster will get a current zoom level of 20. If the user then zooms out to level 20 the check will get level 21. The ClusterItems gets rendered as individual items like I want, but one zoom action too late.

I get the "Current Zoom" (which is not really current apparently) from the variable mZoom that is set in DefaultClusterRenderer.

This is the code:

public class UserClusterRenderer extends PosMapDefaultClusterRenderer<UserClusterItem> {

    // ...

    @Override
    protected boolean shouldRenderAsCluster(Cluster cluster) {
        return mZoom < mMap.getMaxZoomLevel() && cluster.getSize() > 1;
    }
}

The variables mZoom and mMap are set in DefaultClusterRenderer. Because they have private access there I've made a copy of DefaultClusterRenderer called PosMapDefaultClusterRenderer. The only difference is that mZoom and mMap are declared protected instead so they're accessible in UserClusterRenderer.

Why is mZoom lagging one zoom action behind? Is there a way to get the real zoom level for which the clusters are being rendered?

The zooming is done with a call to animateCamera using a cameraUpdate from a cameraPosition.

Also this is my first question on StackOverflow so any input on tags, formatting etc is welcome.

Edit: Working code after Vai's answer:

public class UserClusterRenderer extends DefaultClusterRenderer<UserClusterItem> {

    GoogleMap mMapCopy; // Store a ref to the map here from constructor

    // ...

    @Override
    protected boolean shouldRenderAsCluster(Cluster cluster) {
        float currentZoom = mMapCopy.getCameraPosition().zoom;
        float currentMaxZoom = mMapCopy.getMaxZoomLevel();
        return currentZoom < currentMaxZoom && cluster.getSize() > 1;
    }
}
like image 391
Erik Hedvall Avatar asked Dec 24 '22 13:12

Erik Hedvall


2 Answers

@Marc D I was having the same issue with the thread exception, and the solution proposed above would not work for me.

To get around this, I passed in the initial zoom level and desired maximum zoom level for clustering to occur into the custom renderer as parameters, and then made the custom renderer class implement the GoogleMap.onCameraMoveListener so that it could pick up further changes to the zoom level, and set the onCameraMoveListener on the map to my instance of the custom renderer class.

The below worked perfectly for me:

public class MaxZoomClusterRenderer extends DefaultClusterRenderer<JobClusterItem> implements ClusterManager.OnClusterItemClickListener<JobClusterItem>, GoogleMap.OnCameraMoveListener {

    private final GoogleMap mMap;
    private float currentZoomLevel, maxZoomLevel;

    public MaxZoomClusterRenderer(Context context, GoogleMap map, ClusterManager<JobClusterItem> clusterManager, float currentZoomLevel, float maxZoomLevel) {
        super(context, map, clusterManager);

        this.mMap = map;
        this.currentZoomLevel = currentZoomLevel;
        this.maxZoomLevel = maxZoomLevel;
    }

    @Override
    public void onCameraMove() {
        currentZoomLevel = mMap.getCameraPosition().zoom;
    }

    @Override
    protected boolean shouldRenderAsCluster(Cluster<JobClusterItem> cluster) {
        // determine if superclass would cluster first, based on cluster size
        return super.shouldRenderAsCluster(cluster) && currentZoomLevel < maxZoomLevel;
    }
}

Then in fragment/activity when configuring the map:

mClusterManager = new ClusterManager<>(getContext(), _map);

// Set custom renderer to allow us to control appearance and behaviour of the clustering
MaxZoomClusterRenderer customRenderer = new MaxZoomClusterRenderer(getContext(), _map, mClusterManager, _map.getCameraPosition().zoom, 18.0f);
mClusterManager.setRenderer(customRenderer);
_map.setOnCameraMoveListener(customRenderer);
_map.setOnCameraIdleListener(mClusterManager);
_map.setOnMarkerClickListener(mClusterManager);

JobClusterItem is just my custom cluster item class that implements ClusterItem.

The key thing is that there is no attempt to reference the map instance inside the shouldRenderAsCluster method.

like image 192
Breeno Avatar answered Apr 28 '23 06:04

Breeno


Thank you Breeno for pointing me in the right direction. I would suggest making the listener the onCameraIdleListener as onCameraMoved get called sooooo much. I had already been using the onCameraIdle function for the map, so I did something like this and used Breenos answer to update the current zoom level inside the renderer, just switching out the onCameraMoved with onCameraIdle.

private ClusterManager<ClusterItems> mClusterManager;
private ClusterRenderer mRenderer;
private GoogleMap mMap;

@Override
public void onMapReady(GoogleMap googleMap) {
    Log.v("------Map Ready--","-----yuppers");
    mMap = googleMap;
    mMap.setOnCameraIdleListener(this);
    mClusterManager = new ClusterManager<>(this, mMap);
    mRenderer = new ClusterRenderer(getContext(), mMap, mMap.getCameraPosition().zoom);
    mClusterManager.setRenderer(mRenderer);
//        mMap.setOnCameraMoveListener(mRenderer);
//        mMap.setOnCameraIdleListener(mClusterManager);
    mMap.setOnMarkerClickListener(mClusterManager);
    mMap.setOnInfoWindowClickListener(mClusterManager);
    mClusterManager.setOnClusterClickListener(this);
    mClusterManager.setOnClusterInfoWindowClickListener(this);
    mClusterManager.setOnClusterItemClickListener(this);
    mClusterManager.setOnClusterItemInfoWindowClickListener(this); 

}   
@Override
public void onCameraIdle() {
    mClusterManager.onCameraIdle();
    mRenderer.onCameraIdle();
}
like image 23
BrianG7 Avatar answered Apr 28 '23 08:04

BrianG7