说一说Android的地图聚合

最近遇到一个需求,其中涉及到一些聚合的东西,给大家说说我的不成熟的小想法。国际惯例,先上黄图:

cluster.gif
  • 首先说说什么是聚合,如果你不怎么用地图的话,可能对聚合这个东西几乎没什么概念,聚合呢,其实就是将地图上过于密集的覆盖物集合到一块,当地图舒展开了,集合中的覆盖物又会分布开,就是这么个效果。

  • 再来说说为什么要聚合,说到底就是让交互变得更友善,没聚合之前,图上总共1400多个点,不能想象密集恐惧症的人看了会有什么感觉,反正我自己看着也毛毛的;再一个呢,这么多的点,图片加载渲染的时候难免会卡顿,聚合之后的话,会有效减少卡顿的现象。

  • 其实在我用过的地图中,官方实现了聚合功能的只有百度地图,其他都得自己来实现了,OK,进入我们的正题,到底如何实现聚合呢?

说下我写的两种聚合的方法:
第一种:以地图上的某个点作为聚合点,以这个点的坐标为中心点,创建出一个Rect,再去计算在这个Rect中是否包含了其他的点,如果包含了,这些个点就合体成为了一个聚合点。


说一说Android的地图聚合_第1张图片
98ED363C-B79E-4B2D-ADCF-8B0EA0F15D24.png

好了,我们来写一个聚合类:

public class Cluster {
    //聚合大小控制,就是控制Rect的宽高
    private int bounds;
    private PointF f;
    private MapView mapView;
    private List ps = new ArrayList();//用来存储该聚合内有多少个覆盖物,就是地图上的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中有多少个覆盖物,如果多于一个覆盖物的话,那么这个就是聚合,否则,就是一个覆盖物。

说一说Android的地图聚合_第2张图片
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 ps = new ArrayList();

    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();
        }

    }

以上,哪里说的不对欢迎指正

你可能感兴趣的:(说一说Android的地图聚合)