From 8f509d43785ccfc5d57ac679a6166299ed857815 Mon Sep 17 00:00:00 2001 From: Chris Broadfoot Date: Wed, 2 Oct 2013 11:43:26 +1000 Subject: [PATCH] Clean-up. Re-name "SimpleDistanceBased" to "NonHierarchicalDistanceBased" --- .../android/clustering/ClusterManager.java | 18 +++------ ...java => NonHierarchicalDistanceBased.java} | 37 +++++++++++-------- .../maps/android/quadtree/PointQuadTree.java | 11 +++--- 3 files changed, 33 insertions(+), 33 deletions(-) rename library/src/com/google/maps/android/clustering/algo/{SimpleDistanceBased.java => NonHierarchicalDistanceBased.java} (76%) diff --git a/library/src/com/google/maps/android/clustering/ClusterManager.java b/library/src/com/google/maps/android/clustering/ClusterManager.java index 743178b67..d2b211376 100644 --- a/library/src/com/google/maps/android/clustering/ClusterManager.java +++ b/library/src/com/google/maps/android/clustering/ClusterManager.java @@ -8,7 +8,7 @@ import com.google.maps.android.MarkerManager; import com.google.maps.android.clustering.algo.Algorithm; import com.google.maps.android.clustering.algo.PreCachingDecorator; -import com.google.maps.android.clustering.algo.SimpleDistanceBased; +import com.google.maps.android.clustering.algo.NonHierarchicalDistanceBased; import com.google.maps.android.clustering.view.ClusterView; import com.google.maps.android.clustering.view.DefaultClusterView; @@ -20,7 +20,6 @@ */ public class ClusterManager implements GoogleMap.OnCameraChangeListener { private static final String TAG = ClusterManager.class.getName(); - private static final boolean ASYNC = true; private final MarkerManager mMarkerManager; private final MarkerManager.Collection mMarkers; @@ -46,7 +45,7 @@ public ClusterManager(Context context, GoogleMap map, MarkerManager markerManage mClusterMarkers = markerManager.newCollection(); mMarkers = markerManager.newCollection(); mView = new DefaultClusterView(context, map, this); - setAlgorithm(new SimpleDistanceBased()); + setAlgorithm(new NonHierarchicalDistanceBased()); mClusterTask = new ClusterTask(); } @@ -113,12 +112,7 @@ public void removeItem(T item) { public void cluster() { mClusterTask.cancel(true); mClusterTask = new ClusterTask(); - if (ASYNC) { - mClusterTask.execute(mMap.getCameraPosition().zoom); - } else { - Set> clusters = mClusterTask.doInBackground(mMap.getCameraPosition().zoom); - mClusterTask.onPostExecute(clusters); - } + mClusterTask.execute(mMap.getCameraPosition().zoom); } /** @@ -141,7 +135,7 @@ public void onCameraChange(CameraPosition cameraPosition) { /** * Runs the clustering algorithm in a background thread, then re-paints when results come - * back.. + * back. */ private class ClusterTask extends AsyncTask>> { @Override @@ -157,12 +151,12 @@ protected void onPostExecute(Set> clusters) { } public void setOnClusterClickListener(OnClusterClickListener listener) { - this.mOnClusterClickListener = listener; + mOnClusterClickListener = listener; mView.setOnClusterClickListener(listener); } public void setOnClusterItemClickListener(OnClusterItemClickListener listener) { - this.mOnClusterItemClickListener = listener; + mOnClusterItemClickListener = listener; mView.setOnClusterItemClickListener(listener); } diff --git a/library/src/com/google/maps/android/clustering/algo/SimpleDistanceBased.java b/library/src/com/google/maps/android/clustering/algo/NonHierarchicalDistanceBased.java similarity index 76% rename from library/src/com/google/maps/android/clustering/algo/SimpleDistanceBased.java rename to library/src/com/google/maps/android/clustering/algo/NonHierarchicalDistanceBased.java index 32d4d45f9..ee477fb75 100644 --- a/library/src/com/google/maps/android/clustering/algo/SimpleDistanceBased.java +++ b/library/src/com/google/maps/android/clustering/algo/NonHierarchicalDistanceBased.java @@ -19,19 +19,20 @@ import java.util.Set; /** - * A simple clustering algorithm. + * A simple clustering algorithm with O(nlog n) performance. Resulting clusters are not + * hierarchical. *

* High level algorithm: * 1. Iterate over items in the order they were added (candidate clusters).
* 2. Create a cluster with the center of the item.
* 3. Add all items that are within a certain distance to the cluster.
- * 4. Remove those items from the list of candidate clusters. + * 4. Move any items out of an existing cluster if they are closer to another cluster.
+ * 5. Remove those items from the list of candidate clusters. *

- * This means that items that were added first will tend to have more items in their cluster. * Clusters have the center of the first element (not the centroid of the items within it). */ -public class SimpleDistanceBased implements Algorithm { - public static final int MAX_DISTANCE_AT_ZOOM = 100; +public class NonHierarchicalDistanceBased implements Algorithm { + public static final int MAX_DISTANCE_AT_ZOOM = 100; // essentially 100 dp. private final LinkedHashSet> mItems = new LinkedHashSet>(); private final PointQuadTree> mQuadTree = new PointQuadTree>(0, 1, 0, 1); @@ -60,30 +61,33 @@ public void clearItems() { @Override public void removeItem(T item) { // TODO: delegate QuadItem#hashCode and QuadItem#equals to its item. - throw new UnsupportedOperationException("SimpleDistanceBased.remove not implemented"); + throw new UnsupportedOperationException("NonHierarchicalDistanceBased.remove not implemented"); } @Override public Set> getClusters(double zoom) { - int discreteZoom = (int) zoom; + final int discreteZoom = (int) zoom; - double zoomSpecificSpan = MAX_DISTANCE_AT_ZOOM / Math.pow(2, discreteZoom) / 256; + final double zoomSpecificSpan = MAX_DISTANCE_AT_ZOOM / Math.pow(2, discreteZoom) / 256; - Set> visitedCandidates = new HashSet>(); - HashSet> results = new HashSet>(); - Map, Double> distanceToCluster = new HashMap, Double>(); - Map, StaticCluster> itemToCluster = new HashMap, StaticCluster>(); + final Set> visitedCandidates = new HashSet>(); + final Set> results = new HashSet>(); + final Map, Double> distanceToCluster = new HashMap, Double>(); + final Map, StaticCluster> itemToCluster = new HashMap, StaticCluster>(); - // TODO: Investigate use of ConcurrentSkipListSet. for (QuadItem candidate : mItems) { - if (visitedCandidates.contains(candidate)) continue; + if (visitedCandidates.contains(candidate)) { + // Candidate is already part of another cluster. + continue; + } Bounds searchBounds = createBoundsFromSpan(candidate.getPoint(), zoomSpecificSpan); Collection> clusterItems = mQuadTree.search(searchBounds); if (clusterItems.size() == 1) { - // Only the current market is in range. + // 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()); @@ -97,6 +101,7 @@ public Set> getClusters(double zoom) { if (existingDistance < distance) { continue; } + // Move item to the closer cluster. itemToCluster.get(clusterItem).remove(clusterItem.mClusterItem); } distanceToCluster.put(clusterItem, distance); @@ -122,6 +127,8 @@ private double distanceSquared(Point a, Point b) { } private Bounds createBoundsFromSpan(Point p, double span) { + // TODO: Use a span that takes into account the visual size of the marker, not just its + // LatLng. double halfSpan = span / 2; return new Bounds( p.x - halfSpan, p.x + halfSpan, diff --git a/library/src/com/google/maps/android/quadtree/PointQuadTree.java b/library/src/com/google/maps/android/quadtree/PointQuadTree.java index ef8e761d8..4d56a0f70 100644 --- a/library/src/com/google/maps/android/quadtree/PointQuadTree.java +++ b/library/src/com/google/maps/android/quadtree/PointQuadTree.java @@ -28,7 +28,6 @@ * See http://en.wikipedia.org/wiki/Quadtree for details on the data structure. * This class is not thread safe. */ -@Deprecated // Experimental. public class PointQuadTree { public interface Item { Point getPoint(); @@ -62,7 +61,7 @@ public interface Item { /** * Child quads. */ - private PointQuadTree[] mChildren = null; + private PointQuadTree[] mChildren = null; /** * Creates a new quad tree with specified bounds. @@ -125,10 +124,10 @@ private boolean insert(double x, double y, T item) { */ private void split() { mChildren = new PointQuadTree[]{ - new PointQuadTree(mBounds.minX, mBounds.midX, mBounds.minY, mBounds.midY, mDepth + 1), - new PointQuadTree(mBounds.midX, mBounds.maxX, mBounds.minY, mBounds.midY, mDepth + 1), - new PointQuadTree(mBounds.minX, mBounds.midX, mBounds.midY, mBounds.maxY, mDepth + 1), - new PointQuadTree(mBounds.midX, mBounds.maxX, mBounds.midY, mBounds.maxY, mDepth + 1) + new PointQuadTree(mBounds.minX, mBounds.midX, mBounds.minY, mBounds.midY, mDepth + 1), + new PointQuadTree(mBounds.midX, mBounds.maxX, mBounds.minY, mBounds.midY, mDepth + 1), + new PointQuadTree(mBounds.minX, mBounds.midX, mBounds.midY, mBounds.maxY, mDepth + 1), + new PointQuadTree(mBounds.midX, mBounds.maxX, mBounds.midY, mBounds.maxY, mDepth + 1) }; List items = mItems;