1. 程式人生 > >使用百度地圖的點聚合功能

使用百度地圖的點聚合功能

百度地圖的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;
            }
        }
    };

有圖有真相:
這裡寫圖片描述
第一張圖,是都聚合為一個點的情況;
第二張圖,是點選一次聚合點後的情況,進行地圖展開;
第三張圖,是再次點選聚合點後的情況,對新聚合點進行地圖展開;
第四張圖,進行縮放後,對比不同的標記點圖示,並且點選了一個標記點;

demo下載地址:

參考: