mapbox / mapbox/mapbox-navigation-ios
[错误]:MabBox does not automatically plan a new path when navigating a wrong route
Nobody has claimed this yet.
- Dominant language
- Swift
- Stars
- 918
- Forks
- 326
- Avg merge
- 1h 16m
- Merged PRs (30d)
- 3
Description
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
-
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
}
}
`
- 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()
}
})
}
`
- 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)
- We turned off the official navigation SDK's auto-correct path feature
func navigationViewController(_ navigationViewController: NavigationViewController, shouldRerouteFrom location: CLLocation) -> Bool {
return false
}
- 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
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with StartNavigationController.setupSubViews and startNav, then trace how CustomProvider is passed into MapboxNavigationService and NavigationViewController. Reproduce the wrong-route behavior across two navigation sessions and inspect the rerouting path and provider callbacks. Done means a later navigation session automatically recalculates a route after leaving the planned path.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- swift
- Domain
- mobile
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100