最近遇到一個需求,其中涉及到一些聚合的東西,給大家說說我的不成熟的小想法。國際慣例,先上黃圖:

cluster.gif
首先說說什么是聚合,如果你不怎么用地圖的話,可能對聚合這個東西幾乎沒什么概念,聚合呢,其實就是將地圖上過于密集的覆蓋物集合到一塊,當?shù)貓D舒展開了,集合中的覆蓋物又會分布開,就是這么個效果。
再來說說為什么要聚合,說到底就是讓交互變得更友善,沒聚合之前,圖上總共1400多個點,不能想象密集恐懼癥的人看了會有什么感覺,反正我自己看著也毛毛的;再一個呢,這么多的點,圖片加載渲染的時候難免會卡頓,聚合之后的話,會有效減少卡頓的現(xiàn)象。
其實在我用過的地圖中,官方實現(xiàn)了聚合功能的只有百度地圖,其他都得自己來實現(xiàn)了,OK,進入我們的正題,到底如何實現(xiàn)聚合呢?
說下我寫的兩種聚合的方法:
第一種:以地圖上的某個點作為聚合點,以這個點的坐標為中心點,創(chuàng)建出一個Rect,再去計算在這個Rect中是否包含了其他的點,如果包含了,這些個點就合體成為了一個聚合點。

98ED363C-B79E-4B2D-ADCF-8B0EA0F15D24.png
好了,我們來寫一個聚合類:
public class Cluster {
//聚合大小控制,就是控制Rect的寬高
private int bounds;
private PointF f;
private MapView mapView;
private List<PointF> ps = new ArrayList<PointF>();//用來存儲該聚合內有多少個覆蓋物,就是地圖上的Overlay
public Cluster() {}
//新建的時候會扔一個覆蓋物進來,如果沒有聚合產生那么這個聚合就是原來的覆蓋物
public Cluster(PointF f, MapView mapView, int bounds) {
this.f = f;
this.mapView = mapView;
this.bounds = bounds;
ps.add(f);
}
//返回一個方形的范圍區(qū)域,用作判定聚合
public Rect getRect() {
float x = f.x;
float y = f.y;
//將地圖的坐標轉換成屏幕的坐標
float[] floats = mapView.convertMapXYToScreenXY1(x, y);
Rect rect = new Rect((int) floats[0], (int) floats[1], (int) (floats[0] + bounds), (int) (floats[1] + bounds));
return rect;
}
//如果被判定在聚合內,那么就將這個點加入聚合類中的集合
public void addPoints(PointF p) {
ps.add(p);
}
//當所有的覆蓋物聚合計算完成后,次方法返回聚合的坐標
public PointF getPosition(){
if (ps.size() == 1) {
return f;
}
float x = 0;
float y = 0;
for (PointF p : ps) {
x += p.x;
y += p.y;
}
x = x / ps.size();
y = y / ps.size();
return new PointF(x,y);
}
}
接下來聚合的算法:
public void getCluster() {
clusters.clear();
newPoints.clear();
//遍歷地圖上所有的覆蓋物進行聚合操作
for (PointF mark : marks) {
float[] floats1 = mapView.convertMapXYToScreenXY1(mark.x, mark.y);//地圖上的點轉換成屏幕坐標點
int width = mapView.getWidth();
int height = mapView.getHeight();
//計算出屏幕中的點,不在屏幕中的不用聚合
if (floats1[0] < 0 || floats1[1] < 0 || floats1[0] > width || floats1[1] > height) {
continue;
}
boolean isIn = false;//是否已經(jīng)聚合
//如果沒有的話就先創(chuàng)建一個聚合類扔進去
if (clusters.size() == 0) {
clusters.add(new Cluster(mark, mapView, 100));
} else {//有了聚合類就開始計算點是否在聚合內
for (Cluster cluster : clusters) {
float[] floats = mapView.convertMapXYToScreenXY1(mark.x, mark.y);
boolean isContian = cluster.getRect().contains((int) floats[0], (int) floats[1]);//是否在聚合內
if (isContian) {
cluster.addPoints(mark);
isIn = true;
break;
}
}
//如果不在那幾個聚合點內的話,重新添加到一個新的聚合類中去
if (!isIn) {
clusters.add(new Cluster(mark, mapView, bounds));
}
}
}
//將聚合中的重新計算取出
for (Cluster cluster : clusters) {
newPoints.add(new PointF(cluster.getPosition().x,cluster.getPosition().y));
}
}
注釋應該寫的挺清楚的,還是那句話,寫代碼之前多想想你要什么,就往這個聚合類中添加什么,慢慢的這個類就會越來越健壯。
接下來第二種方法:
將整個屏幕分成N個Rect,分別計算在某個Rect中有多少個覆蓋物,如果多于一個覆蓋物的話,那么這個就是聚合,否則,就是一個覆蓋物。

203BDB66-3DD2-4288-8456-EFEF14AA58B2.png
再來個聚合類:
public class MCluster {
public boolean isClick() {
return isClick;
}
public void setClick(boolean click) {
isClick = click;
}
private boolean isClick;//是否可以點擊
private String PntName;
private String Unit;
private float Value;
public String getPntName() {
return PntName;
}
public void setPntName(String pntName) {
PntName = pntName;
}
public String getUnit() {
return Unit;
}
public void setUnit(String unit) {
Unit = unit;
}
public float getValue() {
return Value;
}
public void setValue(float value) {
Value = value;
}
//覆蓋物集合
private List<PointF> ps = new ArrayList<PointF>();
private Rect rect;
public MCluster() {
}
public MCluster(Rect rect) {
this.rect = rect;
}
public void addPoint(PointF pointF){
ps.add(pointF);
}
//將集合清空
public void clear(){
ps.clear();
}
public Rect getRect(){
return rect;
}
//看這個聚合內是否有覆蓋物
public boolean hasPoint(){
if (ps.size() == 0) {
return false;
}
return true;
}
//判斷是否是聚合,如果集合中點數(shù)大于1說明是聚合了,否則不聚合
public boolean isCluster(){
if (ps.size() == 1) {
return false;
}
return true;
}
//計算坐標
public MyPoint getPosition(){
float x = 0;
float y = 0;
for (PointF p : ps) {
x += p.x;
y += p.y;
}
x = x / ps.size();
y = y / ps.size();
MyPoint myPoint = new MyPoint(x, y,isCluster(),PntName,Unit,Value,ps.size(),isClick());
return myPoint;
}
//得到聚合的數(shù)量
public int getSize(){
return ps.size();
}
}
其實大同小異。
劃分聚合:
private void makeCluster() {
//以320像素密度為基礎設置Rect的寬高為50像素
float base = 320;
int width1 = (int) SPUtils.get(mapView.getContext(), "width", -1);//屏幕的寬
int height1 = (int) SPUtils.get(mapView.getContext(), "height", -1);//屏幕的高
int density = (int) SPUtils.get(mapView.getContext(), "density", -1);//屏幕的像素密度
float scale = (density/base);
final float width = 50*scale;//Rect的寬高
int round = Math.round(width);
final int h = height1/round;
final int w = width1 / round;
//將屏幕劃分成N個聚合區(qū)
for (int j = 0; j < h+1; j++) {
for (int i = 0; i < (w + 1); i++) {
mClusters.add(new MCluster( new Rect(i * round,j * round,i * round + round,j * round + round)));
}
}
}
接著看聚合的算法:
public void getNewCluster(){
//遍歷所有的覆蓋物
for (Points mark : marks) {
PointF pointF = mark.getPointF();
if (pointF == null) {
return;
}
float[] floats1 = mapView.convertMapXYToScreenXY1(pointF.x, pointF.y);//地圖上的點轉換成屏幕坐標點
int x = (int) floats1[0];
int y = (int) floats1[1];
int width = mapView.getWidth();
int height = mapView.getHeight();
//計算出屏幕中的點,不在屏幕中的不用聚合
if (x< 0 || y < 0 || x > width || y > height) {
continue;
}
//遍歷所有的聚合
for (MCluster mCluster : mClusters) {
Rect rect = mCluster.getRect();
//在聚合內
if (rect.contains(x, y)) {
mCluster.addPoint(pointF);
mCluster.setClick(mark.isClick());
mCluster.setPntName(mark.getPntName());
mCluster.setUnit(mark.getUnit());
mCluster.setValue(mark.getValue());
break;
}
}
}
newPoints.clear();
for (MCluster mCluster : mClusters) {
if (mCluster.hasPoint()) {
newPoints.add(mCluster.getPosition());
}
}
//將聚合中的數(shù)據(jù)清除
for (MCluster mCluster : mClusters) {
mCluster.clear();
}
}
以上,哪里說的不對歡迎指正