Android 基于高德地图的锁屏后定位和轨迹自动纠偏(离线版)

       

目录

一、后台如何持续获取定位

1、后台以及锁屏后持续定位异常的原因以及应对方案探索 

2、后台持续获取定位失败的应对方案

二、对坐标点进行加工处理

     (1)、为什么要加工处理

      (2)、如何加工处理


本文主要是给以下两个问题提供解决方案建议:

1、后台如何持续获取定位,包括处于以下环境:(1)应用已经切换到后台;(2)手机已经锁屏

2、成功获取到的一堆坐标点后应该如何加工处理,让坐标点尽可能准确地描绘手机的移动轨迹

一、后台如何持续获取定位

1、后台以及锁屏后持续定位异常的原因以及应对方案探索 

       关于App切换到后台并且息屏后,持续定位失效的探索:
(1)、在Android8.0 之后,谷歌已经禁用了后台服务的功能,只允许使用前台服务,意味着app一旦看不见后,不管是主页键还是返回键导致的,黑屏后,后台的功能都会即将或者立即进入冷冻状态,即服务是活着的,但是逻辑却没有正常运行,定位系统就会出现坐标不及时更新甚至不更新的情况。

(2)、针对此类型的系统管制,在参考了网上资料后,主要的有效资料来源于百度和高德的后台定位保活建议处理。百度的保活处理已经在app上得到应用,要提及的关键一点就是系统的电源锁机制。app可以通过获取锁,从影响甚至小程度的控制系统的电源运行情况,从而使自身app免于进入冷冻状态。但该锁在低端机出现明显的兼容问题,就是该保活处理在高端机或者中端机得到较好的成效,但在低端机例如高通骁龙4系的处理器平台上,保活失效,定位系统的更新频率会被系统压制到30秒更新一次,也就是说,app 的定位30秒才更新一次,会对用户带来一定的迷惑。推测原因可能是低端机的电源管控可能更加严格,或者一刀切处理。

2、后台持续获取定位失败的应对方案

    为什么说是应对方案了,因为这个方法不能保证一定起作用。方法主要是参考百度地图的鹰眼sdk文档中的服务存活做法建议,这个是百度的链接:百度鹰眼SDK后台保活的做法建议

   百度的建议中最强的一点是:启用多媒体锁

   什么是多媒体锁?可以自行百度,以下是百度参考建议中直接复制粘贴过来的内容:

     为了确保MediaPlayer的承载的服务在系统睡眠的时候继续正常执行下去。Android为我们提供了一种唤醒锁(wake locks)的机制。它能够在系统睡眠时,依旧保持锁定硬件的正常工作。基于这种思路,可以在集成鹰眼SDK的APP中,使用Service继承MediaPlayer,播放一段无声音频文件,达到保活效果。确保在MediaPlayer执行的时候,哪怕系统睡眠了CUP也能正常执行。需要使用MediaPlayer.setWakeMode()为MediaPlayer设定唤醒锁。以下是setWakMode()的定义:setWakeMode(Context context, int mode)第一个參数是当前上下文,第二个參数为须要加锁的状态,被设定为int类型的常量,定义在PowerManager这个final类中。在这里仅仅须要设定为PARTIAL_WAKE_LOCK就可以。

// 设定CUP锁定 mediaPlayer = new MediaPlayer(); mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);

一般对于锁而言。锁定了通常须要解锁。可是这里的唤醒锁与MediaPlayer关联,所以仅仅须要在使用完之后release()释放MediaPlayer就可以,无需显式的为其解锁。在使用setWakeMode设定唤醒锁的时候,还必须为应用赋予对应的权限: <uses-permission android:name="android.permission.WAKE_LOCK"/>

   怎么使用这个多媒体锁呢?我是直接找了一个无声音的音频文件,然后在后台持续循环播放该音频文件,音频文件在文末云盘分享链接

   示例代码:

    /**
     * 新建这个播放器,主要是用来获取媒体锁,从而使服务不被系统杀掉,一般对于锁而言。锁定了通常须要解锁。
     * 可是这里的唤醒锁与MediaPlayer关联,所以仅仅须要在使用完之后release()释放MediaPlayer就可以,无需显式的为其解锁
     */
    private MediaPlayer mMediaPlayer;

    //播放音频文件
    private void playMuteMusic() {
        mMediaPlayer = new MediaPlayer();
        setMusicSource();
        mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
        mMediaPlayer.start();
        mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                mMediaPlayer.reset();
                setMusicSource();
                mMediaPlayer.start();
            }
        });
        //一般对于锁而言。锁定了通常须要解锁。
        //可是这里的唤醒锁与MediaPlayer关联,所以仅仅须要在使用完之后release()释放MediaPlayer就可以,无需显式的为其解锁
        mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
    }

   //设置音频文件路径
    private void setMusicSource() {
        AssetFileDescriptor fileDescriptor = null;
        try {
            fileDescriptor = getAssets().openFd("mute.mp3");//播放的是assets下的音频文件
            mMediaPlayer.setDataSource(fileDescriptor.getFileDescriptor(), fileDescriptor.getStartOffset(), fileDescriptor.getLength());
            mMediaPlayer.prepare();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

     其中以上代码起关键作用的是开启锁这一行,至于锁的其中缘由,不在本文探索范围内

MediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);

   以上代码需要放在后台服务类中,启动运行。

二、对坐标点进行加工处理

     (1)、为什么要加工处理

         坐标点拿回来的时候,会附带有误差值(即定位准确度,数字越小,越准),一般根据项目实际使用情况进行对应的筛选,例如本人负责项目会过滤误差在30米以上的坐标点,也就是大于30米误差的直接扔掉。这是一点,其次是坐标重复冗余的问题。当用户在原地不动的时候,GPS却是时刻保持工作的,但是返回来的坐标点不一定相同,如果这时候也将这些坐标点直接描绘在地图上,轨迹会显示得像马蜂窝那般杂乱无章。最后是视觉体验问题,在不影响轨迹准确性的情况下,将坐标点连接起来后,如何将轨迹显示得圆润些,没那么多直角或者折线。

      (2)、如何加工处理

      这里主要是个人处理加上高德开源的算法。

     个人处理逻辑里,会先在准确度以及速度的前提下,将数据进行先过滤。例如GPS会返回速度,如果速度为0的情况下,坐标点就是无效的。其二,准确度,像刚刚说的那样,根据项目实际需要误差在30米以上的直接扔掉。

     然后是依靠高德开源的算法,参考  高德轨迹平滑处理示例

     我主要是使用了这个示例中的这个类  PathSmoothTool

     以下是我修改后的类代码,主要是修改了3个值,(1)卡尔曼滤波程度(2)抽稀程度(3)去噪程度,三个值在代码注释里有对应解释,不在此赘述

      



import android.util.Log;

import com.amap.api.maps.AMapUtils;
import com.amap.api.maps.model.LatLng;

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

/**
 * 从高德开源工具那里复制过来
 * 轨迹优化工具类
 * Created by my94493 on 2017/3/31.
 * <p>
 * 使用方法:
 * <p>
 *     PathSmoothTool pathSmoothTool = new PathSmoothTool();
 *     pathSmoothTool.setIntensity(2);//设置滤波强度,默认3
 *     List<LatLng> mList = LatpathSmoothTool.kalmanFilterPath(list);
 */

public class PathSmoothTool {
    private int mIntensity = 5;    //卡尔曼滤波程度   默认值是3   这东西影响着轨迹地流畅性
    private float mThreshhold = 0.1f;  //抽稀程度  数字越大  冗余轨迹点越少  轨迹越不圆润  默认值是0.3f
    private float mNoiseThreshhold = 10;  //去噪程度,轨迹点漂移偏差大小的判断,默认值是10   就是以10为准,两点之间偏差大于10的话,丢弃那个坐标点

    public PathSmoothTool(){

    }

    public int getIntensity() {
        return mIntensity;
    }

    public void setIntensity(int mIntensity) {
        this.mIntensity = mIntensity;
    }

    public float getThreshhold() {
        return mThreshhold;
    }

    public void setThreshhold(float mThreshhold) {
        this.mThreshhold = mThreshhold;
    }

    public void setNoiseThreshhold(float mnoiseThreshhold) {
        this.mNoiseThreshhold = mnoiseThreshhold;
    }

    /**
     * 轨迹平滑优化
     * @param originlist 原始轨迹list,list.size大于2
     * @return 优化后轨迹list
     */
    public List<LatLng> pathOptimize(List<LatLng> originlist){

        List<LatLng> list = removeNoisePoint(originlist);//去噪
        List<LatLng> afterList = kalmanFilterPath(list,mIntensity);//滤波
        List<LatLng> pathoptimizeList = reducerVerticalThreshold(afterList,mThreshhold);//抽稀
//        Log.i("MY","originlist: "+originlist.size());
//        Log.i("MY","list: "+list.size());
//        Log.i("MY","afterList: "+afterList.size());
//        Log.i("MY","pathoptimizeList: "+pathoptimizeList.size());
        return pathoptimizeList;
    }

    /**
     * 轨迹线路滤波
     * @param originlist 原始轨迹list,list.size大于2
     * @return 滤波处理后的轨迹list
     */
    public List<LatLng> kalmanFilterPath(List<LatLng> originlist) {
        return kalmanFilterPath(originlist,mIntensity);
    }


    /**
     * 轨迹去噪,删除垂距大于20m的点
     * @param originlist 原始轨迹list,list.size大于2
     * @return
     */
    public List<LatLng> removeNoisePoint(List<LatLng> originlist){
        return reduceNoisePoint(originlist,mNoiseThreshhold);
    }

    /**
     * 单点滤波
     * @param lastLoc 上次定位点坐标
     * @param curLoc 本次定位点坐标
     * @return 滤波后本次定位点坐标值
     */
    public LatLng kalmanFilterPoint(LatLng lastLoc, LatLng curLoc) {
        return kalmanFilterPoint(lastLoc,curLoc,mIntensity);
    }

    /**
     * 轨迹抽稀
     * @param inPoints 待抽稀的轨迹list,至少包含两个点,删除垂距小于mThreshhold的点
     * @return 抽稀后的轨迹list
     */
    public List<LatLng> reducerVerticalThreshold(List<LatLng> inPoints) {
        return reducerVerticalThreshold(inPoints,mThreshhold);
    }

    /********************************************************************************************************/
    /**
     * 轨迹线路滤波
     * @param originlist 原始轨迹list,list.size大于2
     * @param intensity 滤波强度(1—5)
     * @return
     */
    private List<LatLng> kalmanFilterPath(List<LatLng> originlist,int intensity) {
        List<LatLng> kalmanFilterList = new ArrayList<LatLng>();
        if (originlist == null || originlist.size() <= 2)
            return kalmanFilterList;
        initial();//初始化滤波参数
        LatLng latLng = null;
        LatLng lastLoc = originlist.get(0);
        kalmanFilterList.add(lastLoc);
        for (int i = 1; i < originlist.size(); i++) {
            LatLng curLoc = originlist.get(i);
            latLng = kalmanFilterPoint(lastLoc,curLoc,intensity);
            if (latLng != null) {
                kalmanFilterList.add(latLng);
                lastLoc = latLng;
            }
        }
        return kalmanFilterList;
    }

    /**
     * 单点滤波
     * @param lastLoc 上次定位点坐标
     * @param curLoc 本次定位点坐标
     * @param intensity 滤波强度(1—5)
     * @return 滤波后本次定位点坐标值
     */
    private LatLng kalmanFilterPoint(LatLng lastLoc, LatLng curLoc, int intensity) {
        if (pdelt_x == 0 || pdelt_y == 0 ){
            initial();
        }
        LatLng kalmanLatlng = null;
        if (lastLoc == null || curLoc == null){
            return kalmanLatlng;
        }
        if (intensity < 1){
            intensity = 1;
        } else if (intensity > 5){
            intensity = 5;
        }
        for (int j = 0; j < intensity; j++){
            kalmanLatlng = kalmanFilter(lastLoc.longitude,curLoc.longitude,lastLoc.latitude,curLoc.latitude);
            curLoc = kalmanLatlng;
        }
        return kalmanLatlng;
    }


    /***************************卡尔曼滤波开始********************************/
    private double lastLocation_x; //上次位置
    private double currentLocation_x;//这次位置
    private double lastLocation_y; //上次位置
    private double currentLocation_y;//这次位置
    private double estimate_x; //修正后数据
    private double estimate_y; //修正后数据
    private double pdelt_x; //自预估偏差
    private double pdelt_y; //自预估偏差
    private double mdelt_x; //上次模型偏差
    private double mdelt_y; //上次模型偏差
    private double gauss_x; //高斯噪音偏差
    private double gauss_y; //高斯噪音偏差
    private double kalmanGain_x; //卡尔曼增益
    private double kalmanGain_y; //卡尔曼增益

    private double m_R= 0;
    private double m_Q= 0;
    //初始模型
    private void initial(){
        pdelt_x =  0.001;
        pdelt_y =  0.001;
//        mdelt_x = 0;
//        mdelt_y = 0;
        mdelt_x =  5.698402909980532E-4;
        mdelt_y =  5.698402909980532E-4;
    }
    private LatLng kalmanFilter(double oldValue_x, double value_x, double oldValue_y, double value_y){
        lastLocation_x = oldValue_x;
        currentLocation_x= value_x;
        gauss_x = Math.sqrt(pdelt_x * pdelt_x + mdelt_x * mdelt_x)+m_Q;     //计算高斯噪音偏差
        kalmanGain_x = Math.sqrt((gauss_x * gauss_x)/(gauss_x * gauss_x + pdelt_x * pdelt_x)) +m_R; //计算卡尔曼增益
        estimate_x = kalmanGain_x * (currentLocation_x - lastLocation_x) + lastLocation_x;    //修正定位点
        mdelt_x = Math.sqrt((1-kalmanGain_x) * gauss_x *gauss_x);      //修正模型偏差

        lastLocation_y = oldValue_y;
        currentLocation_y = value_y;
        gauss_y = Math.sqrt(pdelt_y * pdelt_y + mdelt_y * mdelt_y)+m_Q;     //计算高斯噪音偏差
        kalmanGain_y = Math.sqrt((gauss_y * gauss_y)/(gauss_y * gauss_y + pdelt_y * pdelt_y)) +m_R; //计算卡尔曼增益
        estimate_y = kalmanGain_y * (currentLocation_y - lastLocation_y) + lastLocation_y;    //修正定位点
        mdelt_y = Math.sqrt((1-kalmanGain_y) * gauss_y * gauss_y);      //修正模型偏差

        LatLng latlng = new LatLng(estimate_y,estimate_x);


        return latlng;
    }
    /***************************卡尔曼滤波结束**********************************/

    /***************************抽稀算法*************************************/
    private List<LatLng> reducerVerticalThreshold(List<LatLng> inPoints,
                                          float threshHold) {
        if (inPoints == null) {
            return null;
        }
        if (inPoints.size() <= 2) {
            return inPoints;
        }
        List<LatLng> ret = new ArrayList<LatLng>();
        for (int i = 0; i < inPoints.size(); i++) {
            LatLng pre = getLastLocation(ret);
            LatLng cur = inPoints.get(i);
            if (pre == null || i == inPoints.size() - 1) {
                ret.add(cur);
                continue;
            }
            LatLng next = inPoints.get(i + 1);
            double distance = calculateDistanceFromPoint(cur, pre, next);
            if (distance > threshHold){
                ret.add(cur);
            }
        }
        return ret;
    }
    private static LatLng getLastLocation(List<LatLng> oneGraspList) {
        if (oneGraspList == null || oneGraspList.size() == 0) {
            return null;
        }
        int locListSize = oneGraspList.size();
        LatLng lastLocation = oneGraspList.get(locListSize - 1);
        return lastLocation;
    }

    /**
     * 计算当前点到线的垂线距离
     * @param p 当前点
     * @param lineBegin 线的起点
     * @param lineEnd 线的终点
     *
     */
    private static double calculateDistanceFromPoint(LatLng p, LatLng lineBegin,
                                      LatLng lineEnd) {
        double A = p.longitude - lineBegin.longitude;
        double B = p.latitude - lineBegin.latitude;
        double C = lineEnd.longitude - lineBegin.longitude;
        double D = lineEnd.latitude - lineBegin.latitude;

        double dot = A * C + B * D;
        double len_sq = C * C + D * D;
        double param = dot / len_sq;

        double xx, yy;

        if (param < 0 || (lineBegin.longitude == lineEnd.longitude
                && lineBegin.latitude == lineEnd.latitude)) {
            xx = lineBegin.longitude;
            yy = lineBegin.latitude;
//            return -1;
        } else if (param > 1) {
            xx = lineEnd.longitude;
            yy = lineEnd.latitude;
//            return -1;
        } else {
            xx = lineBegin.longitude + param * C;
            yy = lineBegin.latitude + param * D;
        }
        return AMapUtils.calculateLineDistance(p,new LatLng(yy,xx));
    }
    /***************************抽稀算法结束*********************************/

    private List<LatLng> reduceNoisePoint(List<LatLng> inPoints, float threshHold) {
            if (inPoints == null) {
                return null;
            }
            if (inPoints.size() <= 2) {
                return inPoints;
            }
            List<LatLng> ret = new ArrayList<LatLng>();
            for (int i = 0; i < inPoints.size(); i++) {
                LatLng pre = getLastLocation(ret);
                LatLng cur = inPoints.get(i);
                if (pre == null || i == inPoints.size() - 1) {
                    ret.add(cur);
                    continue;
                }
                LatLng next = inPoints.get(i + 1);
                double distance = calculateDistanceFromPoint(cur, pre, next);
                if (distance < threshHold){
                    ret.add(cur);
                }
            }
            return ret;
        }
}

        这个工具类的主要使用方法是    pathOptimize(List<LatLng> originlist)

        传入的坐标点是高德自身定义的 LatLng  类,但我觉得应该可以使用自定义的因为坐标点传进去,只是用在了高德自身的距离计算方法,该计算方法可以自己网上自行百度,当然,只是推测,大家可以试验下。

        最后,pathOptimize 方法处理过后的数据就可以直接描绘在地图上了。

百度云盘链接:

链接:https://pan.baidu.com/s/1QaWASPI9-qaQ-dALVkpZqQ 
提取码:26bj 

  • 6
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值