summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMinh Nguyễn <mxn@1ec5.org>2016-01-25 14:17:59 -0800
committerMinh Nguyễn <mxn@1ec5.org>2016-01-26 17:19:22 -0800
commit7057a1bb4aa6777b277214c91f273c014934d1e4 (patch)
treee57500f22997933e3bb8e181a9804c888b68fe15
parentc5017652b97d0e519289b972a6b39762540a61a3 (diff)
downloadqtlocation-mapboxgl-7057a1bb4aa6777b277214c91f273c014934d1e4.tar.gz
[ios] Bifocal viewport in course tracking mode
When a targetCoordinate is specified in course tracking mode, the map automatically resizes the viewport to show both the user puck and the target, one at the top and the other at the bottom. The user puck now rotates its arrow in the course direction, no longer assuming that the viewport is facing the same way as the course.
-rw-r--r--include/mbgl/ios/MGLMapView.h40
-rw-r--r--platform/ios/src/MGLMapView.mm151
-rw-r--r--platform/ios/src/MGLUserLocationAnnotationView.m4
3 files changed, 165 insertions, 30 deletions
diff --git a/include/mbgl/ios/MGLMapView.h b/include/mbgl/ios/MGLMapView.h
index c0f4cd1365..b27db4976a 100644
--- a/include/mbgl/ios/MGLMapView.h
+++ b/include/mbgl/ios/MGLMapView.h
@@ -272,6 +272,46 @@ IB_DESIGNABLE
*/
@property (nonatomic, assign) BOOL displayHeadingCalibration;
+/**
+ The geographic coordinate that is the subject of observation as the user
+ location is being tracked.
+
+ By default, this property is set to an invalid coordinate, indicating that
+ there is no target. In course tracking mode, the target forms one of two foci
+ in the viewport, the other being the user location annotation. Typically, this
+ property is set to a destination or waypoint in a real-time navigation scene.
+ As the user annotation moves toward the target, the map automatically zooms in
+ to fit both foci optimally within the viewport.
+
+ This property has no effect if the `userTrackingMode` property is set to a
+ value other than `MGLUserTrackingModeFollowWithCourse`.
+
+ Changing the value of this property updates the map view with an animated
+ transition. If you don’t want to animate the change, use the
+ `-setTargetCoordinate:animated:` method instead.
+ */
+@property (nonatomic, assign) CLLocationCoordinate2D targetCoordinate;
+
+/**
+ Sets the geographic coordinate that is the subject of observation as the user
+ location is being tracked, with an optional transition animation.
+
+ By default, the target coordinate is set to an invalid coordinate, indicating
+ that there is no target. In course tracking mode, the target forms one of two
+ foci in the viewport, the other being the user location annotation. Typically,
+ the target is set to a destination or waypoint in a real-time navigation scene.
+ As the user annotation moves toward the target, the map automatically zooms in
+ to fit both foci optimally within the viewport.
+
+ This method has no effect if the `userTrackingMode` property is set to a value
+ other than `MGLUserTrackingModeFollowWithCourse`.
+
+ @param targetCoordinate The target coordinate to fit within the viewport.
+ @param animated If `YES`, the map animates to fit the target within the map
+ view. If `NO`, the map fits the target instantaneously.
+ */
+- (void)setTargetCoordinate:(CLLocationCoordinate2D)targetCoordinate animated:(BOOL)animated;
+
#pragma mark Configuring How the User Interacts with the Map
/**
diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm
index 5d0086c21c..f10dfce837 100644
--- a/platform/ios/src/MGLMapView.mm
+++ b/platform/ios/src/MGLMapView.mm
@@ -22,6 +22,7 @@
#include <mbgl/util/math.hpp>
#include <mbgl/util/constants.hpp>
#include <mbgl/util/image.hpp>
+#include <mbgl/util/projection.hpp>
#include <mbgl/util/std.hpp>
#include <mbgl/util/default_styles.hpp>
#include <mbgl/util/chrono.hpp>
@@ -69,7 +70,7 @@ const NSTimeInterval MGLUserLocationAnimationDuration = 1.0;
/// Distance between the map view’s edge and that of the user location
/// annotation view.
-const CGFloat MGLUserLocationAnnotationViewPadding = 50;
+const UIEdgeInsets MGLUserLocationAnnotationViewInset = UIEdgeInsetsMake(50, 0, 50, 0);
const CGSize MGLAnnotationUpdateViewportOutset = {150, 150};
const CGFloat MGLMinimumZoom = 3;
@@ -447,6 +448,7 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
_mbglMap->jumpTo(options);
_pendingLatitude = NAN;
_pendingLongitude = NAN;
+ _targetCoordinate = kCLLocationCoordinate2DInvalid;
// metrics: map load event
mbgl::LatLng latLng = _mbglMap->getLatLng(padding);
@@ -1831,9 +1833,13 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
- (void)setVisibleCoordinates:(CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets direction:(CLLocationDirection)direction duration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion
{
self.userTrackingMode = MGLUserTrackingModeNone;
+ [self _setVisibleCoordinates:coordinates count:count edgePadding:insets direction:direction duration:duration animationTimingFunction:function completionHandler:completion];
+}
+
+- (void)_setVisibleCoordinates:(CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets direction:(CLLocationDirection)direction duration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion
+{
_mbglMap->cancelTransitions();
- // NOTE: does not disrupt tracking mode
[self willChangeValueForKey:@"visibleCoordinateBounds"];
mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(insets);
padding += MGLEdgeInsetsFromNSEdgeInsets(self.contentInset);
@@ -1882,8 +1888,7 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
{
if ( ! animated && ! self.rotationAllowed) return;
- if (self.userTrackingMode == MGLUserTrackingModeFollowWithHeading ||
- self.userTrackingMode == MGLUserTrackingModeFollowWithCourse)
+ if (self.userTrackingMode == MGLUserTrackingModeFollowWithHeading)
{
self.userTrackingMode = MGLUserTrackingModeFollow;
}
@@ -3106,6 +3111,26 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
}
}
+- (void)setTargetCoordinate:(CLLocationCoordinate2D)targetCoordinate
+{
+ [self setTargetCoordinate:targetCoordinate animated:YES];
+}
+
+- (void)setTargetCoordinate:(CLLocationCoordinate2D)targetCoordinate animated:(BOOL)animated
+{
+ if (targetCoordinate.latitude != self.targetCoordinate.latitude
+ || targetCoordinate.longitude != self.targetCoordinate.longitude)
+ {
+ _targetCoordinate = targetCoordinate;
+ if (CLLocationCoordinate2DIsValid(targetCoordinate)
+ && self.userTrackingMode == MGLUserTrackingModeFollowWithCourse
+ && self.userLocation.location)
+ {
+ [self locationManager:self.locationManager didUpdateLocations:@[self.userLocation.location] animated:animated];
+ }
+ }
+}
+
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
[self locationManager:manager didUpdateLocations:locations animated:YES];
@@ -3140,14 +3165,31 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
if (std::abs(currentPoint.x - correctPoint.x) > 1.0 || std::abs(currentPoint.y - correctPoint.y) > 1.0)
{
- CLLocationDirection course = self.userLocation.location.course;
- if (self.userTrackingMode != MGLUserTrackingModeFollowWithCourse)
- {
- course = -1;
- }
- else if (course >= 0 && self.userLocationVerticalAlignment == MGLAnnotationVerticalAlignmentTop)
+ CLLocationDirection course = -1;
+ if (self.userTrackingMode == MGLUserTrackingModeFollowWithCourse)
{
- course += 180;
+ if (CLLocationCoordinate2DIsValid(self.targetCoordinate))
+ {
+ mbgl::LatLng userLatLng = MGLLatLngFromLocationCoordinate2D(self.userLocation.coordinate);
+ mbgl::LatLng targetLatLng = MGLLatLngFromLocationCoordinate2D(self.targetCoordinate);
+ mbgl::ProjectedMeters userMeters = mbgl::Projection::projectedMetersForLatLng(userLatLng);
+ mbgl::ProjectedMeters targetMeters = mbgl::Projection::projectedMetersForLatLng(targetLatLng);
+ double angle = atan2(targetMeters.easting - userMeters.easting,
+ targetMeters.northing - userMeters.northing);
+ course = mbgl::util::wrap(MGLDegreesFromRadians(angle), 0., 360.);
+ }
+ else
+ {
+ course = self.userLocation.location.course;
+ }
+
+ if (course >= 0)
+ {
+ if (self.userLocationVerticalAlignment == MGLAnnotationVerticalAlignmentTop)
+ {
+ course += 180;
+ }
+ }
}
// Shift the center point upward or downward to accommodate a
@@ -3158,22 +3200,44 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
correctPoint.y - CGRectGetMidY(bounds));
UIEdgeInsets insets = UIEdgeInsetsMake(CGRectGetMinY(boundsAroundCorrectPoint) - CGRectGetMinY(bounds), 0,
CGRectGetMaxY(bounds) - CGRectGetMaxY(boundsAroundCorrectPoint), 0);
+ UIEdgeInsets courseInset = MGLUserLocationAnnotationViewInset;
+ courseInset.top += CGRectGetHeight(self.userLocationAnnotationView.frame);
+ courseInset.bottom += CGRectGetHeight(self.userLocationAnnotationView.frame);
- if (self.zoomLevel >= MGLMinimumZoomLevelForUserTracking)
+ if (self.zoomLevel >= MGLMinimumZoomLevelForUserTracking || self.userTrackingMode == MGLUserTrackingModeFollowWithCourse)
{
+ CAMediaTimingFunction *linearFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
+
// at sufficient detail, just re-center the map; don't zoom
//
if (self.userTrackingState == MGLUserTrackingStateChanged)
{
// Ease incrementally to the new user location.
- CAMediaTimingFunction *linearFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
- [self _setCenterCoordinate:self.userLocation.location.coordinate
- edgePadding:insets
- zoomLevel:self.zoomLevel
- direction:course
- duration:animated ? MGLUserLocationAnimationDuration : 0
- animationTimingFunction:linearFunction
- completionHandler:NULL];
+ if (self.userTrackingMode == MGLUserTrackingModeFollowWithCourse
+ && CLLocationCoordinate2DIsValid(self.targetCoordinate))
+ {
+ CLLocationCoordinate2D foci[] = {
+ self.userLocation.location.coordinate,
+ self.targetCoordinate,
+ };
+ [self _setVisibleCoordinates:foci
+ count:sizeof(foci) / sizeof(foci[0])
+ edgePadding:courseInset
+ direction:course
+ duration:animated ? MGLUserLocationAnimationDuration : 0
+ animationTimingFunction:linearFunction
+ completionHandler:NULL];
+ }
+ else
+ {
+ [self _setCenterCoordinate:self.userLocation.location.coordinate
+ edgePadding:insets
+ zoomLevel:self.zoomLevel
+ direction:course
+ duration:animated ? MGLUserLocationAnimationDuration : 0
+ animationTimingFunction:linearFunction
+ completionHandler:NULL];
+ }
}
else if (self.userTrackingState == MGLUserTrackingStatePossible)
{
@@ -3181,15 +3245,36 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
// 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 _flyToCamera:camera edgePadding:insets withDuration:animated ? -1 : 0 peakAltitude:-1 completionHandler:^{
- MGLMapView *strongSelf = weakSelf;
- strongSelf.userTrackingState = MGLUserTrackingStateChanged;
- }];
+ if (self.userTrackingMode == MGLUserTrackingModeFollowWithCourse
+ && CLLocationCoordinate2DIsValid(self.targetCoordinate))
+ {
+ CLLocationCoordinate2D foci[] = {
+ self.userLocation.location.coordinate,
+ self.targetCoordinate,
+ };
+ [self _setVisibleCoordinates:foci
+ count:sizeof(foci) / sizeof(foci[0])
+ edgePadding:courseInset
+ direction:course
+ duration:animated ? MGLUserLocationAnimationDuration : 0
+ animationTimingFunction:linearFunction
+ completionHandler:^{
+ MGLMapView *strongSelf = weakSelf;
+ strongSelf.userTrackingState = MGLUserTrackingStateChanged;
+ }];
+ }
+ else
+ {
+ MGLMapCamera *camera = self.camera;
+ camera.centerCoordinate = self.userLocation.location.coordinate;
+ camera.heading = course;
+
+ [self _flyToCamera:camera edgePadding:insets withDuration:animated ? -1 : 0 peakAltitude:-1 completionHandler:^{
+ MGLMapView *strongSelf = weakSelf;
+ strongSelf.userTrackingState = MGLUserTrackingStateChanged;
+ }];
+ }
}
}
else if (self.userTrackingState == MGLUserTrackingStatePossible)
@@ -3534,6 +3619,12 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
- (CGPoint)userLocationAnnotationViewCenter
{
CGRect contentFrame = self.contentFrame;
+ contentFrame = UIEdgeInsetsInsetRect(contentFrame, MGLUserLocationAnnotationViewInset);
+ contentFrame = CGRectInset(contentFrame, 0, CGRectGetHeight(self.userLocationAnnotationView.frame));
+ if (CGRectIsEmpty(contentFrame))
+ {
+ contentFrame = self.contentFrame;
+ }
CGPoint center = CGPointMake(CGRectGetMidX(contentFrame), CGRectGetMidY(contentFrame));
// When tracking course, it’s more important to see the road ahead, so
@@ -3542,10 +3633,10 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration)
case MGLAnnotationVerticalAlignmentCenter:
break;
case MGLAnnotationVerticalAlignmentTop:
- center.y = CGRectGetMinY(contentFrame) + CGRectGetHeight(self.userLocationAnnotationView.frame) + MGLUserLocationAnnotationViewPadding;
+ center.y = CGRectGetMinY(contentFrame);
break;
case MGLAnnotationVerticalAlignmentBottom:
- center.y = CGRectGetMaxY(contentFrame) - CGRectGetHeight(self.userLocationAnnotationView.frame) - MGLUserLocationAnnotationViewPadding;
+ center.y = CGRectGetMaxY(contentFrame);
break;
}
diff --git a/platform/ios/src/MGLUserLocationAnnotationView.m b/platform/ios/src/MGLUserLocationAnnotationView.m
index 2e46416830..9adbd45f24 100644
--- a/platform/ios/src/MGLUserLocationAnnotationView.m
+++ b/platform/ios/src/MGLUserLocationAnnotationView.m
@@ -179,6 +179,10 @@ const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuck
[self.layer addSublayer:_puckArrow];
}
+ if (self.annotation.location.course >= 0)
+ {
+ _puckArrow.affineTransform = CGAffineTransformRotate(CGAffineTransformIdentity, -MGLRadiansFromDegrees(self.mapView.direction - self.annotation.location.course));
+ }
if ( ! _puckModeActivated)
{