日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

android double精度_Android车辆运动轨迹平滑移动(高仿滴滴打车)最佳实践

發布時間:2025/3/20 Android 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 android double精度_Android车辆运动轨迹平滑移动(高仿滴滴打车)最佳实践 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

點擊上方“Android技術雜貨鋪”,選擇“標星”

干貨文章,第一時間送達!

作者:門心叼龍 鏈接:https://www.jianshu.com/p/015bd44a56e6

關于車輛運動的相關文章一共寫過兩篇,一篇為Android車輛運動軌跡大數據采集最佳實踐,另外一篇是Android車輛運動軌跡數據采集服務保活的探索與發現。

一直打算寫一篇車輛運動軌跡平滑移動的文章,年后由于工作項目太忙也就沒時間寫,工作的事情忙完了,緊接著就是忙自己的另外兩個開源項目,一個是Android客戶端框架FlyTour、和另外一個SpringCloud微服務框架FlyCloud,上個周終于告一段落。

數據也能采集了,而且采集服務保活也做了,有效的避免了數據采集服務在后臺被系統殺死的可能,接著就需要把采集到的數據在移動設備上實時的展示出來,這兩天我也特意在網上搜索了一下,關于Android車輛運動軌跡平滑移動的文章,還真沒有,大部分都是提問的多,問怎么實現的?都是一些只言片語很零碎的一些回答,在實戰項目當中沒有太大的實用價值。

關于車輛運動,在我們在日常生活中見到最多的就是滴滴打車,想必這款app大家都使用過,當你在app的叫車頁面,輸入完畢你的目的地,點擊叫車,如果有司機接單了,你就清楚的看到車輛會平滑的移動到你所在的位置去接你。今天我就帶領大家一步步的實現一個車輛平滑移動的功能。

這個功能是基于高德地圖開發的,因此我特意去高德官網查閱了一下,高德的確提供了官方軌跡移動api,我們暫且不用官方API.先用自己的方法去實現一個最簡單的功能 我們先來一睹為快:

單次軌跡回放

已知有一段軌跡數據,點擊回放按鈕,小車沿著路線自動的往前運動,播放完畢也就結束了

public class MoveSingleThread extends Thread{
private List mLatLngList;private Marker mCarMarker;public MoveSingleThread(List latLngs, Marker marker) {super();
mLatLngList = latLngs;
mCarMarker = marker;
}@Overridepublic void run() {super.run();
}public void moveTrack(){// 第一個for循環用來計算走了多少部int step = 0;for (int i = 0; i < mLatLngList.size() - 1; i++) {
LatLng startPoint = mLatLngList.get(i);
LatLng endPoint = mLatLngList.get(i + 1);double slope = getSlope(startPoint, endPoint);// 是不是正向的標示(向上設為正向)boolean isReverse = (startPoint.latitude > endPoint.latitude);double xMoveDistance = isReverse ? getXMoveDistance(slope) : -1 * getXMoveDistance(slope);// 應該對經緯度同時處理for (double j = startPoint.latitude; !((j >= endPoint.latitude) ^ isReverse); j =
j - xMoveDistance) {
step++;
}
}// 通過距離,計算軌跡動畫時間間隔double mTimeInterval = 0;// 軌跡回放時間戳if (!TextUtils.isEmpty(mDistance)) {float totalDistance = Float.parseFloat(mDistance) * 1000;if (totalDistance <= 500) {
mTimeInterval = 1000.0 / step;
} else if (totalDistance > 500 && totalDistance <= 7500) {
mTimeInterval = 2.0 * totalDistance / step;
} else {
mTimeInterval = 15000.0 / step;
}
}// while (true) {for (int i = 0; i < mLatLngList.size() - 1; i++) {if (stopFlag) {
stopFlag = false;break;
}
mIsCarMoveing = true;
LatLng startPoint = mLatLngList.get(i);
LatLng endPoint = mLatLngList.get(i + 1);
mCarMarker.setPosition(startPoint);
mCarMarker.setRotateAngle((float) getAngle(startPoint, endPoint));double slope = getSlope(startPoint, endPoint);// 是不是正向的標示(向上設為正向)boolean isReverse = (startPoint.latitude > endPoint.latitude);double intercept = getInterception(slope, startPoint);double xMoveDistance = isReverse ? getXMoveDistance(slope) : -1 * getXMoveDistance(slope);// 應該對經緯度同時處理double mSleep = 0;for (double j = startPoint.latitude; !((j >= endPoint.latitude) ^ isReverse); j =
j - xMoveDistance) {
LatLng latLng = null;if (slope != Double.MAX_VALUE) {
latLng = new LatLng(j, (j - intercept) / slope);// latLng = new LatLng(j, k);
} else {
latLng = new LatLng(j, startPoint.longitude);
}
mCarMarker.setPosition(latLng);// 如果間隔時間小于1毫秒,則略過當前休眠,累加直到休眠時間到1毫秒:會損失精度if (mTimeInterval < 1) {
mSleep += mTimeInterval;if (mSleep >= 1) {
SystemClock.sleep((long) mSleep);
mSleep = 0;
}
} else
SystemClock.sleep((long) mTimeInterval);
}
}
}
}

實時軌跡數據排隊問題

如果要顯示實時軌跡怎么辦 ,上面的代碼就有問題了,Thread.start()方法調用后,就會立馬執行他的run方法,run方法執行完畢,線程也就結束了,也是說上面的代碼只能跑一次軌跡數據,如果每間隔五秒從后臺取一次軌跡數據,就需要一數據隊列來存儲這些數據,每跑完一次數據,就從數據隊列里面去取,如果有就取來接著跑,如果沒有就處于等待狀態。

我們創建異步消息處理線程,這一問題就可以迎刃而解,來數據了我們就可以通過handler把數據post給我們的子線程,Handler自帶數據隊列,它處于排隊狀態,如果有數據了就開始跑軌跡,如果沒有數據就處于等待狀態,直到有數據的到來,如果對異步消息處理線程不熟悉,請查看我的另外一篇文章《Android實戰開發Handler機制深度解析》https://menxindiaolong.blog.csdn.net/article/details/86560330

一個標準的異步消息處理線程應該怎么寫?

方法1:

class LooperThread extends Thread {
public Handler mHandler;

public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}

方法2:

// Step 1: 創建并啟動HandlerThread線程,內部包含Looper
HandlerThread handlerThread = new HandlerThread("gityuan.com");
handlerThread.start();

// Step 2: 創建Handler
Handler handler = new Handler(handlerThread.getLooper()) {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
// Step 3: 發送消息
handler.post(new Runnable() {
@Override
public void run() {
System.out.println("thread id="+Thread.currentThread().getId());
}
});

上面就是Android系統中異步消息處理線程的通用寫法

運動軌跡的暫停、繼續問題

由于運動軌跡是在子線程里面完成的,我們自然而然會想到線程的等待、喚醒,也就是wait、notify的問題了 因此我們在運動過程加上就如下代碼就可以了

if (pause) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

怎么讓他恢復運動呢?notify一下即可

public void reStartMove() {
synchronized (lock) {
pause = false;
lock.notify();
}
}

完整的代碼如下:

public class MoveCarCustomThread extends Thread {
public static final String TAG = MoveCarCustomThread.class.getSimpleName();
private Handler moveCarHandler;//發送數據的異步消息處理器
private Object lock = new Object();//線程鎖
private boolean moveing = false;//是否線程正在移動
private boolean pause = false;//暫停狀態,為true則暫停
private boolean stop = false;//停止狀態,為true則停止移動
private WeakReference mActivityWeakReference;//防止內存Activity導致的內容泄漏private MOVE_STATE currMoveState = MOVE_STATE.START_STATUS;public void setCurrMoveState(MOVE_STATE currMoveState) {this.currMoveState = currMoveState;
}public MOVE_STATE getCurrMoveState() {return currMoveState;
}public MoveCarCustomThread(MainActivity activity) {
mActivityWeakReference = new WeakReference<>(activity);
}//暫停移動public void pauseMove() {
pause = true;
}//設置暫停之后,再次移動調用它public void reStartMove() {synchronized (lock) {
pause = false;
lock.notify();
}
}public void stopMove() {
stop = true;if(moveCarHandler != null){
moveCarHandler.removeCallbacksAndMessages(null);
}if(mActivityWeakReference.get() != null){
mActivityWeakReference.get().mLatLngList.clear();
mActivityWeakReference.get().mMainHandler.removeCallbacksAndMessages(null);
}
}public Handler getMoveCarHandler() {return moveCarHandler;
}public boolean isMoveing() {return moveing;
}@Overridepublic void run() {super.run();//設置該線程為loop線程
Looper.prepare();
moveCarHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);//通過鎖保證發過來的數據同步入列synchronized (lock) {if (msg.obj != null && msg.obj instanceof List) {
List latLngList = (List) msg.obj;
moveCoarseTrack(latLngList);
}
}
}
};//啟動loop線程
Looper.loop();
}private void moveCoarseTrack(List latLngList) {if (latLngList == null || latLngList.size() == 0 || latLngList.size() == 1) {return;
}
Log.v(TAG, "moveCoarseTrack start.........................................................");long startTime = System.currentTimeMillis();
Log.v(TAG, "startTime:" + startTime);int step = TrackMoveUtil.getStep(latLngList);// 通過距離,計算軌跡動畫運動步數
Log.v(TAG, "move step:" + step);float distance = TrackMoveUtil.getDistance(latLngList);
Log.v(TAG, "move distance:" + distance);double mTimeInterval = TrackMoveUtil.getMoveTime(distance, step);// 通過距離,計算軌跡動畫時間間隔
mTimeInterval = 10;// 每走一步停止10毫秒
Log.v(TAG, "move mTimeInterval:" + mTimeInterval);
moveing = true;for (int i = 0; i < latLngList.size() - 1; i++) {// 暫停狀態,線程停止了if (pause) {
movePause();
}if (stop) {break;
}
moveing = true;
LatLng startLatLng = latLngList.get(i);
LatLng endLatLng = latLngList.get(i + 1);
MainActivity mainActivity = mActivityWeakReference.get();
moveCar(startLatLng, endLatLng, mainActivity);
moveLine(startLatLng, mainActivity);
moveCamera(startLatLng, mainActivity);double slope = TrackMoveUtil.getSlope(startLatLng, endLatLng);// 計算兩點間的斜率double intercept = TrackMoveUtil.getInterception(slope, startLatLng);// 根據點和斜率算取截距boolean isReverse = (startLatLng.latitude > endLatLng.latitude);// 是不是正向的標示(向上設為正向)double xMoveDistance = isReverse ? TrackMoveUtil.getXMoveDistance(slope) : -1 * TrackMoveUtil.getXMoveDistance(slope);// 應該對經緯度同時處理double sleep = 0;int flag = 0;for (double j = startLatLng.latitude; !((j >= endLatLng.latitude) ^ isReverse); j = j - xMoveDistance) {// 非暫停狀態地圖才進行跟隨移動if (pause) {
movePause();
}if (stop) {break;
}
moveing = true;
flag++;if (slope != Double.MAX_VALUE) {
startLatLng = new LatLng(j, (j - intercept) / slope);
} else {
startLatLng = new LatLng(j, startLatLng.longitude);
}
moveCar(startLatLng, mainActivity);
moveLine(startLatLng, mainActivity);if (flag % 100 == 0) {
moveCamera(startLatLng, mainActivity);
}// 如果間隔時間小于1毫秒,則略過當前休眠,累加直到休眠時間到1毫秒:會損失精度if (mTimeInterval < 1) {
sleep += mTimeInterval;if (sleep >= 1) {
Log.v(TAG, "sleep:" + sleep);
SystemClock.sleep((long) sleep);
sleep = 0;
}
} else {
SystemClock.sleep((long) mTimeInterval);
}
}
}long endTime = System.currentTimeMillis();
moveing = false;
Log.v(TAG, "endTime:" + endTime);
Log.v(TAG, "run mTimeInterval:" + (endTime - startTime));
Log.v(TAG, "moveCoarseTrack end.........................................................");
}private void moveLine(LatLng startLatLng, MainActivity mainActivity) {
mainActivity.mLatLngList.add(startLatLng);// 向軌跡集合增加軌跡點
mainActivity.mMovePolyline.setPoints(mainActivity.mLatLngList);// 軌跡畫線開始
}private void moveCar(LatLng startLatLng, LatLng endLatLng, MainActivity mainActivity) {
moveCar(startLatLng,mainActivity);if (mainActivity.mCarMarker != null) {
mainActivity.mCarMarker.setRotateAngle((float) TrackMoveUtil.getAngle(startLatLng, endLatLng));// 設置小車車頭的方向
}
}private void moveCar(LatLng startLatLng,MainActivity mainActivity) {if (mainActivity.mCarMarker != null) {
mainActivity.mCarMarker.setPosition(startLatLng);// 小車移動
}
}private void movePause() {try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}private void moveCamera(LatLng startLatLng, MainActivity mainActivity) {
Message message = Message.obtain();
message.what = MainActivity.EventType.MapMove;
message.obj = startLatLng;
mainActivity.mMainHandler.sendMessage(message);
}
}

核心算法工具類

*

軌跡平滑所需要的工具方法

Date: 2016-10-27 Created by mxdl
*/
public class TrackMoveUtil {
private static double DISTANCE = 0.0001;

/**
* 根據兩點算斜率
*/
public static double getSlope(LatLng fromPoint, LatLng toPoint) {
if (fromPoint == null || toPoint == null) {
return 0;
}
if (toPoint.longitude == fromPoint.longitude) {
return Double.MAX_VALUE;
}
double slope =
((toPoint.latitude - fromPoint.latitude) / (toPoint.longitude - fromPoint.longitude));
return slope;

}

/**
* 根據兩點算取圖標轉的角度
*/
public static double getAngle(LatLng fromPoint, LatLng toPoint) {
if (fromPoint == null || toPoint == null) {
return 0;
}
double slope = getSlope(fromPoint, toPoint);
if (slope == Double.MAX_VALUE) {
if (toPoint.latitude > fromPoint.latitude) {
return 0;
} else {
return 180;
}
}
float deltAngle = 0;
if ((toPoint.latitude - fromPoint.latitude) * slope < 0) {
deltAngle = 180;
}
double radio = Math.atan(slope);
double angle = 180 * (radio / Math.PI) + deltAngle - 90;
return angle;
}

/**
* 根據點和斜率算取截距
*/
public static double getInterception(double slope, LatLng point) {
if (point == null) {
return 0;
}
return point.latitude - slope * point.longitude;
}

/**
* 計算x方向每次移動的距離
*/
public static double getXMoveDistance(double slope) {
if (slope == Double.MAX_VALUE) {
return DISTANCE;
}
return Math.abs((DISTANCE * slope) / Math.sqrt(1 + slope * slope));
}

/**
* 根據軌跡線段計算小車走了多少步
*
* @param latLngList
* @return
*/
public static int getStep(List latLngList) {
int step = 0;
if (latLngList != null && latLngList.size() > 1) {
for (int i = 0; i < latLngList.size() - 1; i++) {
try {
LatLng startPoint = latLngList.get(i);
LatLng endPoint = latLngList.get(i + 1);
double slope = getSlope(startPoint, endPoint);
// 是不是正向的標示(向上設為正向)
boolean isReverse = (startPoint.latitude > endPoint.latitude);
double xMoveDistance = isReverse ? getXMoveDistance(slope) : -1 * getXMoveDistance(slope);
// 應該對經緯度同時處理
for (double j = startPoint.latitude; !((j >= endPoint.latitude) ^ isReverse); j =
j - xMoveDistance) {
step++;
}
} catch (Exception e) {
e.printStackTrace();
}
}

}
return step;
}

/**
* 根據總距離和步數計算運動時間
*
* @param distance
* @param step
* @return
*/
public static double getMoveTime(float distance, int step) {
double timeInterval = 0;
if (distance > 0) {
float totalDistance = distance * 1000;
if (totalDistance <= 500) {
timeInterval = 1000.0 / step;
} else if (totalDistance > 500 && totalDistance <= 7500) {
timeInterval = 2.0 * totalDistance / step;
} else {
timeInterval = 15000.0 / step;
}
}
return timeInterval;
}

/**
* 根據軌跡點集合計算總距離
*
* @param latLngList
* @return
*/
public static float getDistance(List latLngList) {
float distance = 0;
if (latLngList != null && latLngList.size() > 1) {
for (int i = 0; i < latLngList.size() - 1; i++) {
try {
distance += AMapUtils.calculateLineDistance(latLngList.get(i), latLngList.get(i + 1));
} catch (Exception e) {
e.printStackTrace();
}
}
}
return distance;
}

// latitude - 地點的緯度,在-90 與90 之間的double 型數值。
// longitude - 地點的經度,在-180 與180 之間的double 型數值。
/**
* 根據一個經緯度字符串求一個經緯度集合a|b|c|d;
*
* @param latlonStr
* @return
*/
public static ListgetListLatLng(String latlonStr) {
if (!TextUtils.isEmpty(latlonStr)) {
String[] trackArr = latlonStr.split("\\|");
if (trackArr != null && trackArr.length > 0) {
List latLngList = new ArrayList();for (int i = 0; i < trackArr.length - 1; i = i + 2) {try {
String lat = trackArr[i + 1];
String lng = trackArr[i];// Logger.v(TAG,"trackArr index:" + i);// Logger.v(TAG,"trackArr lat:" + lat);// Logger.v(TAG,"trackArr lng:" + lng);if (!TextUtils.isEmpty(lat) && !TextUtils.isEmpty(lng)) {
Double dLat = Double.valueOf(lat);
Double dLng = Double.valueOf(lng);if (dLat >= -90 && dLat <= 90 && dLng >= -180 && dLng <= 180
&& !(dLat == 0 && dLng == 0)) {
LatLng latLng = new LatLng(dLat, dLng);
latLngList.add(latLng);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}return latLngList;
}
}return null;
}
}

高德API實現的實時運動軌跡代碼:

public class MoveCarSmoothThread implements IMoveCar {
public static final String TAG = MoveCarSmoothThread.class.getSimpleName();
private MovingPointOverlay mMovingPointOverlay;
private WeakReference mActivityWeakReference;private boolean isfirst = true;private MOVE_STATE currMoveState = MOVE_STATE.START_STATUS;public void setCurrMoveState(MOVE_STATE currMoveState) {this.currMoveState = currMoveState;
}public MOVE_STATE getCurrMoveState() {return currMoveState;
}public MoveCarSmoothThread(MainActivity activity) {
mActivityWeakReference = new WeakReference<>(activity);
}@Overridepublic void startMove(List latLngs) {if (latLngs == null || latLngs.size() == 0) {return;
}
Log.v("MYTAG","startMove start:"+Thread.currentThread().getName());
Log.v(TAG, "moveCoarseTrack start.........................................................");long startTime = System.currentTimeMillis();
Log.v(TAG, "startTime:" + startTime);final MainActivity mainActivity = mActivityWeakReference.get();if (mMovingPointOverlay == null) {
mMovingPointOverlay = new MovingPointOverlay(mainActivity.mAMap, mainActivity.mCarMarker);
mMovingPointOverlay.setTotalDuration(5);
mMovingPointOverlay.setMoveListener(new MovingPointOverlay.MoveListener() {@Overridepublic void move(double v) {if(isfirst){
isfirst = false;
Log.v("MYTAG","MoveCarSmoolthThread move start:"+Thread.currentThread().getName());
}
LatLng position = mMovingPointOverlay.getPosition();
mainActivity.mLatLngList.add(position);// 向軌跡集合增加軌跡點
mainActivity.mMovePolyline.setPoints(mainActivity.mLatLngList);// 軌跡畫線開始
Message message = Message.obtain();
message.what = MainActivity.EventType.MapMove;
message.obj = position;
message.arg1 = (int)v;
mainActivity.mMainHandler.sendMessage(message);
}
});
}
mMovingPointOverlay.setPoints(latLngs);
mMovingPointOverlay.startSmoothMove();long endTime = System.currentTimeMillis();
Log.v(TAG, "endTime:" + endTime);
Log.v(TAG, "moveCoarseTrack end.........................................................");
}@Overridepublic void reStartMove() {if(mMovingPointOverlay != null){
mMovingPointOverlay.startSmoothMove();
}
}@Overridepublic void pauseMove(){if(mMovingPointOverlay != null){
mMovingPointOverlay.stopMove();
}
}@Overridepublic void stopMove(){if(mMovingPointOverlay != null){
mMovingPointOverlay.destroy();
mMovingPointOverlay = null;
}if(mActivityWeakReference.get() != null){
mActivityWeakReference.get().mLatLngList.clear();
}
}
}

最后我把整個項目的的完整代碼傳到GitHub上了:https://github.com/geduo83/android-amap-movecar

---end---

推薦閱讀:

七夕,不能送個對象,還不能送一本書嗎?

一篇文章徹底明白Android文件存儲

Android仿網易云音樂鯨云音效動效

每一個“在看”,我都當成真的喜歡

總結

以上是生活随笔為你收集整理的android double精度_Android车辆运动轨迹平滑移动(高仿滴滴打车)最佳实践的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。