道格拉斯轨迹抽稀算法Android 百度地图SDK

参考博客

https://blog.csdn.net/qq_28602957/article/details/89339944
https://blog.csdn.net/weixin_34190136/article/details/91752983
https://blog.csdn.net/c10WTiybQ1Ye3/article/details/78126082

参考开发文档

http://lbsyun.baidu.com/index.php?title=androidsdk

实现效果

抽稀前:
在这里插入图片描述
抽稀后:
在这里插入图片描述
算法的详细原理请参考1、2两篇文献

抽析代码

public class DouglasPeuckerUtil {

    public static List<LatLng> DouglasPeucker(List<LatLng> points ,int epsilon) {
        double maxH = 0 ;
        int index = 0;
        int end = points.size();
        for (int i = 1; i < end - 1; i++){
            double h = H(points.get(0),points.get(i),points.get(end-1));
            if(h > maxH){
                maxH = h;
                index = i;
            }
        }
        List<LatLng> result = new ArrayList<>();
        if (maxH > epsilon){
            List<LatLng> leftPoints = new ArrayList<>();//左曲线
            List<LatLng> rightPoints = new ArrayList<>();//右曲线
            //分别保存左曲线和右曲线的坐标点
            for (int i = 0; i < end; i++){
                if (i <= index){
                    leftPoints.add(points.get(i));
                    if (i == index){
                        rightPoints.add(points.get(i));
                    }
                }else{
                    rightPoints.add(points.get(i));
                }
            }
            List<LatLng>leftResult = new ArrayList<>();
            List<LatLng>rightResult = new ArrayList<>();
            leftResult = DouglasPeucker(leftPoints,epsilon);
            rightResult = DouglasPeucker(rightPoints,epsilon);

            rightResult.remove(0);//移除重复的点
            leftResult.addAll(rightResult);
            result = leftResult;
        }else {
            result.add(points.get(0));
            result.add(points.get(end - 1));
        }
        return result;
    }

    public static double H (LatLng A, LatLng B, LatLng C){
        double c = DistanceUtil.getDistance(A,B);

        double b = DistanceUtil.getDistance(A,C);

        double a = DistanceUtil.getDistance(C,B);

        double S = helen(a,b,c);

        double H = 2 * S / c;

        return H;
    }

    public static double helen(double a,double b,double c){
        double p = (a + b + c)/2;
        double S = Math.sqrt(p * (p - a) * (p - b) * (p - c));
        return S;
    }
}

MainActivity

package com.example.tracksparsetest;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.app.ProgressDialog;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.baidu.location.BDAbstractLocationListener;
import com.baidu.location.BDLocation;
import com.baidu.location.LocationClient;
import com.baidu.location.LocationClientOption;
import com.baidu.mapapi.CoordType;
import com.baidu.mapapi.SDKInitializer;
import com.baidu.mapapi.map.BaiduMap;
import com.baidu.mapapi.map.BitmapDescriptor;
import com.baidu.mapapi.map.BitmapDescriptorFactory;
import com.baidu.mapapi.map.MapStatus;
import com.baidu.mapapi.map.MapStatusUpdate;
import com.baidu.mapapi.map.MapStatusUpdateFactory;
import com.baidu.mapapi.map.MapView;
import com.baidu.mapapi.map.MarkerOptions;
import com.baidu.mapapi.map.MyLocationConfiguration;
import com.baidu.mapapi.map.MyLocationData;
import com.baidu.mapapi.map.Overlay;
import com.baidu.mapapi.map.OverlayOptions;
import com.baidu.mapapi.map.Polyline;
import com.baidu.mapapi.map.PolylineOptions;
import com.baidu.mapapi.model.LatLng;
import com.baidu.mapapi.utils.DistanceUtil;

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

public class MainActivity extends AppCompatActivity {

//    //始点图层图标
//    private BitmapDescriptor startBD = BitmapDescriptorFactory
//            .fromResource(R.drawable.ic_start);
//    //终点图层图标
//    private BitmapDescriptor finishBD = BitmapDescriptorFactory
//            .fromResource(R.drawable.ic_stop);

    public LocationClient mlocationClient;

    public MapView mMapView = null;

    private TextView positionText;

    private BaiduMap mBaiduMap;

    private boolean isFirstLocate = true;

    private float mCurrentZoom =16f;

    private LatLng lastPoint;//记录上一个定位点

    private Overlay mPolyline;

    private double mCurrentLat;

    private double mCurrentLon;

    private float mCurrentDirection;

    private MapStatus.Builder builder;

    private MyLocationData locData;

    private Button start;

    private Button finish;

    private ProgressDialog progressDialog;

//    List<OverlayOptions> markers = new ArrayList<>();

    List<LatLng> points = new ArrayList<LatLng>();

    List<LatLng> points_Sparsed = new ArrayList<LatLng>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //在使用SDK各组件之前初始化context信息,传入ApplicationContext
        SDKInitializer.initialize(getApplicationContext());//this报错
        //自4.3.0起,百度地图SDK所有接口均支持百度坐标和国测局坐标,用此方法设置您使用的坐标类型.
        //包括BD09LL和GCJ02两种坐标,默认是BD09LL坐标。
        SDKInitializer.setCoordType(CoordType.BD09LL);
        mlocationClient = new LocationClient(getApplicationContext());
        mlocationClient.registerLocationListener(new MyLocationListener());
        initLocation();
        setContentView(R.layout.activity_main);
        start = (Button) findViewById(R.id.start_b);
        finish = (Button)findViewById(R.id.finish_b);
        positionText = (TextView)findViewById(R.id.position_text_view);
        mMapView = (MapView)findViewById(R.id.bmapView);
        mBaiduMap = mMapView.getMap();
        mBaiduMap.setMapType(BaiduMap.MAP_TYPE_SATELLITE);
        //开启地图定位图层
        mBaiduMap.setMyLocationEnabled(true);

        /**添加地图缩放状态变化监听,当手动放大或缩小地图时,拿到缩放后的比例,然后获取到下次定位,
         *  给地图重新设置缩放比例,否则地图会重新回到默认的mCurrentZoom缩放比例
         */
        mCurrentZoom = 20;

        mBaiduMap.setOnMapStatusChangeListener(new BaiduMap.OnMapStatusChangeListener() {
            @Override
            public void onMapStatusChangeStart(MapStatus mapStatus) {

            }

            @Override
            public void onMapStatusChangeStart(MapStatus mapStatus, int i) {

            }

            @Override
            public void onMapStatusChange(MapStatus mapStatus) {

            }

            @Override
            public void onMapStatusChangeFinish(MapStatus mapStatus) {
                mCurrentZoom = mapStatus.zoom;
            }
        });

        //设置定位图标类型为跟随模式
        mBaiduMap.setMyLocationConfiguration(new MyLocationConfiguration(MyLocationConfiguration.LocationMode.FOLLOWING,true,null));

        //运行时权限检查
        List<String> permissionList = new ArrayList<>();
        if (ContextCompat.checkSelfPermission(MainActivity.this,
                Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED){
            permissionList.add(Manifest.permission.ACCESS_FINE_LOCATION);
        }
        if (ContextCompat.checkSelfPermission(MainActivity.this,
                Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED){
            permissionList.add(Manifest.permission.READ_PHONE_STATE);
        }
        if (ContextCompat.checkSelfPermission(MainActivity.this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
            permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
        }
        if (!permissionList.isEmpty()){
            String[] permissions = permissionList.toArray(new String[permissionList.size()]);
            ActivityCompat.requestPermissions(MainActivity.this,permissions,1);
        }else {
            requestLocation();
        }
    }

    private void requestLocation(){
//        initLocation();
//        mlocationClient.start();
        start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mlocationClient.start();
//                progressDialog = new ProgressDialog(MainActivity.this);
//                progressDialog.setTitle("搜索GPS中");
//                progressDialog.setMessage("....");
//                progressDialog.setCancelable(true);
//                progressDialog.show();
            }
        });
        finish.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (mlocationClient != null && mlocationClient.isStarted()) {
                    mlocationClient.stop();//停止定位

                    if(points.size() <= 0){
                        return;
                    }

                    //运动结束记得标记终点图标
                    MarkerOptions oFinish = new MarkerOptions();
                    oFinish.position(points.get(points.size() - 1));
                    BitmapDescriptor bitmap = BitmapDescriptorFactory.fromResource(R.drawable.ic_stop);
                    oFinish.icon(bitmap);
                    mBaiduMap.addOverlay(oFinish);
                }
            }
        });

    }

    private void initLocation(){
        LocationClientOption option = new LocationClientOption();
        option.setScanSpan(1000);
        option.setCoorType("bd09ll");
        option.setLocationMode(LocationClientOption.LocationMode.Hight_Accuracy);
        mlocationClient.setLocOption(option);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode){
            case 1:
                if (grantResults.length > 0){
                    for (int result : grantResults){
                        if (result != PackageManager.PERMISSION_GRANTED){
                            Toast.makeText(this,"必须同意所有权限才能使用",Toast.LENGTH_SHORT).show();
                            finish();
                            return;
                        }
                    }
                    //
                    requestLocation();
                }else {
                    Toast.makeText(this,"发生未知错误",Toast.LENGTH_SHORT).show();
                    finish();
                }
                break;
            default:
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        mMapView.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        mMapView.onPause();
    }

    @Override
    protected void onDestroy() {
        // 退出时销毁定位
//        mlocationClient.unRegisterLocationListener(myListener);
        mlocationClient.stop();
        mBaiduMap.setMyLocationEnabled(false);
        mMapView.onDestroy();
        mMapView = null;
//        startBD.recycle();
//        finishBD.recycle();
        super.onDestroy();
    }

    private void navigateTo(BDLocation bdLocation){
        if (isFirstLocate){
            LatLng ll = new LatLng(bdLocation.getLatitude(),bdLocation.getLongitude());
            MapStatusUpdate update = MapStatusUpdateFactory.newLatLng(ll);
            mBaiduMap.animateMapStatus(update);
            update = MapStatusUpdateFactory.zoomTo(16f);
            mBaiduMap.animateMapStatus(update);
            isFirstLocate = false;
        }
        if (bdLocation == null || mMapView == null){
            return;
        }
        MyLocationData locData1 = new MyLocationData.Builder()
                .accuracy(bdLocation.getRadius())
                .direction(bdLocation.getDirection())
                .latitude(bdLocation.getLatitude())
                .longitude(bdLocation.getLongitude())
                .build();
        mBaiduMap.setMyLocationData(locData1);
    }

    public class MyLocationListener extends BDAbstractLocationListener {

        @Override
        public void onReceiveLocation(final BDLocation bdLocation) {
//            LatLng l = getMostAccuracyLocation(bdLocation);
//            locateAndZoom(bdLocation,l);
//            navigateTo(bdLocation);
//            drawPoint(bdLocation);
//            drawLine(bdLocation);
            runOnUiThread(new Runnable() {
                @Override
                public void run() {

                    StringBuilder currentPosition = new StringBuilder();
                    currentPosition.append("纬度:").append(bdLocation.getLatitude()).append("
");
                    currentPosition.append("经线:").append(bdLocation.getLongitude()).append("
");
                    currentPosition.append("定位方式");
                    if (bdLocation.getLocType() == BDLocation.TypeGpsLocation){
                        currentPosition.append("GPS "+"|抽稀后:"+ points_Sparsed.size()+"|抽稀前:"+points.size());
                    }else if (bdLocation.getLocType() == BDLocation.TypeNetWorkLocation){
                        currentPosition.append("netWork"+"|抽稀后:"+ points_Sparsed.size()+"|抽稀前:"+points.size());
                    }
                    positionText.setText(currentPosition);

                }
            });
            if ((bdLocation.getLocType() == BDLocation.TypeGpsLocation)){
                if (isFirstLocate){
                    LatLng ll = null;
                    ll = getMostAccuracyLocation(bdLocation);
//                    ll = new LatLng(bdLocation.getLatitude(),bdLocation.getLongitude());
//                    progressDialog.dismiss();
//                    Log.d("onReceiveLocation1",  ll.toString());
                    if (ll == null){
                        return;
                    }
                    isFirstLocate = false;
                    points.add(ll);//加入集合
//                    points_Sparsed = DouglasPeuckerUtil.DouglasPeucker(points,1);
                    lastPoint = ll;//记录上一个点

                    //显示当前定位点,缩放地图
                    locateAndZoom(bdLocation,ll);

                    BitmapDescriptor bitmap = BitmapDescriptorFactory.fromResource(R.drawable.ic_start);
                    OverlayOptions oStart = new MarkerOptions()
                            .position(points.get(0))
                            .icon(bitmap);//地图标记覆盖物参数配置;
                    mBaiduMap.addOverlay(oStart);
                    return;//画轨迹至少两个点,这里返回
                }
//                progressDialog.dismiss();
                //从第二个点开始
                LatLng ll = new LatLng(bdLocation.getLatitude(),bdLocation.getLongitude());
                //sdk回调gps位置的频率是1秒1个,位置点太近动态画在图上不是很明显,可以设置点之间距离大于为5米才添加到集合中,这里可以加入点抽稀算法
                if (DistanceUtil.getDistance(lastPoint, ll) < 5) {
                    return;
                }


                points.add(ll);
                //进行轨迹抽稀
//                points_Sparsed = DouglasPeuckerUtil.DouglasPeucker(points,10);


                lastPoint = ll;

                //显示当前定位点,缩放地图
                locateAndZoom(bdLocation,ll);
                //清除上一次轨迹,避免重叠绘画
                mMapView.getMap().clear();
                //起始点的图层也会被清除重新绘画
                BitmapDescriptor bitmap = BitmapDescriptorFactory.fromResource(R.drawable.ic_start);
                OverlayOptions oStart = new MarkerOptions()
                        .position(points_Sparsed.get(0))
                        .icon(bitmap);//地图标记覆盖物参数配置
                mBaiduMap.addOverlay(oStart);
                //绘制每一个点的标记点
                BitmapDescriptor mPoint = BitmapDescriptorFactory.fromResource(R.drawable.ic_point);
                for(LatLng point : points_Sparsed){
                    OverlayOptions posi = new MarkerOptions()
                        .position(point)
                        .icon(mPoint)
                        .draggable(true)
                        .flat(true)
                        .alpha(0.5f);//地图标记覆盖物参数配置
                    mBaiduMap.addOverlay(posi);
                }
//                OverlayOptions posi = new MarkerOptions()
//                        .position(points_Sparsed.get(points_Sparsed.size()-1))
//                        .icon(mPoint)
//                        .draggable(true)
//                        .flat(true)
//                        .alpha(0.5f);//地图标记覆盖物参数配置
//                markers.add(posi);

                //将points集合中的点绘制轨迹线条图层,显示在地图上
                //设置折线的属性
                OverlayOptions mOverlayOptions = new PolylineOptions()
                        .width(13)
                        .color(0xAAFF0000)
                        .points(points_Sparsed);

                //在地图上绘制折线
                //mPloyline 折线对象
                Overlay mPolyline = mBaiduMap.addOverlay(mOverlayOptions);
                //添加所有的点
//                for (OverlayOptions options : markers){
//                    mBaiduMap.addOverlay(options);
//                }
            }
        }
    }

    //首次定位很重要,选一个精度相对较高的起始点
    private LatLng getMostAccuracyLocation(final BDLocation location){

        if (location.getRadius()>25) {//gps位置精度大于25米的点直接弃用
            return null;
        }

        LatLng ll = new LatLng(location.getLatitude(), location.getLongitude());

        if (DistanceUtil.getDistance(lastPoint, ll ) > 5) {
            lastPoint = ll;
            points.clear();//有两点位置大于5,重新来过
            return null;
        }
        points.add(ll);
        lastPoint = ll;
        //有5个连续的点之间的距离小于5,认为gps已稳定,以最新的点为起始点
        if(points.size() >= 5){
            points.clear();
            return ll;
        }
        return null;
    }
    //显示当前定位点,缩放地图
    private void locateAndZoom(BDLocation location, LatLng ll){
        /**
         * 记录当前经纬度,当位置不变,手机转动,取得方向传感器的方向,
         给地图重新设置位置参数,在跟随模式下可使地图箭头随手机转动而转动
         */
        mCurrentLat = location.getLatitude();
        mCurrentLon = location.getLongitude();
        locData = new MyLocationData.Builder().accuracy(location.getRadius())//去掉精度圈
                //此mCurrentDirection为自己获取到的手机传感器方向信息,顺时针0-360
                .direction(location.getDirection()).latitude(location.getLatitude())
                .longitude(location.getLongitude()).build();
        mBaiduMap.setMyLocationData(locData);//显示当前定位位置点

//给地图设置缩放中心点,和缩放比例值
        builder = new MapStatus.Builder();
        builder.target(ll).zoom(mCurrentZoom);
        mBaiduMap.animateMapStatus(MapStatusUpdateFactory.newMapStatus(builder.build()));
    }

}

原文地址:https://www.cnblogs.com/PythonFCG/p/13860127.html