mapbox / mapbox-navigation-ios

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

Wrong map view orientation when device is moving in landscape mode #2406

Open IAntolK opened 4 years ago

IAntolK commented 4 years ago

I am using custom mapbox turn-by-turn navigation in our bike app. Navigation should work in landscape mode and camera should follow user with heading. Everything works fine when I'm testing the navigation in office by simulating location changes, but the problem occurs when I put the device on bike and start to ride. After few hundred meters map view changes orientation to portrait and, because device is in landscape/faceUp mode, user heading indicator shows wrong direction. I've tried with few approaches but result was the same. First implementation was with userTrackingMode set to followWithHeading. Second one was with userTrackingMode set to follow and I was changing map view direction to fit current CLLocationManager heading. The last one was with userTrackingMode set to follow and I was changing camera to point current CLLocationManager heading.

The last approach code example MGLMapView initialization: mapNavigationView.styleURL = mapStyle.url mapNavigationView.delegate = self mapNavigationView.showsUserLocation = true mapNavigationView.userTrackingMode = .follow mapNavigationView.compassView.isHidden = true mapNavigationView.attributionButton.isHidden = true mapNavigationView.logoView.isHidden = true mapNavigationView.direction = 0

output .heading .asDriverIgnoringErrors() .drive(onNext: { [unowned self] in guard self.isUserNavigating else { return } var heading: CLLocationDirection switch UIApplication.shared.statusBarOrientation { case .landscapeLeft: heading = $0 - 90 case .landscapeRight: heading = $0 + 90 default: heading = $0 } self.zoomToUser(withHeading: heading) }) .disposed(by: disposeBag)

func zoomToUser( fromDistance: CLLocationDistance = Constants.Map.distance, withPitch pitch: CGFloat = Constants.Map.pitch, withAltitude altitude: CLLocationDistance = Constants.Map.altitude, withHeading heading: CLLocationDirection = Constants.Map.heading ) { guard let center = mapNavigationView.userLocation?.coordinate else { return } let camera = MGLMapCamera(lookingAtCenter: center, acrossDistance: fromDistance, pitch: pitch, heading: heading) camera.altitude = altitude mapNavigationView.fly(to: camera, completionHandler: nil) }

I thought that device orientation changes was the cause of the problem, but it occurs when device is in landscape/faceUp mode all the time too.

IAntolK commented 4 years ago

https://www.dropbox.com/s/a8exy8z30lf8fh2/IMG_2437.jpeg?dl=0

Udumft commented 4 years ago

Hello, @IAntolK! Let's see if I got it correct: during turn-by-turn navigation on a real bike ride, navigation map view starts in a landscape mode and everything works correct, but after some time, it switches to portrait mode and User Puck does not point to correct heading anymore?

If it is so, I have few questions to help identify the issue:

  1. Can you make screenshot while in that portrait mode issue?
  2. Can it happen that device orientation is not locked to landscape mode and device accelerometer treats your bike movement as orientation change gesture?
  3. On your provided screenshot, I see that User Puck is not located on the route. How did that happen? Is it a result of recent screen orientation change, or maybe user just started navigation session from unmapped road area? Or something else?
  4. You mentioned that route simulation works fine. Does simulation also works correct if you force device rotation mid-navigation?
IAntolK commented 4 years ago

Yes, you got it. If device is oriented to landscape left, user puck points up and camera is set to point in user direction. After some time (device orientation is landscape left all the time), map rotates for 90 degrees to the left. User puck and camera stil point up. It happens while riding a bike in straight direction with relatively contant speed (30-40 km/h). I think the problem is that mapbox desn't update heading calculation when device orientation changes. Because, when device orientation is changed from portrait to landscape, it still calculates heading like in portrait mode - top of the device points to north. I think, after some time, during the ride, it updates heading calculation based on current orientation (landscape) but I don't know why because status bar orientation and device orientation don't change.

  1. https://www.dropbox.com/s/2b5kmdyovov4thl/103777471_1612328912250691_8440811647844573874_n.jpg?dl=0
  2. I was observing device orientation changes during the ride. Orientation eventually changes from landscape to faceUp, but bug occur even if device is in landscape mode all the time.
  3. Screenshot shows situation where user just started navigation.
  4. Yes, I've tried to rotate the device during the navigation and couldn't reproduce the bug.
IAntolK commented 4 years ago

https://www.dropbox.com/s/eqcgai8drouegoz/RPReplay_Final1592487517.mov?dl=0

This is example with followWithHeading user tracking mode set.

IAntolK commented 4 years ago

Bug is definitely in the framework. I've run this example https://docs.mapbox.com/ios/navigation/examples/advanced/ with only one change mapView?.userTrackingMode = .followWithHeading and succeed to reproduce the bug.

Steps to reproduce:

Link to video: https://www.dropbox.com/s/iexn06qegvkkcfh/FFTW9970.mov?dl=0.

IvanKalaica commented 4 years ago

@Udumft any updates on this one? This is a blocker for our project for almost 2 months now. :(

zugaldia commented 4 years ago

The team will be investigating this issue and we'll report back as soon as we know more.

1ec5 commented 4 years ago

First implementation was with userTrackingMode set to followWithHeading. Second one was with userTrackingMode set to follow and I was changing map view direction to fit current CLLocationManager heading. The last one was with userTrackingMode set to follow and I was changing camera to point current CLLocationManager heading.

Each of these implementations sets the map SDK’s MGLMapView.userTrackingMode property. During turn-by-turn navigation, NavigationMapView uses its own puck and camera implementation in order to more accurately track the user’s direction of motion. This behavior is controlled by the tracksUserCourse property. Setting userTrackingMode overrides this behavior with the less sophisticated map SDK behavior. #1741 would clean up the relationship between MGLMapView and NavigationMapView in this regard.

Heading is affected by the heading orientation. The map SDK normally does account for heading orientation, but it could be that the navigation SDK’s customizations prevent the map SDK’s heading orientation correction from taking effect. The navigation SDK doesn’t currently use heading in any capacity, so it isn’t setting CLLocationManager.headingOrientation. But if you’re feeding the location manager’s heading into the map, then you’ll probably need to set that property. We can use this issue to track setting the property out of the box, since it couldn’t hurt to set it even if we aren’t using it yet.

As the name implies, tracksUserCourse is specifically designed to track CLLocation.course rather than CLHeading. This design is ideal for driving and may also be suitable for biking. However, if you prefer to track heading instead of course, that would be good feedback for #2215, which does that for walking. (That PR still has a to-do item about accounting for heading orientation changes.)

IAntolK commented 4 years ago

Thanks for the answer, but I don't understand what can we do to solve the problem. We are not using NavigationMapView, just MGLMapView, drawing a route, showing navigation directions, and tracking user location (see https://www.dropbox.com/s/eqcgai8drouegoz/RPReplay_Final1592487517.mov?dl=0). Are you suggesting, if we want to track heading, to set tracking mode to none and update camera manually? We've tried that but the same problem occurred.

Are there any code examples of how we can implement custom design, as on the attached video, using NavigationViewController or NavigationMapView?

1ec5 commented 4 years ago

We are not using NavigationMapView, just MGLMapView, drawing a route, showing navigation directions, and tracking user location

Ah, I was under the assumption that it was simply a heavily customized NavigationMapView (which is in turn a heavily customized subclass of MGLMapView). It sounds like this issue doesn’t involve the navigation SDK at all (even if your application uses the MapboxDirections framework). The mapbox-gl-native-ios repository may be a better place to report the issue to get some attention on it.

Are there any code examples of how we can implement custom design, as on the attached video, using NavigationViewController or NavigationMapView?

It should be feasible to implement a custom design like that with NavigationMapView. As it is merely a subclass of MGLMapView, it should behave almost identically if you were to replace MGLMapView with NavigationMapView. NavigationMapView comes with additional conveniences for common navigation-related customizations, like displaying a route line. In principle, NavigationViewController is also designed for your use case; however, it unfortunately isn’t optimized for landscape orientation (#1139), so you’d probably still have to continue to implement your own view controller for now.