道格拉斯轨迹抽稀算法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("\n");
currentPosition.append("经线:").append(bdLocation.getLongitude()).append("\n");
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()));
}
}