diff --git a/library/src/main/java/com/google/maps/android/clustering/algo/ContinuousZoomEuclideanCentroidAlgorithm.java b/library/src/main/java/com/google/maps/android/clustering/algo/ContinuousZoomEuclideanCentroidAlgorithm.java index c49dcb634..b42a65599 100644 --- a/library/src/main/java/com/google/maps/android/clustering/algo/ContinuousZoomEuclideanCentroidAlgorithm.java +++ b/library/src/main/java/com/google/maps/android/clustering/algo/ContinuousZoomEuclideanCentroidAlgorithm.java @@ -16,20 +16,23 @@ package com.google.maps.android.clustering.algo; +import com.google.android.gms.maps.model.LatLng; +import com.google.maps.android.clustering.Cluster; import com.google.maps.android.clustering.ClusterItem; import com.google.maps.android.geometry.Bounds; -import com.google.maps.android.quadtree.PointQuadTree; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; /** * A variant of {@link CentroidNonHierarchicalDistanceBasedAlgorithm} that uses * continuous zoom scaling and Euclidean distance for clustering. * - *

This class overrides {@link #getClusteringItems(PointQuadTree, float)} to compute + *

This class overrides {@link #getClusters(float)} to compute * clusters with a zoom-dependent radius, while keeping the centroid-based cluster positions.

* * @param the type of cluster item @@ -38,15 +41,21 @@ public class ContinuousZoomEuclideanCentroidAlgorithm extends CentroidNonHierarchicalDistanceBasedAlgorithm { @Override - protected Collection> getClusteringItems(PointQuadTree> quadTree, float zoom) { + public Set> getClusters(float zoom) { // Continuous zoom — no casting to int final double zoomSpecificSpan = getMaxDistanceBetweenClusteredItems() / Math.pow(2, zoom) / 256; final Set> visitedCandidates = new HashSet<>(); - final Collection> result = new ArrayList<>(); + final Set> results = new HashSet<>(); + final Map, Double> distanceToCluster = new HashMap<>(); + final Map, StaticCluster> itemToCluster = new HashMap<>(); + synchronized (mQuadTree) { - for (QuadItem candidate : mItems) { - if (visitedCandidates.contains(candidate)) continue; + for (QuadItem candidate : getClusteringItems(mQuadTree, zoom)) { + if (visitedCandidates.contains(candidate)) { + // Candidate is already part of another cluster. + continue; + } Bounds searchBounds = createBoundsFromSpan(candidate.getPoint(), zoomSpecificSpan); Collection> clusterItems = new ArrayList<>(); @@ -58,11 +67,46 @@ protected Collection> getClusteringItems(PointQuadTree> } } + if (clusterItems.size() == 1) { + // Only the current marker is in range. Just add the single item to the results. + results.add(candidate); + visitedCandidates.add(candidate); + distanceToCluster.put(candidate, 0d); + continue; + } + StaticCluster cluster = new StaticCluster<>(candidate.mClusterItem.getPosition()); + results.add(cluster); + + for (QuadItem clusterItem : clusterItems) { + Double existingDistance = distanceToCluster.get(clusterItem); + double distance = distanceSquared(clusterItem.getPoint(), candidate.getPoint()); + if (existingDistance != null) { + // Item already belongs to another cluster. Check if it's closer to this cluster. + if (existingDistance < distance) { + continue; + } + // Move item to the closer cluster. + itemToCluster.get(clusterItem).remove(clusterItem.mClusterItem); + } + distanceToCluster.put(clusterItem, distance); + cluster.add(clusterItem.mClusterItem); + itemToCluster.put(clusterItem, cluster); + } visitedCandidates.addAll(clusterItems); - result.add(candidate); } } - return result; - } -} + // Now, apply the centroid logic from CentroidNonHierarchicalDistanceBasedAlgorithm + Set> newClusters = new HashSet<>(); + for (Cluster cluster : results) { + LatLng centroid = computeCentroid(cluster.getItems()); + StaticCluster newCluster = new StaticCluster<>(centroid); + for (T item : cluster.getItems()) { + newCluster.add(item); + } + newClusters.add(newCluster); + } + + return newClusters; + } +} \ No newline at end of file