最近遇到一个需求,其中涉及到一些聚合的东西,给大家说说我的不成熟的小想法。国际惯例,先上黄图:
首先说说什么是聚合,如果你不怎么用地图的话,可能对聚合这个东西几乎没什么概念,聚合呢,其实就是将地图上过于密集的覆盖物集合到一块,当地图舒展开了,集合中的覆盖物又会分布开,就是这么个效果。
再来说说为什么要聚合,说到底就是让交互变得更友善,没聚合之前,图上总共1400多个点,不能想象密集恐惧症的人看了会有什么感觉,反正我自己看着也毛毛的;再一个呢,这么多的点,图片加载渲染的时候难免会卡顿,聚合之后的话,会有效减少卡顿的现象。
其实在我用过的地图中,官方实现了聚合功能的只有百度地图,其他都得自己来实现了,OK,进入我们的正题,到底如何实现聚合呢?
说下我写的两种聚合的方法:
第一种:以地图上的某个点作为聚合点,以这个点的坐标为中心点,创建出一个Rect,再去计算在这个Rect中是否包含了其他的点,如果包含了,这些个点就合体成为了一个聚合点。
好了,我们来写一个聚合类:
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);
}
//返回一个方形的范围区域,用作判定聚合
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;//是否已经聚合
//如果没有的话就先创建一个聚合类扔进去
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中有多少个覆盖物,如果多于一个覆盖物的话,那么这个就是聚合,否则,就是一个覆盖物。
再来个聚合类:
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;
}
//判断是否是聚合,如果集合中点数大于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;
}
//得到聚合的数量
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个聚合区
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());
}
}
//将聚合中的数据清除
for (MCluster mCluster : mClusters) {
mCluster.clear();
}
}
以上,哪里说的不对欢迎指正