地圖聚合算法

一、爲什麼需要點聚合

      在地圖上查詢結果通常以標記點的形式展現,但是如果標記點較多,不僅會大大增加客戶端的渲染時間,讓客戶端變得很卡,而且會讓人產生密集恐懼症(圖1)。爲了解決這一問題,我們需要一種手段能在用戶有限的可視區域範圍內,利用最小的區域展示出最全面的信息,而又不產生重疊覆蓋。

 

圖1

二、已嘗試的方案---kmeans

         直覺上用聚類算法能較好達成我們目標,因此採用簡單的kmeans聚類。根據客戶端的請求,我們知道了客戶端顯示的範圍,併到索引引擎裏取出在此範圍內的數據,並對這些數據進行kmeans聚類,最後將結果返回給客戶端。

         但是上線之後發現kmeans效果並不如意,主要有以下兩個缺點。

a)性能問題

         kmeans是計算密集型算法,需要迭代多次才能完成,而且每次迭代過程中都涉及到複雜的距離計算,比較消耗cpu。

         我們在上線後遇到load較高的問題。

b)效果問題

         kmeans未能徹底解決重疊覆蓋問題!可以看到有些聚合後的圖標會疊合在一起。

三、優化方案

       再次回顧我們的目的:我們需要一種手段能在用戶有限的可視區域範圍內,利用最小的區域展示出最全面的信息,而又不產生重疊覆蓋。

3.1. 直接網格法

      解決地理空間相關問題時,對空間劃分網格這種方法往往屢試不爽。

      原理:將地圖範圍劃分成指定尺寸的正方形(每個縮放級別不同尺寸),然後將落在對應格子中的點聚合到該正方形中(正方形的中心),最終一個正方形內只顯示一箇中心點,並且點上顯示該聚合點所包含的原始點的數量。

      如何將點落到正方形內呢?我們將空間人爲指定100*100大小,通過這個公式進行映射。

        優點:運算速度較快,每個原始點只需計算一次,沒有複雜的距離計算。

        缺點:有時明明很相近的點,卻僅僅因爲網絡的分界線而被逼分開在不同的聚合點中,此外,聚合點的位置採用的是該網格的中心,而非該網格的質心,這樣聚合出來的點可能不能較精確反映原始點的信息。

3.2. 網格距離法

       原理:沿用方案一思想,1)將各個點落到相應正方形內;2)求解各個網格的質心;3)合併質心:判斷各個質心是否在某一範圍內,如果在某一範圍內則進行合併。

      如何判斷各個質心點是否需要合併呢?以A點爲例,畫一個矩形或者圓範圍,落在此範圍內的合併,B、C均落在範圍內,因此A、B、C三點合併。

         優點:運算速度同樣較快,相對於方案一,多了求解質心以及質心合併兩個步驟,但這兩個步驟都較爲簡單,能很快完成。

可以參考:https://blog.csdn.net/qq_17810899/article/details/85788486 

3.3. 直接距離法

         原理:初始時沒有任何已知聚合點,然後對每個點進行迭代,計算一個點的外包正方形,若此點的外包正方形與現有的聚合點的外包正方形不相交,則新建聚合點(這裏不是計算點與點間的距離,而是計算一個點的外包正方形,正方形的變長由用戶指定或程序設置一個默認值),若相交,則把該點聚合到該聚合點中,若點與多個已知的聚合點的外包正方形相交,則計算該點到到聚合點的距離,聚合到距離最近的聚合點中,如此循環,直到所有點都遍歷完畢。每個縮放級別都重新遍歷所有原始點要素。

         優點:運算速度相對較快,每個原始點只需計算一次,可能會有點與點距離計算,聚合點較精確的反映了所包含的原始點要素的位置信息。

         缺點:速度不如完全基於網格的速度快等,此法還有個缺點,就是各個點迭代順序不同導致最終結果不同。因此涉及到制定迭代順序的問題。

下面的代碼,重點在於 排序,這樣的話,每一個點只需要和 集合的最後一個點比較一次

@Test
    public void testDis() {
        GlobalCoordinates source = new GlobalCoordinates(120.586632, 30.851061);//緯度,經度
        GlobalCoordinates target = new GlobalCoordinates(120.908287, 30.831963);

        double meter1 = getDistanceMeter(source, target, Ellipsoid.Sphere);
        double meter2 = getDistanceMeter(source, target, Ellipsoid.WGS84);

        System.out.println("Sphere座標系計算結果:" + meter1 + "米");
        System.out.println("WGS84座標系計算結果:" + meter2 + "米");

        System.out.println("here");
    }

    private double getDistanceMeter(GlobalCoordinates gpsFrom, GlobalCoordinates gpsTo, Ellipsoid ellipsoid) {
        //創建GeodeticCalculator,調用計算方法,傳入座標系、經緯度用於計算距離
        GeodeticCurve geoCurve = new GeodeticCalculator().calculateGeodeticCurve(ellipsoid, gpsFrom, gpsTo);
        return geoCurve.getEllipsoidalDistance();
    }

    public static void main(String args[]) {
        new CommunityServiceImplTest().testJuhe();
    }

    public void testJuhe() {
        final float width = 7;//屏幕寬度
        final float space = 10;//10KM
        final int cutNum = 5;

        //1.分割,計算點的距離
        double distance = (width * space / cutNum) * 1000;//單位m

        List<MyPoint> myPoints = getData();

        List<CollectPoint> collectPoints = new ArrayList<>();//集合點

        for (MyPoint myPoint : myPoints) {
            //1.第一個點直接放到集合
            if (CollectionUtils.isEmpty(collectPoints)) {
                CollectPoint collectPoint = new CollectPoint();
                collectPoint.setBaseMyPoint(myPoint);
                collectPoint.addPoint(myPoint);
                collectPoints.add(collectPoint);
            }
            //2.後面的每一個點都和集合中的去對比,每次只和集合的最後一個對比
            else {
                CollectPoint lastCollectPoint = collectPoints.get(collectPoints.size() - 1);
                GlobalCoordinates source = new GlobalCoordinates(lastCollectPoint.getBaseMyPoint().getLatitude(),
                        lastCollectPoint.getBaseMyPoint().getLongitude());
                GlobalCoordinates target = new GlobalCoordinates(myPoint.getLatitude(), myPoint.getLongitude());
                double meter1 = getDistanceMeter(source, target, Ellipsoid.Sphere);//單位 米
                if (meter1 < distance) {
                    lastCollectPoint.addPoint(myPoint);
                } else {
                    CollectPoint collectPoint = new CollectPoint();
                    collectPoint.setBaseMyPoint(myPoint);
                    collectPoint.addPoint(myPoint);
                    collectPoints.add(collectPoint);
                }
            }
        }

        System.out.println(
                "Rsp:" + JSONObject.toJSONString(collectPoints, SerializerFeature.DisableCircularReferenceDetect));
    }

    private List<MyPoint> getData() {
        List<MyPoint> myPoints = new ArrayList<MyPoint>();
        myPoints.add(new MyPoint(30.765439, 120.995178));
        myPoints.add(new MyPoint(30.836215, 120.932007));
        myPoints.add(new MyPoint(31.316101, 120.879822));
        myPoints.add(new MyPoint(31.606610, 121.039124));
        myPoints.add(new MyPoint(31.676758, 121.497803));
        myPoints.add(new MyPoint(31.353637, 121.599426));
        myPoints.add(new MyPoint(31.346600, 121.602173));
        myPoints.add(new MyPoint(31.330179, 121.593933));
        myPoints.add(new MyPoint(31.233940, 121.484070));
        myPoints.add(new MyPoint(31.205754, 121.497803));

        //按照經度 排序,從左到右,從小到大
        Collections.sort(myPoints, new Comparator<MyPoint>() {
            @Override
            public int compare(MyPoint o1, MyPoint o2) {
                return (int) (o1.getLongitude() - o2.getLongitude());
            }
        });

        System.out.println("排序後:" + JSONObject.toJSONString(myPoints));
        return myPoints;
    }

    @Data
    class MyPoint {
        private double longitude;//經度
        private double latitude; //緯度

        public MyPoint(double longitude, double latitude) {
            this.longitude = longitude;
            this.latitude = latitude;
        }
    }

    @Data
    class CollectPoint {
        private MyPoint       baseMyPoint;
        private List<MyPoint> myPoints = new ArrayList<>();

        public void addPoint(MyPoint myPoint) {
            myPoints.add(myPoint);
        }

    }

 

3.4. K-D樹方法

       這種方法需要結合PCA(主成分分析)和K-D樹,在效果上比較好,但是性能較差,實現也較爲複雜。

http://applidium.com/en/news/too_many_pins_on_your_map/

 

參考文獻

https://developers.google.com/maps/articles/toomanymarkers

http://applidium.com/en/news/too_many_pins_on_your_map/

基於百度地圖的標記點聚合算法研究

在線地圖的點聚合算法及現狀

轉載請標明源地址:http://www.cnblogs.com/LBSer

 

求中心點:https://www.biaodianfu.com/calculate-the-center-point-of-multiple-latitude-longitude-coordinate-pairs.html 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章