Andorid百度地图聚合优化(大量marker卡顿)

百度地图聚合方法使用:http://blog.csdn.net/aconghui/article/details/50958715;

百度地图聚合源码(上): http://blog.csdn.net/javine/article/details/51195014

百度地图聚合源码(下): http://blog.csdn.net/javine/article/details/51234279

百度地图官方聚合demo,对于大量marker来说,使用起来非常卡,在网上也搜寻的不少资料,但是优化聚合卡的方法基本没找到,这里在研究了聚合源码之后,本人优化的思路,仅供参考(阅读之前,请先浏览聚合源码);

以下是百度地图优化的两个点:

1.降低marker之间聚合的条件。

看下百度地图聚合核心算法(NonHierarchicalDistanceBasedAlgorithm):

 /**
     *  cluster算法核心
     * @param zoom map的级别
     * @return
     */
    @Override
    public Set> getClusters(double zoom) {
        final int discreteZoom = (int) zoom;
        final double zoomSpecificSpan = MAX_DISTANCE_AT_ZOOM / Math.pow(2, discreteZoom);
        final Set> visitedCandidates = new HashSet>();
        final Set> results = new HashSet>();
        final Map, Double> distanceToCluster = new HashMap, Double>();
        final Map, com.baidu.mapapi.clusterutil.clustering.algo.StaticCluster> itemToCluster =
                new HashMap, com.baidu.mapapi.clusterutil.clustering.algo.StaticCluster>();

        synchronized (mQuadTree) {
            for (QuadItem candidate : mItems) {
                if (visitedCandidates.contains(candidate)) {
                    // Candidate is already part of another cluster.
                    continue;
                }

                Bounds searchBounds = createBoundsFromSpan(candidate.getPoint(), zoomSpecificSpan);
                Collection> clusterItems;
                // search 某边界范围内的clusterItems
                clusterItems = mQuadTree.search(searchBounds);
                if (clusterItems.size() == 1) {
                    // 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;
                }
                com.baidu.mapapi.clusterutil.clustering.algo.StaticCluster cluster =
                        new com.baidu.mapapi.clusterutil.clustering.algo
                                .StaticCluster(candidate.mClusterItem.getPosition());
                results.add(cluster);

                for (QuadItem clusterItem : clusterItems) {
                    Double existingDistance = distanceToCluster.get(clusterItem);
                    double distance = distanceSquared(clusterItem.getPoint(), candidate.getPoint());
                    if (existingDistance != null) {
                        // Item already belongs to another cluster. Check if it's closer to this cluster.
                        if (existingDistance < distance) {
                            continue;
                        }
                        // Move item to the closer cluster.
                        itemToCluster.get(clusterItem).remove(clusterItem.mClusterItem);
                    }
                    distanceToCluster.put(clusterItem, distance);
                    cluster.add(clusterItem.mClusterItem);
                    itemToCluster.put(clusterItem, cluster);
                }
                visitedCandidates.addAll(clusterItems);
            }
        }
        return results;
    }
其中 final double zoomSpecificSpan = MAX_DISTANCE_AT_ZOOM / Math.pow(2, discreteZoom),表明marker之间聚合的距离,如果zoomSpecificSpanyu越大越容易聚合,反之越不容易聚合,因此我将它修改为final double zoomSpecificSpan = MAX_DISTANCE_AT_ZOOM / Math.pow(2, discreteZoom-2)。这样降低了聚合的条件,会使地图上的marker减少,节省渲染时间。

2.减少marker渲染数量(DefaultClusterRenderer)

这是节省时间最大的地方,先看源码:

public void run() {
            if (clusters.equals(DefaultClusterRenderer.this.mClusters)) {
                mCallback.run();//判断如果新的clusters等于上一次保存的clusters,直接return出去
                return;
            }

            final MarkerModifier markerModifier = new MarkerModifier();//这个类处理显示和动画

            final float zoom = mMapZoom;//最新的zoom值
            final boolean zoomingIn = zoom > mZoom;//mZoom为上一次保存的zoom值
            final float zoomDelta = zoom - mZoom;//zoom变化量级,超过一定量级就不执行动画了

            final Set markersToRemove = mMarkers;//需呀删除的点。请思考什么样的点需要被删除?
            final LatLngBounds visibleBounds = mMap.getMapStatus().bound;//地图在手机屏幕上的可见范围

            //1.添加点
            // 找出所有屏幕上的原来的cluster中心点,在增加点的时候有些动画需要用到这些点
            List existingClustersOnScreen = null;
            if (DefaultClusterRenderer.this.mClusters != null && SHOULD_ANIMATE) {
                existingClustersOnScreen = new ArrayList();
                for (Cluster c : DefaultClusterRenderer.this.mClusters) { //迭代上一次保存的clusters
                    if (shouldRenderAsCluster(c) && visibleBounds.contains(c.getPosition())) {//只有已经聚合了的cluster才可以新增点
                        Point point = mSphericalMercatorProjection.toPoint(c.getPosition());//position转换成point
                        existingClustersOnScreen.add(point);//保存屏幕上已经聚合的cluster
                    }
                }
            }

            // Create the new markers and animate them to their new positions.
            final Set newMarkers = Collections.newSetFromMap( 
                    new ConcurrentHashMap());//保存新的clusters中需要显示的点,转成MarkerWithPosition类型
            for (Cluster c : clusters) {             //迭代新的clusters
                boolean onScreen = visibleBounds.contains(c.getPosition());//是否在屏幕内
                if (zoomingIn && onScreen && SHOULD_ANIMATE) { //地图放大 + 此cluster在屏幕内 + 可以动画(SDK版本>11)
                    Point point = mSphericalMercatorProjection.toPoint(c.getPosition());//position转成point
                    Point closest = findClosestCluster(existingClustersOnScreen, point);//找出与这个cluster距离最近的原屏幕上的点
                    if (closest != null) {//存在,则实现动画
                        LatLng animateTo = mSphericalMercatorProjection.toLatLng(closest);
                        markerModifier.add(true, new CreateMarkerTask(c, newMarkers, animateTo));
                    } else {//不存在,则直接添加不生成动画
                        markerModifier.add(true, new CreateMarkerTask(c, newMarkers, null));
                    }
                } else {//直接添加点,不生成动画
                    markerModifier.add(onScreen, new CreateMarkerTask(c, newMarkers, null));
                }
            }

            // 2.等待添加点的任务完成
            markerModifier.waitUntilFree();

            // 把newMarkers中的点从markersToRemove中移除,markersToRemove中的点都是需要从地图上移除的
            markersToRemove.removeAll(newMarkers);

            //3.移除点
            // 找出现在屏幕上显示的cluster中心点,在移除点时需要用到这些点来实现动画
            List newClustersOnScreen = null;
            if (SHOULD_ANIMATE) {
                newClustersOnScreen = new ArrayList();
                for (Cluster c : clusters) {
                    if (shouldRenderAsCluster(c) && visibleBounds.contains(c.getPosition())) {
                        Point p = mSphericalMercatorProjection.toPoint(c.getPosition());
                        newClustersOnScreen.add(p);
                    }
                }
            }
            
            for (final MarkerWithPosition marker : markersToRemove) { //迭代所有需要移除的点
                boolean onScreen = visibleBounds.contains(marker.position);

                if (!zoomingIn && zoomDelta > -3 && onScreen && SHOULD_ANIMATE) { // 地图缩小 + zoom改变不超过3
                    final Point point = mSphericalMercatorProjection.toPoint(marker.position);
                    final Point closest = findClosestCluster(newClustersOnScreen, point);//找出最近的cluster
                    if (closest != null) {
                        LatLng animateTo = mSphericalMercatorProjection.toLatLng(closest);//动画移动的终点
                        markerModifier.animateThenRemove(marker, marker.position, animateTo);
                    } else {
                        markerModifier.remove(true, marker.marker);//无动画
                    }
                } else {
                    markerModifier.remove(onScreen, marker.marker);//无动画
                }
            }
            //等待移除点的任务完成
            markerModifier.waitUntilFree();

            mMarkers = newMarkers;//保存新的点
            DefaultClusterRenderer.this.mClusters = clusters;
            mZoom = zoom;//保存最新的zoom

            mCallback.run();//执行线程执行完成的回调函数
        }
这是渲染marker的代码部分,我们看到在这部分代码中,说明渲染时是先添加点位,再删除点位,主要耗时部分在于添加点位,百度的做法是将所有的merker全部添加一遍,这样非常消耗时间,特别在大量merker的时候。我的思路是,只渲染屏幕能看到的marker:

	   for (Cluster c : clusters) {
                boolean onScreen = visibleBounds.contains(c.getPosition());
                if(onScreen){
                    if (zoomingIn && SHOULD_ANIMATE) {
                        Point point = mSphericalMercatorProjection.toPoint(c.getPosition());
                        Point closest = findClosestCluster(existingClustersOnScreen, point);
                        if (closest != null) {
                            LatLng animateTo = mSphericalMercatorProjection.toLatLng(closest);
                            markerModifier.add(true, new CreateMarkerTask(c, newMarkers, animateTo));
                        } else {
                            markerModifier.add(true, new CreateMarkerTask(c, newMarkers, null));
                        }
                    } else {
                        markerModifier.add(onScreen, new CreateMarkerTask(c, newMarkers, null));
                    }
                }

            }
只有在视线范围内的merker才去创建CreateMarkerTask渲染,不在视线范围内的不创建CreateMarkerTask,好了性能方面优化接结束了,不敢说能承载多少marker量,我想1W以内应该还能接受吧。

补充:

经过上述2优化后,会出现一个问题,就是移动地图时不会重新渲染marker,只有缩放地图时才重新渲染merker.

解决方案:

找到ClusterManager的onMapStatusChange方法,注释一下代码:

if (mPreviousCameraPosition != null && mPreviousCameraPosition.zoom == position.zoom) {
            return;
        }
这段代码 的意思是地图zoom不发生变化时,将不调用后面的方法。

找到2中的源码,在优化后的这个循环之前添加:

if(DefaultClusterRenderer.this.mClustersOnScreen!=null&&DefaultClusterRenderer.this.mClustersOnScreen.equals(existingClustersOnScreen)){
                mCallback.run();
                return;
            }
            DefaultClusterRenderer.this.mClustersOnScreen = existingClustersOnScreen;
  并在DefaultClusterRenderer中添加新属性List mClustersOnScreen,用于存储在地图视线内的marker位置,如果视线内的marker没有发生变化,将不再重新渲染。

注释代码:

if (clusters.equals(DefaultClusterRenderer.this.mClusters)) {
                mCallback.run();
                return;
            }
 这段代码的意思是,如果核心算法计算后的marker没有发生变化,那么就不再执行后面的渲染代码,因为移动地图时聚合marker并没有发生变化,因此移动地图时永远不会出现重绘marker。






你可能感兴趣的:(android学习笔记)