Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Crash rotating padded map — latitude must not be NaN #15165

Open
1ec5 opened this issue Jul 18, 2019 · 5 comments
Open

Crash rotating padded map — latitude must not be NaN #15165

1ec5 opened this issue Jul 18, 2019 · 5 comments
Labels
crash iOS Mapbox Maps SDK for iOS macOS Mapbox Maps SDK for macOS

Comments

@1ec5
Copy link
Contributor

1ec5 commented Jul 18, 2019

A map that has a top and bottom content inset crashes when the user rotates it with two fingers.

Steps to reproduce

Add the following code to any view controller with a full-screen map:

- (void)mapView:(MGLMapView *)mapView didFinishLoadingStyle:(MGLStyle *)style {
    self.automaticallyAdjustsScrollViewInsets = NO;
    self.mapView.contentInset = UIEdgeInsetsMake(CGRectGetHeight(self.mapView.bounds) * 0.8, CGRectGetWidth(self.mapView.bounds) * 0, CGRectGetHeight(self.mapView.bounds) * 0.2, 0);
    MGLMapCamera *camera = [MGLMapCamera cameraLookingAtCenterCoordinate:CLLocationCoordinate2DMake(39.101944,-84.509842) altitude:1000 pitch:60 heading:249];
    [self.mapView setCamera:camera withDuration:0 animationTimingFunction:nil edgePadding:UIEdgeInsetsZero completionHandler:nil];
}

For example, iosapp:

diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m
index 2fb95e1b17..702df27eab 100644
--- a/platform/ios/app/MBXViewController.m
+++ b/platform/ios/app/MBXViewController.m
@@ -2253,6 +2253,11 @@ CLLocationCoordinate2D randomWorldCoordinate() {
     // that a device with an English-language locale is already effectively
     // using locale-based country labels.
     _localizingLabels = [[self bestLanguageForUser] isEqualToString:@"en"];
+    
+    self.automaticallyAdjustsScrollViewInsets = NO;
+    self.mapView.contentInset = UIEdgeInsetsMake(CGRectGetHeight(self.mapView.bounds) * 0.8, CGRectGetWidth(self.mapView.bounds) * 0, CGRectGetHeight(self.mapView.bounds) * 0.2, 0);
+    MGLMapCamera *camera = [MGLMapCamera cameraLookingAtCenterCoordinate:CLLocationCoordinate2DMake(39.101944,-84.509842) altitude:1000 pitch:60 heading:249];
+    [self.mapView setCamera:camera withDuration:0 animationTimingFunction:nil edgePadding:UIEdgeInsetsZero completionHandler:nil];
 }
 
 - (BOOL)mapView:(MGLMapView *)mapView shouldChangeFromCamera:(MGLMapCamera *)oldCamera toCamera:(MGLMapCamera *)newCamera {

Wait for the map to fully load, then perform a two-finger rotation gesture in either direction.

Logs

The crash occurs here:

if (std::isnan(lat)) {
throw std::domain_error("latitude must not be NaN");
}

#1	0x0000000107676abd in mbgl::LatLng::LatLng(double, double, mbgl::LatLng::WrapMode) at /path/to/mapbox-gl-native/include/mbgl/util/geo.hpp:35
#2	0x0000000107676979 in mbgl::LatLng::LatLng(double, double, mbgl::LatLng::WrapMode) at /path/to/mapbox-gl-native/include/mbgl/util/geo.hpp:33
#3	0x00000001079cca23 in mbgl::Projection::unproject(mapbox::geometry::point<double> const&, double, mbgl::LatLng::WrapMode) at /path/to/mapbox-gl-native/include/mbgl/util/projection.hpp:88
#4	0x00000001079d5039 in mbgl::TransformState::screenCoordinateToLatLng(mapbox::geometry::point<double> const&, mbgl::LatLng::WrapMode) const at /path/to/mapbox-gl-native/src/mbgl/map/transform_state.cpp:328
#5	0x00000001079c9870 in mbgl::Transform::screenCoordinateToLatLng(mapbox::geometry::point<double> const&, mbgl::LatLng::WrapMode) const at /path/to/mapbox-gl-native/src/mbgl/map/transform.cpp:576
#6	0x00000001079be911 in mbgl::cameraForLatLngs(std::__1::vector<mbgl::LatLng, std::__1::allocator<mbgl::LatLng> > const&, mbgl::Transform const&, mbgl::EdgeInsets const&) at /path/to/mapbox-gl-native/src/mbgl/map/map.cpp:229
#7	0x00000001079be0ca in mbgl::Map::cameraForLatLngs(std::__1::vector<mbgl::LatLng, std::__1::allocator<mbgl::LatLng> > const&, mbgl::EdgeInsets const&, std::experimental::optional<double>, std::experimental::optional<double>) const at /path/to/mapbox-gl-native/src/mbgl/map/map.cpp:235
#8	0x00000001079bdfac in mbgl::Map::cameraForLatLngBounds(mbgl::LatLngBounds const&, mbgl::EdgeInsets const&, std::experimental::optional<double>, std::experimental::optional<double>) const at /path/to/mapbox-gl-native/src/mbgl/map/map.cpp:169
#9	0x000000010772ce7e in ::-[MGLMapView cameraThatFitsCoordinateBounds:edgePadding:](MGLCoordinateBounds, UIEdgeInsets) at /path/to/mapbox-gl-native/platform/ios/src/MGLMapView.mm:3662
#10	0x000000010772cbc0 in ::-[MGLMapView cameraThatFitsCoordinateBounds:](MGLCoordinateBounds) at /path/to/mapbox-gl-native/platform/ios/src/MGLMapView.mm:3650
#11	0x0000000107711a15 in ::-[MGLMapView cameraByZoomingToZoomLevel:aroundAnchorPoint:](double, CGPoint) at /path/to/mapbox-gl-native/platform/ios/src/MGLMapView.mm:2081
#12	0x000000010770c9e5 in ::-[MGLMapView handlePinchGesture:](UIPinchGestureRecognizer *) at /path/to/mapbox-gl-native/platform/ios/src/MGLMapView.mm:1686

Here’s some of the console spew leading up to the crash:

-[MGLAnnotationView setCenter:] - 122: Setting center: {181, 575.33333333333337}
2019-07-18 16:01:17.997956-0700 Mapbox GL[24780:16398126] [DEBUG] -[MGLMapCamera initWithCenterCoordinate:altitude:pitch:heading:] - 95: Initializing withCenterCoordinate: (lat: 39.101944, lon: -84.509842) altitude: 1000m pitch: 60.000000° heading: 249.000000°
2019-07-18 16:01:17.998246-0700 Mapbox GL[24780:16398126] [DEBUG] -[MGLMapCamera initWithCenterCoordinate:altitude:pitch:heading:] - 95: Initializing withCenterCoordinate: (lat: 39.101944, lon: -84.509842) altitude: 1000m pitch: 60.000000° heading: 249.000000°
2019-07-18 16:01:17.998523-0700 Mapbox GL[24780:16398126] [DEBUG] -[MGLMapCamera initWithCenterCoordinate:altitude:pitch:heading:] - 95: Initializing withCenterCoordinate: (lat: 39.101944, lon: -84.509842) altitude: 1000m pitch: 60.000000° heading: 249.000000°
2019-07-18 16:01:19.835907-0700 Mapbox GL[24780:16398126] [DEBUG] -[MGLMapCamera initWithCenterCoordinate:altitude:pitch:heading:] - 95: Initializing withCenterCoordinate: (lat: 39.101944, lon: -84.509842) altitude: 1000m pitch: 60.000000° heading: 249.000000°
2019-07-18 16:01:19.836505-0700 Mapbox GL[24780:16398126] [ERROR] {}[General]: Unable to calculate appropriate zoom level for bounds. Vertical or horizontal padding is greater than map's height or width.
libc++abi.dylib: terminating with uncaught exception of type std::domain_error
2019-07-18 16:04:55.397865-0700 Mapbox GL[24807:16404346] [DEBUG] -[MGLMapCamera initWithCenterCoordinate:altitude:pitch:heading:] - 95: Initializing withCenterCoordinate: (lat: 39.094539, lon: -84.510441) altitude: 1000m pitch: 60.000000° heading: 298.296944°
2019-07-18 16:04:55.397993-0700 Mapbox GL[24807:16404346] [DEBUG] -[MGLMapCamera initWithCenterCoordinate:altitude:pitch:heading:] - 95: Initializing withCenterCoordinate: (lat: 39.094539, lon: -84.510441) altitude: 1000m pitch: 60.000000° heading: 298.296944°
2019-07-18 16:04:57.188128-0700 Mapbox GL[24807:16404346] [DEBUG] -[MGLMapCamera initWithCenterCoordinate:altitude:pitch:heading:] - 95: Initializing withCenterCoordinate: (lat: 39.094539, lon: -84.510441) altitude: 1000m pitch: 60.000000° heading: 298.296944°
2019-07-18 16:04:57.188431-0700 Mapbox GL[24807:16404346] [DEBUG] -[MGLMapCamera initWithCenterCoordinate:altitude:pitch:heading:] - 95: Initializing withCenterCoordinate: (lat: 39.094539, lon: -84.510441) altitude: 1000m pitch: 60.000000° heading: 298.296944°
2019-07-18 16:04:57.189068-0700 Mapbox GL[24807:16404346] [DEBUG] -[MGLMapCamera initWithCenterCoordinate:altitude:pitch:heading:] - 95: Initializing withCenterCoordinate: (lat: 39.094539, lon: -84.510441) altitude: 1000m pitch: 60.000000° heading: 298.296944°
2019-07-18 16:04:57.189240-0700 Mapbox GL[24807:16404346] [DEBUG] -[MGLMapCamera initWithCenterCoordinate:altitude:pitch:heading:] - 95: Initializing withCenterCoordinate: (lat: 39.094539, lon: -84.510441) altitude: 1000m pitch: 60.000000° heading: 298.296944°
2019-07-18 16:04:58.485414-0700 Mapbox GL[24807:16404346] [DEBUG] -[MGLMapCamera initWithCenterCoordinate:altitude:pitch:heading:] - 95: Initializing withCenterCoordinate: (lat: 39.094539, lon: -84.510441) altitude: 1000m pitch: 60.000000° heading: 298.296944°
2019-07-18 16:04:58.485905-0700 Mapbox GL[24807:16404346] [ERROR] {}[General]: Unable to calculate appropriate zoom level for bounds. Vertical or horizontal padding is greater than map's height or width.
libc++abi.dylib: terminating with uncaught exception of type std::domain_error

Diagnosis

The passed-in padding is zero on all sides, despite the warning about vertical or horizontal padding. However, it does seem incorrect that -[MGLMapView cameraByZoomingToZoomLevel:aroundAnchorPoint:] calls -cameraThatFitsCoordinateBounds: rather than -cameraThatFitsCoordinateBounds:edgePadding:.

return [self cameraThatFitsCoordinateBounds:bounds];

Configuration

Mapbox SDK versions: 4a93f39
iOS/macOS versions: iOS 12.2
Device/simulator models: iPhone X
Xcode version: 10.2.1

/cc @mapbox/maps-ios @astojilj

@1ec5 1ec5 added iOS Mapbox Maps SDK for iOS macOS Mapbox Maps SDK for macOS crash labels Jul 18, 2019
@1ec5 1ec5 changed the title Crash rotating padded map Crash rotating padded map — latitude must not be NaN Jul 18, 2019
@chloekraw chloekraw added the release blocker Blocks the next final release label Jul 18, 2019
@1ec5
Copy link
Contributor Author

1ec5 commented Jul 19, 2019

The passed-in padding is zero on all sides, despite the warning about vertical or horizontal padding.

As of #14813, the content inset is added to this padding. The code snippet above sets the top inset to 80% of the view’s height and the bottom inset to 20% of the view’s height. Together, the insets add up to the full height of the view. While it may seem rather contrived to pad the entire view, this is in fact how various implementations affix the user puck at just the right spot on the screen.

@astojilj
Copy link
Contributor

this is in fact how various implementations affix the user puck at just the right spot on the screen.

@1ec5 ,
This seems like a wrong approach to me - to set content size to 0 height.
Is it something that customers are using as a warkaround?

@astojilj
Copy link
Contributor

astojilj commented Jul 19, 2019

@chloekraw , @1ec5

chloekraw added the release blocker label 16 hours ago

Suggest removing release blocker: this is reproducible (the same exception is thrown throw std::domain_error("latitude must not be NaN"); ) using iosapp with release-mojito, too. I didn't check release before mojito. I'd like to verify usage and purpose of doing it...

@chloekraw chloekraw removed the release blocker Blocks the next final release label Jul 19, 2019
@friedbunny friedbunny modified the milestone: release-picklejuice Jul 19, 2019
@1ec5
Copy link
Contributor Author

1ec5 commented Jul 19, 2019

This seems like a wrong approach to me - to set content size to 0 height.
Is it something that customers are using as a warkaround?

In fact, on a tilted map, setting the edge padding (not the content inset) like this is the only way to ensure that the map’s center coordinate is at the desired screen coordinate – important for positioning the user puck at the correct location. We implemented this approach in mapbox/mapbox-navigation-ios#402 because of the need to synchronize the user puck view with a specific geographic coordinate while keeping the view’s screen coordinate fixed:

https://github.com/mapbox/mapbox-navigation-ios/blob/55dad5ff424e3af8ba43688dc542b84ad2319cfc/MapboxNavigation/NavigationMapView.swift#L340-L343

The map SDK takes a somewhat different approach for its built-in puck:

/// Returns the edge padding to apply when moving the map to a tracked location.
- (UIEdgeInsets)edgePaddingForFollowing
{
// Center on user location unless we're already centered there (or very close).
CGPoint correctPoint = self.userLocationAnnotationViewCenter;
// Shift the entire frame upward or downward to accommodate a shifted user
// location annotation view.
CGRect bounds = self.bounds;
CGRect boundsAroundCorrectPoint = CGRectOffset(bounds,
correctPoint.x - CGRectGetMidX(bounds),
correctPoint.y - CGRectGetMidY(bounds));
return UIEdgeInsetsMake(CGRectGetMinY(boundsAroundCorrectPoint) - CGRectGetMinY(bounds),
CGRectGetMaxX(boundsAroundCorrectPoint) - CGRectGetMaxX(bounds),
CGRectGetMaxY(bounds) - CGRectGetMaxY(boundsAroundCorrectPoint),
CGRectGetMaxX(bounds) - CGRectGetMaxX(boundsAroundCorrectPoint));
}

which is easier to see if we simplify the formula:

CGRect bounds = self.bounds;
return UIEdgeInsetsMake(correctPoint.y - CGRectGetMidY(bounds),
                        correctPoint.x - CGRectGetMidX(bounds),
                        CGRectGetMidY(bounds) - correctPoint.y,
                        CGRectGetMidX(bounds) - correctPoint.x);

The map SDK’s edge padding results in a screen rectangle as large as the original content frame, shifted according to the desired anchor point. But this approach is inaccurate because, on a tilted map, the scale varies as you go from the bottom of the screen to the top of the screen. It’s even more inaccurate if there’s an additional top content inset.

The navigation SDK’s approach would be unnecessary if there were a way to set the map’s camera with respect to an anchor point, as there is internally for zooming and rotating.

@1ec5
Copy link
Contributor Author

1ec5 commented Jul 19, 2019

I think it would be OK to disallow covering the entire map with content insets, as long as it remains valid to cover the entire map with non-persistent edge padding, as required for user location tracking. If we disallow full-view content insets, I think the exception should happen when setting the content insets, not when the user rotates the map and the map view asks its delegate whether rotating should be allowed.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
crash iOS Mapbox Maps SDK for iOS macOS Mapbox Maps SDK for macOS
Projects
None yet
Development

No branches or pull requests

4 participants