天天看點

android double精度_Android車輛運動軌迹平滑移動(高仿滴滴打車)最佳實踐軌迹平滑所需要的工具方法

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

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

android double精度_Android車輛運動軌迹平滑移動(高仿滴滴打車)最佳實踐軌迹平滑所需要的工具方法

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

關于車輛運動的相關文章一共寫過兩篇,一篇為

Android車輛運動軌迹大資料采集最佳實踐

,另外一篇是

Android車輛運動軌迹資料采集服務保活的探索與發現

一直打算寫一篇車輛運動軌迹平滑移動的文章,年後由于工作項目太忙也就沒時間寫,工作的事情忙完了,緊接着就是忙自己的另外兩個開源項目,一個是Android用戶端架構

FlyTour

、和另外一個SpringCloud微服務架構

FlyCloud

,上個周終于告一段落。

資料也能采集了,而且采集服務保活也做了,有效的避免了資料采集服務在背景被系統殺死的可能,接着就需要把采集到的資料在移動裝置上實時的展示出來,這兩天我也特意在網上搜尋了一下,關于Android車輛運動軌迹平滑移動的文章,還真沒有,大部分都是提問的多,問怎麼實作的?都是一些隻言片語很零碎的一些回答,在實戰項目當中沒有太大的實用價值。

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

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

android double精度_Android車輛運動軌迹平滑移動(高仿滴滴打車)最佳實踐軌迹平滑所需要的工具方法

單次軌迹回放

已知有一段軌迹資料,點選回放按鈕,小車沿着路線自動的往前運動,播放完畢也就結束了

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:

方法2:

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

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

由于運動軌迹是在子線程裡面完成的,我們自然而然會想到線程的等待、喚醒,也就是

wait

notify

的問題了 是以我們在運動過程加上就如下代碼就可以了

怎麼讓他恢複運動呢?notify一下即可

android double精度_Android車輛運動軌迹平滑移動(高仿滴滴打車)最佳實踐軌迹平滑所需要的工具方法

完整的代碼如下:

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; } public static double getXMoveDistance(double slope) { if (slope == Double.MAX_VALUE) { return DISTANCE; } return Math.abs((DISTANCE * slope) / Math.sqrt(1 + slope * slope)); } 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; } 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; } 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 型數值。 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車輛運動軌迹平滑移動(高仿滴滴打車)最佳實踐軌迹平滑所需要的工具方法

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

android double精度_Android車輛運動軌迹平滑移動(高仿滴滴打車)最佳實踐軌迹平滑所需要的工具方法

繼續閱讀