-
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[currentIndex]);
//将原来的线段以当前点为中心拆成两段,分别进行递归处理
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、设置数据
《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》无偿开源 徽信搜索公众号【编程进阶路】
/**
-
绘制运动轨迹
-
@param mPositions 道格拉斯算法抽稀过后对应的点坐标
-
@param startPointResId 起点图片的资源id
-
@param lightBall 小亮球的资源id
-
@param listener 轨迹绘制完成的监听
*/
public void drawSportLine(final List mPositions, @DrawableRes int startPointResId,@DrawableRes int lightBall, final OnTrailChangeListener listener) {
if (mPositions.size() <= 1) {
listener.onFinish();
return;
}
//用于
Path path = new Path();
for (int i = 0; i < mPositions.size(); i++) {
if (i == 0) {
path.moveTo(mPositions.get(i).x, mPositions.get(i).y);
} else {
path.lineTo(mPositions.get(i).x, mPositions.get(i).y);
}
}
final PathMeasure pathMeasure = new PathMeasure(path, false);
//轨迹的长度
final float length = pathMeasure.getLength();
if (length < ViewUtil.dip2Px(getContext(), 16)) {
listener.onFinish();
return;
}
//动态图中展示的亮色小球(UI切图)
mLightBallBitmap = BitmapFactory.decodeResource(getResources(), lightBall);
//起点
mStartPoint = new Point(mPositions.get(0).x, mPositions.get(0).y);
mStartBitmap = BitmapFactory.decodeResource(getResources(), startPointResId);
ValueAnimator animator = ValueAnimator.ofFloat(0, length);
animator.setDuration(3000);
animator.setInterpolator(new AccelerateDecelerateInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (Float) animation.getAnimatedValue();
// 获取当前点坐标封装到mCurrentPosition
pathMeasure.getPosTan(value, mCurrentPosition, null);
if (value == 0) {
//如果当前的运动轨迹长度为0,设置path的起点
mLinePath.moveTo(mPositions.get(0).x, mPositions.get(0).y);
}
//pathMeasure.getSegment()方法用于保存当前path路径,
//下次绘制时从上一次终点位置处绘制,不会从开始的位置开始绘制。
pathMeasure.getSegment(0, value, mLinePath, true);
invalidate();
//如果当前的长度等于pathMeasure测量的长度,则表示绘制完毕,
if (value == length && listener != null) {
listener.onFinish();
}
}
});
animator.start();
}