使用百度地圖的點聚合功能
百度地圖的demo中,已經提供了點聚合功能。
一、先大體瞭解下,主要關注點聚合裡面的兩個類:
1.ClusterItem介面
這個就是地圖上一個一個獨立的標記點。這個介面提供兩個方法需要實現:
一個是提供marker的位置:
LatLng getPosition();
一個是提供marker的圖示:
BitmapDescriptor getBitmapDescriptor();
我們自定義的點MyItem必須繼承ClusterItem這個介面,才能被ClusterManager管理。
2,ClusterManager
顧名思義就是Cluster的管理類,其中有設定相應的點選事件。
聚合點的點選監聽:
mClusterManager.setOnClusterClickListener(new OnClusterClickListener<BaiduNearbyActivity.MyItem>(){
聚合點中單項的點選監聽:
mClusterManager.setOnClusterItemClickListener(new OnClusterItemClickListener<BaiduNearbyActivity.MyItem>(){
百度地圖的demo中,是在MarkerClusterDemo.java中演示了點聚合功能,不過比較簡單,不能滿足實際應用的需求。
二、在其基礎功能之上,做了如下改進:
1,給標記點傳遞資料;
2,實現聚合點的點選功能,點選後在地圖上展開聚合點的內容;
3,聚合的起始數目修改,支援2個點也能聚合;
4,聚合的範圍調整,避免聚合點圖示的互相覆蓋;
5,實現地圖狀態變化的監聽;
1,給標記點傳遞資料;
其實就是新建一個類MyItem,繼承ClusterItem介面,並增加資料成員來傳遞資料;
主要就是添加了一個Bundle成員,實現了Bundle的設定及使用的邏輯;
我在Bundle中也只是添加了一個字串,在選擇圖示時(getBitmapDescriptor()),用於判斷,可以選擇不同的圖示展示在地圖上。
下面是MyItem類的實現:
/**
* 每個Marker點,包含Marker點座標以及圖示
*/
public class MyItem implements ClusterItem {
private final LatLng mPosition;
private Bundle mBundle;
public MyItem(LatLng latLng) {
mPosition = latLng;
mBundle = null;
}
public MyItem(LatLng latLng, Bundle bundle) {
mPosition = latLng;
mBundle = bundle;
}
@Override
public LatLng getPosition() {
return mPosition;
}
@Override
public BitmapDescriptor getBitmapDescriptor() {
int iconId = R.drawable.icon_gcoding;
if(mBundle!=null){
if("001".contentEquals(mBundle.getString("index"))) {
iconId = R.drawable.icon_marka;
} else if("002".contentEquals(mBundle.getString("index")))
{
iconId = R.drawable.icon_markb;
}
}
return BitmapDescriptorFactory .fromResource(iconId);//R.drawable.icon_gcoding);
}
public Bundle getBundle(){
return mBundle;
}
}
在點選ClusterItem時使用Toast顯示一下:
mClusterManager.setOnClusterItemClickListener(new ClusterManager.OnClusterItemClickListener<MyItem>() {
@Override
public boolean onClusterItemClick(MyItem item) {
String showText = "點選單個Item";
if(item.getBundle()!=null) {
showText += " index="+item.getBundle().getString("index");
}
Toast.makeText(MarkerClusterDemo.this,
showText, Toast.LENGTH_SHORT).show();
return false;
}
});
2,實現點選功能,點選後在地圖上展開聚合點的內容;
點選功能,demo中已經實現了,可是,點選聚合點,如何放大到恰好包含那些點?
這裡用到了LatLngBounds類來進行地理範圍管理,具體實現如下:
mClusterManager.setOnClusterClickListener(new ClusterManager.OnClusterClickListener<MyItem>() {
@Override
public boolean onClusterClick(Cluster<MyItem> cluster) {
Toast.makeText(MarkerClusterDemo.this,
"有" + cluster.getSize() + "個點", Toast.LENGTH_SHORT).show();
List<MyItem> items = (List<MyItem>) cluster.getItems();
LatLngBounds.Builder builder2 = new LatLngBounds.Builder();
int i=0;
for(MyItem myItem : items){
builder2 = builder2.include(myItem.getPosition());
Log.i("map","log: i="+ i++ +" pos="+myItem.getPosition().toString());
}
LatLngBounds latlngBounds = builder2.build();
MapStatusUpdate u = MapStatusUpdateFactory.newLatLngBounds(latlngBounds,mMapView.getWidth(),mMapView.getHeight());
mBaiduMap.animateMapStatus(u);
Log.i("map","log: mBaiduMap.animateMapStatus(u)");
return false;
}
});
對於聚合點的點選操作,需要注意進行傳遞:
ClusterManager.java中:
public boolean onMarkerClick(Marker marker) {
// return false;
return getMarkerManager().onMarkerClick(marker);
}
3,聚合的起始數目修改:4->1;
原demo中,要至少5個點才能聚合,而實際使用時,我可不能這樣來實現,只要有兩個點靠近了,也是要聚合的。
修改這個檔案:
com\baidu\mapapi\clusterutil\clustering\view\DefaultClusterRenderer.java
其中有個MIN_CLUSTER_SIZE,修改4->1:
private static final int MIN_CLUSTER_SIZE = 1;//4
檢視判斷邏輯,也就是使用MIN_CLUSTER_SIZE的地方:
/**
* Determine whether the cluster should be rendered as individual markers or a cluster.
*/
protected boolean shouldRenderAsCluster(Cluster<T> cluster) {
return cluster.getSize() > MIN_CLUSTER_SIZE;
}
4,聚合的範圍調整,避免聚合點圖示的互相覆蓋;
執行時發現,點聚合後還是有點互相覆蓋的問題,這樣就應該有個聚合點負責的範圍值,我通過測試,發現修改MAX_DISTANCE_AT_ZOOM有效,在這個檔案中:
com\baidu\mapapi\clusterutil\clustering\algo\NonHierarchicalDistanceBasedAlgorithm.java
修改 NonHierarchicalDistanceBasedAlgorithm 類中的:
public static final int MAX_DISTANCE_AT_ZOOM = 300; // essentially 100 dp ->300dp
這樣基本上就不再有重疊問題了。
5,實現地圖變化的監聽;
在實際使用過程中,發現一個問題,就是進行兩次設定地圖狀態改變的監聽,第一次設定的,就不管用了。
原來的程式碼中已經設定了一次監聽:
mBaiduMap.setOnMapStatusChangeListener(new BaiduMap.OnMapStatusChangeListener() {
對點聚合功能,又設定了一次監聽:
// 設定地圖監聽,當地圖狀態發生改變時,進行點聚合運算
mBaiduMap.setOnMapStatusChangeListener(mClusterManager);
然後,第一次設定的監聽中的onMapStatusChangeFinish()走不到了。
感覺是對同一個BaiduMap,只允許設定一次地圖狀態監聽:
setOnMapStatusChangeListener()
當時挺犯愁的:總不能添加了點聚合功能,導致原先的部分功能失效吧。
後來是在檢視ClusterManager類的定義時發現瞭解決方法:
public class ClusterManager<T extends ClusterItem> implements
BaiduMap.OnMapStatusChangeListener, BaiduMap.OnMarkerClickListener {
原來,ClusterManager類裡面已經有了nMapStatusChangeListener這個介面,可以由我們來實現這個介面中的方法:
public void onMapStatusChangeStart(MapStatus mapStatus) {
public void onMapStatusChangeFinish(MapStatus mapStatus) {
接下來又遇到一個問題:原來是在activity中直接響應地圖改變,訪問的變數都是本地的,現在轉移到ClusterManager這個類中了,如何實現原來的動作呢?
最開始的想法,是直接傳遞context下來,然後強制轉換型別,去呼叫原Activity的方法,應該也可以實現,可是,如果ClusterManager這個類被別的activity呼叫,這個強制轉換就會帶來問題了。
所以,還是使用一個handler,來進行訊息的傳遞,在activity中來實現相應的響應動作。
這樣,能很好的適應不同調用環境。
具體實現方法:
在ClusterManager類中定義兩個變數:
Handler handler;
int result; //msg.what
新增一個對外的介面,用於設定這兩個值:
public void setHandler(Handler handler, int result){
this.handler = handler;
this.result = result;
}
然後,在地圖狀態發生變化完成時,使用handler傳送訊息:
@Override
public void onMapStatusChangeFinish(MapStatus mapStatus) {
Log.i("ClusterManger","onMapStatusChangeFinish");
Message message = handler.obtainMessage(result);
message.obj = mapStatus;
handler.sendMessage(message);
}
在activity中呼叫setHandler進行設定:
mClusterManager.setHandler(handler, MAP_STATUS_CHANGE); //設定handler
然後實現對訊息的處理:
private final int MAP_STATUS_CHANGE = 100;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case MAP_STATUS_CHANGE:
MapStatus mapStatus = (MapStatus) msg.obj;
if(mapStatus!=null){
Log.i("MarkerClusterDemo", "mapStatus="+mapStatus.toString());
// to do : 判斷地圖狀態,進行相應處理
}
break;
default:
break;
}
}
};
有圖有真相:
第一張圖,是都聚合為一個點的情況;
第二張圖,是點選一次聚合點後的情況,進行地圖展開;
第三張圖,是再次點選聚合點後的情況,對新聚合點進行地圖展開;
第四張圖,進行縮放後,對比不同的標記點圖示,並且點選了一個標記點;