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

Affix user dot on screen in user tracking mode #3589

Merged
merged 13 commits into from
Jan 20, 2016
Merged
Prev Previous commit
Next Next commit
[ios] Fly to user location
Fly to the user location when entering user tracking mode. Keep user location updates from interfering with the initial flight.
  • Loading branch information
1ec5 committed Jan 20, 2016
commit 41ce7b2526ec338a10a1bfcfbee0a30342b2c49a
97 changes: 54 additions & 43 deletions platform/ios/src/MGLMapView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@
class MBGLView;
class MGLAnnotationContext;

/// Indicates the manner in which the map view is tracking the user location.
typedef NS_ENUM(NSUInteger, MGLUserTrackingState) {
/// The map view is not yet tracking the user location.
MGLUserTrackingStatePossible = 0,
/// The map view has begun to move to the first reported user location.
MGLUserTrackingStateBegan = 1,
/// The map view has finished moving to the first reported user location.
MGLUserTrackingStateChanged = 2,
};

NSString *const MGLMapboxSetupDocumentationURLDisplayString = @"mapbox.com/help/first-steps-ios-sdk";

const NSTimeInterval MGLAnimationDuration = 0.3;
Expand Down Expand Up @@ -143,8 +153,8 @@ @interface MGLMapView () <UIGestureRecognizerDelegate,
/// Currently shown popover representing the selected annotation.
@property (nonatomic) UIView<MGLCalloutView> *calloutViewForSelectedAnnotation;
@property (nonatomic) MGLUserLocationAnnotationView *userLocationAnnotationView;
/// True if the map view has completed its move to the first reported user location in user tracking mode.
@property (nonatomic) BOOL hasBegunTrackingUserLocation;
/// Indicates how thoroughly the map view is tracking the user location.
@property (nonatomic) MGLUserTrackingState userTrackingState;
@property (nonatomic) CLLocationManager *locationManager;
@property (nonatomic) CGFloat scale;
@property (nonatomic) CGFloat angle;
Expand Down Expand Up @@ -1929,6 +1939,13 @@ - (void)flyToCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration
}

- (void)flyToCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration peakAltitude:(CLLocationDistance)peakAltitude completionHandler:(nullable void (^)(void))completion
{
self.userTrackingMode = MGLUserTrackingModeNone;

[self _flyToCamera:camera withDuration:duration peakAltitude:peakAltitude completionHandler:completion];
}

- (void)_flyToCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration peakAltitude:(CLLocationDistance)peakAltitude completionHandler:(nullable void (^)(void))completion
{
_mbglMap->cancelTransitions();
if ([self.camera isEqual:camera])
Expand Down Expand Up @@ -2942,7 +2959,11 @@ - (void)setUserTrackingMode:(MGLUserTrackingMode)mode animated:(BOOL)animated
}

_userTrackingMode = mode;
self.hasBegunTrackingUserLocation = NO;

if (_userTrackingMode == MGLUserTrackingModeNone
|| _userTrackingMode == MGLUserTrackingModeFollowWithCourse) {
self.userTrackingState = MGLUserTrackingStatePossible;
}

switch (_userTrackingMode)
{
Expand Down Expand Up @@ -3038,8 +3059,9 @@ - (void)locationManager:(__unused CLLocationManager *)manager didUpdateLocations
{
// at sufficient detail, just re-center the map; don't zoom
//
if (self.hasBegunTrackingUserLocation)
if (self.userTrackingState == MGLUserTrackingStateChanged)
{
// Ease incrementally to the new user location.
UIEdgeInsets insets = UIEdgeInsetsMake(correctPoint.y, correctPoint.x,
CGRectGetHeight(self.bounds) - correctPoint.y,
CGRectGetWidth(self.bounds) - correctPoint.x);
Expand All @@ -3052,51 +3074,39 @@ - (void)locationManager:(__unused CLLocationManager *)manager didUpdateLocations
animationTimingFunction:linearFunction
completionHandler:NULL];
}
else
else if (self.userTrackingState == MGLUserTrackingStatePossible)
{
// Fly to the first reported location, which may be far away
// from the current viewport.
self.userTrackingState = MGLUserTrackingStateBegan;

MGLMapCamera *camera = self.camera;
camera.centerCoordinate = self.userLocation.location.coordinate;
camera.heading = course;

__weak MGLMapView *weakSelf = self;
[self _setCenterCoordinate:self.userLocation.location.coordinate
edgePadding:self.contentInset
zoomLevel:self.zoomLevel
direction:course
duration:MGLAnimationDuration
animationTimingFunction:nil
completionHandler:^{
MGLMapView *strongSelf = weakSelf;
strongSelf.hasBegunTrackingUserLocation = YES;
}];
[self _flyToCamera:camera withDuration:-1 peakAltitude:-1 completionHandler:^{
MGLMapView *strongSelf = weakSelf;
strongSelf.userTrackingState = MGLUserTrackingStateChanged;
}];
}
}
else
else if (self.userTrackingState == MGLUserTrackingStatePossible)
{
// otherwise re-center and zoom in to near accuracy confidence
//
float delta = (newLocation.horizontalAccuracy / 110000) * 1.2; // approx. meter per degree latitude, plus some margin

CLLocationCoordinate2D desiredSouthWest = CLLocationCoordinate2DMake(newLocation.coordinate.latitude - delta,
newLocation.coordinate.longitude - delta);

CLLocationCoordinate2D desiredNorthEast = CLLocationCoordinate2DMake(newLocation.coordinate.latitude + delta,
newLocation.coordinate.longitude + delta);

CGFloat pixelRadius = fminf(self.bounds.size.width, self.bounds.size.height) / 2;

CLLocationCoordinate2D actualSouthWest = [self convertPoint:CGPointMake(currentPoint.x - pixelRadius,
currentPoint.y - pixelRadius)
toCoordinateFromView:self];

CLLocationCoordinate2D actualNorthEast = [self convertPoint:CGPointMake(currentPoint.x + pixelRadius,
currentPoint.y + pixelRadius)
toCoordinateFromView:self];

if (desiredNorthEast.latitude != actualNorthEast.latitude ||
desiredNorthEast.longitude != actualNorthEast.longitude ||
desiredSouthWest.latitude != actualSouthWest.latitude ||
desiredSouthWest.longitude != actualSouthWest.longitude)
{
// assumes we won't disrupt tracking mode
[self setVisibleCoordinateBounds:MGLCoordinateBoundsMake(desiredSouthWest, desiredNorthEast) edgePadding:UIEdgeInsetsZero direction:course animated:YES];
}
self.userTrackingState = MGLUserTrackingStateBegan;

MGLMapCamera *camera = self.camera;
camera.centerCoordinate = self.userLocation.location.coordinate;
camera.heading = course;
camera.altitude = newLocation.horizontalAccuracy;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach seems to be more reliable, and it also works when the map is tilted, something unsupported by the old code, which was ported from the raster SDK, which didn’t support tilting.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy that this is simplified! The camera movement is much slicker, too.

One continued quibble is that highly-accurate location fixes zoom in too far and context is lost. This is somewhat mitigated by the camera animation, but perhaps we could also adopt a minimum altitude?

~~We should additionally consider only zooming in if the map's zoom level is relatively low; i.e., Apple Maps doesn't change the altitude/zoom unless the map is zoomed out to ~z8.~~ Strike that, we already do this at <z10.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also seeing this zoom in beyond the accuracy ring, which is unintuitive.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, it does always seem to zoom into z18 for some reason. Perhaps it's because iOS-specific code assumes a field of view of 30 degrees, which is less than what some mbgl calculations assume.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in dbfc363. I haven’t nailed down exactly what MapKit does, but it does seem to like z14 quite a lot.


__weak MGLMapView *weakSelf = self;
[self _flyToCamera:camera withDuration:-1 peakAltitude:-1 completionHandler:^{
MGLMapView *strongSelf = weakSelf;
strongSelf.userTrackingState = MGLUserTrackingStateChanged;
}];
}
[self unrotateIfNeededAnimated:YES];
}
Expand Down Expand Up @@ -3367,7 +3377,8 @@ - (void)updateUserLocationAnnotationView
if ( ! self.userLocationAnnotationView.superview) [self.glView addSubview:self.userLocationAnnotationView];

CGPoint userPoint;
if (self.userTrackingMode != MGLUserTrackingModeNone && self.hasBegunTrackingUserLocation)
if (self.userTrackingMode != MGLUserTrackingModeNone
&& self.userTrackingState == MGLUserTrackingStateChanged)
{
userPoint = self.userLocationAnnotationViewCenter;
}
Expand Down