說一說Android的地圖聚合

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

cluster.gif
  • 首先說說什么是聚合,如果你不怎么用地圖的話,可能對(duì)聚合這個(gè)東西幾乎沒什么概念,聚合呢,其實(shí)就是將地圖上過于密集的覆蓋物集合到一塊,當(dāng)?shù)貓D舒展開了,集合中的覆蓋物又會(huì)分布開,就是這么個(gè)效果。

  • 再來說說為什么要聚合,說到底就是讓交互變得更友善,沒聚合之前,圖上總共1400多個(gè)點(diǎn),不能想象密集恐懼癥的人看了會(huì)有什么感覺,反正我自己看著也毛毛的;再一個(gè)呢,這么多的點(diǎn),圖片加載渲染的時(shí)候難免會(huì)卡頓,聚合之后的話,會(huì)有效減少卡頓的現(xiàn)象。

  • 其實(shí)在我用過的地圖中,官方實(shí)現(xiàn)了聚合功能的只有百度地圖,其他都得自己來實(shí)現(xiàn)了,OK,進(jìn)入我們的正題,到底如何實(shí)現(xiàn)聚合呢?

說下我寫的兩種聚合的方法:
第一種:以地圖上的某個(gè)點(diǎn)作為聚合點(diǎn),以這個(gè)點(diǎn)的坐標(biāo)為中心點(diǎn),創(chuàng)建出一個(gè)Rect,再去計(jì)算在這個(gè)Rect中是否包含了其他的點(diǎn),如果包含了,這些個(gè)點(diǎn)就合體成為了一個(gè)聚合點(diǎn)。


98ED363C-B79E-4B2D-ADCF-8B0EA0F15D24.png

好了,我們來寫一個(gè)聚合類:

public class Cluster {
    //聚合大小控制,就是控制Rect的寬高
    private int bounds;
    private PointF f;
    private MapView mapView;
    private List<PointF> ps = new ArrayList<PointF>();//用來存儲(chǔ)該聚合內(nèi)有多少個(gè)覆蓋物,就是地圖上的Overlay

    public Cluster() {}

    //新建的時(shí)候會(huì)扔一個(gè)覆蓋物進(jìn)來,如果沒有聚合產(chǎn)生那么這個(gè)聚合就是原來的覆蓋物
    public Cluster(PointF f, MapView mapView, int bounds) {
        this.f = f;
        this.mapView = mapView;
        this.bounds = bounds;
        ps.add(f);
    }

    //返回一個(gè)方形的范圍區(qū)域,用作判定聚合
    public Rect getRect() {
        float x = f.x;
        float y = f.y;
        //將地圖的坐標(biāo)轉(zhuǎn)換成屏幕的坐標(biāo)
        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;
    }

//如果被判定在聚合內(nèi),那么就將這個(gè)點(diǎn)加入聚合類中的集合
    public void addPoints(PointF p) {
        ps.add(p);
    }

//當(dāng)所有的覆蓋物聚合計(jì)算完成后,次方法返回聚合的坐標(biāo)
    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();
        //遍歷地圖上所有的覆蓋物進(jìn)行聚合操作
        for (PointF mark : marks) {
            float[] floats1 = mapView.convertMapXYToScreenXY1(mark.x, mark.y);//地圖上的點(diǎn)轉(zhuǎn)換成屏幕坐標(biāo)點(diǎn)
            int width = mapView.getWidth();
            int height = mapView.getHeight();
            //計(jì)算出屏幕中的點(diǎn),不在屏幕中的不用聚合
            if (floats1[0] < 0 || floats1[1] < 0 || floats1[0] > width || floats1[1] > height) {
                continue;
            }

            boolean isIn = false;//是否已經(jīng)聚合
            //如果沒有的話就先創(chuàng)建一個(gè)聚合類扔進(jìn)去
            if (clusters.size() == 0) {
                clusters.add(new Cluster(mark, mapView, 100));
            } else {//有了聚合類就開始計(jì)算點(diǎn)是否在聚合內(nèi)

                for (Cluster cluster : clusters) {

                    float[] floats = mapView.convertMapXYToScreenXY1(mark.x, mark.y);

                    boolean isContian = cluster.getRect().contains((int) floats[0], (int) floats[1]);//是否在聚合內(nèi)

                    if (isContian) {
                        cluster.addPoints(mark);
                        isIn = true;
                        break;
                    }

                }

                //如果不在那幾個(gè)聚合點(diǎn)內(nèi)的話,重新添加到一個(gè)新的聚合類中去
                if (!isIn) {
                    clusters.add(new Cluster(mark, mapView, bounds));
                }
            }
        }

//將聚合中的重新計(jì)算取出
        for (Cluster cluster : clusters) {

          newPoints.add(new PointF(cluster.getPosition().x,cluster.getPosition().y));
        }


    }

注釋應(yīng)該寫的挺清楚的,還是那句話,寫代碼之前多想想你要什么,就往這個(gè)聚合類中添加什么,慢慢的這個(gè)類就會(huì)越來越健壯。

接下來第二種方法:
將整個(gè)屏幕分成N個(gè)Rect,分別計(jì)算在某個(gè)Rect中有多少個(gè)覆蓋物,如果多于一個(gè)覆蓋物的話,那么這個(gè)就是聚合,否則,就是一個(gè)覆蓋物。

203BDB66-3DD2-4288-8456-EFEF14AA58B2.png

再來個(gè)聚合類:

public class MCluster {

    public boolean isClick() {
        return isClick;
    }

    public void setClick(boolean click) {
        isClick = click;
    }

    private boolean isClick;//是否可以點(diǎn)擊

    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;

    }

    //看這個(gè)聚合內(nèi)是否有覆蓋物
    public boolean hasPoint(){

        if (ps.size() == 0) {
            return false;
        }

        return true;
    }

    //判斷是否是聚合,如果集合中點(diǎn)數(shù)大于1說明是聚合了,否則不聚合
    public boolean isCluster(){

        if (ps.size() == 1) {
            return false;
        }

        return true;
    }

    //計(jì)算坐標(biāo)
    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();

    }
}

其實(shí)大同小異。
劃分聚合:

 private void makeCluster() {

        //以320像素密度為基礎(chǔ)設(shè)置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個(gè)聚合區(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);//地圖上的點(diǎn)轉(zhuǎn)換成屏幕坐標(biāo)點(diǎn)

            int x = (int) floats1[0];
            int y = (int) floats1[1];
            int width = mapView.getWidth();
            int height = mapView.getHeight();
            //計(jì)算出屏幕中的點(diǎn),不在屏幕中的不用聚合
            if (x< 0 || y < 0 || x > width || y > height) {
                continue;
            }

            //遍歷所有的聚合
            for (MCluster mCluster : mClusters) {

                Rect rect = mCluster.getRect();

                //在聚合內(nèi)
                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();
        }

    }

以上,哪里說的不對(duì)歡迎指正

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容