mapbox / mapbox-navigation-ios

Turn-by-turn navigation logic and UI in Swift on iOS
https://docs.mapbox.com/ios/navigation/
Other
860 stars 310 forks source link

[错误]:MabBox does not automatically plan a new path when navigating a wrong route #4207

Open xiaoxin3838438 opened 1 year ago

xiaoxin3838438 commented 1 year ago

Mapbox Navigation SDK version

2.5.1

Steps to reproduce

Hello, I now use mapBox navigation SDK2.5.1 met weird bugs, starting for the first time in my APP, and began to navigation for the first time, if I go the wrong route, navigation will help me to switch to the new navigation route automatically, if I quit the current navigation, again start navigation, takes the wrong course, won't help me planning path automatically, No matter how many times I navigate, it won't help me automatically plan a new path, unless I kill the APP and go to the navigation again for the first time.

Here is my MapBox integration code

  1. Gets the navigation address path: `func calculateRoute(_ options: NavigationRouteOptions) { guard let startPoint = self.startPoint, let endPoint = self.endPoint else{ return }

    showNavRouteViewFont()
    
    _ = self.customRoutingProvider.calculateRoutes(options: options, completionHandler: { [weak self] (_, result) in
        guard let self = self else { return }
    
        switch result {
        case .failure(let error): // 路径获取失败
            DPrint(error.localizedDescription)
            self.view.tgc_makeToastTop(message: VHLLocalizedString("搜索不到线路"))
        case .success(let response): // 路径获取成功
            guard let routes = response.routes else {return}
    
            // 获取线路
            if routes.count > 0 {
    
                self.showNavRouteView()
                self.routeResponse = response
                self.routeOptions = options
                self.routes = routes
                self.mapView.show(routes)
                self.mapView.showWaypoints(on: (self.currentRoute) ?? routes[0])
    
                self.addViewAnnotation(at: startPoint, mapBoxNaviPointType: .start)
                self.addViewAnnotation(at: endPoint, mapBoxNaviPointType: .end)
                self.addPassPointAnnotion(coordinates: self.passPoints)
    
                self.routeModels = MapBoxToolEngine.routeItemConvert(routes: routes, selectRoute: (self.currentRoute) ?? routes[0])
    
                // 缩放到合适的大小
                var arr:[CLLocationCoordinate2D] = []
                arr.append(startPoint)
                if self.passPoints.count > 0 {
                    arr.append(contentsOf: self.passPoints)
                }
                arr.append(endPoint)
    
                let option = self.mapView.mapView.mapboxMap.camera(for: arr, padding: UIEdgeInsets(top: 200, left: 50, bottom: 200, right: 50), bearing: 0, pitch: 0)
                self.mapView.mapView.mapboxMap.setCamera(to: option)
            }else {
                // 没有线路
                self.hiddenNavRouteView()
            }
        }
    })`

customRoutingProvider implementation code inside: ` import UIKit import MapboxCoreNavigation import MapboxDirections import MapboxNavigation

class CustomProvider: RoutingProvider { // This can encapsulate any route building engine we need. For simplicity let's use MapboxRoutingProvider. // 这可以封装我们需要的任何路由构建引擎。为了简单起见,让我们使用' MapboxRoutingProvider '。 let routeCalculator = MapboxRoutingProvider()

// We can also modify the options used to calculate a route.
// 我们还可以修改用于计算路由的选项。
func applyOptionsModification(_ options: DirectionsOptions) {
    options.attributeOptions = [.congestionLevel, .speed, .maximumSpeedLimit, .expectedTravelTime]
}

// Here any manipulations on the reponse data can take place
// 在这里可以对响应数据进行任何操作
func applyMapMatchingModifications(_ response: MapMatchingResponse) {
    response.matches?.forEach { match in
        match.legs.forEach { leg in
            leg.incidents = fetchExternalIncidents(for: leg)
        }
    }
}

// Let's say we have an external source of incidents data, we want to apply to the route.
// 假设我们有一个事件数据的外部来源,我们想要应用到路线上。
func fetchExternalIncidents(for leg: RouteLeg) -> [Incident] {
    return [Incident(identifier: "\(leg.name) incident",
                     type: .otherNews,
                     description: "Custom Incident",
                     creationDate: Date(),
                     startDate: Date(),
                     endDate: Date().addingTimeInterval(60),
                     impact: nil,
                     subtype: nil,
                     subtypeDescription: nil,
                     alertCodes: [],
                     lanesBlocked: nil,
                     shapeIndexRange: 0..<1)]
}

func calculateRoutes(options: RouteOptions, completionHandler: @escaping Directions.RouteCompletionHandler) -> NavigationProviderRequest? {
    applyOptionsModification(options)

    // Using `MapboxRoutingProvider` also illustrates cases when we need to modify just a part of the route, or dynamically edit `RouteOptions` for each reroute.
    // 使用“MapboxRoutingProvider”还说明了当我们需要修改路由的一部分,或为每个重路由动态编辑“RouteOptions”的情况。
    return routeCalculator.calculateRoutes(options: options,
                                           completionHandler: completionHandler)
}

func calculateRoutes(options: MatchOptions, completionHandler: @escaping Directions.MatchCompletionHandler) -> NavigationProviderRequest? {
    applyOptionsModification(options)

    return routeCalculator.calculateRoutes(options: options,
                                           completionHandler: { [weak self] (session, result) in
        switch result {
        case .failure(let error):
            print(error.localizedDescription)
            completionHandler(session, result)
        case .success(let response):
            guard let strongSelf = self else {
                return
            }
            strongSelf.applyMapMatchingModifications(response)

            completionHandler(session, .success(response))
        }
    })
}

// Let's make our custom routing provider prevent route refreshes.
// 让我们的自定义路由提供程序防止路由刷新。
func refreshRoute(indexedRouteResponse: IndexedRouteResponse, fromLegAtIndex: UInt32, completionHandler: @escaping Directions.RouteCompletionHandler) -> NavigationProviderRequest? {

    var options: DirectionsOptions!
    switch indexedRouteResponse.routeResponse.options {
    case.match(let matchOptions):
        options = matchOptions
    case .route(let routeOptions):
        options = routeOptions
    }

    completionHandler((options, NavigationSettings.shared.directions.credentials),
                        .failure(.unableToRoute))
    return nil
}

} `

  1. Start to navigate:

` func startNav(simulationMode: SimulationMode) { guard let route = self.currentRoute, let routeOptions = self.routeOptions, let routeResponse = self.routeResponse else { return } var routeIndex = 0 for (idx,temp) in routeModels.enumerated() { if temp.selected == true { routeIndex = idx } }

    NavigationSettings.shared.voiceMuted =  false
    NavigationSettings.shared.voiceVolume = TRMapPreferenceManager.share.getVoiceBroadcast() ? 1 : 0

    _ = self.customRoutingProvider.calculateRoutes(options: routeOptions, completionHandler: { [weak self] (_, result) in
        guard let self = self else { return }
        switch result {
        case .failure(let error):
            DPrint(error.localizedDescription)
            self.view.tgc_makeErrorToast(message: "路径开启失败")
        case .success(let response):

            let navigationService = MapboxNavigationService(routeResponse: response,
                                                            routeIndex: routeIndex,
                                                            routeOptions: routeOptions,
                                                            customRoutingProvider: self.customRoutingProvider,
                                                            credentials: NavigationSettings.shared.directions.credentials,
                                                            simulating: simulationMode)

          // 模拟导航3倍速度
            navigationService.simulationSpeedMultiplier = 3
            let navigationOptions = NavigationOptions(styles: [CustomDayStyle()], navigationService: navigationService, topBanner: TRCustomNavViewController(), bottomBanner: TRCustomBottomNavViewController())

            let navigationViewController = NavigationViewController(for: response,
                                                                        routeIndex: routeIndex,
                                                                        routeOptions: routeOptions,
                                                                        navigationOptions: navigationOptions)

            self.startNaviBlock!(self.startPointName, self.endPointName, navigationViewController)
            if self.selectedUnit == 2 {
                NavigationSettings.shared.distanceUnit = .mile
            } else {
                self.selectedUnit == 1 ? (NavigationSettings.shared.distanceUnit = .kilometer) : (NavigationSettings.shared.distanceUnit = Locale.current.measuresDistancesInMetricUnits ? .kilometer : .mile)
            }
            self.backAction()
        }
    })
}

`

  1. The wrong path in the navigation does not automatically plan the route, and I print out that the proxy value has a value

We mainly to achieve in this class (StartNavigationController) `
static func createStartNavigationController(mapNavController: NavigationViewController) -> StartNavigationController { let nv = StartNavigationController() nv.k_navigationViewController = mapNavController return nv }

override func viewDidLoad() { super.viewDidLoad() setupSubViews() }

func setupSubViews() { view.addSubview(naviControlView) if let nav = self.k_navigationViewController { self.naviControlView.addSubview(nav.view) nav.delegate = self nav.routeLineTracksTraversal = true // 关闭意见反馈 nav.showsReportFeedback = false // 到达最终目的地时,显示“End of route Feedback”界面 nav.showsEndOfRouteFeedback = false // 在后台时,切换UILocalNotification的发送 nav.sendsNotifications = false // 移动地图的回调 let navigationViewportDataSource = NavigationViewportDataSource(nav.navigationMapView!.mapView, viewportDataSourceType: .active) navigationViewportDataSource.options.followingCameraOptions.zoomUpdatesAllowed = true navigationViewportDataSource.options.followingCameraOptions.bearingSmoothing.maximumBearingSmoothingAngle = 30.0 navigationViewportDataSource.followingMobileCamera.zoom = 14.0 navigationViewportDataSource.options.followingCameraOptions.paddingUpdatesAllowed = true nav.navigationMapView?.navigationCamera.viewportDataSource = navigationViewportDataSource

        let cameraStateTransition = TRCameraStateTransition(nav.navigationMapView!.mapView)
        nav.navigationMapView?.navigationCamera.cameraStateTransition = cameraStateTransition

        nav.navigationMapView?.mapView.gestures.delegate = self
        ResumeButton.appearance().alpha = 0.0
        if TRMapPreferenceManager.share.getVoiceBroadcast(){
            nav.voiceController.speechSynthesizer.volume = 1
        }else {
            nav.voiceController.speechSynthesizer.volume = 0
        }

        // 展示最高限速
        nav.showsSpeedLimits = true

        // 隐藏按钮
        FloatingButton.appearance().isHidden = true

        // 当前位置路口信息
        WayNameLabel.appearance().normalTextColor = UIColor.white
        WayNameView.appearance().backgroundColor = rgba(57, 118, 255, 1)

        // 到达当前时间
        ArrivalTimeLabel.appearance().textColor = colorTitle

        // 当前位置路口信息
        WayNameLabel.appearance().normalTextColor = UIColor.white
        WayNameView.appearance().backgroundColor = rgba(57, 118, 255, 1)
        WayNameView.appearance().alpha = 0
    }

    self.naviControlView.addSubview(self.maskButton)
    self.naviControlView.addSubview(self.moreButton)
    self.naviControlView.addSubview(self.quitButton)
    self.naviControlView.addSubview(self.overViewButton)

    self.naviControlView.addSubview(self.VoiceView)

    self.view.addSubview(self.maskAlertView)
    self.maskAlertView.addSubview(self.popAlertView)

    self.view.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi / 2))
}

`

The extension implementation

`extension StartNavigationController: NavigationViewControllerDelegate {

// 导航结束时调用
func navigationViewControllerDidDismiss(_ navigationViewController: NavigationViewController, byCanceling canceled: Bool) {
    delegate?.startNaviViewCloseButtonClicked()

    naviControlView.removeFromSuperview()
    self.volumeView?.removeFromSuperview()
    self.volumeView = nil
    self.k_navigationViewController?.delegate = nil
    self.k_navigationViewController?.navigationMapView?.mapView.removeFromSuperview()
    self.k_navigationViewController?.navigationOptions = nil
    self.k_navigationViewController?.view.removeFromSuperview()
    self.k_navigationViewController = nil
}

// 当用户移动更新路由进程模型时调用
func navigationViewController(_ navigationViewController: NavigationViewController, didUpdate progress: RouteProgress, with location: CLLocation, rawLocation: CLLocation) {

    self.view.tgc_makeToastTop(message: "当前代理:\(navigationViewController.delegate)")

    ///TODO: 以下代码为解决MapBoxSDK出现的模拟导航规划路线出现漂移现象,如若SDK解决可去掉此段代码

    let distanceRemaining = progress.currentLegProgress.currentStepProgress.distanceRemaining
    let distanceFormatter = DistanceFormatter()
    distanceFormatter.locale = Locale.init(identifier: VHLI18nManager.manager.language.isZH() ? "zh-Hans" : "en_US")
    // 下个点的距离
    let distance:CLLocationDistance = distanceRemaining > 5 ? distanceRemaining : 0
    DPrint("errorCountDistance:\(distance)")
    if distance == 0 {
        // 缩放到合适的大小
        errorCount += 1
        if errorCount < 3 {

        navigationViewController.navigationMapView?.navigationCamera.cameraStateTransition.update(to: CameraOptions(center: CLLocationCoordinate2D(latitude: rawLocation.coordinate.latitude, longitude: rawLocation.coordinate.longitude)), state: .transitionToOverview)
        }

    } else {
        errorCount = 0
    }
    ///TODO: 以下代码为解决MapBoxSDK出现的模拟导航规划路线出现漂移现象,如若SDK解决可去掉此段代码

    guard (progress.currentLegProgress.currentStep.instructionsDisplayedAlongStep?.first?.primaryInstruction.maneuverType!.rawValue) != nil else {
        return
    }

    guard (progress.currentLegProgress.currentStep.instructionsDisplayedAlongStep?.first?.primaryInstruction.maneuverDirection!.rawValue) != nil else {
        return
    }

    if !ScreenRecordTools.shared.isNavigating {
        ScreenRecordTools.shared.isNavigating = true
    }
}

// 当用户到达某个路线段的目的地航路点时调用。
// 这个方法在导航视图控制器到达路径点时被调用。你可以实现这个方法来防止导航视图控制器自动移动到下一个分支。例如,你可以在到达时显示一个插页,并通过返回' false '暂停导航,然后在用户取消该插页时继续该路径。如果这个方法没有实现,导航视图控制器在到达一个路径点时自动前进到下一段。
func navigationViewController(_ navigationViewController: NavigationViewController, didArriveAt waypoint: Waypoint) -> Bool {
    let isFinalLeg = navigationViewController.navigationService.routeProgress.isFinalLeg
    if isFinalLeg {
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) {
            let navigationInfo = NavigationInfo()
            navigationInfo.iconType = 15
            navigationInfo.nextRoadName = NSLocalizedString("END_OF_ROUTE_ARRIVED", bundle: .mapboxNavigation, value:"You have arrived", comment:"Title used for arrival")
            navigationInfo.segmentRemainDistance = 0
            if T7LogicManager.shared.selectDevice_isWIFIConnect() {
                WiFiSendDataTool.shared.sendNaviInfoToCar(info: navigationInfo)
            }

            let selectedUnit = UserDefaults.standard.integer(forKey: UD_BK_Set_Unit)
            let remainDis = TRMapSearchViewModel.normalizedRemainDistance(remainDistance: navigationInfo.routeRemainDistance, selectunit: (selectedUnit == 2 || Locale.current.measuresDistancesInMetricUnits == false) ? 2 : 0)

            let routeRemainTime = TRMapSearchViewModel.normalizedRemainTime(remainTime: navigationInfo.routeRemainTime)
            let remainSegmentDis = TRMapSearchViewModel.normalizedRemainDistance(remainDistance: navigationInfo.segmentRemainDistance, selectunit: (selectedUnit == 2 || Locale.current.measuresDistancesInMetricUnits == false) ? 2 : 0)
            let arriveTime = TRMapSearchViewModel.caculateArriveDate(timeInterval: TimeInterval(navigationInfo.routeRemainTime))

            let startNavigationInfoEx = NavigationInfo()
            startNavigationInfoEx.iconType = navigationInfo.iconType
            startNavigationInfoEx.next_road = navigationInfo.nextRoadName

            if remainDis != nil {
                startNavigationInfoEx.cur_retain_distance = remainDis!.0
                startNavigationInfoEx.path_cur_unittype = remainDis!.1 == "公里" ? 1 : 0
            }

            if remainSegmentDis != nil {
                startNavigationInfoEx.path_retain_distance = remainSegmentDis!.0
                startNavigationInfoEx.cur_unittype = remainDis!.1 == "公里" ? 1 : 0
            }

            startNavigationInfoEx.remain_time = arriveTime.replacingOccurrences(of: "预计", with: "").replacingOccurrences(of: "到达", with: "")

            if routeRemainTime != nil {
                startNavigationInfoEx.cur_retain_time = routeRemainTime!
            }

            if let CV = T7LogicManager.shared.selectDevice?.CV,CV.count > 0{
                NavigationInfoTools.shared.sendNaviInfoToCarIconType(info: startNavigationInfoEx, turnImage: nil)
            }else{
                NavigationInfoTools.shared.sendNaviInfoToCar(info: navigationInfo, turnImage: nil)
            }

            self.naviControlView.removeFromSuperview()
            self.volumeView?.removeFromSuperview()
            self.volumeView = nil
            self.k_navigationViewController?.delegate = nil
            self.k_navigationViewController?.view.removeFromSuperview()
            self.k_navigationViewController = nil

            // 已经到达目的地
            self.delegate?.startNaviViewCloseButtonClicked()
        }
    }
    return true
}

// 当用户接近一个路径点时调用。
// 当用户接近一个路径点时,这个消息会在每次进度更新时发送一次。你可以用它来提示UI,进行网络预加载等等。
func navigationViewController(_ navigationViewController: NavigationViewController, willArriveAt waypoint: Waypoint, after remainingTimeInterval: TimeInterval, distance: CLLocationDistance) {

}

/**
 返回导航视图控制器是否应该被允许计算一个新的路由。"msg_source":2,"speed":-1,"max_speed":0,"msg_type":5,"msg_id":2

 如果实现了,这个方法会在导航视图控制器检测到用户离开预定的路径时被调用。实现此方法以有条件地防止重新路由。如果这个方法返回' true ', ' navigationViewController(_:willRerouteFrom:) '之后会立即被调用。
 */
func navigationViewController(_ navigationViewController: NavigationViewController, shouldRerouteFrom location: CLLocation) -> Bool{
          return true
}

/**
 在导航视图控制器计算新路由之前立即调用。

 这个方法在' navigationViewController(_: shouldroutefrom:) '被调用后被调用,同时调用' Notification.Name '。routeControllerWillReroute '通知被发布,并且在' navigationViewController(_: didroutealong:) '之前被调用。
 */
func navigationViewController(_ navigationViewController: NavigationViewController, willRerouteFrom location: CLLocation?){
    let alert = UIAlertView.init(title: "willRerouteFrom", message: "在导航视图控制器计算新路由之前立即调用", delegate: self, cancelButtonTitle: "确定")
    alert.show()
}

/**
 在导航视图控制器收到一个新路由后立即调用。

 这个方法在' navigationViewController(_:willRerouteFrom:) '之后被调用,并与' Notification.Name '同时调用。routeccontrollerdidreroute '通知被发布。
 */
func navigationViewController(_ navigationViewController: NavigationViewController, didRerouteAlong route: Route){
    let alert = UIAlertView.init(title: "didRerouteAlong", message: "在导航视图控制器收到一个新路由后立即调用", delegate: self, cancelButtonTitle: "确定")
    alert.show()
}

/**
 当导航视图控制器接收到新路由失败时调用。

 这个方法在' navigationViewController(_:willRerouteFrom:) '之后被调用,并与' Notification.Name '同时调用。routeControllerDidFailToReroute '通知被发布。
 */
func navigationViewController(_ navigationViewController: NavigationViewController, didFailToRerouteWith error: Error){
    self.view.tgc_makeErrorToast(message: "didFailToRerouteWith:从新规划路由失败")
}

/**
 在导航视图控制器刷新路由后立即调用。

 此方法与“Notification.Name”同时调用。routeccontrollerdidrefreshroute的通知正在被发布。
 */
func navigationViewController(_ navigationViewController: NavigationViewController, didRefresh routeProgress: RouteProgress){}

}`

Major in (TRCustomNavViewController) map navigation:

`class TRCustomNavViewController: ContainerViewController {

@IBOutlet weak var instructionsBannerView: UIView!

@IBOutlet weak var maneuverView: ManeuverView!

@IBOutlet weak var primaryLabel: UILabel!

@IBOutlet weak var distanceLabel: UILabel!

@IBOutlet weak var infoView: UIView!

@IBOutlet weak var laneView: LanesView!

@IBOutlet weak var junctionView: JunctionView!

@IBOutlet weak var infoViewBW: NSLayoutConstraint!

@IBOutlet weak var infoViewBtoTop: NSLayoutConstraint!

@IBOutlet weak var arriveDistence: UILabel!

@IBOutlet weak var arriveTime: UILabel!

@IBOutlet weak var margeToTop: NSLayoutConstraint!

@IBOutlet weak var infoViewW: NSLayoutConstraint!

@IBOutlet weak var laneViewW: NSLayoutConstraint!

@IBOutlet weak var laneViewH: NSLayoutConstraint!

@IBOutlet weak var laneViewLeft: NSLayoutConstraint!
@IBOutlet weak var infoViewToLeft: NSLayoutConstraint!

@IBOutlet weak var infoViewBToLeft: NSLayoutConstraint!

@IBOutlet weak var maneuverViewH: NSLayoutConstraint!

@IBOutlet weak var iconToLeft: NSLayoutConstraint!

@IBOutlet weak var junctionViewLeft: NSLayoutConstraint!
@IBOutlet weak var juctionConstWidth: NSLayoutConstraint!
@IBOutlet weak var juctionConstHeight: NSLayoutConstraint!

override func viewDidLoad() {
    super.viewDidLoad()

    instructionsBannerView.layer.cornerRadius = CGFloat(cornerValue)
    instructionsBannerView.layer.opacity = 1.0
    instructionsBannerView.backgroundColor = rgba(30, 37, 49, 1)

    infoView.layer.cornerRadius = CGFloat(cornerValue)
    infoView.layer.opacity = 1.0
    infoView.backgroundColor = UIColor.white

    infoView.layer.shadowColor = colorTitle?.withAlphaComponent(0.2).cgColor
    infoView.layer.shadowOpacity = 0.8
    infoView.layer.shadowRadius = 8
    infoView.layer.shadowOffset = CGSize(width: 1, height: 1)

    maneuverView.primaryColor = UIColor.white
    primaryLabel.textColor = UIColor.white
    laneView.layer.cornerRadius = CGFloat(cornerValue)
    junctionView.isHidden = true

    if let userDevice = T7LogicManager.shared.selectDevice {
        if userDevice.deviceType == .circlScreen { // 圆屏

            margeToTop.constant = KScreenW * 0.45

            infoViewW.constant = KScreenW * 0.42

            laneViewW.constant = KScreenW * 0.35

            infoViewBW.constant = KScreenW - KScreenW/3.6

            infoViewToLeft.constant = KScreenW/7.2 + KScreenH/4.1
            laneViewLeft.constant = KScreenW/7.2 + KScreenH/4.1
            infoViewBToLeft.constant = KScreenW/7.2 + KScreenH/4.1
            junctionViewLeft.constant = KScreenW/7.2 + KScreenH/4.1
            juctionConstWidth.constant =  90
            juctionConstHeight.constant = 90
            maneuverViewH.constant = 50
            iconToLeft.constant = 10
            laneViewH.constant = 55

        } else {
            margeToTop.constant = ScaleWidth(108)

            infoViewW.constant = KScreenW * 0.70
            laneViewW.constant = KScreenW * 0.70

            infoViewBW.constant = KScreenW * 0.70

            infoViewToLeft.constant = 60 + TopSafe
            infoViewBToLeft.constant = 60 + TopSafe
            laneViewLeft.constant = 60 + TopSafe
            junctionViewLeft.constant = 60 + TopSafe
            juctionConstWidth.constant =  ScaleWidth(260)
            juctionConstHeight.constant = isiPhoneX() ? ScaleWidth(180) : ScaleWidth(200)

            maneuverViewH.constant = ScaleWidth(75)
            laneViewH.constant = ScaleWidth(55)
            iconToLeft.constant = 20
        }
    } else {
        margeToTop.constant = ScaleWidth(108)

        infoViewW.constant = KScreenW * 0.65
        laneViewW.constant = KScreenW * 0.65

        infoViewBW.constant = KScreenW * 0.65

        infoViewToLeft.constant = 60 + TopSafe
        infoViewBToLeft.constant = 60 + TopSafe
        laneViewLeft.constant = 60 + TopSafe
        junctionViewLeft.constant = 60 + TopSafe

        maneuverViewH.constant = ScaleWidth(75)
        laneViewH.constant = ScaleWidth(55)
        juctionConstWidth.constant =  ScaleWidth(260)
        juctionConstHeight.constant = isiPhoneX() ? ScaleWidth(180) : ScaleWidth(200)
    }
}

// MARK: - Device rotation

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)

}

open override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    super.traitCollectionDidChange(previousTraitCollection)

}

// MARK: - NavigationServiceDelegate implementation
public func navigationService(_ service: NavigationService, didUpdate progress: RouteProgress, with location: CLLocation, rawLocation: CLLocation) {

    let distanceFormatter = DistanceFormatter()
    distanceFormatter.locale = Locale.init(identifier: VHLI18nManager.manager.language.isZH() ? "zh-Hans" : "en_US")

    // 下个点的距离
    let distanceRemaining = progress.currentLegProgress.currentStepProgress.distanceRemaining
    let distance:CLLocationDistance = distanceRemaining > 5 ? distanceRemaining : 0

    let emphasizedDistanceString = NSMutableAttributedString(attributedString: distanceFormatter.attributedString(for: distance)!)
    let nextCrossroads = distanceFormatter.string(from: distance)

    let wholeRange = NSRange(location: 0, length: emphasizedDistanceString.length)
    var hasQuantity = false
    emphasizedDistanceString.enumerateAttribute(.quantity, in: wholeRange, options: .longestEffectiveRangeNotRequired) { (value, range, stop) in
        let font: UIFont
        if let _ = emphasizedDistanceString.attribute(.quantity, at: range.location, effectiveRange: nil) {
            if let userDevice = T7LogicManager.shared.selectDevice {
                if userDevice.deviceType == .circlScreen { // 圆屏
                    font = UIFont.boldSystemFont(ofSize: 28)
                }else{
                    font = UIFont.boldSystemFont(ofSize: 42)
                }
            }else{
                font = UIFont.boldSystemFont(ofSize: 42)
            }
            hasQuantity = true
        } else {
            if let userDevice = T7LogicManager.shared.selectDevice {
                if userDevice.deviceType == .circlScreen { // 圆屏
                    font = UIFont.systemFont(ofSize: 14)
                }else{
                    font = UIFont.systemFont(ofSize: 17)
                }
            }else{
                font = UIFont.systemFont(ofSize: 17)
            }
        }
        emphasizedDistanceString.addAttributes([.font: font], range: range)
    }

    if !hasQuantity {
        if let userDevice = T7LogicManager.shared.selectDevice {
            if userDevice.deviceType == .circlScreen { // 圆屏
                emphasizedDistanceString.addAttributes([ .font: UIFont.boldSystemFont(ofSize: 28)], range: wholeRange)
            }else{
                emphasizedDistanceString.addAttributes([ .font: UIFont.boldSystemFont(ofSize: 42)], range: wholeRange)
            }
        }else{
            emphasizedDistanceString.addAttributes([ .font: UIFont.boldSystemFont(ofSize: 42)], range: wholeRange)
        }
    }

    emphasizedDistanceString.mutableString.replaceOccurrences(of: " ", with: "\u{200A}", options: [], range: wholeRange)
    distanceLabel.attributedText = emphasizedDistanceString

    guard let arrivalDate = NSCalendar.current.date(byAdding: .second, value: Int(progress.durationRemaining), to: Date()) else { return }

    // 到达时间
    let dateFormatter = DateFormatter()
    dateFormatter.timeStyle = .short
    dateFormatter.locale = Locale.init(identifier: VHLI18nManager.manager.language.isZH() ? "zh-Hans" : "en_US")
    arriveTime.text = "\(VHLLocalizedString("到达时间")) \(dateFormatter.string(from: arrivalDate))"

    // 剩余距离
    var distanceStr = VHLLocalizedString("剩余")
    let remainingDis = distanceFormatter.string(from: progress.distanceRemaining)
    let str = remainingDis.replacingOccurrences(of: "公里", with: "")
    if progress.durationRemaining < 5 {
        distanceStr = ""
    } else {
        distanceStr = distanceStr + " " + distanceFormatter.string(from: progress.distanceRemaining) + ", "
    }

    // 剩余时间
    let dateComponentsFormatter = DateComponentsFormatter()
    dateComponentsFormatter.calendar?.locale = Locale.init(identifier: VHLI18nManager.manager.language.isZH() ? "zh-Hans" : "en_US")
    dateComponentsFormatter.allowedUnits = [.hour, .minute]
    dateComponentsFormatter.unitsStyle = .abbreviated
    dateComponentsFormatter.unitsStyle = progress.durationRemaining < 3600 ? .short : .abbreviated

    if let hardcodedTime = dateComponentsFormatter.string(from: 61), progress.durationRemaining < 60 {
        distanceStr = distanceStr + String.localizedStringWithFormat(NSLocalizedString("LESS_THAN", bundle: .mapboxNavigation, value: "<%@", comment: "Format string for a short distance or time less than a minimum threshold; 1 = duration remaining"), hardcodedTime)
    } else {
        distanceStr = distanceStr + dateComponentsFormatter.string(from: progress.durationRemaining)!
    }

    arriveDistence.text = distanceStr

    let navigationInfo = NavigationInfo()
    navigationInfo.iconType =  MapBoxToolEngine.convertIconType(maneuverType: ManeuverType(rawValue: (progress.currentLegProgress.currentStep.instructionsDisplayedAlongStep?.first?.primaryInstruction.maneuverType!.rawValue)!)!, maneuverDirection: ManeuverDirection(rawValue: (progress.currentLegProgress.currentStep.instructionsDisplayedAlongStep?.first?.primaryInstruction.maneuverDirection)!.rawValue)!, drivingSide: DrivingSide(rawValue: (progress.currentLegProgress.currentStep.instructionsDisplayedAlongStep?.first?.drivingSide)!.rawValue)!)
    navigationInfo.nextRoadName =  (progress.currentLegProgress.currentStep.instructionsDisplayedAlongStep?.first?.primaryInstruction.text)!
    navigationInfo.routeRemainDistance = Int(progress.distanceRemaining)
    navigationInfo.segmentRemainDistance = Int((progress.currentLegProgress.currentStepProgress.distanceRemaining))
    navigationInfo.routeRemainTime = Int(progress.durationRemaining)

    let routeRemainTime = TRMapSearchViewModel.normalizedRemainTime(remainTime: navigationInfo.routeRemainTime)
    let arriveTime = TRMapSearchViewModel.caculateArriveDate(timeInterval: TimeInterval(navigationInfo.routeRemainTime))

    let startNavigationInfoEx = NavigationInfo()
    startNavigationInfoEx.iconType = navigationInfo.iconType
    startNavigationInfoEx.next_road = navigationInfo.nextRoadName
    startNavigationInfoEx.remain_time = arriveTime.replacingOccurrences(of: "预计", with: "").replacingOccurrences(of: "到达", with: "")

    let selectedUnit = UserDefaults.standard.integer(forKey: UD_BK_Set_Unit)

    if !remainingDis.isEmpty {
        var remainingDisStr = ""
        if selectedUnit == 2 {
            if VHLI18nManager.manager.language.isZH() {
                remainingDisStr = remainingDis.replacingOccurrences(of: "英里", with: "").replacingOccurrences(of: "英尺", with: "")
                startNavigationInfoEx.path_cur_unittype = remainingDis.contains("英里") ? 1 : 0
            } else {
                remainingDisStr = remainingDis.replacingOccurrences(of: "mi", with: "").replacingOccurrences(of: "ft", with: "")
                startNavigationInfoEx.path_cur_unittype = remainingDis.contains("mi") ? 1 : 0
            }
            startNavigationInfoEx.path_retain_distance = remainingDisStr
        } else {
            if VHLI18nManager.manager.language.isZH() {
                remainingDisStr = remainingDis.replacingOccurrences(of: "公里", with: "").replacingOccurrences(of: "米", with: "")
                startNavigationInfoEx.path_cur_unittype = remainingDis.contains("公里") ? 1 : 0
            } else {
                remainingDisStr = remainingDis.replacingOccurrences(of: "km", with: "").replacingOccurrences(of: "m", with: "")
                startNavigationInfoEx.path_cur_unittype = remainingDis.contains("km") ? 1 : 0
            }
            startNavigationInfoEx.path_retain_distance = remainingDisStr
        }
    }
    DPrint("终点距离:\(startNavigationInfoEx.cur_retain_distance)-终点单位:\(startNavigationInfoEx.cur_unittype)")

    if !nextCrossroads.isEmpty {
        var nextCrossroadsStr = nextCrossroads
        if selectedUnit == 2 {
            if VHLI18nManager.manager.language.isZH() {
                nextCrossroadsStr = nextCrossroads.replacingOccurrences(of: "英里", with: "").replacingOccurrences(of: "英尺", with: "")
                startNavigationInfoEx.cur_unittype = nextCrossroads.contains("英里") ? 1 : 0
            } else {
                nextCrossroadsStr = nextCrossroadsStr.replacingOccurrences(of: "mi", with: "").replacingOccurrences(of: "ft", with: "")
                startNavigationInfoEx.cur_unittype = nextCrossroads.contains("mi") ? 1 : 0
            }
        } else {
            if VHLI18nManager.manager.language.isZH() {
                nextCrossroadsStr = nextCrossroads.replacingOccurrences(of: "公里", with: "").replacingOccurrences(of: "米", with: "")
                startNavigationInfoEx.cur_unittype = nextCrossroads.contains("公里") ? 1 : 0
            } else {
                nextCrossroadsStr = nextCrossroadsStr.replacingOccurrences(of: "km", with: "").replacingOccurrences(of: "m", with: "")
                startNavigationInfoEx.cur_unittype = nextCrossroads.contains("km") ? 1 : 0
            }
        }
        startNavigationInfoEx.cur_retain_distance = nextCrossroadsStr
    }
    DPrint("下个点距离:\(startNavigationInfoEx.cur_retain_distance)-下个点单位:\(startNavigationInfoEx.cur_unittype)")

    if routeRemainTime != nil {
        startNavigationInfoEx.cur_retain_time = routeRemainTime!
    }

    if T7LogicManager.shared.selectDevice_isWIFIConnect() {
        WiFiSendDataTool.shared.sendNaviInfoToCar(info: navigationInfo)
    }
    if let CV = T7LogicManager.shared.selectDevice?.CV,CV.count > 0{
        NavigationInfoTools.shared.sendNaviInfoToCarIconType(info: startNavigationInfoEx, turnImage: nil)
    }else{
        NavigationInfoTools.shared.sendNaviInfoToCar(info: navigationInfo, turnImage: nil)
    }
}

public func navigationService(_ service: NavigationService, didPassVisualInstructionPoint instruction: VisualInstructionBanner, routeProgress: RouteProgress) {

    // 方向
    maneuverView.visualInstruction = instruction.primaryInstruction
    maneuverView.drivingSide = instruction.drivingSide
    primaryLabel.text = instruction.primaryInstruction.text

    ///路口放大图
    junctionView.update(for: instruction, service: service)

    // 车道信息
    let tertiaryInstructionTemp = instruction.tertiaryInstruction
    laneView.update(for: instruction, animated: true, duration: 10, completion: nil)
    //终端设备显示车道信息
    if tertiaryInstructionTemp != nil {
        let lanes = MapBoxToolEngine.convertLaneMsgType(instruction: instruction)
        if let CV = T7LogicManager.shared.selectDevice?.CV,CV.count > 0{
            NavigationInfoTools.shared.sendAbordLaneInfoIconType(lanearray: lanes)
            GCDTimer.dispatch_timer(timeInterval: 1, repeatCount: 10) {[weak self] timer, count in
                guard let self = self else { return }
                if count == 0 {
                    self.laneView.hide()
                    NavigationInfoTools.shared.sendHideAbordLaneInfo()
                }
            }
        }
    }

    let roadBigImage = junctionView.image
    if roadBigImage != nil {
        //显示路口放大图并且发送至终端设备
        junctionView.isHidden = false
        junctionView.show()
        instructionsBannerView.isHidden = true
        NavigationInfoTools.shared.sendCrossImage(image: roadBigImage)
        GCDTimer.dispatch_timer(timeInterval: 1, repeatCount: 5) {[weak self] timer, count in
            guard let self = self else { return }
            if count == 0 {
                self.junctionView.hide()
                self.instructionsBannerView.isHidden = false
                NavigationInfoTools.shared.sendCrossImage(image: nil)
            }
        }
    } else {
        junctionView.hide()
        instructionsBannerView.isHidden = false
        junctionView.isHidden = true
    }
}

public func navigationService(_ service: NavigationService, willRerouteFrom location: CLLocation) {
    laneView.hide()
    NavigationInfoTools.shared.hideLaneInfo()
}

public func navigationService(_ service: NavigationService, didRerouteAlong route: Route, at location: CLLocation?, proactive: Bool) {

}

} ` These are all the steps we took to implement the navigation code.

Below is our test effect:

https://thinkerride.oss-cn-zhangjiakou.aliyuncs.com/10001/20221021/normal%20video.mp4

Temporary solution Now that we have a temporary solution, we don't know how this solution will affect the performance of the map, and what unpredictability will be the problem, so let's explain the logic of the solution:

The main changes in (StartNavigationController)

  1. We turned off the official navigation SDK's auto-correct path feature

func navigationViewController(_ navigationViewController: NavigationViewController, shouldRerouteFrom location: CLLocation) -> Bool { return false }

  1. In the navigation process, we detect whether the user is in the current route. If not, we help the user to plan a new navigation route

` // 当用户移动更新路由进程模型时调用 func navigationViewController(_ navigationViewController: NavigationViewController, didUpdate progress: RouteProgress, with location: CLLocation, rawLocation: CLLocation) {

    ///TODO:关闭系统自动规划路径, 从新自动规划路径,防止SDK走错路不会规划路径
    ///TODO:Disable the system automatic path planning function to automatically plan a new path to prevent the SDK from failing to plan a path
    if !(navigationViewController.navigationService.router.userIsOnRoute(location)) {
        let routeOptions = NavigationRouteOptions(waypoints: [Waypoint(location: location), navigationViewController.routeOptions!.waypoints.last!])

        Directions.shared.calculate(routeOptions) { [weak self] (_, result) in
            switch result {
            case .failure(let error):
                DPrint(error.localizedDescription)
            case .success(let response):
                guard let self = self else { return }
                navigationViewController.navigationService.router.updateRoute(with: .init(routeResponse: response, routeIndex: 0), routeOptions: routeOptions, completion: { isFinsh in
                    if isFinsh == false {
                        ///从新规划路径失败提示用户,结束导航
                        let alert = UIAlertView.init(title: "路径切换失败", message: "您已走错路线,切换路线失败,请退出当前页面,从新开始导航", delegate: self, cancelButtonTitle: "确定")
                        alert.show()
                    }
                })
            }
        }
    }

    ///TODO: 以下代码为解决MapBoxSDK出现的模拟导航规划路线出现漂移现象,如若SDK解决可去掉此段代码
    let distanceRemaining = progress.currentLegProgress.currentStepProgress.distanceRemaining
    let distanceFormatter = DistanceFormatter()
    distanceFormatter.locale = Locale.init(identifier: VHLI18nManager.manager.language.isZH() ? "zh-Hans" : "en_US")
    // 下个点的距离
    let distance:CLLocationDistance = distanceRemaining > 5 ? distanceRemaining : 0
    DPrint("errorCountDistance:\(distance)")
    if distance == 0 {
        // 缩放到合适的大小
        errorCount += 1
        if errorCount < 3 {
        navigationViewController.navigationMapView?.navigationCamera.cameraStateTransition.update(to: CameraOptions(center: CLLocationCoordinate2D(latitude: rawLocation.coordinate.latitude, longitude: rawLocation.coordinate.longitude)), state: .transitionToOverview)
        }

    } else {
        errorCount = 0
    }
    ///TODO: 以上代码为解决MapBoxSDK出现的模拟导航规划路线出现漂移现象,如若SDK解决可去掉此段代码

    guard (progress.currentLegProgress.currentStep.instructionsDisplayedAlongStep?.first?.primaryInstruction.maneuverType!.rawValue) != nil else {
        return
    }

    guard (progress.currentLegProgress.currentStep.instructionsDisplayedAlongStep?.first?.primaryInstruction.maneuverDirection!.rawValue) != nil else {
        return
    }

    if !ScreenRecordTools.shared.isNavigating {
        ScreenRecordTools.shared.isNavigating = true
    }
}`

I hope the official answer, thank you

Expected behavior

Expect the navigation to be normal, and automatically plan the latest route when you take a wrong route

Actual behavior

During the navigation, a wrong route is taken and a new navigation route cannot be planned

Is this a one-time issue or a repeatable issue?

repeatable

jill-cardamon commented 1 year ago

Hi @xiaoxin3838438! Thank you for submitting this report. To make sure I understand the issue you're seeing: when you start navigating and the user goes off-route, the Nav SDK automatically reroutes them; however, when you cancel that session, start a new navigation session, and the user goes off-route, the Nav SDK is not automatically rerouting them? Is this correct?

Is there no reroute at all? Or there is a reroute, but the route line and camera jumped to the route origin instead of the current location?

xiaoxin3838438 commented 1 year ago

Hi @xiaoxin3838438! Thank you for submitting this report. To make sure I understand the issue you're seeing: when you start navigating and the user goes off-route, the Nav SDK automatically reroutes them; however, when you cancel that session, start a new navigation session, and the user goes off-route, the Nav SDK is not automatically rerouting them? Is this correct?

Yes, when I finish the previous navigation and start again, the route is not automatically planned, and the camera view changes position as I move, but no new route is planned

Is there no reroute at all? Or there is a reroute, but the route line and camera jumped to the route origin instead of the current location?

No, the camera position will change with my position, and it will be updated to the new position, but no new route will be planned

jill-cardamon commented 1 year ago

@xiaoxin3838438 Could you attach a video to this ticket? Your link is not working unfortunately.

xiaoxin3838438 commented 1 year ago

@xiaoxin3838438 Could you attach a video to this ticket? Your link is not working unfortunately.

I was just able to upload the video so you can check it out Video Address:

https://user-images.githubusercontent.com/11389246/199937943-476ec5ae-ac48-4369-9e1d-03f6c68513b6.mp4

jill-cardamon commented 1 year ago

Thanks @xiaoxin3838438! I'll take a look.

jill-cardamon commented 1 year ago

Hi @xiaoxin3838438! Is navigationService(_:, didFailToRerouteWith:) ever called? I see the alerts that say that it did reroute, but no route pops up. With your startNavigationController conforming to the NavigationViewControllerDelegate, it looks like you are overriding the methods related to rerouting.

xiaoxin3838438 commented 1 year ago

Hi @xiaoxin3838438! Is navigationService(_:, didFailToRerouteWith:) ever called? I see the alerts that say that it did reroute, but no route pops up. With your startNavigationController conforming to the NavigationViewControllerDelegate, it looks like you are overriding the methods related to rerouting.

Yes, because my previous code would not automatically plan the path from new, so I rewrote the agent method and turned off the system default automatic route planning. If I judged the user's current position in the map callback agent and the route was not planned, I planned a new route from new to avoid this problem. After I modified it, Is there a potential impact on mapbox's other navigation features? It would be best if you can solve it later. If not, I will temporarily use this method to avoid this problem

Udumft commented 1 year ago

hi, @xiaoxin3838438! I have few more questions about your setup:

  1. It seems like you are attempting to navigate a bicycle ride, is that correct? If so, do you use cycling profile when requesting a route?
  2. I see a weird user puck position in between the actual roads and it is jumping between coordinates. Do you start your navigation session away from the road? Provided video also does not show wether puck is actually moving or is drifting (presumably due to GPS signal fluctuation), so could you please describe how user did move in reality on this video and what new route you expected to appear?
  3. Why do you use a Custom Routing Provider? Your code snippet looks like straight snippet from the Examples repo, which is just a demo. Do you actually do some modifications for requesting/calculating the routes? If so - what exactly? If no - I suggest you use plain MapboxRoutingProvider for requests directly and instantiate your MapboxNavigationService with customRoutingProvider: nil for the default behavior instead.

And a side note: SDK allows some deviation between user actual position and route's origin point when starting the route before triggering a re-route. This is done for cases when navigation is started in surrounding territory, to allow user some gap to get to the route before attempting to "fix" it. So, to be sure that re-routing does not actually work, please make sure that user puck was connected to the route line in the first place OR that user has travelled more or less significant distance (>500m will do) after starting a session to let the engine know that user is somewhere unexpected.