From a07770450cc86980b087e5f1e00733fa41a6eff6 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Fri, 4 Sep 2015 17:48:34 -0400 Subject: [PATCH 01/29] Fix location permissions ask, add delay for map to load --- ios/app/MBXViewController.mm | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ios/app/MBXViewController.mm b/ios/app/MBXViewController.mm index 99c207d24..af25fa4dc 100644 --- a/ios/app/MBXViewController.mm +++ b/ios/app/MBXViewController.mm @@ -49,7 +49,6 @@ mbgl::Settings_NSUserDefaults *settings = nullptr; self.mapView = [[MGLMapView alloc] initWithFrame:self.view.bounds]; self.mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - self.mapView.showsUserLocation = YES; self.mapView.delegate = self; [self.view addSubview:self.mapView]; @@ -76,6 +75,13 @@ mbgl::Settings_NSUserDefaults *settings = nullptr; settings = new mbgl::Settings_NSUserDefaults(); [self restoreState:nil]; + + if ( ! settings->showsUserLocation) + { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + self.mapView.showsUserLocation = YES; + }); + } } - (void)saveState:(__unused NSNotification *)notification From 584c36f348d586bd07d27e6a4c9a7f318a98044c Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Fri, 4 Sep 2015 17:49:38 -0400 Subject: [PATCH 02/29] Update NSLocationWhenInUseUsageDescription string Fixes #1930 --- ios/app/app-info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/app/app-info.plist b/ios/app/app-info.plist index 978cc271e..665324693 100644 --- a/ios/app/app-info.plist +++ b/ios/app/app-info.plist @@ -40,7 +40,7 @@ NSHumanReadableCopyright (c) 2014 Mapbox NSLocationWhenInUseUsageDescription - The map will be centered on the user's location. + The map will display the user's location. UILaunchStoryboardName Default UIRequiredDeviceCapabilities From 0a172a21fdc2a87473560fd7d45f4d495d95de91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguy=E1=BB=85n?= Date: Thu, 30 Jul 2015 15:34:49 -0700 Subject: [PATCH 03/29] CameraOptions 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 #1643, fixes #1834. Ref #1581. --- gyp/platform-ios.gypi | 2 + include/mbgl/ios/MGLMapCamera.h | 36 +++ include/mbgl/ios/MGLMapView.h | 26 +- include/mbgl/ios/Mapbox.h | 1 + include/mbgl/map/camera.hpp | 22 ++ include/mbgl/map/map.hpp | 11 +- {src => include}/mbgl/util/optional.hpp | 0 {src => include}/mbgl/util/unitbezier.hpp | 0 ios/app/MBXViewController.mm | 68 ++--- ios/app/mapboxgl-app.gypi | 1 - platform/ios/MGLMapCamera.mm | 101 ++++++++ platform/ios/MGLMapView.mm | 289 ++++++++++++++++++---- src/mbgl/map/camera.cpp | 1 + src/mbgl/map/map.cpp | 42 +++- src/mbgl/map/transform.cpp | 182 +++++++------- src/mbgl/map/transform.hpp | 11 +- 16 files changed, 589 insertions(+), 204 deletions(-) create mode 100644 include/mbgl/ios/MGLMapCamera.h create mode 100644 include/mbgl/map/camera.hpp rename {src => include}/mbgl/util/optional.hpp (100%) rename {src => include}/mbgl/util/unitbezier.hpp (100%) create mode 100644 platform/ios/MGLMapCamera.mm create mode 100644 src/mbgl/map/camera.cpp diff --git a/gyp/platform-ios.gypi b/gyp/platform-ios.gypi index 516d5e832..6234f4d88 100644 --- a/gyp/platform-ios.gypi +++ b/gyp/platform-ios.gypi @@ -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', diff --git a/include/mbgl/ios/MGLMapCamera.h b/include/mbgl/ios/MGLMapCamera.h new file mode 100644 index 000000000..68f3923fd --- /dev/null +++ b/include/mbgl/ios/MGLMapCamera.h @@ -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 + +/** 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 diff --git a/include/mbgl/ios/MGLMapView.h b/include/mbgl/ios/MGLMapView.h index ba332d984..da500f1ef 100644 --- a/include/mbgl/ios/MGLMapView.h +++ b/include/mbgl/ios/MGLMapView.h @@ -1,4 +1,5 @@ #import "MGLGeometry.h" +#import "MGLMapCamera.h" #import #import @@ -6,6 +7,7 @@ NS_ASSUME_NONNULL_BEGIN @class MGLAnnotationImage; +@class MGLMapCamera; @class MGLUserLocation; @class MGLPolyline; @class MGLPolygon; @@ -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. */ @@ -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 diff --git a/include/mbgl/ios/Mapbox.h b/include/mbgl/ios/Mapbox.h index 401a62e82..f05f0c842 100644 --- a/include/mbgl/ios/Mapbox.h +++ b/include/mbgl/ios/Mapbox.h @@ -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" diff --git a/include/mbgl/map/camera.hpp b/include/mbgl/map/camera.hpp new file mode 100644 index 000000000..bd0b353ba --- /dev/null +++ b/include/mbgl/map/camera.hpp @@ -0,0 +1,22 @@ +#ifndef MBGL_MAP_CAMERA +#define MBGL_MAP_CAMERA + +#include +#include +#include +#include + +namespace mbgl { + +struct CameraOptions { + mapbox::util::optional center; + mapbox::util::optional zoom; + mapbox::util::optional angle; + mapbox::util::optional pitch; + mapbox::util::optional duration; + mapbox::util::optional easing; +}; + +} + +#endif /* MBGL_MAP_CAMERA */ diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index ddf86045e..4cd9293c9 100644 --- a/include/mbgl/map/map.hpp +++ b/include/mbgl/map/map.hpp @@ -2,6 +2,7 @@ #define MBGL_MAP_MAP #include +#include #include #include #include @@ -94,6 +95,10 @@ public: 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()); @@ -107,8 +112,8 @@ public: 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 latLngs, EdgeInsets padding); void resetZoom(); double getMinZoom() const; double getMaxZoom() const; @@ -121,7 +126,7 @@ public: void resetNorth(); // Pitch - void setPitch(double pitch); + void setPitch(double pitch, const Duration& = Duration::zero()); double getPitch() const; // Size diff --git a/src/mbgl/util/optional.hpp b/include/mbgl/util/optional.hpp similarity index 100% rename from src/mbgl/util/optional.hpp rename to include/mbgl/util/optional.hpp diff --git a/src/mbgl/util/unitbezier.hpp b/include/mbgl/util/unitbezier.hpp similarity index 100% rename from src/mbgl/util/unitbezier.hpp rename to include/mbgl/util/unitbezier.hpp diff --git a/ios/app/MBXViewController.mm b/ios/app/MBXViewController.mm index af25fa4dc..b32371770 100644 --- a/ios/app/MBXViewController.mm +++ b/ios/app/MBXViewController.mm @@ -2,8 +2,6 @@ #import -#import - #import static UIColor *const kTintColor = [UIColor colorWithRed:0.120 green:0.550 blue:0.670 alpha:1.000]; @@ -26,10 +24,20 @@ static NSUInteger const kStyleVersion = 8; @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]; @@ -38,6 +46,7 @@ mbgl::Settings_NSUserDefaults *settings = nullptr; { [[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; @@ -73,7 +82,6 @@ mbgl::Settings_NSUserDefaults *settings = nullptr; target:self action:@selector(locateUser)]; - settings = new mbgl::Settings_NSUserDefaults(); [self restoreState:nil]; if ( ! settings->showsUserLocation) @@ -86,30 +94,37 @@ mbgl::Settings_NSUserDefaults *settings = nullptr; - (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"]; } } @@ -339,12 +354,7 @@ mbgl::Settings_NSUserDefaults *settings = nullptr; { [[NSNotificationCenter defaultCenter] removeObserver:self]; - if (settings) - { - [self saveState:nil]; - delete settings; - settings = nullptr; - } + [self saveState:nil]; } #pragma mark - MGLMapViewDelegate diff --git a/ios/app/mapboxgl-app.gypi b/ios/app/mapboxgl-app.gypi index 34381ca15..8407ea7da 100644 --- a/ios/app/mapboxgl-app.gypi +++ b/ios/app/mapboxgl-app.gypi @@ -32,7 +32,6 @@ './MBXAppDelegate.m', './MBXViewController.h', './MBXViewController.mm', - '../../platform/darwin/settings_nsuserdefaults.mm', ], 'xcode_settings': { diff --git a/platform/ios/MGLMapCamera.mm b/platform/ios/MGLMapCamera.mm new file mode 100644 index 000000000..d04e46fa9 --- /dev/null +++ b/platform/ios/MGLMapCamera.mm @@ -0,0 +1,101 @@ +#import "MGLMapCamera.h" + +#include + +@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:@"", + self, _centerCoordinate.latitude, _centerCoordinate.longitude, _altitude, _heading, _pitch]; +} + +@end diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index 959f294f0..ef6ae52c3 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -48,6 +48,9 @@ NSUInteger const MGLStyleVersion = 8; const NSTimeInterval MGLAnimationDuration = 0.3; const CGSize MGLAnnotationUpdateViewportOutset = {150, 150}; const CGFloat MGLMinimumZoom = 3; +const CGFloat MGLMinimumPitch = 0; +const CGFloat MGLMaximumPitch = 60; +const CLLocationDegrees MGLAngularFieldOfView = M_PI / 6.; NSString *const MGLAnnotationIDKey = @"MGLAnnotationIDKey"; NSString *const MGLAnnotationSymbolKey = @"MGLAnnotationSymbolKey"; @@ -67,6 +70,18 @@ CLLocationDegrees MGLDegreesFromRadians(CGFloat radians) return radians * 180 / M_PI; } +mbgl::util::UnitBezier MGLUnitBezierForMediaTimingFunction(CAMediaTimingFunction *function) +{ + if ( ! function) + { + function = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]; + } + float p1[2], p2[2]; + [function getControlPointAtIndex:0 values:p1]; + [function getControlPointAtIndex:1 values:p2]; + return { p1[0], p1[1], p2[0], p2[1] }; +} + #pragma mark - Private - @interface MGLMapView () @@ -368,7 +383,10 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) // set initial position // - _mbglMap->setLatLngZoom(mbgl::LatLng(0, 0), _mbglMap->getMinZoom()); + mbgl::CameraOptions options; + options.center = mbgl::LatLng(0, 0); + options.zoom = _mbglMap->getMinZoom(); + _mbglMap->jumpTo(options); _pendingLatitude = NAN; _pendingLongitude = NAN; @@ -1347,12 +1365,10 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) else if (twoFingerDrag.state == UIGestureRecognizerStateBegan || twoFingerDrag.state == UIGestureRecognizerStateChanged) { CGFloat gestureDistance = CGPoint([twoFingerDrag translationInView:twoFingerDrag.view]).y; - double currentPitch = _mbglMap->getPitch(); - double minPitch = 0; - double maxPitch = 60.0; - double slowdown = 20.0; + CGFloat currentPitch = _mbglMap->getPitch(); + CGFloat slowdown = 20.0; - double pitchNew = fmax(fmin(currentPitch - (gestureDistance / slowdown), maxPitch), minPitch); + CGFloat pitchNew = mbgl::util::clamp(currentPitch - (gestureDistance / slowdown), MGLMinimumPitch, MGLMaximumPitch); _mbglMap->setPitch(pitchNew); } @@ -1530,7 +1546,7 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) + (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingCenterCoordinate { - return [NSSet setWithObjects:@"latitude", @"longitude", nil]; + return [NSSet setWithObjects:@"latitude", @"longitude", @"camera", nil]; } - (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)animated preservingTracking:(BOOL)tracking @@ -1542,13 +1558,7 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) - (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)animated { - CGFloat duration = (animated ? MGLAnimationDuration : 0); - - _mbglMap->setLatLngZoom(MGLLatLngFromLocationCoordinate2D(coordinate), - fmaxf(_mbglMap->getZoom(), self.currentMinimumZoom), - secondsAsDuration(duration)); - - [self notifyMapChange:(animated ? mbgl::MapChangeRegionDidChangeAnimated : mbgl::MapChangeRegionDidChange)]; + [self setCenterCoordinate:coordinate zoomLevel:self.zoomLevel animated:animated]; } - (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate @@ -1562,43 +1572,58 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) } - (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel animated:(BOOL)animated +{ + [self setCenterCoordinate:centerCoordinate zoomLevel:zoomLevel direction:self.direction animated:animated]; +} + +- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel direction:(CLLocationDirection)direction animated:(BOOL)animated { self.userTrackingMode = MGLUserTrackingModeNone; - CGFloat duration = (animated ? MGLAnimationDuration : 0); + [self _setCenterCoordinate:centerCoordinate zoomLevel:zoomLevel direction:direction animated:animated]; +} - _mbglMap->setLatLngZoom(MGLLatLngFromLocationCoordinate2D(centerCoordinate), zoomLevel, secondsAsDuration(duration)); +- (void)_setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel direction:(CLLocationDirection)direction animated:(BOOL)animated +{ + mbgl::CameraOptions options; + options.center = MGLLatLngFromLocationCoordinate2D(centerCoordinate); + options.zoom = fmaxf(zoomLevel, self.currentMinimumZoom); + if (direction >= 0) + { + options.angle = MGLRadiansFromDegrees(-direction); + } + if (animated) + { + options.duration = secondsAsDuration(MGLAnimationDuration); + options.easing = MGLUnitBezierForMediaTimingFunction(nil); + } + _mbglMap->easeTo(options); [self unrotateIfNeededAnimated:animated]; [self notifyMapChange:(animated ? mbgl::MapChangeRegionDidChangeAnimated : mbgl::MapChangeRegionDidChange)]; } ++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingZoomLevel +{ + return [NSSet setWithObject:@"camera"]; +} + - (double)zoomLevel { return _mbglMap->getZoom(); } -- (void)setZoomLevel:(double)zoomLevel animated:(BOOL)animated -{ - self.userTrackingMode = MGLUserTrackingModeNone; - - CGFloat duration = (animated ? MGLAnimationDuration : 0); - - _mbglMap->setLatLngZoom(_mbglMap->getLatLng(), - fmaxf(zoomLevel, self.currentMinimumZoom), - secondsAsDuration(duration)); - - [self unrotateIfNeededAnimated:animated]; - - [self notifyMapChange:(animated ? mbgl::MapChangeRegionDidChangeAnimated : mbgl::MapChangeRegionDidChange)]; -} - - (void)setZoomLevel:(double)zoomLevel { [self setZoomLevel:zoomLevel animated:NO]; } +- (void)setZoomLevel:(double)zoomLevel animated:(BOOL)animated +{ + [self setCenterCoordinate:self.centerCoordinate zoomLevel:zoomLevel animated:animated]; +} + MGLCoordinateBounds MGLCoordinateBoundsFromLatLngBounds(mbgl::LatLngBounds latLngBounds) { return MGLCoordinateBoundsMake(MGLLocationCoordinate2DFromLatLng(latLngBounds.sw), @@ -1639,11 +1664,34 @@ mbgl::LatLngBounds MGLLatLngBoundsFromCoordinateBounds(MGLCoordinateBounds coord animated:animated]; } +- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets direction:(CLLocationDirection)direction animated:(BOOL)animated +{ + CLLocationCoordinate2D coordinates[] = { + {bounds.ne.latitude, bounds.sw.longitude}, + bounds.sw, + {bounds.sw.latitude, bounds.ne.longitude}, + bounds.ne, + }; + [self setVisibleCoordinates:coordinates + count:sizeof(coordinates) / sizeof(coordinates[0]) + edgePadding:insets + direction:direction + animated:animated]; +} + - (void)setVisibleCoordinates:(CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets animated:(BOOL)animated +{ + [self setVisibleCoordinates:coordinates count:count edgePadding:insets direction:self.direction animated:animated]; +} + +- (void)setVisibleCoordinates:(CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets direction:(CLLocationDirection)direction animated:(BOOL)animated +{ + [self setVisibleCoordinates:coordinates count:count edgePadding:insets direction:direction duration:animated ? MGLAnimationDuration : 0 animationTimingFunction:nil]; +} + +- (void)setVisibleCoordinates:(CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets direction:(CLLocationDirection)direction duration:(NSTimeInterval)duration animationTimingFunction:(CAMediaTimingFunction *)function { // NOTE: does not disrupt tracking mode - CGFloat duration = animated ? MGLAnimationDuration : 0; - [self willChangeValueForKey:@"visibleCoordinateBounds"]; mbgl::EdgeInsets mbglInsets = {insets.top, insets.left, insets.bottom, insets.right}; mbgl::AnnotationSegment segment; @@ -1652,12 +1700,27 @@ mbgl::LatLngBounds MGLLatLngBoundsFromCoordinateBounds(MGLCoordinateBounds coord { segment.push_back({coordinates[i].latitude, coordinates[i].longitude}); } - _mbglMap->fitBounds(segment, mbglInsets, secondsAsDuration(duration)); + mbgl::CameraOptions options = _mbglMap->cameraForLatLngs(segment, mbglInsets); + if (direction >= 0) + { + options.angle = MGLRadiansFromDegrees(-direction); + } + if (duration > 0) + { + options.duration = secondsAsDuration(duration); + options.easing = MGLUnitBezierForMediaTimingFunction(function); + } + _mbglMap->easeTo(options); [self didChangeValueForKey:@"visibleCoordinateBounds"]; - [self unrotateIfNeededAnimated:animated]; + [self unrotateIfNeededAnimated:duration > 0]; - [self notifyMapChange:(animated ? mbgl::MapChangeRegionDidChangeAnimated : mbgl::MapChangeRegionDidChange)]; + [self notifyMapChange:(duration > 0 ? mbgl::MapChangeRegionDidChangeAnimated : mbgl::MapChangeRegionDidChange)]; +} + ++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingDirection +{ + return [NSSet setWithObject:@"camera"]; } - (CLLocationDirection)direction @@ -1683,23 +1746,139 @@ mbgl::LatLngBounds MGLLatLngBoundsFromCoordinateBounds(MGLCoordinateBounds coord [self setDirection:direction animated:NO]; } -- (double)pitch ++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingPitch { - return _mbglMap->getPitch(); + return [NSSet setWithObject:@"camera"]; } -- (void)setPitch:(double)pitch ++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingCamera { - // constrain pitch to between 0º and 60º - // - _mbglMap->setPitch(fmax(fmin(pitch, 60), 0)); + return [NSSet setWithObjects:@"longitude", @"latitude", @"centerCoordinate", @"zoomLevel", @"direction", nil]; +} + +- (MGLMapCamera *)camera +{ + CGRect frame = self.frame; + CGPoint edgePoint; + // Constrain by the shorter of the two axes. + if (frame.size.width > frame.size.height) // landscape + { + edgePoint = CGPointMake(0, frame.size.height / 2.); + } + else // portrait + { + edgePoint = CGPointMake(frame.size.width / 2., 0); + } + CLLocationCoordinate2D edgeCoordinate = [self convertPoint:edgePoint toCoordinateFromView:self]; + mbgl::ProjectedMeters edgeMeters = _mbglMap->projectedMetersForLatLng(MGLLatLngFromLocationCoordinate2D(edgeCoordinate)); - //[self notifyMapChange:(mbgl::MapChangeRegionDidChange)]; + // Because we constrain the zoom level vertically in portrait orientation, + // the visible medial span is affected by pitch: the distance from the + // center point to the near edge is less than than distance from the center + // point to the far edge. Average the two distances. + mbgl::ProjectedMeters nearEdgeMeters; + if (frame.size.width > frame.size.height) + { + nearEdgeMeters = edgeMeters; + } + else + { + CGPoint nearEdgePoint = CGPointMake(frame.size.width / 2., frame.size.height); + CLLocationCoordinate2D nearEdgeCoordinate = [self convertPoint:nearEdgePoint toCoordinateFromView:self]; + nearEdgeMeters = _mbglMap->projectedMetersForLatLng(MGLLatLngFromLocationCoordinate2D(nearEdgeCoordinate)); + } + + // The opposite side is the distance between the center and one edge. + CLLocationCoordinate2D centerCoordinate = self.centerCoordinate; + mbgl::ProjectedMeters centerMeters = _mbglMap->projectedMetersForLatLng(MGLLatLngFromLocationCoordinate2D(centerCoordinate)); + CLLocationDistance centerToEdge = std::hypot(centerMeters.easting - edgeMeters.easting, + centerMeters.northing - edgeMeters.northing); + CLLocationDistance centerToNearEdge = std::hypot(centerMeters.easting - nearEdgeMeters.easting, + centerMeters.northing - nearEdgeMeters.northing); + CLLocationDistance altitude = (centerToEdge + centerToNearEdge) / 2 / std::tan(MGLAngularFieldOfView / 2.); + + CGFloat pitch = _mbglMap->getPitch(); + + return [MGLMapCamera cameraLookingAtCenterCoordinate:centerCoordinate + fromDistance:altitude + pitch:pitch + heading:self.direction]; } -- (void)resetPitch +- (void)setCamera:(MGLMapCamera *)camera { - [self setPitch:0]; + [self setCamera:camera animated:NO]; +} + +- (void)setCamera:(MGLMapCamera *)camera animated:(BOOL)animated +{ + [self setCamera:camera withDuration:animated ? MGLAnimationDuration : 0 animationTimingFunction:nil]; +} + +- (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(CAMediaTimingFunction *)function +{ + // The opposite side is the distance between the center and one edge. + mbgl::LatLng centerLatLng = MGLLatLngFromLocationCoordinate2D(camera.centerCoordinate); + mbgl::ProjectedMeters centerMeters = _mbglMap->projectedMetersForLatLng(centerLatLng); + CLLocationDistance centerToEdge = camera.altitude * std::tan(MGLAngularFieldOfView / 2.); + + double angle = -1; + if (camera.heading >= 0) + { + angle = MGLRadiansFromDegrees(mbgl::util::wrap(-camera.heading, 0., 360.)); + } + double pitch = -1; + if (camera.pitch >= 0) + { + pitch = MGLRadiansFromDegrees(mbgl::util::clamp(camera.pitch, MGLMinimumPitch, MGLMaximumPitch)); + } + + // Make a visible bounds that extends in the constrained direction (the + // shorter of the two axes). + CGRect frame = self.frame; + mbgl::LatLng sw, ne; + if (frame.size.width > frame.size.height) // landscape + { + sw = _mbglMap->latLngForProjectedMeters({ + centerMeters.northing - centerToEdge * std::sin(angle), + centerMeters.easting - centerToEdge * std::cos(angle), + }); + ne = _mbglMap->latLngForProjectedMeters({ + centerMeters.northing + centerToEdge * std::sin(angle), + centerMeters.easting + centerToEdge * std::cos(angle), + }); + } + else // portrait + { + sw = _mbglMap->latLngForProjectedMeters({ + centerMeters.northing - centerToEdge * std::cos(angle) + centerToEdge * std::cos(angle) * std::sin(pitch) / 2, + centerMeters.easting - centerToEdge * std::sin(angle) + centerToEdge * std::sin(angle) * std::sin(pitch) / 2, + }); + ne = _mbglMap->latLngForProjectedMeters({ + centerMeters.northing + centerToEdge * std::cos(angle) - centerToEdge * std::cos(angle) * std::sin(pitch) / 2, + centerMeters.easting + centerToEdge * std::sin(angle) - centerToEdge * std::sin(angle) * std::sin(pitch) / 2, + }); + } + + // Fit the viewport to the bounds. Correct the center in case pitch should + // cause the visual center to lie above the screen center. + mbgl::CameraOptions options = _mbglMap->cameraForLatLngs({ sw, ne }, {}); + options.center = centerLatLng; + + if (angle >= 0) + { + options.angle = angle; + } + if (pitch >= 0) + { + options.pitch = pitch; + } + if (duration > 0) + { + options.duration = secondsAsDuration(duration); + options.easing = MGLUnitBezierForMediaTimingFunction(function); + } + _mbglMap->easeTo(options); } - (CLLocationCoordinate2D)convertPoint:(CGPoint)point toCoordinateFromView:(nullable UIView *)view @@ -2482,6 +2661,12 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) [self.delegate mapView:self didUpdateUserLocation:self.userLocation]; } } + + CLLocationDirection course = self.userLocation.location.course; + if (course < 0 || self.userTrackingMode != MGLUserTrackingModeFollowWithCourse) + { + course = -1; + } if (self.userTrackingMode != MGLUserTrackingModeNone) { @@ -2496,7 +2681,7 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) { // at sufficient detail, just re-center the map; don't zoom // - [self setCenterCoordinate:self.userLocation.location.coordinate animated:YES preservingTracking:YES]; + [self _setCenterCoordinate:self.userLocation.location.coordinate zoomLevel:self.zoomLevel direction:course animated:YES]; } else { @@ -2526,17 +2711,11 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) desiredSouthWest.longitude != actualSouthWest.longitude) { // assumes we won't disrupt tracking mode - [self setVisibleCoordinateBounds:MGLCoordinateBoundsMake(desiredSouthWest, desiredNorthEast) animated:YES]; + [self setVisibleCoordinateBounds:MGLCoordinateBoundsMake(desiredSouthWest, desiredNorthEast) edgePadding:UIEdgeInsetsZero direction:course animated:YES]; } } } } - - CLLocationDirection course = self.userLocation.location.course; - if (course >= 0 && self.userTrackingMode == MGLUserTrackingModeFollowWithCourse) - { - _mbglMap->setBearing(course); - } self.userLocationAnnotationView.haloLayer.hidden = ! CLLocationCoordinate2DIsValid(self.userLocation.coordinate) || newLocation.horizontalAccuracy > 10; @@ -3088,7 +3267,7 @@ class MBGLView : public mbgl::View + (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingLatitude { - return [NSSet setWithObject:@"centerCoordinate"]; + return [NSSet setWithObjects:@"centerCoordinate", @"camera", nil]; } - (double)latitude @@ -3114,7 +3293,7 @@ class MBGLView : public mbgl::View + (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingLongitude { - return [NSSet setWithObject:@"centerCoordinate"]; + return [NSSet setWithObjects:@"centerCoordinate", @"camera", nil]; } - (double)longitude diff --git a/src/mbgl/map/camera.cpp b/src/mbgl/map/camera.cpp new file mode 100644 index 000000000..4a45e904f --- /dev/null +++ b/src/mbgl/map/camera.cpp @@ -0,0 +1 @@ +#include diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index 82ab72db2..9195f6b58 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -9,6 +9,7 @@ #include #include +#include namespace mbgl { @@ -114,6 +115,18 @@ void Map::setGestureInProgress(bool inProgress) { update(Update::Repaint); } +#pragma mark - + +void Map::jumpTo(CameraOptions options) { + transform->jumpTo(options); + update(Update::Repaint); +} + +void Map::easeTo(CameraOptions options) { + transform->easeTo(options); + update(options.zoom ? Update::Zoom : Update::Repaint); +} + #pragma mark - Position void Map::moveBy(double dx, double dy, const Duration& duration) { @@ -131,9 +144,11 @@ LatLng Map::getLatLng() const { } void Map::resetPosition() { - transform->setAngle(0); - transform->setLatLng(LatLng(0, 0)); - transform->setZoom(0); + CameraOptions options; + options.angle = 0; + options.center = LatLng(0, 0); + options.zoom = 0; + transform->jumpTo(options); update(Update::Zoom); } @@ -168,25 +183,26 @@ void Map::setLatLngZoom(LatLng latLng, double zoom, const Duration& duration) { update(Update::Zoom); } -void Map::fitBounds(LatLngBounds bounds, EdgeInsets padding, const Duration& duration) { +CameraOptions Map::cameraForLatLngBounds(LatLngBounds bounds, EdgeInsets padding) { AnnotationSegment segment = { {bounds.ne.latitude, bounds.sw.longitude}, bounds.sw, {bounds.sw.latitude, bounds.ne.longitude}, bounds.ne, }; - fitBounds(segment, padding, duration); + return cameraForLatLngs(segment, padding); } -void Map::fitBounds(AnnotationSegment segment, EdgeInsets padding, const Duration& duration) { - if (segment.empty()) { - return; +CameraOptions Map::cameraForLatLngs(std::vector latLngs, EdgeInsets padding) { + CameraOptions options; + if (latLngs.empty()) { + return options; } // Calculate the bounds of the possibly rotated shape with respect to the viewport. vec2<> nePixel = {-INFINITY, -INFINITY}; vec2<> swPixel = {INFINITY, INFINITY}; - for (LatLng latLng : segment) { + for (LatLng latLng : latLngs) { vec2<> pixel = pixelForLatLng(latLng); swPixel.x = std::min(swPixel.x, pixel.x); nePixel.x = std::max(nePixel.x, pixel.x); @@ -214,7 +230,9 @@ void Map::fitBounds(AnnotationSegment segment, EdgeInsets padding, const Duratio vec2<> centerPixel = (paddedNEPixel + paddedSWPixel) * 0.5; LatLng centerLatLng = latLngForPixel(centerPixel); - setLatLngZoom(centerLatLng, zoom, duration); + options.center = centerLatLng; + options.zoom = zoom; + return options; } void Map::resetZoom() { @@ -270,8 +288,8 @@ void Map::resetNorth() { #pragma mark - Pitch -void Map::setPitch(double pitch) { - transform->setPitch(std::min(pitch, 60.0) * M_PI / 180); +void Map::setPitch(double pitch, const Duration& duration) { + transform->setPitch(util::clamp(pitch, 0., 60.) * M_PI / 180, duration); update(Update::Repaint); } diff --git a/src/mbgl/map/transform.cpp b/src/mbgl/map/transform.cpp index 60c55c3f0..bb5590914 100644 --- a/src/mbgl/map/transform.cpp +++ b/src/mbgl/map/transform.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -53,6 +54,38 @@ bool Transform::resize(const std::array size) { #pragma mark - Position +void Transform::jumpTo(const CameraOptions options) { + CameraOptions jumpOptions = options; + jumpOptions.duration.reset(); + easeTo(jumpOptions); +} + +void Transform::easeTo(CameraOptions options) { + LatLng latLng = options.center ? *options.center : getLatLng(); + double zoom = options.zoom ? *options.zoom : getZoom(); + double angle = options.angle ? *options.angle : getAngle(); + if (std::isnan(latLng.latitude) || std::isnan(latLng.longitude) || std::isnan(zoom)) { + return; + } + + double new_scale = std::pow(2.0, zoom); + + const double s = new_scale * util::tileSize; + state.Bc = s / 360; + state.Cc = s / util::M2PI; + + const double m = 1 - 1e-15; + const double f = std::fmin(std::fmax(std::sin(util::DEG2RAD * latLng.latitude), -m), m); + + double xn = -latLng.longitude * state.Bc; + double yn = 0.5 * state.Cc * std::log((1 + f) / (1 - f)); + + options.center.reset(); + options.zoom.reset(); + options.angle.reset(); + _easeTo(options, new_scale, angle, xn, yn); +} + void Transform::moveBy(const double dx, const double dy, const Duration& duration) { if (std::isnan(dx) || std::isnan(dy)) { return; @@ -62,71 +95,30 @@ void Transform::moveBy(const double dx, const double dy, const Duration& duratio } void Transform::_moveBy(const double dx, const double dy, const Duration& duration) { + double x = state.x + std::cos(state.angle) * dx + std::sin( state.angle) * dy; double y = state.y + std::cos(state.angle) * dy + std::sin(-state.angle) * dx; state.constrain(state.scale, y); - - if (duration == Duration::zero()) { - view.notifyMapChange(MapChangeRegionWillChange); - - state.x = x; - state.y = y; - - view.notifyMapChange(MapChangeRegionDidChange); - } else { - view.notifyMapChange(MapChangeRegionWillChangeAnimated); - - const double startX = state.x; - const double startY = state.y; - state.panning = true; - - startTransition( - [=](double t) { - state.x = util::interpolate(startX, x, t); - state.y = util::interpolate(startY, y, t); - view.notifyMapChange(MapChangeRegionIsChanging); - return Update::Repaint; - }, - [=] { - state.panning = false; - view.notifyMapChange(MapChangeRegionDidChangeAnimated); - }, duration); - } + + CameraOptions options; + options.duration = duration; + _easeTo(options, state.scale, state.angle, x, y); } void Transform::setLatLng(const LatLng latLng, const Duration& duration) { - if (std::isnan(latLng.latitude) || std::isnan(latLng.longitude)) { - return; - } - - const double m = 1 - 1e-15; - const double f = ::fmin(::fmax(std::sin(util::DEG2RAD * latLng.latitude), -m), m); - - double xn = -latLng.longitude * state.Bc; - double yn = 0.5 * state.Cc * std::log((1 + f) / (1 - f)); - - _setScaleXY(state.scale, xn, yn, duration); + CameraOptions options; + options.center = latLng; + options.duration = duration; + easeTo(options); } void Transform::setLatLngZoom(const LatLng latLng, const double zoom, const Duration& duration) { - if (std::isnan(latLng.latitude) || std::isnan(latLng.longitude) || std::isnan(zoom)) { - return; - } - - double new_scale = std::pow(2.0, zoom); - - const double s = new_scale * util::tileSize; - state.Bc = s / 360; - state.Cc = s / util::M2PI; - - const double m = 1 - 1e-15; - const double f = ::fmin(::fmax(std::sin(util::DEG2RAD * latLng.latitude), -m), m); - - double xn = -latLng.longitude * state.Bc; - double yn = 0.5 * state.Cc * std::log((1 + f) / (1 - f)); - - _setScaleXY(new_scale, xn, yn, duration); + CameraOptions options; + options.center = latLng; + options.zoom = zoom; + options.duration = duration; + easeTo(options); } @@ -206,13 +198,27 @@ void Transform::_setScale(double new_scale, double cx, double cy, const Duration void Transform::_setScaleXY(const double new_scale, const double xn, const double yn, const Duration& duration) { + CameraOptions options; + options.duration = duration; + _easeTo(options, new_scale, state.angle, xn, yn); +} + +void Transform::_easeTo(CameraOptions options, const double new_scale, const double new_angle, const double xn, const double yn) { + Update update = state.scale == new_scale ? Update::Repaint : Update::Zoom; double scale = new_scale; double x = xn; double y = yn; state.constrain(scale, y); + + double angle = _normalizeAngle(new_angle, state.angle); + state.angle = _normalizeAngle(state.angle, angle); + double pitch = options.pitch ? *options.pitch : state.pitch; - if (duration == Duration::zero()) { + if (!options.duration) { + options.duration = Duration::zero(); + } + if (!options.duration || *options.duration == Duration::zero()) { view.notifyMapChange(MapChangeRegionWillChange); state.scale = scale; @@ -221,18 +227,28 @@ void Transform::_setScaleXY(const double new_scale, const double xn, const doubl const double s = state.scale * util::tileSize; state.Bc = s / 360; state.Cc = s / util::M2PI; + + state.angle = angle; + state.pitch = pitch; view.notifyMapChange(MapChangeRegionDidChange); } else { view.notifyMapChange(MapChangeRegionWillChangeAnimated); const double startS = state.scale; + const double startA = state.angle; + const double startP = state.pitch; const double startX = state.x; const double startY = state.y; state.panning = true; state.scaling = true; + state.rotating = true; startTransition( + [=](double t) { + util::UnitBezier ease = options.easing ? *options.easing : util::UnitBezier(0, 0, 0.25, 1); + return ease.solve(t, 0.001); + }, [=](double t) { state.scale = util::interpolate(startS, scale, t); state.x = util::interpolate(startX, x, t); @@ -240,14 +256,17 @@ void Transform::_setScaleXY(const double new_scale, const double xn, const doubl const double s = state.scale * util::tileSize; state.Bc = s / 360; state.Cc = s / util::M2PI; + state.angle = util::wrap(util::interpolate(startA, angle, t), -M_PI, M_PI); + state.pitch = util::interpolate(startP, pitch, t); view.notifyMapChange(MapChangeRegionIsChanging); - return Update::Zoom; + return update; }, [=] { state.panning = false; state.scaling = false; + state.rotating = false; view.notifyMapChange(MapChangeRegionDidChangeAnimated); - }, duration); + }, *options.duration); } } @@ -307,7 +326,7 @@ void Transform::setAngle(const double new_angle, const double cx, const double c _moveBy(dx, dy, Duration::zero()); } - _setAngle(new_angle, Duration::zero()); + _setAngle(new_angle); if (cx >= 0 && cy >= 0) { _moveBy(-dx, -dy, Duration::zero()); @@ -315,32 +334,10 @@ void Transform::setAngle(const double new_angle, const double cx, const double c } void Transform::_setAngle(double new_angle, const Duration& duration) { - double angle = _normalizeAngle(new_angle, state.angle); - state.angle = _normalizeAngle(state.angle, angle); - - if (duration == Duration::zero()) { - view.notifyMapChange(MapChangeRegionWillChange); - - state.angle = angle; - - view.notifyMapChange(MapChangeRegionDidChange); - } else { - view.notifyMapChange(MapChangeRegionWillChangeAnimated); - - const double startA = state.angle; - state.rotating = true; - - startTransition( - [=](double t) { - state.angle = util::wrap(util::interpolate(startA, angle, t), -M_PI, M_PI); - view.notifyMapChange(MapChangeRegionIsChanging); - return Update::Repaint; - }, - [=] { - state.rotating = false; - view.notifyMapChange(MapChangeRegionDidChangeAnimated); - }, duration); - } + CameraOptions options; + options.angle = new_angle; + options.duration = duration; + easeTo(options); } double Transform::getAngle() const { @@ -349,8 +346,11 @@ double Transform::getAngle() const { #pragma mark - Pitch -void Transform::setPitch(double pitch) { - state.pitch = pitch; +void Transform::setPitch(double pitch, const Duration& duration) { + CameraOptions options; + options.pitch = pitch; + options.duration = duration; + easeTo(options); } double Transform::getPitch() const { @@ -359,7 +359,8 @@ double Transform::getPitch() const { #pragma mark - Transition -void Transform::startTransition(std::function frame, +void Transform::startTransition(std::function easing, + std::function frame, std::function finish, const Duration& duration) { if (transitionFinishFn) { @@ -369,7 +370,7 @@ void Transform::startTransition(std::function frame, transitionStart = Clock::now(); transitionDuration = duration; - transitionFrameFn = [frame, this](const TimePoint now) { + transitionFrameFn = [easing, frame, this](const TimePoint now) { float t = std::chrono::duration(now - transitionStart) / transitionDuration; if (t >= 1.0) { Update result = frame(1.0); @@ -378,8 +379,7 @@ void Transform::startTransition(std::function frame, transitionFinishFn = nullptr; return result; } else { - util::UnitBezier ease(0, 0, 0.25, 1); - return frame(ease.solve(t, 0.001)); + return frame(easing(t)); } }; diff --git a/src/mbgl/map/transform.hpp b/src/mbgl/map/transform.hpp index 167198344..56001fad8 100644 --- a/src/mbgl/map/transform.hpp +++ b/src/mbgl/map/transform.hpp @@ -2,6 +2,7 @@ #define MBGL_MAP_TRANSFORM #include +#include #include #include #include @@ -22,6 +23,9 @@ public: // Map view bool resize(std::array size); + void jumpTo(const CameraOptions options); + void easeTo(const CameraOptions options); + // Position void moveBy(double dx, double dy, const Duration& = Duration::zero()); void setLatLng(LatLng latLng, const Duration& = Duration::zero()); @@ -42,7 +46,7 @@ public: double getAngle() const; // Pitch - void setPitch(double pitch); + void setPitch(double pitch, const Duration& = Duration::zero()); double getPitch() const; // Transitions @@ -60,13 +64,16 @@ private: void _moveBy(double dx, double dy, const Duration& = Duration::zero()); void _setScale(double scale, double cx, double cy, const Duration& = Duration::zero()); void _setScaleXY(double new_scale, double xn, double yn, const Duration& = Duration::zero()); + void _easeTo(CameraOptions options, const double new_scale, const double new_angle, + const double xn, const double yn); void _setAngle(double angle, const Duration& = Duration::zero()); View &view; TransformState state; - void startTransition(std::function frame, + void startTransition(std::function easing, + std::function frame, std::function finish, const Duration& duration); From 7b931078695b6c69ceb6981f9034d7df1227a477 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguy=E1=BB=85n?= Date: Fri, 4 Sep 2015 15:51:38 -0700 Subject: [PATCH 04/29] Corrected roundtripping altitude with rotation --- platform/ios/MGLMapView.mm | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index ef6ae52c3..e509b5c8d 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -1825,7 +1825,7 @@ mbgl::LatLngBounds MGLLatLngBoundsFromCoordinateBounds(MGLCoordinateBounds coord double angle = -1; if (camera.heading >= 0) { - angle = MGLRadiansFromDegrees(mbgl::util::wrap(-camera.heading, 0., 360.)); + angle = MGLRadiansFromDegrees(-camera.heading); } double pitch = -1; if (camera.pitch >= 0) @@ -1851,12 +1851,12 @@ mbgl::LatLngBounds MGLLatLngBoundsFromCoordinateBounds(MGLCoordinateBounds coord else // portrait { sw = _mbglMap->latLngForProjectedMeters({ - centerMeters.northing - centerToEdge * std::cos(angle) + centerToEdge * std::cos(angle) * std::sin(pitch) / 2, - centerMeters.easting - centerToEdge * std::sin(angle) + centerToEdge * std::sin(angle) * std::sin(pitch) / 2, + centerMeters.northing - centerToEdge * std::cos(-angle) + centerToEdge * std::cos(-angle) * std::sin(pitch) / 2, + centerMeters.easting - centerToEdge * std::sin(-angle) + centerToEdge * std::sin(-angle) * std::sin(pitch) / 2, }); ne = _mbglMap->latLngForProjectedMeters({ - centerMeters.northing + centerToEdge * std::cos(angle) - centerToEdge * std::cos(angle) * std::sin(pitch) / 2, - centerMeters.easting + centerToEdge * std::sin(angle) - centerToEdge * std::sin(angle) * std::sin(pitch) / 2, + centerMeters.northing + centerToEdge * std::cos(-angle) - centerToEdge * std::cos(-angle) * std::sin(pitch) / 2, + centerMeters.easting + centerToEdge * std::sin(-angle) - centerToEdge * std::sin(-angle) * std::sin(pitch) / 2, }); } @@ -1865,7 +1865,7 @@ mbgl::LatLngBounds MGLLatLngBoundsFromCoordinateBounds(MGLCoordinateBounds coord mbgl::CameraOptions options = _mbglMap->cameraForLatLngs({ sw, ne }, {}); options.center = centerLatLng; - if (angle >= 0) + if (camera.heading >= 0) { options.angle = angle; } From 04f486cd7a11d0a6aa0908c112df4cedad85cf89 Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Fri, 28 Aug 2015 20:12:33 -0700 Subject: [PATCH 05/29] #894 - Basic port of InfoWindow --- .../mapboxgl/annotations/Annotation.java | 3 +- .../mapboxgl/annotations/InfoWindow.java | 189 ++++++++++++++++++ .../annotations/InfoWindowTipView.java | 40 ++++ .../mapbox/mapboxgl/annotations/Marker.java | 55 +++-- .../src/main/res/layout/infowindow.xml | 52 +++++ .../src/main/res/values/colors.xml | 5 + .../src/main/res/values/strings.xml | 3 + 7 files changed, 332 insertions(+), 15 deletions(-) create mode 100644 android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindow.java create mode 100644 android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindowTipView.java create mode 100644 android/java/MapboxGLAndroidSDK/src/main/res/layout/infowindow.xml create mode 100644 android/java/MapboxGLAndroidSDK/src/main/res/values/colors.xml diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/Annotation.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/Annotation.java index 4f172c6e2..c4dea205e 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/Annotation.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/Annotation.java @@ -2,7 +2,6 @@ package com.mapbox.mapboxgl.annotations; import com.mapbox.mapboxgl.views.MapView; - public abstract class Annotation { /** @@ -11,7 +10,7 @@ public abstract class Annotation { * Internal C++ id is stored as unsigned int. */ protected long id = -1; // -1 unless added to a MapView - private MapView mapView; + protected MapView mapView; float alpha = 1.0f; boolean visible = true; diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindow.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindow.java new file mode 100644 index 000000000..924012db5 --- /dev/null +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindow.java @@ -0,0 +1,189 @@ +package com.mapbox.mapboxgl.annotations; + +import android.content.Context; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import com.mapbox.mapboxgl.geometry.LatLng; +import com.mapbox.mapboxgl.views.MapView; + +/** + * A tooltip view + */ +public class InfoWindow { + + private Marker boundMarker; + private MapView mMapView; + private boolean mIsVisible; + protected View mView; + + static int mTitleId = 0; + static int mDescriptionId = 0; + static int mSubDescriptionId = 0; + static int mImageId = 0; + + public InfoWindow(int layoutResId, MapView mapView) { + mMapView = mapView; + mIsVisible = false; + ViewGroup parent = (ViewGroup) mapView.getParent(); + Context context = mapView.getContext(); + LayoutInflater inflater = + (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mView = inflater.inflate(layoutResId, parent, false); + + if (mTitleId == 0) { + setResIds(mapView.getContext()); + } + + // default behavior: close it when clicking on the tooltip: + setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent e) { + if (e.getAction() == MotionEvent.ACTION_UP) { + close(); + } + return true; + } + }); + } + + public InfoWindow(View view, MapView mapView) { + mMapView = mapView; + mIsVisible = false; + ViewGroup parent = (ViewGroup) mapView.getParent(); + Context context = mapView.getContext(); + LayoutInflater inflater = + (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mView = view; + + // default behavior: close it when clicking on the tooltip: + setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent e) { + if (e.getAction() == MotionEvent.ACTION_UP) { + close(); + } + return true; + } + }); + } + + /** + * open the window at the specified position. + * + * @param object the graphical object on which is hooked the view + * @param position to place the window on the map + * @param offsetX (&offsetY) the offset of the view to the position, in pixels. + * This allows to offset the view from the object position. + * @return this infowindow + */ + public InfoWindow open(Marker object, LatLng position, int offsetX, int offsetY) { + onOpen(object); + MapView.LayoutParams lp = new MapView.LayoutParams(MapView.LayoutParams.WRAP_CONTENT, + MapView.LayoutParams.WRAP_CONTENT, Gravity.CENTER|Gravity.BOTTOM); + close(); //if it was already opened + mMapView.addView(mView, lp); + mIsVisible = true; + return this; + } + + /** + * Close this InfoWindow if it is visible, otherwise don't do anything. + * + * @return this info window + */ + public InfoWindow close() { + if (mIsVisible) { + mIsVisible = false; + ((ViewGroup) mView.getParent()).removeView(mView); +// this.boundMarker.blur(); + setBoundMarker(null); + onClose(); + } + return this; + } + + /** + * Returns the Android view. This allows to set its content. + * + * @return the Android view + */ + public View getView() { + return mView; + } + + /** + * Returns the mapView this InfoWindow is bound to + * + * @return the mapView + */ + public MapView getMapView() { + return mMapView; + } + + /** + * Constructs the view that is displayed when the InfoWindow opens. + * This retrieves data from overlayItem and shows it in the tooltip. + * + * @param overlayItem the tapped overlay item + */ + public void onOpen(Marker overlayItem) { + String title = overlayItem.getTitle(); + ((TextView) mView.findViewById(mTitleId /*R.id.title*/)).setText(title); + String snippet = overlayItem.getSnippet(); + ((TextView) mView.findViewById(mDescriptionId /*R.id.description*/)).setText(snippet); + +/* + //handle sub-description, hiding or showing the text view: + TextView subDescText = (TextView) mView.findViewById(mSubDescriptionId); + String subDesc = overlayItem.getSubDescription(); + if ("".equals(subDesc)) { + subDescText.setVisibility(View.GONE); + } else { + subDescText.setText(subDesc); + subDescText.setVisibility(View.VISIBLE); + } +*/ + } + + public void onClose() { + //by default, do nothing + } + + public InfoWindow setBoundMarker(Marker aBoundMarker) { + this.boundMarker = aBoundMarker; + return this; + } + + public Marker getBoundMarker() { + return boundMarker; + } + + /** + * Given a context, set the resource ids for the layout + * of the InfoWindow. + * + * @param context + */ + private static void setResIds(Context context) { + String packageName = context.getPackageName(); //get application package name + mTitleId = context.getResources().getIdentifier("id/infowindow_title", null, packageName); + mDescriptionId = + context.getResources().getIdentifier("id/infowindow_description", null, packageName); + mSubDescriptionId = context.getResources() + .getIdentifier("id/infowindow_subdescription", null, packageName); + mImageId = context.getResources().getIdentifier("id/infowindow_image", null, packageName); + } + + /** + * Use to override default touch events handling on InfoWindow (ie, close automatically) + * + * @param listener New View.OnTouchListener to use + */ + public void setOnTouchListener(View.OnTouchListener listener) { + mView.setOnTouchListener(listener); + } +} diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindowTipView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindowTipView.java new file mode 100644 index 000000000..657e63a84 --- /dev/null +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindowTipView.java @@ -0,0 +1,40 @@ +package com.mapbox.mapboxgl.annotations; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.util.AttributeSet; +import android.view.View; + +public class InfoWindowTipView extends View { + + private Paint mPaint; + private Path mPath; + + public InfoWindowTipView(Context context, AttributeSet attrs) { + super(context, attrs); + + mPath = new Path(); + + this.mPaint = new Paint(); + this.mPaint.setColor(Color.WHITE); + this.mPaint.setAntiAlias(true); + this.mPaint.setStrokeWidth(0.0f); + this.mPaint.setStyle(Paint.Style.FILL); + } + + @Override + protected void onDraw(Canvas canvas) { + int height = getMeasuredHeight(); + int width = getMeasuredWidth(); + + mPath.rewind(); + mPath.moveTo((width / 2) - height, 0); + mPath.lineTo((width / 2) + height, 0); + mPath.lineTo((width / 2), height); + mPath.lineTo((width / 2) - height, 0); + canvas.drawPath(mPath, this.mPaint); + } +} diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/Marker.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/Marker.java index 515852ae6..4776c37bf 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/Marker.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/Marker.java @@ -1,7 +1,8 @@ package com.mapbox.mapboxgl.annotations; +import android.graphics.Point; import com.mapbox.mapboxgl.geometry.LatLng; - +import com.mapbox.mapboxgl.views.R; public class Marker extends Annotation { @@ -16,10 +17,16 @@ public class Marker extends Annotation { String snippet; String sprite = "default_marker"; String title; + private InfoWindow infoWindow = null; private boolean infoWindowShown = false; - public Marker() {} + /** + * Constructor + */ + public Marker() { + super(); + } /** * If two markers have the same LatLng, they are equal. @@ -41,6 +48,10 @@ public class Marker extends Annotation { return alpha; } + public Point getAnchor() { + return new Point((int)anchorU, (int)anchorV); + } + public float getAnchorU() { return anchorU; } @@ -66,7 +77,7 @@ public class Marker extends Annotation { } public void hideInfoWindow() { - //TODO hideInfoWindow + infoWindow.close(); infoWindowShown = false; } @@ -82,33 +93,33 @@ public class Marker extends Annotation { return infoWindowShown; } - void setAnchor(float u, float v) { + public void setAnchor(float u, float v) { this.anchorU = u; this.anchorV = v; } - void setDraggable(boolean draggable) { + public void setDraggable(boolean draggable) { this.draggable = draggable; } - void setFlat(boolean flat) { + public void setFlat(boolean flat) { this.flat = flat; } - void setInfoWindowAnchor(float u, float v) { + public void setInfoWindowAnchor(float u, float v) { infoWindowAnchorU = u; infoWindowAnchorV = v; } - void setPosition(LatLng latlng) { + public void setPosition(LatLng latlng) { this.position = position; } - void setRotation(float rotation) { + public void setRotation(float rotation) { this.rotation = rotation; } - void setSnippet(String snippet) { + public void setSnippet(String snippet) { this.snippet = snippet; } @@ -120,18 +131,36 @@ public class Marker extends Annotation { * * @param sprite */ - void setSprite(String sprite) { + public void setSprite(String sprite) { this.sprite = sprite; } - void setTitle(String title) { + public void setTitle(String title) { this.title = title; } - void showInfoWindow() { + public void showInfoWindow() { + + if (!isVisible() || mapView == null) { + return; + } + + if (infoWindow == null) { + infoWindow = new InfoWindow(R.layout.infowindow, mapView); + } + + infoWindow.open(this, getPosition(), (int)anchorU, (int)anchorV); + infoWindow.setBoundMarker(this); infoWindowShown = true; } + @Override + public void setVisible(boolean visible) { + super.setVisible(visible); + if (!visible && infoWindowShown) { + hideInfoWindow(); + } + } // TODO Method in Google Maps Android API // public int hashCode() diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/layout/infowindow.xml b/android/java/MapboxGLAndroidSDK/src/main/res/layout/infowindow.xml new file mode 100644 index 000000000..af05e2fd4 --- /dev/null +++ b/android/java/MapboxGLAndroidSDK/src/main/res/layout/infowindow.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/values/colors.xml b/android/java/MapboxGLAndroidSDK/src/main/res/values/colors.xml new file mode 100644 index 000000000..bf3f48431 --- /dev/null +++ b/android/java/MapboxGLAndroidSDK/src/main/res/values/colors.xml @@ -0,0 +1,5 @@ + + + #FFFFFF + #000000 + diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/values/strings.xml b/android/java/MapboxGLAndroidSDK/src/main/res/values/strings.xml index df022a467..ff91a160d 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/res/values/strings.xml +++ b/android/java/MapboxGLAndroidSDK/src/main/res/values/strings.xml @@ -1,4 +1,7 @@ Map compass. Click to reset the map rotation to North. + Title + Description + Address From 10bbfe01641fb0465549e128c8a99502221e5db7 Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Fri, 28 Aug 2015 20:18:21 -0700 Subject: [PATCH 06/29] #894 - Opening InfoWindow on long press in TestApp --- .../src/main/java/com/mapbox/mapboxgl/views/MapView.java | 1 - .../src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index 3718a567a..829e3fe2d 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -26,7 +26,6 @@ import android.support.v4.content.ContextCompat; import android.support.v4.view.GestureDetectorCompat; import android.support.v4.view.ScaleGestureDetectorCompat; import android.text.TextUtils; -import android.util.Log; import android.view.GestureDetector; import android.view.Gravity; import android.view.ScaleGestureDetector; diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java index a4358c773..64832f44c 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java @@ -121,7 +121,7 @@ public class MainActivity extends AppCompatActivity { mMapView.addMarker(new MarkerOptions() .position(position) .title("Dropped Pin") - .sprite("default_marker")); + .sprite("default_marker")).showInfoWindow(); } }); From 43107687ae37151bb32a1e9fc50f4dacca91d914 Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Wed, 2 Sep 2015 14:50:47 -0500 Subject: [PATCH 07/29] #894 - Placement of InfoWindow to marker location base --- .../com/mapbox/mapboxgl/annotations/InfoWindow.java | 12 +++++++++--- .../main/java/com/mapbox/mapboxgl/views/MapView.java | 3 +-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindow.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindow.java index 924012db5..f646386f4 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindow.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindow.java @@ -1,7 +1,7 @@ package com.mapbox.mapboxgl.annotations; import android.content.Context; -import android.view.Gravity; +import android.graphics.PointF; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; @@ -82,8 +82,14 @@ public class InfoWindow { */ public InfoWindow open(Marker object, LatLng position, int offsetX, int offsetY) { onOpen(object); - MapView.LayoutParams lp = new MapView.LayoutParams(MapView.LayoutParams.WRAP_CONTENT, - MapView.LayoutParams.WRAP_CONTENT, Gravity.CENTER|Gravity.BOTTOM); + MapView.LayoutParams lp = new MapView.LayoutParams(MapView.LayoutParams.WRAP_CONTENT, MapView.LayoutParams.WRAP_CONTENT); + PointF coords = mMapView.toScreenLocation(position); + + // Flip y coordinate as Android view origin is upper left corner + coords.y = mMapView.getHeight() - coords.y; + lp.leftMargin = (int) coords.x; + lp.topMargin = (int) coords.y; + close(); //if it was already opened mMapView.addView(mView, lp); mIsVisible = true; diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index 829e3fe2d..98abda1d0 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -1,6 +1,5 @@ package com.mapbox.mapboxgl.views; -import android.annotation.TargetApi; import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.Context; @@ -1476,7 +1475,7 @@ public class MapView extends FrameLayout implements LocationListener { // Called for events that don't fit the other handlers // such as mouse scroll events, mouse moves, joystick, trackpad - @Override @TargetApi(12) + @Override public boolean onGenericMotionEvent(MotionEvent event) { // Mouse events //if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) { // this is not available before API 18 From 1644f51873b6977dd6018cca9d967d34e6876be8 Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Thu, 3 Sep 2015 11:52:38 -0500 Subject: [PATCH 08/29] #894 - Wiring up getTopOffsetPixelsForAnnotationSymbol in JNI --- android/cpp/jni.cpp | 10 ++++++++++ .../java/com/mapbox/mapboxgl/views/NativeMapView.java | 8 ++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/android/cpp/jni.cpp b/android/cpp/jni.cpp index 34bef770d..140cc9f3c 100644 --- a/android/cpp/jni.cpp +++ b/android/cpp/jni.cpp @@ -1227,6 +1227,14 @@ jobject JNICALL nativeLatLngForPixel(JNIEnv *env, jobject obj, jlong nativeMapVi return ret; } +jdouble JNICALL nativeGetTopOffsetPixelsForAnnotationSymbol(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jstring symbolName) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetTopOffsetPixelsForAnnotationSymbol"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + return nativeMapView->getMap().getTopOffsetPixelsForAnnotationSymbol(std_string_from_jstring(env, symbolName)); +} + + } extern "C" { @@ -1708,6 +1716,8 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { reinterpret_cast(&nativePixelForLatLng)}, {"nativeLatLngForPixel", "(JLandroid/graphics/PointF;)Lcom/mapbox/mapboxgl/geometry/LatLng;", reinterpret_cast(&nativeLatLngForPixel)}, + {"nativeGetTopOffsetPixelsForAnnotationSymbol", "(JLjava/lang/String;)D", + reinterpret_cast(&nativeGetTopOffsetPixelsForAnnotationSymbol)}, }; if (env->RegisterNatives(nativeMapViewClass, methods.data(), methods.size()) < 0) { diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/NativeMapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/NativeMapView.java index ca5704987..121402dcf 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/NativeMapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/NativeMapView.java @@ -3,7 +3,6 @@ package com.mapbox.mapboxgl.views; import android.graphics.PointF; import android.view.Surface; -import com.mapbox.mapboxgl.annotations.Annotation; import com.mapbox.mapboxgl.annotations.Marker; import com.mapbox.mapboxgl.annotations.Polygon; import com.mapbox.mapboxgl.annotations.Polyline; @@ -12,7 +11,6 @@ import com.mapbox.mapboxgl.geometry.LatLng; import com.mapbox.mapboxgl.geometry.LatLngZoom; import com.mapbox.mapboxgl.geometry.ProjectedMeters; -import java.util.ArrayList; import java.util.List; // Class that wraps the native methods for convenience @@ -424,6 +422,10 @@ class NativeMapView { return nativeLatLngForPixel(mNativeMapViewPtr, pixel); } + public double getTopOffsetPixelsForAnnotationSymbol(String symbolName) { + return nativeGetTopOffsetPixelsForAnnotationSymbol(mNativeMapViewPtr, symbolName); + } + // // Callbacks // @@ -606,4 +608,6 @@ class NativeMapView { private native PointF nativePixelForLatLng(long nativeMapViewPtr, LatLng latLng); private native LatLng nativeLatLngForPixel(long nativeMapViewPtr, PointF pixel); + + private native double nativeGetTopOffsetPixelsForAnnotationSymbol(long nativeMapViewPtr, String symbolName); } From c094ae46ac0a78995cc7c5fc04207a96a34f8c00 Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Thu, 3 Sep 2015 13:55:49 -0500 Subject: [PATCH 09/29] #894 - Initial port of iOS method for aligning infowindows over markers --- .../java/com/mapbox/mapboxgl/annotations/InfoWindow.java | 6 ++++-- .../src/main/java/com/mapbox/mapboxgl/views/MapView.java | 6 +++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindow.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindow.java index f646386f4..2085eb081 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindow.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindow.java @@ -85,10 +85,12 @@ public class InfoWindow { MapView.LayoutParams lp = new MapView.LayoutParams(MapView.LayoutParams.WRAP_CONTENT, MapView.LayoutParams.WRAP_CONTENT); PointF coords = mMapView.toScreenLocation(position); + double y = mMapView.getTopOffsetPixelsForAnnotationSymbol(object.sprite); + // Flip y coordinate as Android view origin is upper left corner coords.y = mMapView.getHeight() - coords.y; - lp.leftMargin = (int) coords.x; - lp.topMargin = (int) coords.y; + lp.leftMargin = (int) coords.x - 1; + lp.topMargin = (int) coords.y + (int) y; close(); //if it was already opened mMapView.addView(mView, lp); diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index 98abda1d0..4aeb4ee06 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -728,7 +728,11 @@ public class MapView extends FrameLayout implements LocationListener { return new PointF(point.x * mScreenDensity, point.y * mScreenDensity); } - // + public double getTopOffsetPixelsForAnnotationSymbol(@NonNull String symbolName) { + return mNativeMapView.getTopOffsetPixelsForAnnotationSymbol(symbolName); + } + + // // Lifecycle events // From f181f257f7961174670b2e369b8b8c2addb011b1 Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Thu, 3 Sep 2015 16:00:27 -0500 Subject: [PATCH 10/29] #894 - Updating Android Support Libraries to 23.0.1 --- android/java/MapboxGLAndroidSDK/build.gradle | 6 +++--- android/java/MapboxGLAndroidSDKTestApp/build.gradle | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/android/java/MapboxGLAndroidSDK/build.gradle b/android/java/MapboxGLAndroidSDK/build.gradle index ea0be4fa1..00ea98820 100644 --- a/android/java/MapboxGLAndroidSDK/build.gradle +++ b/android/java/MapboxGLAndroidSDK/build.gradle @@ -28,9 +28,9 @@ repositories { } dependencies { - compile 'com.android.support:support-annotations:23.0.0' - compile 'com.android.support:support-v4:23.0.0' - compile 'com.android.support:design:23.0.0' + compile 'com.android.support:support-annotations:23.0.1' + compile 'com.android.support:support-v4:23.0.1' + compile 'com.android.support:design:23.0.1' compile 'com.squareup.okhttp:okhttp:2.4.0' compile 'com.mapzen.android:lost:1.0.1' } diff --git a/android/java/MapboxGLAndroidSDKTestApp/build.gradle b/android/java/MapboxGLAndroidSDKTestApp/build.gradle index b0c0f1a3e..84f627d9e 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/build.gradle +++ b/android/java/MapboxGLAndroidSDKTestApp/build.gradle @@ -82,10 +82,10 @@ dependencies { compile(project(':MapboxGLAndroidSDK')) { transitive = true } - compile 'com.android.support:support-annotations:23.0.0' - compile 'com.android.support:support-v4:23.0.0' - compile 'com.android.support:appcompat-v7:23.0.0' - compile 'com.android.support:design:23.0.0' + compile 'com.android.support:support-annotations:23.0.1' + compile 'com.android.support:support-v4:23.0.1' + compile 'com.android.support:appcompat-v7:23.0.1' + compile 'com.android.support:design:23.0.1' compile('com.crashlytics.sdk.android:crashlytics:2.5.1@aar') { transitive = true; } From e52aa940de1a67f6321044ea666a68a1008e297a Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Thu, 3 Sep 2015 16:15:43 -0500 Subject: [PATCH 11/29] #894 - Getting InfoWindow view to measure itself before being added so that placement can be calculated --- .../java/com/mapbox/mapboxgl/annotations/InfoWindow.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindow.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindow.java index 2085eb081..55bf3d4f7 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindow.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindow.java @@ -83,14 +83,15 @@ public class InfoWindow { public InfoWindow open(Marker object, LatLng position, int offsetX, int offsetY) { onOpen(object); MapView.LayoutParams lp = new MapView.LayoutParams(MapView.LayoutParams.WRAP_CONTENT, MapView.LayoutParams.WRAP_CONTENT); - PointF coords = mMapView.toScreenLocation(position); + mView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); - double y = mMapView.getTopOffsetPixelsForAnnotationSymbol(object.sprite); + PointF coords = mMapView.toScreenLocation(position); +// double y = mMapView.getTopOffsetPixelsForAnnotationSymbol(object.sprite); // Flip y coordinate as Android view origin is upper left corner coords.y = mMapView.getHeight() - coords.y; - lp.leftMargin = (int) coords.x - 1; - lp.topMargin = (int) coords.y + (int) y; + lp.leftMargin = (int) coords.x - (mView.getMeasuredWidth() / 2); + lp.topMargin = (int) coords.y - (mView.getMeasuredHeight()); close(); //if it was already opened mMapView.addView(mView, lp); From f3962b278f533bf767b348047dd011ff5e1bf8af Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Thu, 3 Sep 2015 16:25:54 -0500 Subject: [PATCH 12/29] #894 - Placing InfoWindow directly on top of Marker sprite --- .../com/mapbox/mapboxgl/annotations/InfoWindow.java | 6 ++++-- .../main/java/com/mapbox/mapboxgl/views/MapView.java | 10 +++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindow.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindow.java index 55bf3d4f7..c48227501 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindow.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindow.java @@ -86,12 +86,14 @@ public class InfoWindow { mView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); PointF coords = mMapView.toScreenLocation(position); -// double y = mMapView.getTopOffsetPixelsForAnnotationSymbol(object.sprite); + double y = mMapView.getTopOffsetPixelsForAnnotationSymbol(object.sprite); + y = y * mMapView.getScreenDensity(); // Flip y coordinate as Android view origin is upper left corner coords.y = mMapView.getHeight() - coords.y; lp.leftMargin = (int) coords.x - (mView.getMeasuredWidth() / 2); - lp.topMargin = (int) coords.y - (mView.getMeasuredHeight()); + // Add y because it's a negative value + lp.topMargin = (int) coords.y - mView.getMeasuredHeight() + (int) y; close(); //if it was already opened mMapView.addView(mView, lp); diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index 4aeb4ee06..19a9a755b 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -732,7 +732,15 @@ public class MapView extends FrameLayout implements LocationListener { return mNativeMapView.getTopOffsetPixelsForAnnotationSymbol(symbolName); } - // + /** + * Common Screen Density + * @return Screen Density + */ + public float getScreenDensity() { + return mScreenDensity; + } + + // // Lifecycle events // From d256087f046e76b6bb92bd80b6143b6fd256390e Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Fri, 4 Sep 2015 10:26:04 -0500 Subject: [PATCH 13/29] #894 - Fixing imports from merge from master --- .../src/main/java/com/mapbox/mapboxgl/views/MapView.java | 3 ++- .../src/main/java/com/mapbox/mapboxgl/views/NativeMapView.java | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index 19a9a755b..ba176fcdd 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -25,6 +25,7 @@ import android.support.v4.content.ContextCompat; import android.support.v4.view.GestureDetectorCompat; import android.support.v4.view.ScaleGestureDetectorCompat; import android.text.TextUtils; +import android.util.Log; import android.view.GestureDetector; import android.view.Gravity; import android.view.ScaleGestureDetector; @@ -521,7 +522,7 @@ public class MapView extends FrameLayout implements LocationListener { } } for(int i = 0; i < annotations.size(); i++) { - Log.d(TAG, "tapped: " + Long.toString(annotations.get(i).getId())); + Log.d(TAG, "tapped: " + annotations.get(i).getId()); } return annotations; } diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/NativeMapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/NativeMapView.java index 121402dcf..3cf56a8de 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/NativeMapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/NativeMapView.java @@ -10,7 +10,6 @@ import com.mapbox.mapboxgl.geometry.BoundingBox; import com.mapbox.mapboxgl.geometry.LatLng; import com.mapbox.mapboxgl.geometry.LatLngZoom; import com.mapbox.mapboxgl.geometry.ProjectedMeters; - import java.util.List; // Class that wraps the native methods for convenience From 1a21a0951a521f91c4d257d20738c9b7ba0dfa48 Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Fri, 4 Sep 2015 13:04:29 -0500 Subject: [PATCH 14/29] #894 - Closing InfoWindows when the map moves as alerted to via MapChange events --- .../java/com/mapbox/mapboxgl/views/MapView.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index ba176fcdd..e20e5abda 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -1894,5 +1894,18 @@ public class MapView extends FrameLayout implements LocationListener { mGpsMarker.setVisibility(View.INVISIBLE); } } + + if (change.equals(MapChange.MapChangeRegionWillChange) || change.equals(MapChange.MapChangeRegionWillChangeAnimated)) { + // Close any open InfoWindows + for (Annotation annotation : mAnnotations) { + if (annotation instanceof Marker) { + Marker marker = (Marker) annotation; + if (marker.isInfoWindowShown()) { + marker.hideInfoWindow(); + } + } + } + } + } } From f487fa1d5cb13e1c462adc225ad8e29d007c7e90 Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Fri, 4 Sep 2015 13:13:36 -0500 Subject: [PATCH 15/29] #894 - Adding snippet text to marker --- .../src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java | 1 + 1 file changed, 1 insertion(+) diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java index 64832f44c..de52a31a4 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java @@ -121,6 +121,7 @@ public class MainActivity extends AppCompatActivity { mMapView.addMarker(new MarkerOptions() .position(position) .title("Dropped Pin") + .snippet("Some snippet text.") .sprite("default_marker")).showInfoWindow(); } }); From 4837a247e5287a47510e80a05697a5020693513f Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Fri, 4 Sep 2015 16:46:11 -0500 Subject: [PATCH 16/29] #894 - Initial port of selected annotation api --- .../mapboxgl/annotations/Annotation.java | 33 ++++- .../com/mapbox/mapboxgl/views/MapView.java | 126 ++++++++++++++---- 2 files changed, 131 insertions(+), 28 deletions(-) diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/Annotation.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/Annotation.java index c4dea205e..9cddf9984 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/Annotation.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/Annotation.java @@ -2,7 +2,7 @@ package com.mapbox.mapboxgl.annotations; import com.mapbox.mapboxgl.views.MapView; -public abstract class Annotation { +public abstract class Annotation implements Comparable { /** * The annotation id @@ -59,4 +59,35 @@ public abstract class Annotation { // public void setZIndex(float zIndex) { // // } + + + @Override + public boolean equals(Object o) { + if (o == null) { + return false; + } + + if (o instanceof Annotation) { + Annotation comp = (Annotation) o; + return id == comp.id; + } + return false; + } + + @Override + public int compareTo(Annotation annotation) { + + if (annotation == null) { + return -1; + } + + if (id < annotation.getId()) { + return 1; + } else if (id > annotation.getId()) { + return -1; + } + + // Equal + return 0; + } } diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index e20e5abda..b3172f5e3 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -98,6 +98,8 @@ public class MapView extends FrameLayout implements LocationListener { * Every annotation that has been added to the map. */ private List mAnnotations = new ArrayList<>(); + private List mAnnotationsNearLastTap = new ArrayList<>(); + private Annotation mSelectedAnnotation = null; // // Instance members @@ -436,6 +438,7 @@ public class MapView extends FrameLayout implements LocationListener { marker.setId(id); // the annotation needs to know its id marker.setMapView(this); // the annotation needs to know which map view it is in mAnnotations.add(marker); + selectAnnotation(marker); return marker; } @@ -971,6 +974,86 @@ public class MapView extends FrameLayout implements LocationListener { break; case MotionEvent.ACTION_UP: + + // Open / Close InfoWindow + PointF tapPoint = new PointF(event.getX(), event.getY()); + + float toleranceWidth = 60 * mScreenDensity; + float toleranceHeight = 80 * mScreenDensity; + + PointF tr = new PointF(tapPoint.x + toleranceWidth / 2, tapPoint.y + 2 * toleranceHeight / 3); + PointF bl = new PointF(tapPoint.x - toleranceWidth / 2, tapPoint.y - 1 * toleranceHeight / 3); + + LatLng sw = fromScreenLocation(bl); + LatLng ne = fromScreenLocation(tr); + + BoundingBox bbox = new BoundingBox(ne, sw); + + List nearbyAnnotations = getAnnotationsInBounds(bbox); + + long newSelectedAnnotationID = -1; + + if (nearbyAnnotations.size() > 0) { + + // there is at least one nearby annotation; select one + // + // first, sort for comparison and iteration + Collections.sort(nearbyAnnotations); + + if (nearbyAnnotations == mAnnotationsNearLastTap) + { + // the selection candidates haven't changed; cycle through them + if (mSelectedAnnotation != null && (mSelectedAnnotation.getId() == mAnnotationsNearLastTap.get(mAnnotationsNearLastTap.size() - 1).getId())) + { + // the selected annotation is the last in the set; cycle back to the first + // note: this could be the selected annotation if only one in set + newSelectedAnnotationID = mAnnotationsNearLastTap.get(0).getId(); + } + else if (mSelectedAnnotation != null) + { + // otherwise increment the selection through the candidates + long currentID = mSelectedAnnotation.getId(); + long result = mAnnotationsNearLastTap.indexOf(mSelectedAnnotation); + newSelectedAnnotationID = mAnnotationsNearLastTap.get((int) result + 1).getId(); + } + else + { + // no current selection; select the first one + newSelectedAnnotationID = mAnnotationsNearLastTap.get(0).getId(); + } + } + else + { + // start tracking a new set of nearby annotations + mAnnotationsNearLastTap = nearbyAnnotations; + + // select the first one + newSelectedAnnotationID = mAnnotationsNearLastTap.get(0).getId(); + } + + } else { + // there are no nearby annotations; deselect if necessary + newSelectedAnnotationID = -1; + } + + if (newSelectedAnnotationID > 0) { + + for (Annotation annotation : mAnnotations) { + if (annotation.getId() == newSelectedAnnotationID) { + if (annotation.getId() == mSelectedAnnotation.getId()) { + selectAnnotation(annotation); + } + break; + } + } + + } else { + // deselect any selected annotation + if (mSelectedAnnotation != null) { + deselectAnnotation(); + } + } + // First pointer up long tapInterval = event.getEventTime() - event.getDownTime(); boolean isTap = tapInterval <= ViewConfiguration.getTapTimeout(); @@ -1029,25 +1112,7 @@ public class MapView extends FrameLayout implements LocationListener { public boolean onSingleTapUp(MotionEvent e) { // Cancel any animation mNativeMapView.cancelTransitions(); - - // Select or deselect point annotations - PointF tapPoint = new PointF(e.getX(), e.getY()); - - float toleranceWidth = 60 * mScreenDensity; - float toleranceHeight = 80 * mScreenDensity; - - PointF tr = new PointF(tapPoint.x + toleranceWidth / 2, tapPoint.y + 2 * toleranceHeight / 3); - PointF bl = new PointF(tapPoint.x - toleranceWidth / 2, tapPoint.y - 1 * toleranceHeight / 3); - - LatLng sw = fromScreenLocation(bl); - LatLng ne = fromScreenLocation(tr); - - BoundingBox bbox = new BoundingBox(ne, sw); - - List annotations = getAnnotationsInBounds(bbox); - performClick(); - return true; } @@ -1896,16 +1961,23 @@ public class MapView extends FrameLayout implements LocationListener { } if (change.equals(MapChange.MapChangeRegionWillChange) || change.equals(MapChange.MapChangeRegionWillChangeAnimated)) { - // Close any open InfoWindows - for (Annotation annotation : mAnnotations) { - if (annotation instanceof Marker) { - Marker marker = (Marker) annotation; - if (marker.isInfoWindowShown()) { - marker.hideInfoWindow(); - } - } - } + deselectAnnotation(); } } + + private void selectAnnotation(Annotation annotation) { + + // Need to deselect any currently selected annotation first + + } + + private void deselectAnnotation() { + if (mSelectedAnnotation != null && mSelectedAnnotation instanceof Marker) { + Marker marker = (Marker) mSelectedAnnotation; + if (marker.isInfoWindowShown()) { + marker.hideInfoWindow(); + } + } + } } From 902b80888f0c4da771b0309eeb4ec4d98e5d2624 Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Fri, 4 Sep 2015 17:18:57 -0500 Subject: [PATCH 17/29] #894 - Initial port of selectAnnotation and removing auto display of InfoWindow on long press marker addition in TestApp --- .../java/com/mapbox/mapboxgl/views/MapView.java | 16 ++++++++++++++-- .../mapbox/mapboxgl/testapp/MainActivity.java | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index b3172f5e3..ee1d21034 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -438,7 +438,6 @@ public class MapView extends FrameLayout implements LocationListener { marker.setId(id); // the annotation needs to know its id marker.setMapView(this); // the annotation needs to know which map view it is in mAnnotations.add(marker); - selectAnnotation(marker); return marker; } @@ -1968,8 +1967,21 @@ public class MapView extends FrameLayout implements LocationListener { private void selectAnnotation(Annotation annotation) { - // Need to deselect any currently selected annotation first + if (annotation == null) { + return; + } + if (annotation == mSelectedAnnotation) { + return; + } + + if (annotation instanceof Marker) { + // Need to deselect any currently selected annotation first + deselectAnnotation(); + + ((Marker)annotation).showInfoWindow(); + mSelectedAnnotation = annotation; + } } private void deselectAnnotation() { diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java index de52a31a4..eb5bfbc28 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java @@ -122,7 +122,7 @@ public class MainActivity extends AppCompatActivity { .position(position) .title("Dropped Pin") .snippet("Some snippet text.") - .sprite("default_marker")).showInfoWindow(); + .sprite("default_marker")); } }); From 77951eea939e8f2b4bed00c04e50e4ebd2d5ed05 Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Fri, 4 Sep 2015 17:36:30 -0500 Subject: [PATCH 18/29] #894 - Moving annotation selection determination to onSingleTapUp() to only run it when it's actual single tap. Fixing newSelectedAnnotationID to be greater than or equals to 0 to handle first annotation case --- .../com/mapbox/mapboxgl/views/MapView.java | 160 +++++++++--------- 1 file changed, 80 insertions(+), 80 deletions(-) diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index ee1d21034..0e7c8a929 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -973,86 +973,6 @@ public class MapView extends FrameLayout implements LocationListener { break; case MotionEvent.ACTION_UP: - - // Open / Close InfoWindow - PointF tapPoint = new PointF(event.getX(), event.getY()); - - float toleranceWidth = 60 * mScreenDensity; - float toleranceHeight = 80 * mScreenDensity; - - PointF tr = new PointF(tapPoint.x + toleranceWidth / 2, tapPoint.y + 2 * toleranceHeight / 3); - PointF bl = new PointF(tapPoint.x - toleranceWidth / 2, tapPoint.y - 1 * toleranceHeight / 3); - - LatLng sw = fromScreenLocation(bl); - LatLng ne = fromScreenLocation(tr); - - BoundingBox bbox = new BoundingBox(ne, sw); - - List nearbyAnnotations = getAnnotationsInBounds(bbox); - - long newSelectedAnnotationID = -1; - - if (nearbyAnnotations.size() > 0) { - - // there is at least one nearby annotation; select one - // - // first, sort for comparison and iteration - Collections.sort(nearbyAnnotations); - - if (nearbyAnnotations == mAnnotationsNearLastTap) - { - // the selection candidates haven't changed; cycle through them - if (mSelectedAnnotation != null && (mSelectedAnnotation.getId() == mAnnotationsNearLastTap.get(mAnnotationsNearLastTap.size() - 1).getId())) - { - // the selected annotation is the last in the set; cycle back to the first - // note: this could be the selected annotation if only one in set - newSelectedAnnotationID = mAnnotationsNearLastTap.get(0).getId(); - } - else if (mSelectedAnnotation != null) - { - // otherwise increment the selection through the candidates - long currentID = mSelectedAnnotation.getId(); - long result = mAnnotationsNearLastTap.indexOf(mSelectedAnnotation); - newSelectedAnnotationID = mAnnotationsNearLastTap.get((int) result + 1).getId(); - } - else - { - // no current selection; select the first one - newSelectedAnnotationID = mAnnotationsNearLastTap.get(0).getId(); - } - } - else - { - // start tracking a new set of nearby annotations - mAnnotationsNearLastTap = nearbyAnnotations; - - // select the first one - newSelectedAnnotationID = mAnnotationsNearLastTap.get(0).getId(); - } - - } else { - // there are no nearby annotations; deselect if necessary - newSelectedAnnotationID = -1; - } - - if (newSelectedAnnotationID > 0) { - - for (Annotation annotation : mAnnotations) { - if (annotation.getId() == newSelectedAnnotationID) { - if (annotation.getId() == mSelectedAnnotation.getId()) { - selectAnnotation(annotation); - } - break; - } - } - - } else { - // deselect any selected annotation - if (mSelectedAnnotation != null) { - deselectAnnotation(); - } - } - // First pointer up long tapInterval = event.getEventTime() - event.getDownTime(); boolean isTap = tapInterval <= ViewConfiguration.getTapTimeout(); @@ -1111,6 +1031,86 @@ public class MapView extends FrameLayout implements LocationListener { public boolean onSingleTapUp(MotionEvent e) { // Cancel any animation mNativeMapView.cancelTransitions(); + + // Open / Close InfoWindow + PointF tapPoint = new PointF(e.getX(), e.getY()); + + float toleranceWidth = 60 * mScreenDensity; + float toleranceHeight = 80 * mScreenDensity; + + PointF tr = new PointF(tapPoint.x + toleranceWidth / 2, tapPoint.y + 2 * toleranceHeight / 3); + PointF bl = new PointF(tapPoint.x - toleranceWidth / 2, tapPoint.y - 1 * toleranceHeight / 3); + + LatLng sw = fromScreenLocation(bl); + LatLng ne = fromScreenLocation(tr); + + BoundingBox bbox = new BoundingBox(ne, sw); + + List nearbyAnnotations = getAnnotationsInBounds(bbox); + + long newSelectedAnnotationID = -1; + + if (nearbyAnnotations.size() > 0) { + + // there is at least one nearby annotation; select one + // + // first, sort for comparison and iteration + Collections.sort(nearbyAnnotations); + + if (nearbyAnnotations == mAnnotationsNearLastTap) + { + // the selection candidates haven't changed; cycle through them + if (mSelectedAnnotation != null && (mSelectedAnnotation.getId() == mAnnotationsNearLastTap.get(mAnnotationsNearLastTap.size() - 1).getId())) + { + // the selected annotation is the last in the set; cycle back to the first + // note: this could be the selected annotation if only one in set + newSelectedAnnotationID = mAnnotationsNearLastTap.get(0).getId(); + } + else if (mSelectedAnnotation != null) + { + // otherwise increment the selection through the candidates + long currentID = mSelectedAnnotation.getId(); + long result = mAnnotationsNearLastTap.indexOf(mSelectedAnnotation); + newSelectedAnnotationID = mAnnotationsNearLastTap.get((int) result + 1).getId(); + } + else + { + // no current selection; select the first one + newSelectedAnnotationID = mAnnotationsNearLastTap.get(0).getId(); + } + } + else + { + // start tracking a new set of nearby annotations + mAnnotationsNearLastTap = nearbyAnnotations; + + // select the first one + newSelectedAnnotationID = mAnnotationsNearLastTap.get(0).getId(); + } + + } else { + // there are no nearby annotations; deselect if necessary + newSelectedAnnotationID = -1; + } + + if (newSelectedAnnotationID >= 0) { + + for (Annotation annotation : mAnnotations) { + if (annotation.getId() == newSelectedAnnotationID) { + if (annotation.getId() == mSelectedAnnotation.getId()) { + selectAnnotation(annotation); + } + break; + } + } + + } else { + // deselect any selected annotation + if (mSelectedAnnotation != null) { + deselectAnnotation(); + } + } + performClick(); return true; } From 8f98024f37911c2c8b0e04fe37b9c3271bf0b7fd Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Fri, 4 Sep 2015 18:52:08 -0500 Subject: [PATCH 19/29] #894 - Flipping the Y value to make screen touch event locations line up with marker. Updating marker selection and deselection logic to allow for proper selection and reselection. --- .../com/mapbox/mapboxgl/views/MapView.java | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index 0e7c8a929..1a69fdde3 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -8,7 +8,10 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.TypedArray; import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Matrix; +import android.graphics.Paint; import android.graphics.PointF; import android.hardware.GeomagneticField; import android.hardware.Sensor; @@ -1033,10 +1036,18 @@ public class MapView extends FrameLayout implements LocationListener { mNativeMapView.cancelTransitions(); // Open / Close InfoWindow - PointF tapPoint = new PointF(e.getX(), e.getY()); + + float x = e.getX(); + float y = e.getY(); + + // flip y direction vertically to match core GL + y = getHeight() - y; + + PointF tapPoint = new PointF(x, y); + LatLng tapLatLng = fromScreenLocation(tapPoint); float toleranceWidth = 60 * mScreenDensity; - float toleranceHeight = 80 * mScreenDensity; + float toleranceHeight = 200 * mScreenDensity; PointF tr = new PointF(tapPoint.x + toleranceWidth / 2, tapPoint.y + 2 * toleranceHeight / 3); PointF bl = new PointF(tapPoint.x - toleranceWidth / 2, tapPoint.y - 1 * toleranceHeight / 3); @@ -1046,6 +1057,8 @@ public class MapView extends FrameLayout implements LocationListener { BoundingBox bbox = new BoundingBox(ne, sw); + addPolyline(new PolylineOptions().add(sw, ne).color(Color.RED).width(5)); + List nearbyAnnotations = getAnnotationsInBounds(bbox); long newSelectedAnnotationID = -1; @@ -1096,11 +1109,13 @@ public class MapView extends FrameLayout implements LocationListener { if (newSelectedAnnotationID >= 0) { for (Annotation annotation : mAnnotations) { - if (annotation.getId() == newSelectedAnnotationID) { - if (annotation.getId() == mSelectedAnnotation.getId()) { - selectAnnotation(annotation); + if (annotation instanceof Marker) { + if (annotation.getId() == newSelectedAnnotationID) { + if (mSelectedAnnotation == null || annotation.getId() != mSelectedAnnotation.getId()) { + selectAnnotation(annotation); + } + break; } - break; } } @@ -1989,6 +2004,7 @@ public class MapView extends FrameLayout implements LocationListener { Marker marker = (Marker) mSelectedAnnotation; if (marker.isInfoWindowShown()) { marker.hideInfoWindow(); + mSelectedAnnotation = null; } } } From e8d415367e44662f4cfb7b8e1ac786affc07078a Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Fri, 4 Sep 2015 18:58:34 -0500 Subject: [PATCH 20/29] #894 - Resetting height tolerance to 80 --- .../src/main/java/com/mapbox/mapboxgl/views/MapView.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index 1a69fdde3..d7caf8539 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -1044,10 +1044,9 @@ public class MapView extends FrameLayout implements LocationListener { y = getHeight() - y; PointF tapPoint = new PointF(x, y); - LatLng tapLatLng = fromScreenLocation(tapPoint); float toleranceWidth = 60 * mScreenDensity; - float toleranceHeight = 200 * mScreenDensity; + float toleranceHeight = 80 * mScreenDensity; PointF tr = new PointF(tapPoint.x + toleranceWidth / 2, tapPoint.y + 2 * toleranceHeight / 3); PointF bl = new PointF(tapPoint.x - toleranceWidth / 2, tapPoint.y - 1 * toleranceHeight / 3); From 1fd0cedfd608e216a1a88c397916c6e2b1b807dd Mon Sep 17 00:00:00 2001 From: "Justin R. Miller" Date: Mon, 7 Sep 2015 11:29:06 -0700 Subject: [PATCH 21/29] #894 - properly extend tap bounds for marker selection --- .../com/mapbox/mapboxgl/views/MapView.java | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index d7caf8539..aed036f50 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -13,6 +13,7 @@ import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PointF; +import android.graphics.RectF; import android.hardware.GeomagneticField; import android.hardware.Sensor; import android.hardware.SensorEvent; @@ -1045,20 +1046,24 @@ public class MapView extends FrameLayout implements LocationListener { PointF tapPoint = new PointF(x, y); - float toleranceWidth = 60 * mScreenDensity; - float toleranceHeight = 80 * mScreenDensity; + float toleranceWidth = 40 * mScreenDensity; + float toleranceHeight = 60 * mScreenDensity; - PointF tr = new PointF(tapPoint.x + toleranceWidth / 2, tapPoint.y + 2 * toleranceHeight / 3); - PointF bl = new PointF(tapPoint.x - toleranceWidth / 2, tapPoint.y - 1 * toleranceHeight / 3); + RectF tapRect = new RectF(tapPoint.x - toleranceWidth / 2, tapPoint.y - 2 * toleranceHeight / 3, + tapPoint.x + toleranceWidth / 2, tapPoint.y + 1 * toleranceHeight / 3); - LatLng sw = fromScreenLocation(bl); - LatLng ne = fromScreenLocation(tr); + List corners = Arrays.asList( + fromScreenLocation(new PointF(tapRect.left, tapRect.bottom)), + fromScreenLocation(new PointF(tapRect.left, tapRect.top)), + fromScreenLocation(new PointF(tapRect.right, tapRect.top)), + fromScreenLocation(new PointF(tapRect.right, tapRect.bottom)) + ); - BoundingBox bbox = new BoundingBox(ne, sw); + BoundingBox tapBounds = BoundingBox.fromLatLngs(corners); - addPolyline(new PolylineOptions().add(sw, ne).color(Color.RED).width(5)); + addPolyline(new PolylineOptions().addAll(corners).add(corners.get(0)).color(Color.RED).width(2)); - List nearbyAnnotations = getAnnotationsInBounds(bbox); + List nearbyAnnotations = getAnnotationsInBounds(tapBounds); long newSelectedAnnotationID = -1; From 9210a71fdf9adbab75f3e03590362b5bc16b4421 Mon Sep 17 00:00:00 2001 From: "Justin R. Miller" Date: Mon, 7 Sep 2015 11:38:54 -0700 Subject: [PATCH 22/29] #894 - flip marker tap bounds to be top-biased --- .../src/main/java/com/mapbox/mapboxgl/views/MapView.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index aed036f50..6ab20e910 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -1049,8 +1049,8 @@ public class MapView extends FrameLayout implements LocationListener { float toleranceWidth = 40 * mScreenDensity; float toleranceHeight = 60 * mScreenDensity; - RectF tapRect = new RectF(tapPoint.x - toleranceWidth / 2, tapPoint.y - 2 * toleranceHeight / 3, - tapPoint.x + toleranceWidth / 2, tapPoint.y + 1 * toleranceHeight / 3); + RectF tapRect = new RectF(tapPoint.x - toleranceWidth / 2, tapPoint.y + 2 * toleranceHeight / 3, + tapPoint.x + toleranceWidth / 2, tapPoint.y - 1 * toleranceHeight / 3); List corners = Arrays.asList( fromScreenLocation(new PointF(tapRect.left, tapRect.bottom)), From d394be0bb85221e0a8932350824f10521700d98d Mon Sep 17 00:00:00 2001 From: "Justin R. Miller" Date: Mon, 7 Sep 2015 11:48:00 -0700 Subject: [PATCH 23/29] #894 - remove debug polyline --- .../src/main/java/com/mapbox/mapboxgl/views/MapView.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index 6ab20e910..f81649a7c 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -1061,8 +1061,6 @@ public class MapView extends FrameLayout implements LocationListener { BoundingBox tapBounds = BoundingBox.fromLatLngs(corners); - addPolyline(new PolylineOptions().addAll(corners).add(corners.get(0)).color(Color.RED).width(2)); - List nearbyAnnotations = getAnnotationsInBounds(tapBounds); long newSelectedAnnotationID = -1; From bac590298adc07ddd7eb943b55c70a39fe747b3a Mon Sep 17 00:00:00 2001 From: "Justin R. Miller" Date: Mon, 7 Sep 2015 11:48:13 -0700 Subject: [PATCH 24/29] #894 - add lat/lon to marker snippets --- .../main/java/com/mapbox/mapboxgl/testapp/MainActivity.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java index eb5bfbc28..ba521fc31 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java @@ -32,6 +32,7 @@ import io.fabric.sdk.android.Fabric; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; +import java.text.DecimalFormat; import java.util.ArrayList; public class MainActivity extends AppCompatActivity { @@ -121,7 +122,8 @@ public class MainActivity extends AppCompatActivity { mMapView.addMarker(new MarkerOptions() .position(position) .title("Dropped Pin") - .snippet("Some snippet text.") + .snippet(new DecimalFormat("#.#####").format(position.getLatitude()) + ", " + + new DecimalFormat("#.#####").format(position.getLongitude())) .sprite("default_marker")); } }); From 7ed7729cf2919ef68442612945c4e68fa65c430f Mon Sep 17 00:00:00 2001 From: "Justin R. Miller" Date: Mon, 7 Sep 2015 11:57:18 -0700 Subject: [PATCH 25/29] #894 - remove debug log --- .../src/main/java/com/mapbox/mapboxgl/views/MapView.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index f81649a7c..d191d34c7 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -527,9 +527,6 @@ public class MapView extends FrameLayout implements LocationListener { annotations.add(annotation); } } - for(int i = 0; i < annotations.size(); i++) { - Log.d(TAG, "tapped: " + annotations.get(i).getId()); - } return annotations; } From 704a4e499b4f73bf603974ef85e497c422100056 Mon Sep 17 00:00:00 2001 From: "Justin R. Miller" Date: Mon, 7 Sep 2015 11:57:34 -0700 Subject: [PATCH 26/29] #894 - only select/deselect markers on true single-tap --- .../main/java/com/mapbox/mapboxgl/views/MapView.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index d191d34c7..f5610f5d9 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -1032,7 +1032,11 @@ public class MapView extends FrameLayout implements LocationListener { public boolean onSingleTapUp(MotionEvent e) { // Cancel any animation mNativeMapView.cancelTransitions(); + return true; + } + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { // Open / Close InfoWindow float x = e.getX(); @@ -1125,16 +1129,9 @@ public class MapView extends FrameLayout implements LocationListener { } } - performClick(); return true; } - // Called for single taps after a delay - @Override - public boolean onSingleTapConfirmed(MotionEvent e) { - return false; - } - // Called for a long press @Override public void onLongPress(MotionEvent e) { From 8f37502c719feb151e01cb31d89c2effaf1b91ad Mon Sep 17 00:00:00 2001 From: Leith Bade Date: Tue, 8 Sep 2015 14:01:53 +1000 Subject: [PATCH 27/29] Use MapView as parent for InfoWindow See comment in #894 --- .../main/java/com/mapbox/mapboxgl/annotations/InfoWindow.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindow.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindow.java index c48227501..56eae1951 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindow.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindow.java @@ -28,11 +28,10 @@ public class InfoWindow { public InfoWindow(int layoutResId, MapView mapView) { mMapView = mapView; mIsVisible = false; - ViewGroup parent = (ViewGroup) mapView.getParent(); Context context = mapView.getContext(); LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mView = inflater.inflate(layoutResId, parent, false); + mView = inflater.inflate(layoutResId, (ViewGroup) mapView, false); if (mTitleId == 0) { setResIds(mapView.getContext()); From 9a55e9702915493ac34f58469674eaf9e77c019b Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Tue, 8 Sep 2015 11:43:55 -0400 Subject: [PATCH 28/29] Fix build, only ask for location permission on user's instigation Previously in #2265 we would ask for location permission at app startup if `!settings->showsUserLocation`. #2193 0a172a21fdc2a87473560fd7d45f4d495d95de91 changed the way we used `NSUserDefaults` and mostly removed the `settings` object, which broke #2265. Rather than fix our pestering location permissions ask at startup, this commit now only asks for location permissions when the user hits the locate-me button. Once a user grants permission, the user dot appears because a `userTrackingMode` is set, `showsUserLocation` is permanently set to `YES` in `NSUserDefaults` and is restored at launch. --- ios/app/MBXViewController.mm | 7 ------- 1 file changed, 7 deletions(-) diff --git a/ios/app/MBXViewController.mm b/ios/app/MBXViewController.mm index b32371770..d94e41b5f 100644 --- a/ios/app/MBXViewController.mm +++ b/ios/app/MBXViewController.mm @@ -83,13 +83,6 @@ static NSUInteger const kStyleVersion = 8; action:@selector(locateUser)]; [self restoreState:nil]; - - if ( ! settings->showsUserLocation) - { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ - self.mapView.showsUserLocation = YES; - }); - } } - (void)saveState:(__unused NSNotification *)notification From 3f497ff9d23c8f01d36889d09ef5f3989960115f Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Tue, 8 Sep 2015 14:46:18 -0500 Subject: [PATCH 29/29] #2283 - Supporting custom InfoWindow touch listeners via Marker.setInfoWindowOnTouchListener(). Added Test to Custom Annotations in TestApp's MainActivity. --- .../mapbox/mapboxgl/annotations/Marker.java | 28 ++++++++++++++++--- .../com/mapbox/mapboxgl/views/MapView.java | 16 +++++++---- .../mapbox/mapboxgl/testapp/MainActivity.java | 12 +++++++- 3 files changed, 46 insertions(+), 10 deletions(-) diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/Marker.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/Marker.java index 4776c37bf..17317bffb 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/Marker.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/Marker.java @@ -1,6 +1,7 @@ package com.mapbox.mapboxgl.annotations; import android.graphics.Point; +import android.view.View; import com.mapbox.mapboxgl.geometry.LatLng; import com.mapbox.mapboxgl.views.R; @@ -145,13 +146,32 @@ public class Marker extends Annotation { return; } + getInfoWindow().open(this, getPosition(), (int) anchorU, (int) anchorV); + getInfoWindow().setBoundMarker(this); + infoWindowShown = true; + } + + /** + * Use to set a custom OnTouchListener for the InfoWindow. + * By default the InfoWindow will close on touch. + * @param listener Custom OnTouchListener + */ + public void setInfoWindowOnTouchListener(View.OnTouchListener listener) { + if (listener == null) { + return; + } + getInfoWindow().setOnTouchListener(listener); + } + + /** + * Common internal InfoWindow initialization method + * @return InfoWindow for Marker + */ + private InfoWindow getInfoWindow() { if (infoWindow == null) { infoWindow = new InfoWindow(R.layout.infowindow, mapView); } - - infoWindow.open(this, getPosition(), (int)anchorU, (int)anchorV); - infoWindow.setBoundMarker(this); - infoWindowShown = true; + return infoWindow; } @Override diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index f5610f5d9..0c3429e00 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -8,10 +8,7 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.TypedArray; import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; import android.graphics.Matrix; -import android.graphics.Paint; import android.graphics.PointF; import android.graphics.RectF; import android.hardware.GeomagneticField; @@ -29,7 +26,6 @@ import android.support.v4.content.ContextCompat; import android.support.v4.view.GestureDetectorCompat; import android.support.v4.view.ScaleGestureDetectorCompat; import android.text.TextUtils; -import android.util.Log; import android.view.GestureDetector; import android.view.Gravity; import android.view.ScaleGestureDetector; @@ -483,6 +479,9 @@ public class MapView extends FrameLayout implements LocationListener { private void removeAnnotationsWithId(long annotationId){ for (Iterator iterator = mAnnotations.iterator(); iterator.hasNext();) { Annotation annotation = iterator.next(); + if (annotation instanceof Marker) { + ((Marker)annotation).hideInfoWindow(); + } if (annotation.getId() == annotationId) { iterator.remove(); } @@ -490,6 +489,9 @@ public class MapView extends FrameLayout implements LocationListener { } public void removeAnnotation(Annotation annotation) { + if (annotation instanceof Marker) { + ((Marker)annotation).hideInfoWindow(); + } long id = annotation.getId(); mNativeMapView.removeAnnotation(id); mAnnotations.remove(annotation); @@ -503,8 +505,12 @@ public class MapView extends FrameLayout implements LocationListener { public void removeAnnotations() { long[] ids = new long[mAnnotations.size()]; for(int i = 0; i < mAnnotations.size(); i++) { - long id = mAnnotations.get(i).getId(); + Annotation annotation = mAnnotations.get(i); + long id = annotation.getId(); ids[i] = id; + if (annotation instanceof Marker) { + ((Marker)annotation).hideInfoWindow(); + } } mNativeMapView.removeAnnotations(ids); mAnnotations.clear(); diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java index ba521fc31..ed9b7ef06 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java @@ -22,7 +22,9 @@ import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.widget.TextView; +import android.widget.Toast; import com.crashlytics.android.Crashlytics; +import com.mapbox.mapboxgl.annotations.Marker; import com.mapbox.mapboxgl.annotations.MarkerOptions; import com.mapbox.mapboxgl.annotations.PolygonOptions; import com.mapbox.mapboxgl.annotations.PolylineOptions; @@ -395,10 +397,18 @@ public class MainActivity extends AppCompatActivity { private void addMarkers() { LatLng backLot = new LatLng(38.649441, -121.369064); - mMapView.addMarker(new MarkerOptions() + final Marker marker = mMapView.addMarker(new MarkerOptions() .position(backLot) .title("Back Lot") .snippet("The back lot behind my house")); + marker.setInfoWindowOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + Toast.makeText(getApplicationContext(), "Custom Info Touch Listener!!", Toast.LENGTH_SHORT).show(); + marker.hideInfoWindow(); + return true; + } + }); LatLng cheeseRoom = new LatLng(38.531577,-122.010646); mMapView.addMarker(new MarkerOptions()