private void perform(MarkerModifier markerModifier) {
      // Don't show small clusters. Render the markers inside, instead.
      if (!shouldRenderAsCluster(cluster)) {
        for (T item : cluster.getItems()) {
          Marker marker = mMarkerCache.get(item);
          MarkerWithPosition markerWithPosition;
          if (marker == null) {
            MarkerOptions markerOptions = new MarkerOptions();
            if (animateFrom != null) {
              markerOptions.position(animateFrom);
              markerOptions.icon(item.getBitmapDescriptor());
            } else {
              markerOptions.position(item.getPosition());
              markerOptions.icon(item.getBitmapDescriptor());
            }
            onBeforeClusterItemRendered(item, markerOptions);
            marker = mClusterManager.getMarkerCollection().addMarker(markerOptions);
            markerWithPosition = new MarkerWithPosition(marker);
            mMarkerCache.put(item, marker);
            if (animateFrom != null) {
              markerModifier.animate(markerWithPosition, animateFrom, item.getPosition());
            }
          } else {
            markerWithPosition = new MarkerWithPosition(marker);
          }
          onClusterItemRendered(item, marker);
          newMarkers.add(markerWithPosition);
        }
        return;
      }

      MarkerOptions markerOptions =
          new MarkerOptions().position(animateFrom == null ? cluster.getPosition() : animateFrom);

      onBeforeClusterRendered(cluster, markerOptions);

      Marker marker = mClusterManager.getClusterMarkerCollection().addMarker(markerOptions);
      mMarkerToCluster.put(marker, cluster);
      mClusterToMarker.put(cluster, marker);
      MarkerWithPosition markerWithPosition = new MarkerWithPosition(marker);
      if (animateFrom != null) {
        markerModifier.animate(markerWithPosition, animateFrom, cluster.getPosition());
      }
      onClusterRendered(cluster, marker);
      newMarkers.add(markerWithPosition);
    }
 /**
  * Gets the "bucket" for a particular cluster. By default, uses the number of points within the
  * cluster, bucketed to some set points.
  */
 protected int getBucket(Cluster<T> cluster) {
   int size = cluster.getSize();
   //        if (size <= BUCKETS[0]) {
   //            return size;
   //        }
   //        for (int i = 0; i < BUCKETS.length - 1; i++) {
   //            if (size < BUCKETS[i + 1]) {
   //                return BUCKETS[i];
   //            }
   //        }
   //        return BUCKETS[BUCKETS.length - 1];
   return size;
 }
    @SuppressLint("NewApi")
    public void run() {
      if (clusters.equals(DefaultClusterRenderer.this.mClusters)) {
        mCallback.run();
        return;
      }

      final MarkerModifier markerModifier = new MarkerModifier();

      final float zoom = mMapZoom;
      final boolean zoomingIn = zoom > mZoom;
      final float zoomDelta = zoom - mZoom;

      final Set<MarkerWithPosition> markersToRemove = mMarkers;
      final LatLngBounds visibleBounds = mMap.getMapStatus().bound;
      // TODO: Add some padding, so that markers can animate in from off-screen.

      // Find all of the existing clusters that are on-screen. These are candidates for
      // markers to animate from.
      List<Point> existingClustersOnScreen = null;
      if (DefaultClusterRenderer.this.mClusters != null && SHOULD_ANIMATE) {
        existingClustersOnScreen = new ArrayList<Point>();
        for (Cluster<T> c : DefaultClusterRenderer.this.mClusters) {
          if (shouldRenderAsCluster(c) && visibleBounds.contains(c.getPosition())) {
            Point point = mSphericalMercatorProjection.toPoint(c.getPosition());
            existingClustersOnScreen.add(point);
          }
        }
      }

      // Create the new markers and animate them to their new positions.
      final Set<MarkerWithPosition> newMarkers =
          Collections.newSetFromMap(new ConcurrentHashMap<MarkerWithPosition, Boolean>());
      for (Cluster<T> c : clusters) {
        boolean onScreen = visibleBounds.contains(c.getPosition());
        if (zoomingIn && onScreen && SHOULD_ANIMATE) {
          Point point = mSphericalMercatorProjection.toPoint(c.getPosition());
          Point closest = findClosestCluster(existingClustersOnScreen, point);
          if (closest != null) {
            LatLng animateTo = mSphericalMercatorProjection.toLatLng(closest);
            markerModifier.add(true, new CreateMarkerTask(c, newMarkers, animateTo));
          } else {
            markerModifier.add(true, new CreateMarkerTask(c, newMarkers, null));
          }
        } else {
          markerModifier.add(onScreen, new CreateMarkerTask(c, newMarkers, null));
        }
      }

      // Wait for all markers to be added.
      markerModifier.waitUntilFree();

      // Don't remove any markers that were just added. This is basically anything that had
      // a hit in the MarkerCache.
      markersToRemove.removeAll(newMarkers);

      // Find all of the new clusters that were added on-screen. These are candidates for
      // markers to animate from.
      List<Point> newClustersOnScreen = null;
      if (SHOULD_ANIMATE) {
        newClustersOnScreen = new ArrayList<Point>();
        for (Cluster<T> c : clusters) {
          if (shouldRenderAsCluster(c) && visibleBounds.contains(c.getPosition())) {
            Point p = mSphericalMercatorProjection.toPoint(c.getPosition());
            newClustersOnScreen.add(p);
          }
        }
      }

      // Remove the old markers, animating them into clusters if zooming out.
      for (final MarkerWithPosition marker : markersToRemove) {
        boolean onScreen = visibleBounds.contains(marker.position);
        // Don't animate when zooming out more than 3 zoom levels.
        // TODO: drop animation based on speed of device & number of markers to animate.
        if (!zoomingIn && zoomDelta > -3 && onScreen && SHOULD_ANIMATE) {
          final Point point = mSphericalMercatorProjection.toPoint(marker.position);
          final Point closest = findClosestCluster(newClustersOnScreen, point);
          if (closest != null) {
            LatLng animateTo = mSphericalMercatorProjection.toLatLng(closest);
            markerModifier.animateThenRemove(marker, marker.position, animateTo);
          } else {
            markerModifier.remove(true, marker.marker);
          }
        } else {
          markerModifier.remove(onScreen, marker.marker);
        }
      }

      markerModifier.waitUntilFree();

      mMarkers = newMarkers;
      DefaultClusterRenderer.this.mClusters = clusters;
      mZoom = zoom;

      mCallback.run();
    }
 /** Determine whether the cluster should be rendered as individual markers or a cluster. */
 protected boolean shouldRenderAsCluster(Cluster<T> cluster) {
   return cluster.getSize() > MIN_CLUSTER_SIZE;
 }