Skip to content

Commit 16e4619

Browse files
committed
feat: refactored to create ContinuousZoomEuclideanCentroidAlgorithm.java
1 parent 429a58a commit 16e4619

File tree

4 files changed

+99
-130
lines changed

4 files changed

+99
-130
lines changed

library/src/main/java/com/google/maps/android/clustering/algo/ContinuousZoomEuclideanAlgorithm.java

Lines changed: 0 additions & 124 deletions
This file was deleted.
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.maps.android.clustering.algo;
18+
19+
import com.google.maps.android.clustering.ClusterItem;
20+
import com.google.maps.android.geometry.Bounds;
21+
import com.google.maps.android.quadtree.PointQuadTree;
22+
23+
import java.util.ArrayList;
24+
import java.util.Collection;
25+
import java.util.HashSet;
26+
import java.util.Set;
27+
28+
/**
29+
* A variant of {@link CentroidNonHierarchicalDistanceBasedAlgorithm} that uses
30+
* continuous zoom scaling and Euclidean distance for clustering.
31+
*
32+
* <p>This class overrides {@link #getClusteringItems(PointQuadTree, float)} to compute
33+
* clusters with a zoom-dependent radius, while keeping the centroid-based cluster positions.</p>
34+
*
35+
* @param <T> the type of cluster item
36+
*/
37+
public class ContinuousZoomEuclideanCentroidAlgorithm<T extends ClusterItem>
38+
extends CentroidNonHierarchicalDistanceBasedAlgorithm<T> {
39+
40+
@Override
41+
protected Collection<QuadItem<T>> getClusteringItems(PointQuadTree<QuadItem<T>> quadTree, float zoom) {
42+
// Continuous zoom — no casting to int
43+
final double zoomSpecificSpan = getMaxDistanceBetweenClusteredItems() / Math.pow(2, zoom) / 256;
44+
45+
final Set<QuadItem<T>> visitedCandidates = new HashSet<>();
46+
final Collection<QuadItem<T>> result = new ArrayList<>();
47+
synchronized (mQuadTree) {
48+
for (QuadItem<T> candidate : mItems) {
49+
if (visitedCandidates.contains(candidate)) continue;
50+
51+
Bounds searchBounds = createBoundsFromSpan(candidate.getPoint(), zoomSpecificSpan);
52+
Collection<QuadItem<T>> clusterItems = new ArrayList<>();
53+
for (QuadItem<T> clusterItem : mQuadTree.search(searchBounds)) {
54+
double distance = distanceSquared(clusterItem.getPoint(), candidate.getPoint());
55+
double radiusSquared = Math.pow(zoomSpecificSpan / 2, 2);
56+
if (distance < radiusSquared) {
57+
clusterItems.add(clusterItem);
58+
}
59+
}
60+
61+
visitedCandidates.addAll(clusterItems);
62+
result.add(candidate);
63+
}
64+
}
65+
return result;
66+
}
67+
68+
}

library/src/main/java/com/google/maps/android/clustering/algo/NonHierarchicalDistanceBasedAlgorithm.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public class NonHierarchicalDistanceBasedAlgorithm<T extends ClusterItem> extend
5454
/**
5555
* Any modifications should be synchronized on mQuadTree.
5656
*/
57-
private final Collection<QuadItem<T>> mItems = new LinkedHashSet<>();
57+
protected final Collection<QuadItem<T>> mItems = new LinkedHashSet<>();
5858

5959
/**
6060
* Any modifications should be synchronized on mQuadTree.

library/src/test/java/com/google/maps/android/clustering/algo/ContinuousZoomEuclideanAlgorithmTest.java renamed to library/src/test/java/com/google/maps/android/clustering/algo/ContinuousZoomEuclideanCentroidAlgorithmTest.java

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
1716
package com.google.maps.android.clustering.algo;
1817

1918
import androidx.annotation.NonNull;
@@ -31,7 +30,7 @@
3130
import static org.junit.Assert.assertEquals;
3231
import static org.junit.Assert.assertTrue;
3332

34-
public class ContinuousZoomEuclideanAlgorithmTest {
33+
public class ContinuousZoomEuclideanCentroidAlgorithmTest {
3534

3635
static class TestClusterItem implements ClusterItem {
3736
private final LatLng position;
@@ -64,8 +63,8 @@ public Float getZIndex() {
6463

6564
@Test
6665
public void testContinuousZoomMergesClosePairAtLowZoomAndSeparatesAtHighZoom() {
67-
ContinuousZoomEuclideanAlgorithm<TestClusterItem> algo =
68-
new ContinuousZoomEuclideanAlgorithm<>();
66+
ContinuousZoomEuclideanCentroidAlgorithm<TestClusterItem> algo =
67+
new ContinuousZoomEuclideanCentroidAlgorithm<>();
6968

7069
Collection<TestClusterItem> items = Arrays.asList(
7170
new TestClusterItem(10.0, 10.0),
@@ -83,10 +82,36 @@ public void testContinuousZoomMergesClosePairAtLowZoomAndSeparatesAtHighZoom() {
8382
Set<? extends Cluster<TestClusterItem>> lowZoom = algo.getClusters(5.0f);
8483
assertTrue(lowZoom.size() < 3);
8584

86-
// And specifically, we expect one cluster of size 2 and one singleton
85+
// Specifically, we expect one cluster of size 2 and one singleton
8786
boolean hasClusterOfTwo = lowZoom.stream().anyMatch(c -> c.getItems().size() == 2);
8887
boolean hasClusterOfOne = lowZoom.stream().anyMatch(c -> c.getItems().size() == 1);
8988
assertTrue(hasClusterOfTwo);
9089
assertTrue(hasClusterOfOne);
9190
}
91+
92+
@Test
93+
public void testClusterPositionsAreCentroids() {
94+
ContinuousZoomEuclideanCentroidAlgorithm<TestClusterItem> algo =
95+
new ContinuousZoomEuclideanCentroidAlgorithm<>();
96+
97+
Collection<TestClusterItem> items = Arrays.asList(
98+
new TestClusterItem(0.0, 0.0),
99+
new TestClusterItem(0.0, 2.0),
100+
new TestClusterItem(2.0, 0.0)
101+
);
102+
103+
algo.addItems(items);
104+
105+
Set<? extends Cluster<TestClusterItem>> clusters = algo.getClusters(1.0f);
106+
107+
// Expect all items clustered into one
108+
assertEquals(1, clusters.size());
109+
110+
Cluster<TestClusterItem> cluster = clusters.iterator().next();
111+
112+
// The centroid should be approximately (0.6667, 0.6667)
113+
LatLng centroid = cluster.getPosition();
114+
assertEquals(0.6667, centroid.latitude, 0.0001);
115+
assertEquals(0.6667, centroid.longitude, 0.0001);
116+
}
92117
}

0 commit comments

Comments
 (0)