本篇文章已受权微信公众号 guolin_blog (郭霖)独家发布java
老规矩先上图
最近 没有什么时间,后面项目再补上详细说明git
百度地图SDK新增点聚合功能。经过该功能,可经过缩小地图层级,将定义范围内的多个标注点,聚合显示成一个标注点,解决加载大量点要素到地图上产生覆盖现象的问题,并提升性能。github
加入异步添加屏幕上图片,算法
只加载屏幕范围内的图片微信
优化渲染逻辑
大大减小运算的时间(通过测试1W张不一样经纬度的图片 300-500ms 能够计算完毕)数据结构
一、如何添加点聚合功能到项目中;异步
二、总体结构分析;ide
三、核心算法分析。函数
如官网所示,添加点聚合的方法分为三步:
一、声明点聚合管理类为全局变量,并初始化。核心代码以下图:源码分析
MarkerOptions opts = new MarkerOptions().position(cluster.getPosition()) .icon(BitmapDescriptorFactory.fromBitmap(XX)); Marker marker = (Marker) mMap.addOverlay(opts);
如上图,点聚合有四个类
一、Cluster数据:主要是聚合后的数据类型
二、四叉树:记录初始范围内的全部图片并以四叉树的数据结构组织。核心算法须要用到的数据结构,后面再讲;
三、点聚合算法:基于四叉树的核心算法。后面讲;
四、Cluster管理:对外接口,经过调用核心算法实现点聚合功能、
整个功能的主要流程有两条:
一、添加item:Cluster管理类添加item接口 算法类添加item接口:记录全部的图片信息 四叉树类添加item接口:已四叉树的结构记录全部图片信息
二、获取聚合后的集合:Cluster管理类获取聚合接口 算法类核心算法接口:经过核心算法获取聚合后的集合
首先要说一个概念:世界宽度。
百度地图是把整个地球是按照一个平面来展开,而且经过墨卡托投影投射到xy坐标轴上面。上图:
很明显墨卡托投影把整张世界地图投影成
X∈ [0,1] ; Y∈ [0,1]。
的一个正方型区域。
X 表示的是经度,Y表示的是纬度。
(其实确认来讲是投影一个上下无限延伸的长方体,只是Y属于[0,1]这个范围已经足够咱们使用)上图说明:
从上面看出 -85°的纬度对应Y坐标是1,那么-90°呢,大家本身能够去算一下,是+∞ (正无穷)。
至于为何讲这个,由于计算搜索范围的时候,全部的经纬度都须要换算成Point 来计算,是否是很方便性,并且不易出错。
真是感叹伟人的强大!
SphericalMercatorProjection.java
四叉树的基本思想是把空间递归划分为不一样层次的树结构。它把已知的空间等分红四个相等的子空间,如此递归下去,直到知足当层数目量超过50,或者层级数大于40则中止分割。示意图以下:
遍历QuadItem,只加载屏幕内的点,生成四叉树,方便搜索。
若是图片已被visitedCandidate记录,则continue下面步骤,直到须要处理的图片没有被visitedCandidates记录;
对上一次屏幕上的点QuadItem
先进行处理;
根据MAX_DISTANCE_IN_DP及图片位置计算出searchBounds;
经过四叉树获得searchBounds内全部的图片;
若是图片数量为1,记录并跳到步骤2;
遍历获得的图片;
依次对获得的图片进行处理,
若是图片到中心点的距离比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(); }
重点源码分析,基本上到这里结束。咱们来撸一撸流程:
经过onMapStatusChangeFinish
回调,去执行点聚合运算;
经过 getClusters
把聚合后的结果集算出来;
经过CreateMarkerTask.perform()
把 marker打到屏幕上。
更多细节请看源代码,
喜欢去帮忙start一下,谢谢!
github:
[https://github.com/zhangchaoj...