-
@param end 结束经纬度
-
@param center 前两个点之间的中心点
-
@return 中心点到 start和end所在直线的距离
*/
private double distToSegment(LatLngPoint start, LatLngPoint end, LatLngPoint center) {
double a = Math.abs(AMapUtils.calculateLineDistance(start.latLng, end.latLng));
double b = Math.abs(AMapUtils.calculateLineDistance(start.latLng, center.latLng));
double c = Math.abs(AMapUtils.calculateLineDistance(end.latLng, center.latLng));
double p = (a + b + c) / 2.0;
double s = Math.sqrt(Math.abs(p * (p - a) * (p - b) * (p - c)));
double d = s * 2.0 / a;
return d;
}
Douglas工具类具体代码
public Douglas(ArrayList mLineInit, double dmax) {
if (mLineInit == null) {
throw new IllegalArgumentException(“传入的经纬度坐标list == null”);
}
this.dMax = dmax;
this.start = 0;
this.end = mLineInit.size() - 1;
for (int i = 0; i < mLineInit.size(); i++) {
this.mLineInit.add(new LatLngPoint(i, mLineInit.get(i)));
}
}
/**
-
压缩经纬度点
-
@return
*/
public ArrayList compress() {
int size = mLineInit.size();
ArrayList latLngPoints = compressLine(mLineInit.toArray(new LatLngPoint[size]), mLineFilter, start, end, dMax);
latLngPoints.add(mLineInit.get(0));
latLngPoints.add(mLineInit.get(size-1));
//对抽稀之后的点进行排序
Collections.sort(latLngPoints, new Comparator() {
@Override
public int compare(LatLngPoint o1, LatLngPoint o2) {
return o1.compareTo(o2);
}
});
ArrayList latLngs = new ArrayList<>();
for (LatLngPoint point : latLngPoints) {
latLngs.add(point.latLng);
}
return latLngs;
}
/**
-
根据最大距离限制,采用DP方法递归的对原始轨迹进行采样,得到压缩后的轨迹
-
x
-
@param originalLatLngs 原始经纬度坐标点数组
-
@param endLatLngs 保持过滤后的点坐标数组
-
@param start 起始下标
-
@param end 结束下标
-
@param dMax 预先指定好的最大距离误差
*/
private ArrayList compressLine(LatLngPoint[] originalLatLngs, ArrayList endLatLngs, int start, int end, double dMax) {
if (start < end) {
//递归进行调教筛选
double maxDist = 0;
int currentIndex = 0;
for (int i = start + 1; i < end; i++) {
double currentDist = distToSegment(originalLatLngs[start], originalLatLngs[end], originalLatLngs[i]);
if (currentDist > maxDist) {
maxDist = currentDist;
currentIndex = i;
}
}
//若当前最大距离大于最大距离误差
if (maxDist >= dMax) {
//将当前点加入到过滤数组中
endLatLngs.add(originalLatLngs[current
Index]);
//将原来的线段以当前点为中心拆成两段,分别进行递归处理
compressLine(originalLatLngs, endLatLngs, start, currentIndex, dMax);
compressLine(originalLatLngs, endLatLngs, currentIndex, end, dMax);
}
}
return endLatLngs;
}
结果:
上图中展示的轨迹是定位得到的4417个点,经过抽稀之后绘制在地图上的样式。算法中传入的阙值是10,4417个点处理之后只136个点。而且这136个点绘制的轨迹和4417个点绘制的轨迹几乎没有什么差别。
不知道你们有没有被震撼到,反正我是彻彻底底被震到了。作为算法小白的我,感觉整个世界都被颠覆了。
二、轨迹绘制-自定义运动轨迹View
最开始得时候认为直接在地图上绘制动态轨迹的,根据高德提供绘制轨迹的API,结果直接卡死。当时一脸懵逼的找高德客服,一提高德的客服更让人窝火。算了不提了。后面自己试了好多遍之后放弃直接在地图上绘制,不知道哪一刻,就突然想到在地图上覆盖一个自定义的View。当时有一瞬间觉得自己是这个世界上智商最接近250的 ┐(‘~`;)┌
地图API提供了经纬度转换成手机上的坐标,所以可以拿到地图上点对应的屏幕的位置,也就自然可以自定义一个View动态的绘制轨迹,当自定义View的动画结束之后,隐藏自定义View然后在地图上绘制轨迹。这就是我的整体思路,下面袖子撸起,上代码。
1、初始化变量、画笔、path
- 起点Paint
*/
private Paint mStartPaint;
/**
- 起点
*/
private Point mStartPoint;
/**
- 起点bitmap
*/
private Bitmap mStartBitmap;
/**
- 轨迹
*/
private Paint mLinePaint;
/**
- 小亮球
*/
private Paint mLightBallPaint;
/**
- 小两球的bitmap UI切图
*/
private Bitmap mLightBallBitmap;
/**
- 起点rect 如果为空时不绘制小亮球
*/
private Rect mStartRect;
/**
- 屏幕宽度
*/
private int mWidth;
/**
- 屏幕高度
*/
private int mHeight;
/**
- 轨迹path
*/
private Path mLinePath;
/**
- 保存每一次刷新界面轨迹的重点坐标
*/
private float[] mCurrentPosition = new float[2];
public SportTrailView(Context context) {
this(context, null);
}
public SportTrailView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SportTrailView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initPaint();
}
/**
- 初始化画笔,path
*/
private void initPaint() {
mLinePaint = new Paint();
mLinePaint.setColor(Color.parseColor("#ff00ff42"));
mLinePaint.setStyle(Paint.Style.STROKE);
mLinePaint.setStrokeWidth(10);
mLinePaint.setStrokeCap(Paint.Cap.ROUND);
mLinePaint.setAntiAlias(true);
mLightBallPaint = new Paint();
mLightBallPaint.setAntiAlias(true);
mLightBallPaint.setFilterBitmap(true);
mStartPaint = new Paint();
mStartPaint.setAntiAlias(true);
mStartPaint.setFilterBitmap(true);
mLinePath = new Path();
}
2、轨迹View绘制
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制轨迹
canvas.drawPath(mLinePath, mLinePaint);
//绘制引导亮球
if (mLightBallBitmap !=null && mStartRect !=null){
int width = mLightBallBitmap.getWidth();
int height = mLightBallBitmap.getHeight();
RectF rect = new RectF();
rect.left = mCurrentPosition[0] - width;
rect.right = mCurrentPosition[0] + width;
rect.top = mCurrentPosition[1] - height;
rect.bottom = mCurrentPosition[1] + height;
canvas.drawBitmap(mLightBallBitmap, null, rect, mLightBallPaint);
}
//绘制起点
if (mStartBitmap != null && mStartPoint != null) {
if (mStartRect == null) {
int width = mStartBitmap.getWidth() / 2;
int height = mStartBitmap.getHeight() / 2;
mStartRect = new Rect();
mStartRect.left = mStartPoint.x - width;
mStartRect.right = mStartPoint.x + width;
mStartRect.top = mStartPoint.y - height;
mStartRect.bottom = mStartPoint.y + height;
}
canvas.drawBitmap(mStartBitmap, null, mStartRect, mStartPaint);
}
}
3、设置数据
/**
-
绘制运动轨迹
-
@param mPositions 道格拉斯算法抽稀过后对应的点坐标
map.getWidth() / 2;
int height = mStartBitmap.getHeight() / 2;
mStartRect = new Rect();
mStartRect.left = mStartPoint.x - width;
mStartRect.right = mStartPoint.x + width;
mStartRect.top = mStartPoint.y - height;
mStartRect.bottom = mStartPoint.y + height;
}
canvas.drawBitmap(mStartBitmap, null, mStartRect, mStartPaint);
}
}
3、设置数据
/**
-
绘制运动轨迹
-
@param mPositions 道格拉斯算法抽稀过后对应的点坐标