Skip to content

Commit

Permalink
CameraOptions
Browse files Browse the repository at this point in the history
Plumbed camera options all the way through to MGLMapView. Added a method that lets you specify a direction in addition to center point and zoom level.

Added Map::jumpTo() for parity with mapbox-gl-js. Replaced usage of Map::setLatLng() and Map::setLatLngZoom() with Map::jumpTo() or Map::easeTo() within MGLMapView. Replaced MGLMapView.pitch with MGLMapCamera for setting all supported degrees of freedom simultaneously. Simultaneously move and rotate with course.

Support customizable timing functions on iOS.

iosapp now persists an archived MGLMapCamera instead of separate viewpoint properties and also synchronizes user defaults on termination. This change implements persistence entirely in Objective-C, eliminating the use of the Objective-C++ implementation.

Fixes mapbox#1643, fixes mapbox#1834. Ref mapbox#1581.
  • Loading branch information
1ec5 authored and incanus committed Sep 7, 2015
1 parent d8390ac commit 80d1a7a
Show file tree
Hide file tree
Showing 16 changed files with 588 additions and 203 deletions.
2 changes: 2 additions & 0 deletions gyp/platform-ios.gypi
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
'../include/mbgl/ios/Mapbox.h',
'../platform/ios/MGLMapboxEvents.h',
'../platform/ios/MGLMapboxEvents.m',
'../include/mbgl/ios/MGLMapCamera.h',
'../platform/ios/MGLMapCamera.mm',
'../include/mbgl/ios/MGLMapView.h',
'../include/mbgl/ios/MGLMapView+IBAdditions.h',
'../platform/ios/MGLMapView.mm',
Expand Down
36 changes: 36 additions & 0 deletions include/mbgl/ios/MGLMapCamera.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#import "Mapbox.h"

#pragma once

NS_ASSUME_NONNULL_BEGIN

/** An `MGLMapCamera` object represents a viewpoint from which the user observes some point on an `MGLMapView`. */
@interface MGLMapCamera : NSObject <NSSecureCoding, NSCopying>

/** Coordinate at the center of the map view. */
@property (nonatomic) CLLocationCoordinate2D centerCoordinate;

/** Heading measured in degrees clockwise from true north. */
@property (nonatomic) CLLocationDirection heading;

/** Pitch toward the horizon measured in degrees, with 0 degrees resulting in a two-dimensional map. */
@property (nonatomic) CGFloat pitch;

/** Meters above ground level. */
@property (nonatomic) CLLocationDistance altitude;

/** Returns a new camera with all properties set to 0. */
+ (instancetype)camera;

+ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
fromEyeCoordinate:(CLLocationCoordinate2D)eyeCoordinate
eyeAltitude:(CLLocationDistance)eyeAltitude;

+ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
fromDistance:(CLLocationDistance)distance
pitch:(CGFloat)pitch
heading:(CLLocationDirection)heading;

@end

NS_ASSUME_NONNULL_END
26 changes: 15 additions & 11 deletions include/mbgl/ios/MGLMapView.h
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
#import "MGLGeometry.h"
#import "MGLMapCamera.h"

#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>

NS_ASSUME_NONNULL_BEGIN

@class MGLAnnotationImage;
@class MGLMapCamera;
@class MGLUserLocation;
@class MGLPolyline;
@class MGLPolygon;
Expand Down Expand Up @@ -136,6 +138,8 @@ IB_DESIGNABLE
* @param animated Specify `YES` if you want the map view to animate scrolling and zooming to the new location or `NO` if you want the map to display the new location immediately. */
- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel animated:(BOOL)animated;

- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel direction:(CLLocationDirection)direction animated:(BOOL)animated;

/** The coordinate bounds visible in the receiver’s viewport.
*
* Changing the value of this property updates the receiver immediately. If you want to animate the change, call `setVisibleCoordinateBounds:animated:` instead. */
Expand Down Expand Up @@ -181,19 +185,19 @@ IB_DESIGNABLE
/** Resets the map rotation to a northern heading. */
- (IBAction)resetNorth;

/** The pitch of the map (measured in degrees).
*
* The default value `0` shows a completely flat map. Maximum value is `60`. */
@property (nonatomic) double pitch;
/** A camera representing the current viewpoint of the map. */
@property (nonatomic, copy) MGLMapCamera *camera;

/** Changes the pitch of the map.
* @param pitch The pitch of the map (measured in degrees) relative to top-down.
*
* Changing the pitch tilts the map without changing the current center coordinate or zoom level. */
- (void)setPitch:(double)pitch;
/** Moves the viewpoint to a different location with respect to the map with an optional transition animation.
* @param camera The new viewpoint.
* @param animated Specify `YES` if you want the map view to animate the change to the new viewpoint or `NO` if you want the map to display the new viewpoint immediately. */
- (void)setCamera:(MGLMapCamera *)camera animated:(BOOL)animated;

/** Resets the map pitch to head-on. */
- (IBAction)resetPitch;
/** Moves the viewpoint to a different location with respect to the map with an optional transition duration and timing function.
* @param camera The new viewpoint.
* @param duration The amount of time, measured in seconds, that the transition animation should take. Specify `0` to jump to the new viewpoint instantaneously.
* @param function A timing function used for the animation. Set this parameter to `nil` for a transition that matches most system animations. If the duration is `0`, this parameter is ignored. */
- (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function;

#pragma mark - Converting Map Coordinates

Expand Down
1 change: 1 addition & 0 deletions include/mbgl/ios/Mapbox.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#import "MGLAccountManager.h"
#import "MGLAnnotation.h"
#import "MGLAnnotationImage.h"
#import "MGLMapCamera.h"
#import "MGLGeometry.h"
#import "MGLMapView.h"
#import "MGLMultiPoint.h"
Expand Down
22 changes: 22 additions & 0 deletions include/mbgl/map/camera.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#ifndef MBGL_MAP_CAMERA
#define MBGL_MAP_CAMERA

#include <mbgl/util/geo.hpp>
#include <mbgl/util/optional.hpp>
#include <mbgl/util/chrono.hpp>
#include <mbgl/util/unitbezier.hpp>

namespace mbgl {

struct CameraOptions {
mapbox::util::optional<LatLng> center;
mapbox::util::optional<double> zoom;
mapbox::util::optional<double> angle;
mapbox::util::optional<double> pitch;
mapbox::util::optional<Duration> duration;
mapbox::util::optional<mbgl::util::UnitBezier> easing;
};

}

#endif /* MBGL_MAP_CAMERA */
11 changes: 8 additions & 3 deletions include/mbgl/map/map.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define MBGL_MAP_MAP

#include <mbgl/util/chrono.hpp>
#include <mbgl/map/camera.hpp>
#include <mbgl/map/update.hpp>
#include <mbgl/map/mode.hpp>
#include <mbgl/util/geo.hpp>
Expand Down Expand Up @@ -94,6 +95,10 @@ class Map : private util::noncopyable {
void cancelTransitions();
void setGestureInProgress(bool);

// Camera
void jumpTo(CameraOptions options);
void easeTo(CameraOptions options);

// Position
void moveBy(double dx, double dy, const Duration& = Duration::zero());
void setLatLng(LatLng latLng, const Duration& = Duration::zero());
Expand All @@ -107,8 +112,8 @@ class Map : private util::noncopyable {
void setZoom(double zoom, const Duration& = Duration::zero());
double getZoom() const;
void setLatLngZoom(LatLng latLng, double zoom, const Duration& = Duration::zero());
void fitBounds(LatLngBounds bounds, EdgeInsets padding, const Duration& duration = Duration::zero());
void fitBounds(AnnotationSegment segment, EdgeInsets padding, const Duration& duration = Duration::zero());
CameraOptions cameraForLatLngBounds(LatLngBounds bounds, EdgeInsets padding);
CameraOptions cameraForLatLngs(std::vector<LatLng> latLngs, EdgeInsets padding);
void resetZoom();
double getMinZoom() const;
double getMaxZoom() const;
Expand All @@ -121,7 +126,7 @@ class Map : private util::noncopyable {
void resetNorth();

// Pitch
void setPitch(double pitch);
void setPitch(double pitch, const Duration& = Duration::zero());
double getPitch() const;

// Size
Expand Down
File renamed without changes.
File renamed without changes.
68 changes: 39 additions & 29 deletions ios/app/MBXViewController.mm
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

#import <mbgl/ios/Mapbox.h>

#import <mbgl/platform/darwin/settings_nsuserdefaults.hpp>

#import <CoreLocation/CoreLocation.h>

static UIColor *const kTintColor = [UIColor colorWithRed:0.120 green:0.550 blue:0.670 alpha:1.000];
Expand All @@ -26,10 +24,20 @@ @interface MBXViewController () <UIActionSheetDelegate, MGLMapViewDelegate>

@implementation MBXViewController

mbgl::Settings_NSUserDefaults *settings = nullptr;

#pragma mark - Setup

+ (void)initialize
{
if (self == [MBXViewController class])
{
[[NSUserDefaults standardUserDefaults] registerDefaults:@{
@"userTrackingMode": @(MGLUserTrackingModeNone),
@"showsUserLocation": @NO,
@"debug": @NO,
}];
}
}

- (id)init
{
self = [super init];
Expand All @@ -38,6 +46,7 @@ - (id)init
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveState:) name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(restoreState:) name:UIApplicationWillEnterForegroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveState:) name:UIApplicationWillTerminateNotification object:nil];
}

return self;
Expand Down Expand Up @@ -73,7 +82,6 @@ - (void)viewDidLoad
target:self
action:@selector(locateUser)];

settings = new mbgl::Settings_NSUserDefaults();
[self restoreState:nil];

if ( ! settings->showsUserLocation)
Expand All @@ -86,30 +94,37 @@ - (void)viewDidLoad

- (void)saveState:(__unused NSNotification *)notification
{
if (self.mapView && settings)
if (self.mapView)
{
settings->longitude = self.mapView.centerCoordinate.longitude;
settings->latitude = self.mapView.centerCoordinate.latitude;
settings->zoom = self.mapView.zoomLevel;
settings->bearing = self.mapView.direction;
settings->pitch = self.mapView.pitch;
settings->debug = self.mapView.isDebugActive;
settings->userTrackingMode = self.mapView.userTrackingMode;
settings->showsUserLocation = self.mapView.showsUserLocation;
settings->save();
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *archivedCamera = [NSKeyedArchiver archivedDataWithRootObject:self.mapView.camera];
[defaults setObject:archivedCamera forKey:@"camera"];
[defaults setInteger:self.mapView.userTrackingMode forKey:@"userTrackingMode"];
[defaults setBool:self.mapView.showsUserLocation forKey:@"showsUserLocation"];
[defaults setBool:self.mapView.debugActive forKey:@"debug"];
[defaults synchronize];
}
}

- (void)restoreState:(__unused NSNotification *)notification
{
if (self.mapView && settings) {
settings->load();
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(settings->latitude, settings->longitude) zoomLevel:settings->zoom animated:NO];
self.mapView.direction = settings->bearing;
self.mapView.pitch = settings->pitch;
self.mapView.userTrackingMode = settings->userTrackingMode;
self.mapView.showsUserLocation = settings->showsUserLocation;
[self.mapView setDebugActive:settings->debug];
if (self.mapView) {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *archivedCamera = [defaults objectForKey:@"camera"];
MGLMapCamera *camera = archivedCamera ? [NSKeyedUnarchiver unarchiveObjectWithData:archivedCamera] : nil;
if (camera)
{
self.mapView.camera = camera;
}
NSInteger uncheckedTrackingMode = [defaults integerForKey:@"userTrackingMode"];
if (uncheckedTrackingMode >= 0 &&
(NSUInteger)uncheckedTrackingMode >= MGLUserTrackingModeNone &&
(NSUInteger)uncheckedTrackingMode <= MGLUserTrackingModeFollowWithCourse)
{
self.mapView.userTrackingMode = (MGLUserTrackingMode)uncheckedTrackingMode;
}
self.mapView.showsUserLocation = [defaults boolForKey:@"showsUserLocation"];
self.mapView.debugActive = [defaults boolForKey:@"debug"];
}
}

Expand Down Expand Up @@ -339,12 +354,7 @@ - (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];

if (settings)
{
[self saveState:nil];
delete settings;
settings = nullptr;
}
[self saveState:nil];
}

#pragma mark - MGLMapViewDelegate
Expand Down
1 change: 0 additions & 1 deletion ios/app/mapboxgl-app.gypi
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
'./MBXAppDelegate.m',
'./MBXViewController.h',
'./MBXViewController.mm',
'../../platform/darwin/settings_nsuserdefaults.mm',
],

'xcode_settings': {
Expand Down
101 changes: 101 additions & 0 deletions platform/ios/MGLMapCamera.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#import "MGLMapCamera.h"

#include <mbgl/util/projection.hpp>

@implementation MGLMapCamera

+ (BOOL)supportsSecureCoding
{
return YES;
}

+ (instancetype)camera
{
return [[self alloc] init];
}

+ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
fromEyeCoordinate:(CLLocationCoordinate2D)eyeCoordinate
eyeAltitude:(CLLocationDistance)eyeAltitude
{
mbgl::LatLng centerLatLng = mbgl::LatLng(centerCoordinate.latitude, centerCoordinate.longitude);
mbgl::LatLng eyeLatLng = mbgl::LatLng(eyeCoordinate.latitude, eyeCoordinate.longitude);

mbgl::ProjectedMeters centerMeters = mbgl::Projection::projectedMetersForLatLng(centerLatLng);
mbgl::ProjectedMeters eyeMeters = mbgl::Projection::projectedMetersForLatLng(eyeLatLng);
CLLocationDirection heading = std::atan((centerMeters.northing - eyeMeters.northing) /
(centerMeters.easting - eyeMeters.easting));

double groundDistance = std::hypot(centerMeters.northing - eyeMeters.northing,
centerMeters.easting - eyeMeters.easting);
CGFloat pitch = std::atan(eyeAltitude / groundDistance);

return [[self alloc] initWithCenterCoordinate:centerCoordinate
altitude:eyeAltitude
pitch:pitch
heading:heading];
}

+ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
fromDistance:(CLLocationDistance)distance
pitch:(CGFloat)pitch
heading:(CLLocationDirection)heading
{
return [[self alloc] initWithCenterCoordinate:centerCoordinate
altitude:distance
pitch:(CGFloat)pitch
heading:heading];
}

- (instancetype)initWithCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
altitude:(CLLocationDistance)altitude
pitch:(CGFloat)pitch
heading:(CLLocationDirection)heading
{
if (self = [super init])
{
_centerCoordinate = centerCoordinate;
_altitude = altitude;
_pitch = pitch;
_heading = heading;
}
return self;
}

- (nullable instancetype)initWithCoder:(NSCoder *)coder
{
if (self = [super init])
{
_centerCoordinate = CLLocationCoordinate2DMake([coder decodeDoubleForKey:@"centerLatitude"],
[coder decodeDoubleForKey:@"centerLongitude"]);
_altitude = [coder decodeDoubleForKey:@"altitude"];
_pitch = [coder decodeDoubleForKey:@"pitch"];
_heading = [coder decodeDoubleForKey:@"heading"];
}
return self;
}

- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeDouble:_centerCoordinate.latitude forKey:@"centerLatitude"];
[coder encodeDouble:_centerCoordinate.longitude forKey:@"centerLongitude"];
[coder encodeDouble:_altitude forKey:@"altitude"];
[coder encodeDouble:_pitch forKey:@"pitch"];
[coder encodeDouble:_heading forKey:@"heading"];
}

- (id)copyWithZone:(nullable NSZone *)zone
{
return [[[self class] allocWithZone:zone] initWithCenterCoordinate:_centerCoordinate
altitude:_altitude
pitch:_pitch
heading:_heading];
}

- (NSString *)description
{
return [NSString stringWithFormat:@"<MKMapCamera %p centerCoordinate:%f, %f altitude:%.0fm heading:%.0f° pitch:%.0f°>",
self, _centerCoordinate.latitude, _centerCoordinate.longitude, _altitude, _heading, _pitch];
}

@end
Loading

0 comments on commit 80d1a7a

Please sign in to comment.