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

xiaoxiao2021-02-28  9

百度地图聚合方法使用: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<? extends Cluster<T>> getClusters(double zoom) { final int discreteZoom = (int) zoom; final double zoomSpecificSpan = MAX_DISTANCE_AT_ZOOM / Math.pow(2, discreteZoom); final Set<QuadItem<T>> visitedCandidates = new HashSet<QuadItem<T>>(); final Set<Cluster<T>> results = new HashSet<Cluster<T>>(); final Map<QuadItem<T>, Double> distanceToCluster = new HashMap<QuadItem<T>, Double>(); final Map<QuadItem<T>, com.baidu.mapapi.clusterutil.clustering.algo.StaticCluster<T>> itemToCluster = new HashMap<QuadItem<T>, com.baidu.mapapi.clusterutil.clustering.algo.StaticCluster<T>>(); synchronized (mQuadTree) { for (QuadItem<T> candidate : mItems) { if (visitedCandidates.contains(candidate)) { // Candidate is already part of another cluster. continue; } Bounds searchBounds = createBoundsFromSpan(candidate.getPoint(), zoomSpecificSpan); Collection<QuadItem<T>> 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<T> cluster = new com.baidu.mapapi.clusterutil.clustering.algo .StaticCluster<T>(candidate.mClusterItem.getPosition()); results.add(cluster); for (QuadItem<T> 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<MarkerWithPosition> markersToRemove = mMarkers;//需呀删除的点。请思考什么样的点需要被删除? final LatLngBounds visibleBounds = mMap.getMapStatus().bound;//地图在手机屏幕上的可见范围 //1.添加点 // 找出所有屏幕上的原来的cluster中心点,在增加点的时候有些动画需要用到这些点 List<Point> existingClustersOnScreen = null; if (DefaultClusterRenderer.this.mClusters != null && SHOULD_ANIMATE) { existingClustersOnScreen = new ArrayList<Point>(); for (Cluster<T> 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<MarkerWithPosition> newMarkers = Collections.newSetFromMap( new ConcurrentHashMap<MarkerWithPosition, Boolean>());//保存新的clusters中需要显示的点,转成MarkerWithPosition类型 for (Cluster<T> 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<Point> newClustersOnScreen = null; if (SHOULD_ANIMATE) { newClustersOnScreen = new ArrayList<Point>(); for (Cluster<T> 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<T> 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<Point> mClustersOnScreen,用于存储在地图视线内的marker位置,如果视线内的marker没有发生变化,将不再重新渲染。

注释代码:

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

转载请注明原文地址: https://www.6miu.com/read-2000104.html

最新回复(0)