地图聚合算法

一、为什么需要点聚合

      在地图上查询结果通常以标记点的形式展现,但是如果标记点较多,不仅会大大增加客户端的渲染时间,让客户端变得很卡,而且会让人产生密集恐惧症(图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 

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