高德+React實現H5版高德打車-司機接駕-行程中 汽車行駛功能

先看效果圖:api

1626768140129823.gif

學習高德經緯度實時定位-精確地址 👈 戳這裏數組

首先說一下大致思路:markdown

  1. pathArr爲空時表示沒有規劃路線,根據起點和終點位置開始規劃路線;
  2. 接口5秒輪詢不斷拿到司機的實時位置,而後計算司機的位置是否在這條路線上;
  3. 在這條路線則計算小車的行駛路線,開始行駛等操做;不在這條路線則從新規劃路線。
/** * @description: 輪詢函數 * @param {*} slnglat 起點(司機位置) * @param {*} elnglat 終點(接駕中:起始地做爲終點 / 行程中:目的地做爲終點) */
    drawDistance(slnglat, elnglat) {
        let startLngLat = new AMap.LngLat(slnglat.lng, slnglat.lat)
        let endLngLat = new AMap.LngLat(elnglat.lng, elnglat.lat)
        if (this.pathArr.length === 0) { '表示沒有路線,開始繪製'
            this.drawPath(startLngLat, endLngLat) 
            return
        }
        let isPointInRing = AMap.GeometryUtil.isPointOnLine(startLngLat, this.pathArr, 80)
        if (!isPointInRing) { '小車位置不在這條軌跡上'
            this.drawPath(startLngLat, endLngLat)
        } else {
            this.setPassByPath() // 每次執行前 將上一次行駛的路線添加到總行駛路線
            this.setMoveLine(startLngLat) // 設置小車5秒行駛的路線
            this.moveAlongFn() // 小車開始行駛
        }
    }
複製代碼

跟着我一塊兒來實現吧!!👊app

下面用到的高德數學計算庫:AMap.GeometryUtilide

  1. distance(p1:LngLat, p2:LngLat) 計算兩個經緯度點之間的實際距離。單位:米
  2. closestOnLine(p:LngLat, line:[LngLat]) 計算line上距離P最近的點
  3. isPointOnLine(p:LngLat, line:[LngLat],tolerance:Number) 判斷P是否在line上,tolerance爲偏差範圍
  4. distanceOfLine(ring:[LngLat]) 計算一個經緯度路徑的實際長度。單位:米

1. 路徑規劃

首先咱們須要實現的是路徑規劃,經過起點和終點位置規劃一條小車須要行駛的路線,且路線分爲最快捷、最經濟、最短距離、考慮實時路況;這裏你們能夠選擇一個,暫時沒有具體對比這幾種策略哪一個更好,我選的最快捷:AMap.DrivingPolicy.LEAST_TIMEsvg

/** * @description: 規劃路線 * @param {*} startLngLat 起點 * @param {*} endLngLat 終點 */
    drawPath(startLngLat, endLngLat) {
        this.clearCarState() // 繪製以前要清除全部覆蓋物並重置數據(省略詳細)
        this.map.plugin('AMap.Driving', () => { // 加載插件-插件爲地圖功能的擴展
            let driving = new AMap.Driving({ // 構造路線導航類
                hideMarkers: true,   // 隱藏路徑起始點圖標
                autoFitView: true,  // 自動調整地圖視野
                policy: AMap.DrivingPolicy.LEAST_TIME, // 駕車路線規劃策略
                extensions: 'all',  // 詳細信息
            })
            driving.search(startLngLat, endLngLat, (status, result) => {
                console.log(result, '規劃的路線信息')
                if (status === 'complete') {
                    this.setLineData(result) // * 路線數據處理
                    this.createCover(endLngLat) // * 根據路線開始繪製
                }
            })
        })
    }
複製代碼

對路線信息進行數據處理,獲得一維對象數組函數

setLineData(result) {
        let paths = []
        let routes = result.routes && result.routes.length > 0 ? result.routes[0] : null
        if (routes) {
            this.titDistance = routes.distance // 路徑總長度
            this.titTimes = routes.time  // 路徑總時長
            let steps = routes.steps || []
            for (let i = 0; i < steps.length; i++) {
                for (let j = 0; j < steps[i].path.length; j++) {
                    paths.push(steps[i].path[j])
                }
            }
        }
        this.pathArr = paths
    }
複製代碼

2. 路徑繪製

根據路線pathArr集合開始繪製:總路線Polyline、 單次行駛路線Polyline、 總行駛路線Polyline;時間距離Marker、汽車Marker;Marker和Polyline如何建立,具體能夠查看高德官網API,這裏就不作詳細介紹了;post

createCover(endLngLat) {
        let paths = this.pathArr
        this.carTitMarker = new AMap.Marker({ '距離和時間marker'
            map: this.map,
            position: paths[0],  // 初始位置
            content: '',
            zIndex: 102,
        })
        this.setCarTime() '爲carTitMarker添加內容'
        // this.getAngle() 下面會作詳細介紹-------
        let angle = this.getAngle(paths[0], paths[1]) - 90  // Marker的初始角度0度=90度
        this.carMarker = new AMap.Marker({ '小車marker'
            map: this.map,
            position: paths[0],
            content: `<img class='caricon' src='/images/newtaxi/qiche.svg'/>`,
            offset: new AMap.Pixel(-15, -10),
            autoRotation: true,
            angle: angle, '汽車角度'
            zIndex: 102
        })
        this.currentLine = new AMap.Polyline({ '總路線繪製'
            map: this.map,
            path: paths,
            strokeColor: "#45C184",
            lineJoin: 'round',
            lineCap: 'round',
            strokeOpacity: 1, // 線條透明度
            strokeWeight: 6, //線條寬度
            showDir: true
        });
        let polyConfig = {
            map: this.map,
            strokeColor: "#fff", // 行駛過的路線爲白色
            lineJoin: 'round',
            lineCap: 'round',
            strokeOpacity: 1,
            strokeWeight: 7,
            showDir: false
        }
        this.passByLine = new AMap.Polyline(polyConfig) '小車總行駛後的路線polyline'
        let drivingLine = new AMap.Polyline(polyConfig) '小車5秒行駛的路線polyline'
    }
複製代碼

根據兩個經緯度計算角度:學習

汽車默認繪製的時候,須要設置汽車行駛的角度;傳入兩個經緯度,經過 Math.atan2()、 Math.PI計算出汽車的角度。這裏須要注意的是高德0度=-90度。測試

getAngle(startPoint, endPoint) {
        if (!(startPoint && endPoint)) {
            return 0;
        }
        let dRotateAngle = Math.atan2(
            Math.abs(startPoint.lng - endPoint.lng),
            Math.abs(startPoint.lat - endPoint.lat)
        );
        if (endPoint.lng >= startPoint.lng) {
            if (endPoint.lat >= startPoint.lat) {
            } else {
                dRotateAngle = Math.PI - dRotateAngle;
            }
        } else {
            if (endPoint.lat >= startPoint.lat) {
                dRotateAngle = 2 * Math.PI - dRotateAngle;
            } else {
                dRotateAngle = Math.PI + dRotateAngle;
            }
        }
        dRotateAngle = (dRotateAngle * 180) / Math.PI;
        return dRotateAngle;
    }
複製代碼

監聽小車Marker移動事件、和移動結束後;

移動中監聽:

    1. 行駛後的路線是白色的,在小車移動過程當中設置小車行駛過路線Polyline的path;
    1. 爲了用戶友好體驗,避免出現一些問題,設置司機位置距離個人位置小於150米,軌跡隱藏;距離個人位置小於100米,距離時間提示隱藏。

移動結束後:

  • 1.移動結束後,將移動結束的位置滑動到地圖中心點。
this.carMarker.on('moving', (e) => {
        drivingLine.setPath(e.passedPath)
        this.movingFn(e.passedPath, endLngLat)
    })
    this.carMarker.on('movealong', (e) => {
        this.setCarTime() // 行駛完更新時間
        if (this.carIndex > 0 && this.pathArr.length > this.carIndex) {
            let carLoc = this.pathArr[this.carIndex]
            this.setCenter(carLoc)
        }
    })
    movingFn(path, endLngLat) {
        let distance = AMap.GeometryUtil.distance(path[path.length - 1], endLngLat)
        if (distance < 150) {  // 司機位置距離個人位置小於150米,軌跡隱藏
            this.currentLine && this.currentLine.hide()
        }
        if (distance < 100) { // 距離個人位置小於100米,距離時間提示隱藏
            this.carTitMarker && this.carTitMarker.hide()
        }
    } 
複製代碼

3. 行駛路線

  1. 每次輪詢獲得的司機位置‘startLngLat’與總路線‘pathArr’進行計算拿到距離路線最近的一個點;
  2. 拿到這個點之後,經過循環計算當前這個點與路線全部點的距離,獲得一個距離集合;
  3. 經過遍歷,找到距離最短的點的下標;(也就是司機位置距離這條線哪一個點最近,把最近的點的下標找到)
/** * @description: 設置汽車行駛的路線 * @param {*} startLngLat 司機位置 */  
    setMoveLine(startLngLat) {
        let line_near = AMap.GeometryUtil.closestOnLine(startLngLat, this.pathArr, 50); // 司機位置直線距離軌跡最近的點
        let distanceList = [] //距離集合
        for (let i = this.pathArr.length; i--;) {
            let distances = AMap.GeometryUtil.distance(line_near, this.pathArr[i]) // 計算距離
            distanceList[i] = distances
        }
        let index = this.arrayMin(distanceList) // 距離最短的點的下標
        //.....
    }
複製代碼

處理距離集合,循環遍歷拿到距離最短的下標值

arrayMin(arr) {
        let len = arr.length
        let min = Infinity
        let minIndex = 0
        while (len--) {
            if (arr[len] < min) {
                min = arr[len];
                minIndex = len
            }
        }
        return minIndex
    }
複製代碼
  1. 距離最短的點的下標,也就是汽車位置的下標;好比:小車默認下標爲0,當下次輪詢獲得司機位置與距離下標爲5的位置很近,那咱們就截取0到5的數據,這就是小車須要行駛的路線‘carRunPath’。

  2. 小車從下標0的位置行駛到下標爲5的位置之後,將小車位置保存起來,必定要用全局變量去保存。

  3. 距離最近的下標大於小車當前位置,說明小車向前行駛;相反,小車不動。

  4. 計算行駛路線的長度‘moveRice’並保存,爲汽車行駛計算速度提供。

if (index > this.carIndex) {  // * 司機位置小車當前位置前面,小車行駛
            this.carRunPath = this.pathArr.slice(this.carIndex, index + 1)
            this.moveRice = Math.round(AMap.GeometryUtil.distanceOfLine(this.carRunPath)) '計算行駛路線長度'
            // * 行駛速度進行勻速處理---經過路徑長度去控制,路徑太短則等待下一次
            let overlen = distanceList.length - 5 // 結尾的路程根據小車真實移動去改變位置
            if (index < overlen && this.moveRice < 20) {  // 路徑小於20米,小車不動--解決距離太短汽車行駛不平滑問題
                this.carRunPath = []
                return
            }
            this.carIndex = index
        } else {  // * 司機位置沒動或者在小車位置後面,小車不動(避免小車行駛後重復行駛)
            console.log('位置沒動');
            this.carRunPath = []
        }
複製代碼

4. 汽車行駛

當設置好小車須要行駛的路線‘carRunPath’時,同時也知道了這段路線的長度‘moveRice’,由於咱們設置的5秒輪詢,全部行駛的路程須要在5秒內行駛完,否則會出現小車閃跳現象;

  • 計算速度:【長度/5秒】計算出每秒行駛的長度,【長度*3.6】而後轉換成 公里/小時
moveAlongFn() {
        if (this.carRunPath.length > 0) {
            let speed = (this.moveRice / 5) * 3.6 // 計算5秒內行駛的速度
            this.carMarker.moveAlong(this.carRunPath, speed) '小車行駛'
            this.carTitMarker.moveAlong(this.carRunPath, speed) '距離時間跟隨小車一塊兒行駛'
        }
    }
複製代碼

5. 總行駛路線

最後須要作的是 在小車每次行駛以前,將小車當次行駛的路線記錄到總行駛路線中,否則每次會將前面的路線會從新繪製。總行駛路線的Polyline setPath()

setPassByPath() {
        if (this.carRunPath.length > 0) { // 小車位置可能沒動,就不會執行如下操做
            this.carPassByPath = this.carPassByPath.concat(this.carRunPath)
            this.passByLine.setPath(this.carPassByPath)
        }
    }
複製代碼

6. 結束

不少仍是須要依賴高德的api實現,服務端返回司機經緯度偏差不是很大的話,應該是沒有問題的,這裏還須要後續不斷的測試;沒有服務端支持輪詢接口,能夠寫一個定時器來模擬輪詢,而後在地圖中選擇一些扎點模擬司機位置來測試行駛。還有一些計算這裏沒有詳細介紹,感興趣的你們能夠手動試一試。

製做不易,順便點個贊吧~😄 👍

相關文章
相關標籤/搜索