Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
package com.google.maps.android.clustering

import android.content.Context
import android.os.AsyncTask
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.GoogleMap.OnCameraIdleListener
import com.google.android.gms.maps.GoogleMap.OnInfoWindowClickListener
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import com.google.maps.android.geometry.Bounds
import com.google.maps.android.geometry.Point
import com.google.maps.android.projection.SphericalMercatorProjection
import com.google.maps.android.quadtree.PointQuadTree
import java.util.ArrayList
import java.util.Collections
import java.util.HashMap
import java.util.HashSet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import java.util.concurrent.Executor
import java.util.concurrent.Executors
import java.util.concurrent.locks.ReadWriteLock
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.withLock

/**
* Optimistically fetch clusters for adjacent zoom levels, caching them as necessary.
Expand Down Expand Up @@ -107,24 +108,18 @@ class PreCachingAlgorithmDecorator<T : ClusterItem>(
}

private fun getClustersInternal(discreteZoom: Int): Set<Cluster<T>> {
var results: Set<Cluster<T>>?
mCacheLock.readLock().lock()
results = mCache.get(discreteZoom)
mCacheLock.readLock().unlock()
val cached = mCacheLock.readLock().withLock {
mCache.get(discreteZoom)
}
if (cached != null) {
return cached
}

if (results == null) {
mCacheLock.writeLock().lock()
try {
results = mCache.get(discreteZoom)
if (results == null) {
results = algorithm.getClusters(discreteZoom.toFloat())
mCache.put(discreteZoom, results)
}
} finally {
mCacheLock.writeLock().unlock()
return mCacheLock.writeLock().withLock {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice cleanup here! Using Kotlin's inline withLock ensures the lock is reliably released in a finally block if an exception occurs during cluster calculation, and the double-checked locking idiom with ?: ... .also { mCache.put(...) } cleanly removes the previous !! force unwraps.

mCache.get(discreteZoom) ?: algorithm.getClusters(discreteZoom.toFloat()).also {
mCache.put(discreteZoom, it)
}
}
return results!!
}

private inner class PrecacheRunnable(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package com.google.maps.android.clustering.view
import androidx.annotation.StyleRes
import com.google.maps.android.clustering.Cluster
import com.google.maps.android.clustering.ClusterItem
import com.google.maps.android.clustering.ClusterManager
import com.google.maps.android.clustering.ClusterManager.OnClusterClickListener
import com.google.maps.android.clustering.ClusterManager.OnClusterInfoWindowClickListener
import com.google.maps.android.clustering.ClusterManager.OnClusterInfoWindowLongClickListener
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ import java.util.Queue
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executor
import java.util.concurrent.Executors
import java.util.concurrent.locks.Condition
import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.ReentrantLock
import kotlin.math.abs
Expand Down Expand Up @@ -325,17 +324,19 @@ open class ClusterRendererMultipleItems<T : ClusterItem> @JvmOverloads construct
}
val projection = mMap.projection

var renderTask: RenderTask?
synchronized(this) {
renderTask = mNextClusters
val renderTask = synchronized(this) {
val task = mNextClusters
mNextClusters = null
mViewModificationInProgress = true
task
}

renderTask!!.setCallback { sendEmptyMessage(TASK_FINISHED) }
renderTask!!.setProjection(projection)
renderTask!!.setMapZoom(mMap.cameraPosition.zoom)
mExecutor.execute(renderTask)
renderTask?.let {
it.setCallback { sendEmptyMessage(TASK_FINISHED) }
it.setProjection(projection)
it.setMapZoom(mMap.cameraPosition.zoom)
mExecutor.execute(it)
}
}

fun queue(clusters: Set<Cluster<T>>) {
Expand Down Expand Up @@ -409,6 +410,8 @@ open class ClusterRendererMultipleItems<T : ClusterItem> @JvmOverloads construct
val markerModifier = MarkerModifier()
val zoom = mMapZoom
val markersToRemove = mMarkers
val sphericalMercatorProjection = mSphericalMercatorProjection
val animate = mAnimate && sphericalMercatorProjection != null
var visibleBounds: LatLngBounds

try {
Expand All @@ -421,11 +424,11 @@ open class ClusterRendererMultipleItems<T : ClusterItem> @JvmOverloads construct

// Find all of the existing clusters that are on-screen. These are candidates for markers to animate from.
var existingClustersOnScreen: MutableList<Point>? = null
if (this@ClusterRendererMultipleItems.mClusters != null && mAnimate) {
if (this@ClusterRendererMultipleItems.mClusters != null && animate) {
existingClustersOnScreen = ArrayList()
for (c in this@ClusterRendererMultipleItems.mClusters!!) {
if (shouldRenderAsCluster(c) && visibleBounds.contains(c.position)) {
val point = mSphericalMercatorProjection!!.toPoint(c.position)
val point = sphericalMercatorProjection.toPoint(c.position)
existingClustersOnScreen.add(point)
}
}
Expand All @@ -436,11 +439,11 @@ open class ClusterRendererMultipleItems<T : ClusterItem> @JvmOverloads construct
val newMarkers: MutableSet<MarkerWithPosition<T>> = Collections.newSetFromMap(ConcurrentHashMap())
for (c in clusters) {
val onScreen = visibleBounds.contains(c.position)
if (mAnimate) {
val point = mSphericalMercatorProjection!!.toPoint(c.position)
if (animate) {
val point = sphericalMercatorProjection.toPoint(c.position)
val closest = findClosestCluster(existingClustersOnScreen, point)
if (closest != null) {
val animateFrom = mSphericalMercatorProjection!!.toLatLng(closest)
val animateFrom = sphericalMercatorProjection.toLatLng(closest)
markerModifier.add(true, CreateMarkerTask(c, newMarkers, animateFrom))
RendererLogger.d("ClusterRenderer", "Animating cluster from closest cluster: " + c.position)
} else {
Expand All @@ -463,26 +466,27 @@ open class ClusterRendererMultipleItems<T : ClusterItem> @JvmOverloads construct

// Find all of the new clusters that were added on-screen. These are candidates for markers to animate from.
var newClustersOnScreen: MutableList<Point>? = null
if (mAnimate) {
if (animate) {
newClustersOnScreen = ArrayList()
for (c in clusters) {
if (shouldRenderAsCluster(c) && visibleBounds.contains(c.position)) {
val p = mSphericalMercatorProjection!!.toPoint(c.position)
val p = sphericalMercatorProjection.toPoint(c.position)
newClustersOnScreen.add(p)
}
}
RendererLogger.d("ClusterRenderer", "New clusters on screen found: " + newClustersOnScreen.size)
}

for (marker in markersToRemove) {
val onScreen = marker.position?.let { visibleBounds.contains(it) } ?: false
val position = marker.position
val onScreen = position?.let { visibleBounds.contains(it) } ?: false

if (onScreen && mAnimate) {
val point = mSphericalMercatorProjection!!.toPoint(marker.position!!)
if (onScreen && animate) {
val point = sphericalMercatorProjection.toPoint(position)
val closest = findClosestCluster(newClustersOnScreen, point)
if (closest != null) {
val animateTo = mSphericalMercatorProjection!!.toLatLng(closest)
markerModifier.animateThenRemove(marker, marker.position!!, animateTo!!)
val animateTo = sphericalMercatorProjection.toLatLng(closest)
markerModifier.animateThenRemove(marker, position, animateTo)
RendererLogger.d("ClusterRenderer", "Animating then removing marker at position: " + marker.position)
} else if (mClusterMarkerCache.mCache.keys
.iterator()
Expand Down Expand Up @@ -1143,7 +1147,7 @@ open class ClusterRendererMultipleItems<T : ClusterItem> @JvmOverloads construct
val markerWithPosition: MarkerWithPosition<T>
if (marker == null) {
RendererLogger.d("ClusterRenderer", "Creating new cluster marker")
val markerOptions = MarkerOptions().position(if (animateFrom == null) cluster.position else animateFrom)
val markerOptions = MarkerOptions().position(animateFrom ?: cluster.position)
onBeforeClusterRendered(cluster, markerOptions)
marker = mClusterManager.clusterMarkerCollection.addMarker(markerOptions)
mClusterMarkerCache.put(cluster, marker)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ import java.util.Queue
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executor
import java.util.concurrent.Executors
import java.util.concurrent.locks.Condition
import java.util.concurrent.locks.ReentrantLock
import kotlin.math.abs
import kotlin.math.min
Expand Down Expand Up @@ -269,17 +268,19 @@ open class DefaultAdvancedMarkersClusterRenderer<T : ClusterItem> @JvmOverloads
}
val projection = mMap.projection

var renderTask: RenderTask?
synchronized(this) {
renderTask = mNextClusters
val renderTask = synchronized(this) {
val task = mNextClusters
mNextClusters = null
mViewModificationInProgress = true
task
}

renderTask!!.setCallback { sendEmptyMessage(TASK_FINISHED) }
renderTask!!.setProjection(projection)
renderTask!!.setMapZoom(mMap.cameraPosition.zoom)
mExecutor.execute(renderTask)
renderTask?.let {
it.setCallback { sendEmptyMessage(TASK_FINISHED) }
it.setProjection(projection)
it.setMapZoom(mMap.cameraPosition.zoom)
mExecutor.execute(it)
}
}

fun queue(clusters: Set<Cluster<T>>) {
Expand Down Expand Up @@ -409,14 +410,17 @@ open class DefaultAdvancedMarkersClusterRenderer<T : ClusterItem> @JvmOverloads
}
// TODO: Add some padding, so that markers can animate in from off-screen.

val sphericalMercatorProjection = mSphericalMercatorProjection
val animate = mAnimate && sphericalMercatorProjection != null

// Find all of the existing clusters that are on-screen. These are candidates for
// markers to animate from.
var existingClustersOnScreen: MutableList<Point>? = null
if (this@DefaultAdvancedMarkersClusterRenderer.mClusters != null && mAnimate) {
if (this@DefaultAdvancedMarkersClusterRenderer.mClusters != null && animate) {
existingClustersOnScreen = ArrayList()
for (c in this@DefaultAdvancedMarkersClusterRenderer.mClusters!!) {
if (shouldRenderAsCluster(c) && visibleBounds.contains(c.position)) {
val point = mSphericalMercatorProjection!!.toPoint(c.position)
val point = sphericalMercatorProjection.toPoint(c.position)
existingClustersOnScreen.add(point)
}
}
Expand All @@ -429,11 +433,11 @@ open class DefaultAdvancedMarkersClusterRenderer<T : ClusterItem> @JvmOverloads
)
for (c in clusters) {
val onScreen = visibleBounds.contains(c.position)
if (zoomingIn && onScreen && mAnimate) {
val point = mSphericalMercatorProjection!!.toPoint(c.position)
if (zoomingIn && onScreen && animate) {
val point = sphericalMercatorProjection.toPoint(c.position)
val closest = findClosestCluster(existingClustersOnScreen, point)
if (closest != null) {
val animateTo = mSphericalMercatorProjection!!.toLatLng(closest)
val animateTo = sphericalMercatorProjection.toLatLng(closest)
markerModifier.add(true, CreateMarkerTask(c, newMarkers, animateTo))
} else {
markerModifier.add(true, CreateMarkerTask(c, newMarkers, null))
Expand All @@ -453,11 +457,11 @@ open class DefaultAdvancedMarkersClusterRenderer<T : ClusterItem> @JvmOverloads
// Find all of the new clusters that were added on-screen. These are candidates for
// markers to animate from.
var newClustersOnScreen: MutableList<Point>? = null
if (mAnimate) {
if (animate) {
newClustersOnScreen = ArrayList()
for (c in clusters) {
if (shouldRenderAsCluster(c) && visibleBounds.contains(c.position)) {
val p = mSphericalMercatorProjection!!.toPoint(c.position)
val p = sphericalMercatorProjection.toPoint(c.position)
newClustersOnScreen.add(p)
}
}
Expand All @@ -468,12 +472,12 @@ open class DefaultAdvancedMarkersClusterRenderer<T : ClusterItem> @JvmOverloads
val 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 && mAnimate) {
val point = mSphericalMercatorProjection!!.toPoint(marker.position)
if (!zoomingIn && zoomDelta > -3 && onScreen && animate) {
val point = sphericalMercatorProjection.toPoint(marker.position)
val closest = findClosestCluster(newClustersOnScreen, point)
if (closest != null) {
val animateTo = mSphericalMercatorProjection!!.toLatLng(closest)
markerModifier.animateThenRemove(marker, marker.position, animateTo!!)
val animateTo = sphericalMercatorProjection.toLatLng(closest)
markerModifier.animateThenRemove(marker, marker.position, animateTo)
} else {
markerModifier.remove(true, marker.marker)
}
Expand Down Expand Up @@ -1004,7 +1008,7 @@ open class DefaultAdvancedMarkersClusterRenderer<T : ClusterItem> @JvmOverloads
if (!shouldRenderAsCluster(cluster)) {
for (item in cluster.items) {
var marker = mMarkerCache[item] as AdvancedMarker?
var markerWithPosition: MarkerWithPosition
val markerWithPosition: MarkerWithPosition
if (marker == null) {
val advancedMarkerOptions = AdvancedMarkerOptions()
if (animateFrom != null) {
Expand All @@ -1016,9 +1020,10 @@ open class DefaultAdvancedMarkersClusterRenderer<T : ClusterItem> @JvmOverloads
}
}
onBeforeClusterItemRendered(item, advancedMarkerOptions)
marker = mClusterManager.markerCollection.addMarker(advancedMarkerOptions) as AdvancedMarker?
markerWithPosition = MarkerWithPosition(marker!!)
mMarkerCache.put(item, marker!!)
val newMarker = mClusterManager.markerCollection.addMarker(advancedMarkerOptions) as AdvancedMarker
marker = newMarker
markerWithPosition = MarkerWithPosition(newMarker)
mMarkerCache.put(item, newMarker)
if (animateFrom != null) {
markerModifier.animate(markerWithPosition, animateFrom, item.position)
}
Expand All @@ -1033,22 +1038,22 @@ open class DefaultAdvancedMarkersClusterRenderer<T : ClusterItem> @JvmOverloads
}

var marker = mClusterMarkerCache[cluster] as AdvancedMarker?
var markerWithPosition: MarkerWithPosition
val markerWithPosition: MarkerWithPosition
if (marker == null) {
val advancedMarkerOptions = AdvancedMarkerOptions().position(if (animateFrom == null) cluster.position else animateFrom)
val advancedMarkerOptions = AdvancedMarkerOptions().position(animateFrom ?: cluster.position)
onBeforeClusterRendered(cluster, advancedMarkerOptions)
val `object` = mClusterManager.clusterMarkerCollection.addMarker(advancedMarkerOptions)
marker = `object` as AdvancedMarker?
mClusterMarkerCache.put(cluster, marker!!)
markerWithPosition = MarkerWithPosition(marker)
val newMarker = mClusterManager.clusterMarkerCollection.addMarker(advancedMarkerOptions) as AdvancedMarker
marker = newMarker
mClusterMarkerCache.put(cluster, newMarker)
markerWithPosition = MarkerWithPosition(newMarker)
if (animateFrom != null) {
markerModifier.animate(markerWithPosition, animateFrom, cluster.position)
}
} else {
markerWithPosition = MarkerWithPosition(marker)
onClusterUpdated(cluster, marker)
}
onClusterRendered(cluster, marker!!)
onClusterRendered(cluster, marker)
newMarkers.add(markerWithPosition)
}
}
Expand Down
Loading