Skip to content

Commit

Permalink
Refactored NonHierarchicalDistanceBasedAlgorithm for inheritence.
Browse files Browse the repository at this point in the history
ScreenBasedAlgorithmAdapter is now derived from NonHierarchicalDistanceBasedAlgorithm
Added new ScreenBasedAlgorithm interface. Implementing this interface, Algorithm has map position and can skip clustering if it is not required.
Removed usage of instanceof checks in ClusterManager, which broke algorithms logic, in case of decorations.

googlemaps#82
  • Loading branch information
zamesilyasa committed Mar 7, 2016
1 parent 9db3838 commit 8694da3
Show file tree
Hide file tree
Showing 7 changed files with 238 additions and 245 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
/*
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.maps.android.utils.demo;

import android.util.DisplayMetrics;
Expand All @@ -6,7 +22,7 @@
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.model.LatLng;
import com.google.maps.android.clustering.ClusterManager;
import com.google.maps.android.clustering.algo.VisibleNonHierarchicalDistanceBasedAlgorithm;
import com.google.maps.android.clustering.algo.NonHierarchicalViewBasedAlgorithm;
import com.google.maps.android.utils.demo.model.MyItem;

import org.json.JSONException;
Expand All @@ -25,8 +41,8 @@ protected void startDemo() {
getMap().moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(51.503186, -0.126446), 10));

mClusterManager = new ClusterManager<MyItem>(this, getMap());
mClusterManager.setClusterOnlyVisibleArea(true);
mClusterManager.setAlgorithm(new VisibleNonHierarchicalDistanceBasedAlgorithm<MyItem>(metrics.widthPixels, metrics.heightPixels));
mClusterManager.setAlgorithm(new NonHierarchicalViewBasedAlgorithm<MyItem>(
metrics.widthPixels, metrics.heightPixels));

getMap().setOnCameraChangeListener(mClusterManager);

Expand Down
36 changes: 20 additions & 16 deletions library/src/com/google/maps/android/clustering/ClusterManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import com.google.maps.android.clustering.algo.Algorithm;
import com.google.maps.android.clustering.algo.NonHierarchicalDistanceBasedAlgorithm;
import com.google.maps.android.clustering.algo.PreCachingAlgorithmDecorator;
import com.google.maps.android.clustering.algo.ScreenBasedAlgorithm;
import com.google.maps.android.clustering.algo.ScreenBasedAlgorithmAdapter;
import com.google.maps.android.clustering.view.ClusterRenderer;
import com.google.maps.android.clustering.view.DefaultClusterRenderer;

Expand All @@ -41,12 +43,14 @@
* ClusterManager should be added to the map as an: <ul> <li>{@link com.google.android.gms.maps.GoogleMap.OnCameraChangeListener}</li>
* <li>{@link com.google.android.gms.maps.GoogleMap.OnMarkerClickListener}</li> </ul>
*/
public class ClusterManager<T extends ClusterItem> implements GoogleMap.OnCameraChangeListener, GoogleMap.OnMarkerClickListener, GoogleMap.OnInfoWindowClickListener {
public class ClusterManager<T extends ClusterItem> implements GoogleMap.OnCameraChangeListener,
GoogleMap.OnMarkerClickListener, GoogleMap.OnInfoWindowClickListener {

private final MarkerManager mMarkerManager;
private final MarkerManager.Collection mMarkers;
private final MarkerManager.Collection mClusterMarkers;

private Algorithm<T> mAlgorithm;
private ScreenBasedAlgorithm<T> mAlgorithm;
private final ReadWriteLock mAlgorithmLock = new ReentrantReadWriteLock();
private ClusterRenderer<T> mRenderer;

Expand All @@ -59,7 +63,6 @@ public class ClusterManager<T extends ClusterItem> implements GoogleMap.OnCamera
private OnClusterInfoWindowClickListener<T> mOnClusterInfoWindowClickListener;
private OnClusterItemInfoWindowClickListener<T> mOnClusterItemInfoWindowClickListener;
private OnClusterClickListener<T> mOnClusterClickListener;
private boolean mShowOnlyVisibleArea;

public ClusterManager(Context context, GoogleMap map) {
this(context, map, new MarkerManager(map));
Expand All @@ -71,7 +74,9 @@ public ClusterManager(Context context, GoogleMap map, MarkerManager markerManage
mClusterMarkers = markerManager.newCollection();
mMarkers = markerManager.newCollection();
mRenderer = new DefaultClusterRenderer<T>(context, map, this);
mAlgorithm = new PreCachingAlgorithmDecorator<T>(new NonHierarchicalDistanceBasedAlgorithm<T>());
mAlgorithm = new ScreenBasedAlgorithmAdapter<T>(new PreCachingAlgorithmDecorator<T>(
new NonHierarchicalDistanceBasedAlgorithm<T>()));

mClusterTask = new ClusterTask();
mRenderer.onAdd();
}
Expand Down Expand Up @@ -104,6 +109,10 @@ public void setRenderer(ClusterRenderer<T> view) {
}

public void setAlgorithm(Algorithm<T> algorithm) {
setAlgorithm(new ScreenBasedAlgorithmAdapter<T>(algorithm));
}

public void setAlgorithm(ScreenBasedAlgorithm<T> algorithm) {
mAlgorithmLock.writeLock().lock();
try {
if (mAlgorithm != null) {
Expand All @@ -115,17 +124,13 @@ public void setAlgorithm(Algorithm<T> algorithm) {
mAlgorithmLock.writeLock().unlock();
}

if (mAlgorithm instanceof GoogleMap.OnCameraChangeListener) {
((GoogleMap.OnCameraChangeListener) mAlgorithm).onCameraChange(mMap.getCameraPosition());
if (mAlgorithm.shouldReclusterOnMapMovement()) {
mAlgorithm.onCameraChange(mMap.getCameraPosition());
}

cluster();
}

public void setClusterOnlyVisibleArea(boolean onlyVisibleArea) {
mShowOnlyVisibleArea = onlyVisibleArea;
}

public void clearItems() {
mAlgorithmLock.writeLock().lock();
try {
Expand Down Expand Up @@ -193,14 +198,13 @@ public void onCameraChange(CameraPosition cameraPosition) {
((GoogleMap.OnCameraChangeListener) mRenderer).onCameraChange(cameraPosition);
}

if (mAlgorithm instanceof GoogleMap.OnCameraChangeListener) {
((GoogleMap.OnCameraChangeListener) mAlgorithm).onCameraChange(cameraPosition);
}
mAlgorithm.onCameraChange(cameraPosition);

// Don't re-compute clusters if the map has just been panned/tilted/rotated.
if (mShowOnlyVisibleArea) {
// algorithm will decide if it is need to recompute clusters
// delegate clustering to the algorithm
if (mAlgorithm.shouldReclusterOnMapMovement()) {
cluster();

// Don't re-compute clusters if the map has just been panned/tilted/rotated.
} else if (mPreviousCameraPosition == null || mPreviousCameraPosition.zoom != cameraPosition.zoom) {
mPreviousCameraPosition = mMap.getCameraPosition();
cluster();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public Set<? extends Cluster<T>> getClusters(double zoom) {
final Map<QuadItem<T>, StaticCluster<T>> itemToCluster = new HashMap<QuadItem<T>, StaticCluster<T>>();

synchronized (mQuadTree) {
for (QuadItem<T> candidate : mItems) {
for (QuadItem<T> candidate : getClusteringItems(mQuadTree, discreteZoom)) {
if (visitedCandidates.contains(candidate)) {
// Candidate is already part of another cluster.
continue;
Expand Down Expand Up @@ -148,6 +148,10 @@ public Set<? extends Cluster<T>> getClusters(double zoom) {
return results;
}

protected Collection<QuadItem<T>> getClusteringItems(PointQuadTree<QuadItem<T>> quadTree, int discreteZoom) {
return mItems;
}

@Override
public Collection<T> getItems() {
final List<T> items = new ArrayList<T>();
Expand All @@ -172,7 +176,7 @@ private Bounds createBoundsFromSpan(Point p, double span) {
p.y - halfSpan, p.y + halfSpan);
}

private static class QuadItem<T extends ClusterItem> implements PointQuadTree.Item, Cluster<T> {
static class QuadItem<T extends ClusterItem> implements PointQuadTree.Item, Cluster<T> {
private final T mClusterItem;
private final Point mPoint;
private final LatLng mPosition;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.maps.android.clustering.algo;

import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.maps.android.clustering.ClusterItem;
import com.google.maps.android.geometry.Bounds;
import com.google.maps.android.projection.Point;
import com.google.maps.android.projection.SphericalMercatorProjection;
import com.google.maps.android.quadtree.PointQuadTree;

import java.util.Collection;

/**
* This algorithm works the same way as {@link NonHierarchicalDistanceBasedAlgorithm} but works, only in
* visible area. It requires to be reclustered on camera movement because clustering is done only for visible area.
* @param <T>
*/
public class NonHierarchicalViewBasedAlgorithm<T extends ClusterItem>
extends NonHierarchicalDistanceBasedAlgorithm<T> implements ScreenBasedAlgorithm<T> {

private static final SphericalMercatorProjection PROJECTION = new SphericalMercatorProjection(1);

private int mViewWidth;
private int mViewHeight;

private LatLng mMapCenter;

public NonHierarchicalViewBasedAlgorithm(int screenWidth, int screenHeight) {
mViewWidth = screenWidth;
mViewHeight = screenHeight;
}

@Override
public void onCameraChange(CameraPosition cameraPosition) {
mMapCenter = cameraPosition.target;
}

@Override
protected Collection<QuadItem<T>> getClusteringItems(PointQuadTree<QuadItem<T>> quadTree, int discreteZoom) {
return quadTree.search(getVisibleBounds(discreteZoom));
}

@Override
public boolean shouldReclusterOnMapMovement() {
return true;
}

/**
* Update view width and height in case map size was changed.
* You need to recluster all the clusters, to update view state after view size changes.
* @param width map width
* @param height map height
*/
public void updateViewSize(int width, int height) {
mViewWidth = width;
mViewHeight = height;
}

private Bounds getVisibleBounds(int zoom) {
if (mMapCenter == null) {
return new Bounds(0, 0, 0, 0);
}

Point p = PROJECTION.toPoint(mMapCenter);

final double halfWidthSpan = mViewWidth / Math.pow(2, zoom) / 256 / 2;
final double halfHeightSpan = mViewHeight / Math.pow(2, zoom) / 256 / 2;

return new Bounds(
p.x - halfWidthSpan, p.x + halfWidthSpan,
p.y - halfHeightSpan, p.y + halfHeightSpan);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.maps.android.clustering.algo;

import com.google.android.gms.maps.GoogleMap;
import com.google.maps.android.clustering.ClusterItem;

/**
*
* This algorithm uses map position for clustering, and should be reclustered on map movement
* @param <T>
*/

public interface ScreenBasedAlgorithm<T extends ClusterItem> extends Algorithm<T>, GoogleMap.OnCameraChangeListener {

boolean shouldReclusterOnMapMovement();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.maps.android.clustering.algo;

import com.google.android.gms.maps.model.CameraPosition;
import com.google.maps.android.clustering.Cluster;
import com.google.maps.android.clustering.ClusterItem;

import java.util.Collection;
import java.util.Set;

public class ScreenBasedAlgorithmAdapter<T extends ClusterItem> implements ScreenBasedAlgorithm<T> {

private Algorithm<T> mAlgorithm;

public ScreenBasedAlgorithmAdapter(Algorithm<T> algorithm) {
mAlgorithm = algorithm;
}

@Override
public boolean shouldReclusterOnMapMovement() {
return false;
}

@Override
public void addItem(T item) {
mAlgorithm.addItem(item);
}

@Override
public void addItems(Collection<T> items) {
mAlgorithm.addItems(items);
}

@Override
public void clearItems() {
mAlgorithm.clearItems();
}

@Override
public void removeItem(T item) {
mAlgorithm.removeItem(item);
}

@Override
public Set<? extends Cluster<T>> getClusters(double zoom) {
return mAlgorithm.getClusters(zoom);
}

@Override
public Collection<T> getItems() {
return mAlgorithm.getItems();
}

@Override
public void onCameraChange(CameraPosition cameraPosition) {
// stub
}
}
Loading

0 comments on commit 8694da3

Please sign in to comment.