说一说Android的地图聚合

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

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

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

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

说下我写的两种聚合的方法:
第一种:以地图上的某个点作为聚合点,以这个点的坐标为中心点,创建出一个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);
    }

    //返回一个方形的范围区域,用作判定聚合
    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中有多少个覆盖物,如果多于一个覆盖物的话,那么这个就是聚合,否则,就是一个覆盖物。

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

    //判断是否是聚合,如果集合中点数大于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();
        }

    }

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,817评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,329评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,354评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,498评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,600评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,829评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,979评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,722评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,189评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,519评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,654评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,940评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,762评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,993评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,382评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,543评论 2 349

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,790评论 25 707
  • 温馨小贴士 本文慎入!泣血长文(花了鄙人近一周时间进行构思和写作)!!!吃透耗时大概15分钟,需流量1MB,但读后...
    运营喵怎样炼成阅读 2,632评论 8 54
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • 《我不想长大》 我不想长大 想一直在母亲膝下玩耍 总以为自己还很小 可是母亲已经长满华发 我不想长大 想像孩子一样...
    白清风阅读 247评论 0 0
  • ...在这欢乐的节日,不知为啥,总有些中国笨蛋叫嚣要抵制双旦节(圣诞和元旦),问问笨蛋, 在你抵制双旦之前,以下这...
    咪咪魔魔阅读 465评论 0 0