Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Disable clustering at max zoom level using android-maps-utils

I'm new here, so any feedback is welcome. I'm trying to use a DefaultClusterRenderer to show some custom clusters, and I'm trying to get it not to cluster when the map is fully zoomed in. So I found this answer to the exact same question: Disable clustering at max zoom level with Googles android-maps-utils so I tried that code, but I'm getting a Not on the main thread error. Any help will be greatly appreciated. I have this in my constructor

public EspecieRenderer(Context context, GoogleMap map, ClusterManager<T> clusterManager) {
    super(context, map, clusterManager);
    this.mMap = map;
...
}

and then I'm doing

@Override
protected boolean shouldRenderAsCluster(Cluster<T> cluster) {
    final float currentZoom = mMap.getCameraPosition().zoom;
    final float currentMaxZoom = mMap.getMaxZoomLevel();
    return currentZoom < currentMaxZoom && cluster.getSize() >= 10;
}

Here is how I'm initializing the cluster. I'm using threads because I have close to 200 species to load, each with one or more markers to add to the map

private void setUpClusterer() {
    clusterManager = new ClusterManager<>(this, googleMap);
    clusterManager.setRenderer(new EspecieRenderer<>(this, googleMap, clusterManager));
    googleMap.setOnCameraChangeListener(clusterManager);
    googleMap.setOnMarkerClickListener(clusterManager);
    ArrayList<Especie> especies = (ArrayList<Especie>) Especie.list(this);
    for (Especie especie : especies) {
        ExecutorService queue = Executors.newSingleThreadExecutor();
        queue.execute(new EspecieLoader(this, especie));
    }
}

Where EspecieLoader

public class EspecieLoader implements Runnable {
    private MainActivity context;
    private Especie especie;

    public EspecieLoader(MainActivity mainActivity, Especie especie) {
        this.context = mainActivity;
        this.especie = especie;
    }

    @Override
    public void run() {
        List<Foto> fotos = Foto.findAllByEspecieWithdCoords(context, especie);
        String nombre = especie.getNombreCientifico();
        for (Foto foto : fotos) {
            LatLng latLng = new LatLng(foto.getLatitud(), foto.getLongitud());
            Bitmap bitmap;
            String path = "new/" + foto.getPath().replaceAll("-", "_").toLowerCase();
            try {
                bitmap = ResourcesHelper.getEncyclopediaAssetByName(context, path);
                EspecieMarker especieMarker = new EspecieMarker(nombre, bitmap, latLng);
                context.addSpeciesMarker(especieMarker);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

I'm getting this when I try to run my app:

07-16 19:18:41.402 31151-32075/com.lzm.Cajas E/AndroidRuntime: FATAL EXCEPTION: Thread-47332
Process: com.lzm.Cajas, PID: 31151
java.lang.IllegalStateException: Not on the main thread
    2at maps.f.g.b(Unknown Source)
    at maps.z.F.a(Unknown Source)
    at maps.af.t.a(Unknown Source)
    at vl.onTransact(:com.google.android.gms.DynamiteModulesB:51)
    at android.os.Binder.transact(Binder.java:387)
    at com.google.android.gms.maps.internal.IGoogleMapDelegate$zza$zza.getCameraPosition(Unknown Source)
    at com.google.android.gms.maps.GoogleMap.getCameraPosition(Unknown Source)
    at com.lzm.Cajas.map.EspecieRenderer.shouldRenderAsCluster(EspecieRenderer.java:62)
    at com.google.maps.android.clustering.view.DefaultClusterRenderer$RenderTask.run(DefaultClusterRenderer.java:416)
    at java.lang.Thread.run(Thread.java:818)
like image 245
luzma Avatar asked Jul 17 '16 00:07

luzma


2 Answers

2022 UPDATE - Since the previous answer is a bit messy and the google maps camera events have changed here's a more up-to-date and elegant solution:

Extend ClusterManager end expose a property that indicates whether the markers should be clustered according to the zoom level and update it in the onCameraIdle event.

open class ZoomClusterManager<T : ClusterItem>(
    context: Context,
    private val map: GoogleMap
) : ClusterManager<T>(context, map) {
    companion object {
        private const val CLUSTER_MAX_ZOOM_LEVEL = 9
    }

    private var _shouldClusterZoom: Boolean = true
    val shouldClusterZoom get() = _shouldClusterZoom

    override fun onCameraIdle() {
        super.onCameraIdle()
        _shouldClusterZoom = map.cameraPosition.zoom < CLUSTER_MAX_ZOOM_LEVEL
    }
}

Depending on the usecase you might want to have CLUSTER_MAX_ZOOM_LEVEL as property in the constructor for better reusability of the class.
Then extend DefaultClusterRenderer as well. In the constructor pass your extended version of ClusterManager, override shouldRenderAsCluster and check the previously exposed property like this:

open class ZoomClusterRenderer<T : ClusterItem>(
    context: Context,
    map: GoogleMap,
    private val clusterManager: ZoomClusterManager<T>
) : DefaultClusterRenderer<T>(context, map, clusterManager) {

    override fun shouldRenderAsCluster(cluster: Cluster<T>): Boolean {
        return super.shouldRenderAsCluster(cluster) && clusterManager.shouldClusterZoom
    }
}

In the end wire everything up in your fragment where you're setting up the map:

val clusterManager = ZoomClusterManager<ClusterItem>(requireContext(), googleMap)
clusterManager.renderer = ZoomClusterRenderer(requireContext(), googleMap, clusterManager)
googleMap.setOnCameraIdleListener(clusterManager)

Old answer - I've run into the same need just today. This is my solution: Since you can't set setOnCameraChangeListener twice, remove

googleMap.setOnCameraChangeListener(clusterManager);

and replace it with

googleMap.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener() {
        @Override
        public void onCameraChange(CameraPosition cameraPosition) {
            shouldCluster_zoom = cameraPosition.zoom < 9; //disables the cluster at 9 and higher zoom levels
            clusterManager.onCameraChange(cameraPosition); //Replaces googleMap.setOnCameraChangeListener(clusterManager);
        }
    });

"shouldCluster_zoom" obviously is a global variable, static and boolean. Now in "shouldRenderAsCluster" method check, according to the zoom, if markers should be clustered:

    @Override
    protected boolean shouldRenderAsCluster(Cluster<T> cluster) {
        return super.shouldRenderAsCluster(cluster) && Your_class_name.shouldCluster_zoom;
    }
like image 93
Alberto Pedron Avatar answered Sep 21 '22 02:09

Alberto Pedron


Editing on top of @Alberto97's answer:

OnCameraChange method is now deprecated. They replaced GoogleMap.OnCameraChangeListener() with three camera listeners :

  • GoogleMap.OnCameraMoveStartedListener - Callback interface for when the camera motion starts.
  • GoogleMap.OnCameraMoveListener - Callback interface for when the camera changes position.
  • GoogleMap.OnCameraIdleListener - Callback interface for when camera movement has ended.

I would use OnCameraIdleListener

  googleMap.setOnCameraIdleListener(new GoogleMap.OnCameraIdleListener() {
            @Override
            public void onCameraIdle() {
                shouldCluster_zoom = cameraPosition.zoom < 9;
                clusterManager.onCameraIdle();
            }
        });
like image 42
Sethuraman Srinivasan Avatar answered Sep 22 '22 02:09

Sethuraman Srinivasan