Skip to content

Commit 8b57c3a

Browse files
authored
feat: added centroid algorithm (#1520)
* feat: added CentroidNonHierarchicalDistanceBasedAlgorithm.java * feat: added a test * docs: header
1 parent d035c4e commit 8b57c3a

File tree

2 files changed

+162
-0
lines changed

2 files changed

+162
-0
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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.android.gms.maps.model.LatLng;
20+
import com.google.maps.android.clustering.Cluster;
21+
import com.google.maps.android.clustering.ClusterItem;
22+
23+
import java.util.Collection;
24+
import java.util.HashSet;
25+
import java.util.Set;
26+
27+
/**
28+
* A variant of {@link NonHierarchicalDistanceBasedAlgorithm} that clusters items
29+
* based on distance but assigns cluster positions at the centroid of their items,
30+
* instead of using the position of a single item as the cluster position.
31+
*
32+
* <p>This algorithm overrides {@link #getClusters(float)} to compute a geographic centroid
33+
* for each cluster and creates {@link StaticCluster} instances positioned at these centroids.
34+
* This can provide a more accurate visual representation of the cluster location.</p>
35+
*
36+
* @param <T> the type of cluster item
37+
*/
38+
public class CentroidNonHierarchicalDistanceBasedAlgorithm<T extends ClusterItem>
39+
extends NonHierarchicalDistanceBasedAlgorithm<T> {
40+
41+
/**
42+
* Computes the centroid (average latitude and longitude) of a collection of cluster items.
43+
*
44+
* @param items the collection of cluster items to compute the centroid for
45+
* @return the centroid {@link LatLng} of the items
46+
*/
47+
protected LatLng computeCentroid(Collection<T> items) {
48+
double latSum = 0;
49+
double lngSum = 0;
50+
int count = 0;
51+
for (T item : items) {
52+
latSum += item.getPosition().latitude;
53+
lngSum += item.getPosition().longitude;
54+
count++;
55+
}
56+
return new LatLng(latSum / count, lngSum / count);
57+
}
58+
59+
/**
60+
* Returns clusters of items for the given zoom level, with cluster positions
61+
* set to the centroid of their constituent items rather than the position of
62+
* any single item.
63+
*
64+
* @param zoom the current zoom level
65+
* @return a set of clusters with centroid positions
66+
*/
67+
@Override
68+
public Set<? extends Cluster<T>> getClusters(float zoom) {
69+
Set<? extends Cluster<T>> originalClusters = super.getClusters(zoom);
70+
Set<StaticCluster<T>> newClusters = new HashSet<>();
71+
72+
for (Cluster<T> cluster : originalClusters) {
73+
LatLng centroid = computeCentroid(cluster.getItems());
74+
StaticCluster<T> newCluster = new StaticCluster<>(centroid);
75+
for (T item : cluster.getItems()) {
76+
newCluster.add(item);
77+
}
78+
newClusters.add(newCluster);
79+
}
80+
return newClusters;
81+
}
82+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
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.android.gms.maps.model.LatLng;
20+
import com.google.maps.android.clustering.ClusterItem;
21+
22+
import org.junit.Test;
23+
24+
import java.util.Arrays;
25+
import java.util.Collection;
26+
27+
import static org.junit.Assert.assertEquals;
28+
29+
import androidx.annotation.NonNull;
30+
31+
public class CentroidNonHierarchicalDistanceBasedAlgorithmTest {
32+
33+
static class TestClusterItem implements ClusterItem {
34+
private final LatLng position;
35+
36+
TestClusterItem(double lat, double lng) {
37+
this.position = new LatLng(lat, lng);
38+
}
39+
40+
@NonNull
41+
@Override
42+
public LatLng getPosition() {
43+
return position;
44+
}
45+
46+
@Override
47+
public String getTitle() {
48+
return null;
49+
}
50+
51+
@Override
52+
public String getSnippet() {
53+
return null;
54+
}
55+
56+
@Override
57+
public Float getZIndex() {
58+
return 0f;
59+
}
60+
}
61+
62+
63+
64+
@Test
65+
public void testComputeCentroid() {
66+
CentroidNonHierarchicalDistanceBasedAlgorithm<TestClusterItem> algo =
67+
new CentroidNonHierarchicalDistanceBasedAlgorithm<>();
68+
69+
Collection<TestClusterItem> items = Arrays.asList(
70+
new TestClusterItem(10.0, 20.0),
71+
new TestClusterItem(20.0, 30.0),
72+
new TestClusterItem(30.0, 40.0)
73+
);
74+
75+
LatLng centroid = algo.computeCentroid(items);
76+
77+
assertEquals(20.0, centroid.latitude, 0.0001);
78+
assertEquals(30.0, centroid.longitude, 0.0001);
79+
}
80+
}

0 commit comments

Comments
 (0)