算法-根据经纬度坐标获取距离点集合最近的点

做项目优化的时候有个过滤逻辑耗时严重,因此写了个算法进行优化。
在此记录一下实现的过程和思路历程。
需求:有一条导航规划路线集合listA和一个poi点集合listB,所有点集合均为地图坐标,需要拿到在listA一定范围内所有poi点的集合,需求规定为3km。

当前业务逻辑是拿点listB与listA集合中每个点位用高德api进行距离计算,但是此方法耗时比较大,1000公里距离耗时达到了15分钟,10公里的耗时也有30秒左右,耗时惊人,所以我就自己写了个算法实现,结果为1000公里耗时2秒左右。

算法实现的初步想法:
点位的比较其实主要是计算listB中点位距离listA的距离是否在指定范围内。
所以我们从listB中取出一个点位C。
第一步:取点C上下左右3公里的经纬度。4个经纬度可以围成一个正方形,然后判断正方形里面是否存在listA集合中的点,如果正方形里面没有,则判断不在范围内,然后用正方形里面的poi点集合listPa进行第二步。

第二步:此时取点C为中心,指定范围的圆圈,判断范围内是否有listPa的点位,有则判断在范围内,没有则进行第三步。

第三步:此时listPa集合数据是正方形与圆形相切之后部分的位置里面,此时需判断listPa集合里面的点与其前后相邻点位的切线是否与圆相交,相交则判断在范围内。此过程中需要判断多个边界值问题,比如点位是否第一个或者最后一个,2个点位可能不在正方形内,但是切线在范围内,因为导航路径如果是一条直线可能只会给2个点,而poi点在直线的中间。

以上为分析内容,当然实际开发过程要复杂一点,相关图形分析后续补上。

以下为代码部分:


import java.util.ArrayList;
import java.util.List;


public class PolylineWorker {

    private List<LatLng> mLine;

    public PolylineWorker(List<LatLng> line) {
        mLine = line;
        init();
        initLatLngD(filterDistance);
        initPointDistance();
    }


    public void unInit() {
        if (mPolyline != null) {
            mPolyline.remove();
            mPolyline = null;
        }
    }


    /** 数据过滤半径 */
    private float filterDistance;
    /** 路径中2点间最大间隔距离值 */
    private double pointMaxDistance;
    /** 根据路径中2点间最大间隔距离计算得到的最大过滤范围值 */
    private double filterMaxDistance;

    /**
     * 计算最近点距离--优化算法
     *
     * @return 结果为1则说明在范围内,为-1则说明超过距离
     */
    public int nearestDistanceByFilter(LatLng latLng, float maxDistance) {
        //初始化过滤数据量值
        initLatLngD(maxDistance);
        initPointDistance();
        //过滤符合范围内坐标
        List<LatLng> backFilter = filterByLatLng(latLng, latD, lngD);
        //判断是否在半径内 1在半径内,-1不在半径内
        int type = filterAfter(backFilter, latLng);
        if (type == 1) {
            return 1;
        } else {
            //如果不在半径内,则获取最大范围内路径点集合
            List<LatLng> backs = filterByLatLng(latLng, latDMax, lngDMax);
            //判断路径点集合中是否存在切线在范围内的点
            return filterNearestPoint(backs, latLng);
        }
    }

    /**
     * 初始化路径中2点间最大间隔距离和经纬度偏移值
     */
    private void initPointDistance() {
        if (pointMaxDistance == 0) {
            double dLat = 0;
            double dLng = 0;
            for (int i = 0; i < mLine.size(); i++) {
                if (i < mLine.size() - 2) {
                    double tmpLat = Math.abs(mLine.get(i).latitude - mLine.get(i + 1).latitude);
                    double tmpLng = Math.abs(mLine.get(i).longitude - mLine.get(i + 1).longitude);
                    if (tmpLat > dLat) {
                        dLat = tmpLat;
                    }
                    if (tmpLng > dLng) {
                        dLng = tmpLng;
                    }
                }
            }
            double disLat = filterDistance * dLat / latD;
            double disLng = filterDistance * dLng / lngD;
            pointMaxDistance = Math.max(disLat, disLng);
            filterMaxDistance = Math.sqrt((Math.pow(filterDistance, 2) + Math.pow(pointMaxDistance, 2)));
            Logger.e("最大偏移量距离:" + filterMaxDistance);
            //初始化最大经纬度偏移值
            initDlMax();
        }
    }

    /**
     * 初始化指定范围对应的经纬度偏移值
     *
     * @param maxDistance 指定的范围
     */
    private void initLatLngD(float maxDistance) {
        if ((latD == 0 && lngD == 0) || filterDistance != maxDistance) {
            filterDistance = maxDistance;
            initDl();
        }
    }

    /**
     * 使用圆圈半径的方式进行二次过滤
     */
    private int filterAfter(List<LatLng> backFilter, LatLng latLng) {
        for (LatLng one : backFilter) {
            double tmpDistance = AMapUtils.calculateLineDistance(latLng, one);
            if (tmpDistance < filterDistance) {
                return 1;
            }
        }
        Logger.e("filterAfter过滤之后数量:----" + backFilter.size());
        return -1;
    }

    /**
     * 如果没有符合要求的点,则需要校验是否为沿线点连线中附近距离以内
     */
    private int filterNearestPoint(List<LatLng> tmpLine, LatLng latLng) {
        if (tmpLine.size() == 0) {
            return -1;
        }
        //根据指定范围点集合,判断是否存在满足条件的最近距离点距离
        for (LatLng one : tmpLine) {
            double dis = getNearestDistance(one, latLng);
            if (dis < filterDistance) {
                return 1;
            }
        }
        return -1;
    }

    /**
     * 根据给定点,判断点的前后连线距离过滤点的直线距离
     *
     * @param one    给定点
     * @param latLng 过滤点
     * @return 距离
     */
    private double getNearestDistance(LatLng one, LatLng latLng) {
        int sindex = mLine.indexOf(one);
        if (sindex == 0) {
            //起始点判断
            LatLng right = mLine.get(sindex + 1);
            return getDistanceByThreePoint(latLng, one, right);
        } else if (sindex == mLine.size() - 1) {
            //终点判断
            LatLng left = mLine.get(sindex - 1);
            return getDistanceByThreePoint(latLng, one, left);
        } else {
            //中间点判断
            LatLng right = mLine.get(sindex + 1);
            double rD = getDistanceByThreePoint(latLng, one, right);
            LatLng left = mLine.get(sindex - 1);
            double lD = getDistanceByThreePoint(latLng, one, left);
            return Math.min(rD, lD);
        }
    }

    /**
     * 获取点距离另外2个点连线的距离
     *
     * @param latLng 被过滤点
     * @param one    路径上的点
     * @param other  路径左右的点
     * @return 返回距离
     */
    private double getDistanceByThreePoint(LatLng latLng, LatLng one, LatLng other) {
        double c = AMapUtils.calculateLineDistance(latLng, one);
        //防止经纬度因精度问题导致部分数据未检测到
        if (c < filterDistance) {
            return c;
        }
        double a = AMapUtils.calculateLineDistance(one, other);
        double b = AMapUtils.calculateLineDistance(latLng, other);
        double angleB = Math.acos(((Math.pow(a, 2) + Math.pow(c, 2) - Math.pow(b, 2)) / (2 * a * c))) * 180 / Math.PI;
        double angleC = Math.acos(((Math.pow(a, 2) + Math.pow(b, 2) - Math.pow(c, 2)) / (2 * a * b))) * 180 / Math.PI;
        if (angleB > 90 || angleC > 90) {
            //角度不符合要求,过滤掉
            return filterDistance + 1;
        } else {
            //角度符合要求,计算切线距离
            double P = (a + b + c) / 2;
            double S = Math.sqrt((P * (P - a) * (P - b) * (P - c)));
            return 2 * S / a;
        }
    }

    /** 指定范围对应的经纬度偏差值 */
    private double latD = 0;
    /** 指定范围对应的经纬度偏差值 */
    private double lngD = 0;
    /** 最大偏差范围对应的经纬度偏差值 */
    private double latDMax = 0;
    /** 最大偏差范围对应的经纬度偏差值 */
    private double lngDMax = 0;

    /**
     * 根据坐标经纬度进行第一次过滤
     */
    private List<LatLng> filterByLatLng(LatLng poiItem, double latD, double lngD) {
        List<LatLng> filterLine = new ArrayList<>();
        for (LatLng one : mLine) {
            if (Math.abs(one.latitude - poiItem.latitude) <= latD &&
                    Math.abs(one.longitude - poiItem.longitude) <= lngD) {
                filterLine.add(one);
            }
        }
        Logger.e("filterBefor过滤之后数量:----" + filterLine.size());
        return filterLine;
    }

    /**
     * 根据需要过滤的距离,计算出偏移指定距离所需要偏移的经纬度量
     */
    private void initDl() {
        double lngdis = AMapUtils.calculateLineDistance(new LatLng(0, 1), new LatLng(0, 0));
        double latdis = AMapUtils.calculateLineDistance(new LatLng(1, 0), new LatLng(0, 0));
        lngD = (filterDistance / lngdis) * 1;
        latD = (filterDistance / latdis) * 1;
        Logger.e("每3km经纬度偏移量结果:lat/lng--->" + latD + "/" + lngD);
    }

    /**
     * 根据最大偏移的距离,计算出偏移指定距离所需要偏移的经纬度量
     */
    private void initDlMax() {
        double lngdis = AMapUtils.calculateLineDistance(new LatLng(0, 1), new LatLng(0, 0));
        double latdis = AMapUtils.calculateLineDistance(new LatLng(1, 0), new LatLng(0, 0));
        //一倍范围有遗漏概率,目前使用2倍最大偏移量
        lngDMax = ((filterMaxDistance + pointMaxDistance) / lngdis) * 1;
        latDMax = ((filterMaxDistance + pointMaxDistance) / latdis) * 1;
        Logger.e("最大偏移量结果:lat/lng--->" + latDMax + "/" + lngDMax);
    }
}


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

推荐阅读更多精彩内容