高仿 ios 相冊地圖功能

本篇文章已受權微信公衆號 guolin_blog (郭霖)獨家發佈java

老規矩先上圖
最近 沒有什麼時間,後面項目再補上詳細說明git

下載.gif

百度地圖SDK新增點聚合功能。經過該功能,可經過縮小地圖層級,將定義範圍內的多個標註點,聚合顯示成一個標註點,解決加載大量點要素到地圖上產生覆蓋現象的問題,並提升性能。github

本demo 修改算法流程:
  1. 加入異步添加屏幕上圖片,算法

  2. 只加載屏幕範圍內的圖片微信

  3. 優化渲染邏輯
    大大減小運算的時間(通過測試1W張不一樣經緯度的圖片 300-500ms 能夠計算完畢)數據結構

講解點聚合功能,整個分析過程分爲三部分:

一、如何添加點聚合功能到項目中;異步

二、總體結構分析;ide

三、核心算法分析。函數

1、添加點聚合功能

如官網所示,添加點聚合的方法分爲三步:
一、聲明點聚合管理類爲全局變量,並初始化。核心代碼以下圖:源碼分析

MarkerOptions opts = new MarkerOptions().position(cluster.getPosition())
            .icon(BitmapDescriptorFactory.fromBitmap(XX));
Marker marker = (Marker) mMap.addOverlay(opts);
2、總體結構分析
先上一個思惟導圖:

思惟導圖

如上圖,點聚合有四個類

一、Cluster數據:主要是聚合後的數據類型

二、四叉樹:記錄初始範圍內的全部圖片並以四叉樹的數據結構組織。核心算法須要用到的數據結構,後面再講;

三、點聚合算法:基於四叉樹的核心算法。後面講;

四、Cluster管理:對外接口,經過調用核心算法實現點聚合功能、

整個功能的主要流程有兩條:

一、添加item:Cluster管理類添加item接口 算法類添加item接口:記錄全部的圖片信息 四叉樹類添加item接口:已四叉樹的結構記錄全部圖片信息

二、獲取聚合後的集合:Cluster管理類獲取聚合接口 算法類核心算法接口:經過核心算法獲取聚合後的集合

3、核心算法

首先要說一個概念:世界寬度。
百度地圖是把整個地球是按照一個平面來展開,而且經過墨卡託投影投射到xy座標軸上面。上圖:

世界地圖

墨卡託投影后的座標軸

很明顯墨卡託投影把整張世界地圖投影成

X∈ [0,1] ; Y∈ [0,1]。

的一個正方型區域。
X 表示的是經度,Y表示的是緯度。

(其實確認來講是投影一個上下無限延伸的長方體,只是Y屬於[0,1]這個範圍已經足夠咱們使用)上圖說明:

BG}`)1PW56(T@3K9QSDL4_E.png

從上面看出 -85°的緯度對應Y座標是1,那麼-90°呢,大家本身能夠去算一下,是+∞ (正無窮)。

至於爲何講這個,由於計算搜索範圍的時候,全部的經緯度都須要換算成Point 來計算,是否是很方便性,並且不易出錯。
真是感嘆偉人的強大!

附註
轉換的公式在下面這個類裏面:
SphericalMercatorProjection.java
接下來講說如何經過四叉樹組織數據

四叉樹的基本思想是把空間遞歸劃分爲不一樣層次的樹結構。它把已知的空間等分紅四個相等的子空間,如此遞歸下去,直到知足當層數目量超過50,或者層級數大於40則中止分割。示意圖以下:

fAtyhsYUkslj (1).png

OK,接下來講說具體流程

  1. 遍歷QuadItem,只加載屏幕內的點,生成四叉樹,方便搜索。

  2. 若是圖片已被visitedCandidate記錄,則continue下面步驟,直到須要處理的圖片沒有被visitedCandidates記錄;

  3. 對上一次屏幕上的點QuadItem先進行處理;

  4. 根據MAX_DISTANCE_IN_DP及圖片位置計算出searchBounds;

  5. 經過四叉樹獲得searchBounds內全部的圖片;

  6. 若是圖片數量爲1,記錄並跳到步驟2;

  7. 遍歷獲得的圖片;

  8. 依次對獲得的圖片進行處理,

  9. 若是圖片到中心點的距離比distanceToCluster(此圖片與包含此圖片的前cluster的距離)小,把圖片加入結果集,並移除前Cluster擁有該圖片的引用,並記錄這次更小的距離,跳步驟8繼續遍歷剩餘項。

重點源碼分析:
1.聚合觸發口
ClusterManager.java
@Override
    public void onMapStatusChangeFinish(MapStatus mapStatus) {
        if (mRenderer instanceof BaiduMap.OnMapStatusChangeListener) {
            ((BaiduMap.OnMapStatusChangeListener) mRenderer).onMapStatusChange(mapStatus);
        }

        // 屏幕縮放範圍過小,不進行觸發聚合功能
        if (mPreviousCameraPosition != null
                && Math.abs((int) mPreviousCameraPosition.zoom - (int) mapStatus.zoom) < 1
                && mPreviousCameraPosition.target.latitude == mapStatus.target.latitude
                && mPreviousCameraPosition.target.longitude == mapStatus.target.longitude) {
            return;
        }
       //記錄
        mPreviousCameraPosition = mapStatus;
     
        //算法運算,計算出聚合後結果集,而且addMarker 到屏幕上
        cluster(mapStatus.zoom,mapStatus.bound);
    }

對地圖進行手勢操做,都會進行觸發這個函數,並進行聚合操做

2.算法運算
NonHierarchicalDistanceBasedAlgorithm.java
@Override
    public Set<Cluster<T>> getClusters(double zoom, LatLngBounds visibleBounds) {
    ...
    }

這個函數有點多,不過在github 上面的demo 已經註釋滿滿,請移步github 查看。

3.渲染UI(addMarker) 
class DefaultClusterRenderer {
    class CreateMarkerTask {
       ...
    }
}
private void perform(MarkerModifier markerModifier) {
            // Don't show small clusters. Render the markers inside, instead.
            markRemoveAndAddLock.lock();
            //真正添加Marker 的地方

            Marker marker = mClusterToMarker.get(cluster);
            if (marker == null || (marker != null
                    && mMarkerToCluster.get(marker).getSize() != cluster.getSize())) {
                //異步加載佔時不添加Marker
                Integer size = onReadyAddCluster.get(cluster);
                if (size == null || size != cluster.getSize()) {
                    onReadyAddCluster.put(cluster,cluster.getSize());
                    onBeforeClusterRendered(cluster, new MarkerOptions()
                            .position(cluster.getPosition()));

                }
            }
            markRemoveAndAddLock.unlock();
            newClusters.add(cluster);

        }

主要添加圖片的是onBeforeClusterRendered 這一個函數, 咱們看一下實現:

public class PersonRenderer extends DefaultClusterRenderer<LocalPictrue> {
  DataSource<CloseableReference<CloseableImage>> target = cancleMap1.get(cluster);
        if(target != null) {
            target.close();
            cancleMap1.remove(target);
        }


        final LocalPictrue person = cluster.getItems().iterator().next();

        ImageRequest imageRequest = ImageRequestBuilder
                .newBuilderWithSource(Uri.fromFile(new File(person.path)))
                .setProgressiveRenderingEnabled(false)
                .setResizeOptions(new ResizeOptions(50, 50))
                .setPostprocessor(new BadgViewPostprocessor(mContext,cluster))
                .build();

        ImagePipeline imagePipeline = Fresco.getImagePipeline();
        DataSource<CloseableReference<CloseableImage>> dataSource =
                imagePipeline.fetchDecodedImage(imageRequest,mContext);

        dataSource.subscribe(new BaseBitmapDataSubscriber() {

            @Override
            public void onNewResultImpl(@Nullable Bitmap bitmap) {
                // You can use the bitmap in only limited ways
                // No need to do any cleanup.
                if(bitmap != null && !bitmap.isRecycled()) {
                    //you can use bitmap here
                    setIconByCluster(person.path,cluster,
                            markerOptions.icon(BitmapDescriptorFactory.fromBitmap(bitmap)));
                }
                cancleMap1.remove(cluster);
            }

            @Override
            public void onFailureImpl(DataSource dataSource) {
                // No cleanup required here.
                System.out.println("shibai");
            }

        }, UiThreadImmediateExecutorService.getInstance());

        cancleMap1.put(cluster, dataSource);

}

很明顯我這邊解決了 baiduMap 在UI線程上添加圖片阻塞問題, 添增強大的 fresco 第三方加載庫,進行異步加載圖片,接下來看圖片下載完成後 執行setIconByCluster 函數:

//異步回調回來的icon ,須要
    public void setIconByCluster(String path, Cluster<T> cluster, MarkerOptions markerOptions) {
        markRemoveAndAddLock.lock();
        Integer size = onReadyAddCluster.get(cluster);
        if (size != null && cluster.getSize() == size) {
            Marker marker = mClusterToMarker.get(cluster);
            if (marker != null) {
     //若是該圖在屏幕上已經打了marker,那麼替換icon便可,主要解決圖片從新加載閃爍問題    
              marker.setIcon(markerOptions.getIcon());
            } else {
            //打入新的Marker
                marker = mClusterManager.getClusterMarkerCollection().addMarker(markerOptions);
            }

            mMarkerToCluster.put(marker, cluster);
            mClusterToMarker.put(cluster, marker);
        }
        markRemoveAndAddLock.unlock();
    }
總結:

重點源碼分析,基本上到這裏結束。咱們來擼一擼流程:

  1. 經過onMapStatusChangeFinish回調,去執行點聚合運算;

  2. 經過 getClusters把聚合後的結果集算出來;

  3. 經過CreateMarkerTask.perform() 把 marker打到屏幕上。

備註:

更多細節請看源代碼,
喜歡去幫忙start一下,謝謝!

github:
[https://github.com/zhangchaoj...

相關文章
相關標籤/搜索