From cecfa1834658e4ed65ecf8656f0d32bbe678ad9d Mon Sep 17 00:00:00 2001 From: Martin Hellwig Date: Thu, 25 Jan 2018 17:04:53 +0100 Subject: [PATCH 01/22] Implement new bearingTrackingMode GPS_NORTH_FACING --- .../constants/MyBearingTracking.java | 7 +++++- .../maps/widgets/MyLocationView.java | 23 +++++++++++++++---- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MyBearingTracking.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MyBearingTracking.java index ceac862f3..c042b0057 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MyBearingTracking.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MyBearingTracking.java @@ -22,7 +22,7 @@ import java.lang.annotation.RetentionPolicy; */ public class MyBearingTracking { - @IntDef( {NONE, COMPASS, GPS}) + @IntDef( {NONE, COMPASS, GPS, GPS_NORTH_FACING}) @Retention(RetentionPolicy.SOURCE) public @interface Mode { } @@ -42,4 +42,9 @@ public class MyBearingTracking { */ public static final int GPS = 0x00000008; + /** + * Tracking the bearing of the user based on GPS data, but camera always faces north direction + */ + public static final int GPS_NORTH_FACING = 0x0000000B; + } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationView.java index 4f27e0ada..1cdd91028 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationView.java @@ -356,7 +356,9 @@ public class MyLocationView extends View { foregroundDrawable.draw(canvas); } } else if (foregroundBearingDrawable != null && foregroundBounds != null) { - if (myBearingTrackingMode == MyBearingTracking.GPS || compassListener.isSensorAvailable()) { + if (myBearingTrackingMode == MyBearingTracking.GPS + || myBearingTrackingMode == MyBearingTracking.GPS_NORTH_FACING + || compassListener.isSensorAvailable()) { foregroundBearingDrawable.draw(canvas); } else { // We are tracking MyBearingTracking.COMPASS, but sensor is not available. @@ -383,7 +385,8 @@ public class MyLocationView extends View { public void setBearing(double bearing) { this.bearing = bearing; if (myLocationTrackingMode == MyLocationTracking.TRACKING_NONE) { - if (myBearingTrackingMode == MyBearingTracking.GPS) { + if (myBearingTrackingMode == MyBearingTracking.GPS + || myBearingTrackingMode == MyBearingTracking.GPS_NORTH_FACING) { if (location != null) { setCompass(location.getBearing() - bearing); } @@ -519,7 +522,8 @@ public class MyLocationView extends View { } private void toggleGps(boolean enableGps) { - toggleGps(enableGps, mapboxMap != null && mapboxMap.getTrackingSettings().isCustomLocationSource()); + toggleGps(enableGps, mapboxMap != null + && mapboxMap.getTrackingSettings().isCustomLocationSource()); } /** @@ -580,7 +584,8 @@ public class MyLocationView extends View { this.location = location; myLocationBehavior.updateLatLng(location); - if (mapboxMap != null && myBearingTrackingMode == MyBearingTracking.GPS + if (mapboxMap != null && (myBearingTrackingMode == MyBearingTracking.GPS + || myBearingTrackingMode == MyBearingTracking.GPS_NORTH_FACING) && myLocationTrackingMode == MyLocationTracking.TRACKING_NONE) { setBearing(mapboxMap.getCameraPosition().bearing); } @@ -616,7 +621,8 @@ public class MyLocationView extends View { compassListener.onResume(); } else { compassListener.onPause(); - if (myLocationTrackingMode == MyLocationTracking.TRACKING_FOLLOW) { + if (myLocationTrackingMode == MyLocationTracking.TRACKING_FOLLOW + && myBearingTrackingMode == MyBearingTracking.GPS) { // always face north setCompass(0); } else { @@ -1017,6 +1023,13 @@ public class MyLocationView extends View { setCompass(0, COMPASS_UPDATE_RATE_MS); } + if (myBearingTrackingMode == MyBearingTracking.GPS_NORTH_FACING) { + builder.bearing(0); + if (location.hasBearing()) { + setCompass(location.getBearing(), COMPASS_UPDATE_RATE_MS); + } + } + // accuracy updateAccuracy(location); From c636b382e8bd61a05dd976d9aeb0ce5ae0894b0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Paczos?= Date: Mon, 29 Jan 2018 15:36:38 -0500 Subject: [PATCH 02/22] [android] - log lack of permissions only if enabling location --- .../main/java/com/mapbox/mapboxsdk/maps/TrackingSettings.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/TrackingSettings.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/TrackingSettings.java index 6eacbbaea..81fd091c9 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/TrackingSettings.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/TrackingSettings.java @@ -374,7 +374,7 @@ public final class TrackingSettings { } private void setMyLocationEnabled(boolean locationEnabled, boolean isCustomLocationSource) { - if (!PermissionsManager.areLocationPermissionsGranted(myLocationView.getContext())) { + if (locationEnabled && !PermissionsManager.areLocationPermissionsGranted(myLocationView.getContext())) { Timber.e("Could not activate user location tracking: " + "user did not accept the permission or permissions were not requested."); return; From c8a96bb24ff23513b67c258e60f5820ee3134802 Mon Sep 17 00:00:00 2001 From: Tobrun Date: Thu, 25 Jan 2018 14:04:13 +0100 Subject: [PATCH 03/22] [android] - blacklist adreno 2xx for VAO support --- src/mbgl/gl/context.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mbgl/gl/context.cpp b/src/mbgl/gl/context.cpp index d8ade8b8b..d1a37a861 100644 --- a/src/mbgl/gl/context.cpp +++ b/src/mbgl/gl/context.cpp @@ -249,9 +249,10 @@ UniqueTexture Context::createTexture() { bool Context::supportsVertexArrays() const { static bool blacklisted = []() { - // Blacklist Adreno 3xx as it crashes on glBuffer(Sub)Data + // Blacklist Adreno 2xx, 3xx as it crashes on glBuffer(Sub)Data const std::string renderer = reinterpret_cast(glGetString(GL_RENDERER)); - return renderer.find("Adreno (TM) 3") != std::string::npos; + return renderer.find("Adreno (TM) 2") != std::string::npos + || renderer.find("Adreno (TM) 3") != std::string::npos; }(); return !blacklisted && From ff29a0cb99d5e8aedc66207080d7e83a7e2dc0dd Mon Sep 17 00:00:00 2001 From: Tobrun Date: Wed, 24 Jan 2018 11:28:47 +0100 Subject: [PATCH 04/22] [android] - create Handler using the main thread looper --- .../main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java index c033b1cbc..987756876 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java @@ -1,6 +1,7 @@ package com.mapbox.mapboxsdk.offline; import android.os.Handler; +import android.os.Looper; import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -51,7 +52,7 @@ public class OfflineRegion { private byte[] metadata; // Makes sure callbacks come back to the main thread - private final Handler handler = new Handler(); + private final Handler handler = new Handler(Looper.getMainLooper()); /** * A region can have a single observer, which gets notified whenever a change From 0742f7fb66f832ffae4d96fa430a691e53a0c32d Mon Sep 17 00:00:00 2001 From: Tobrun Date: Wed, 31 Jan 2018 13:18:49 +0100 Subject: [PATCH 05/22] [android] - update changelog for 5.4.0 --- platform/android/CHANGELOG.md | 17 ++++++++++++++++- .../MapboxGLAndroidSDK/gradle.properties | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/platform/android/CHANGELOG.md b/platform/android/CHANGELOG.md index e98ed491a..1e51725ed 100644 --- a/platform/android/CHANGELOG.md +++ b/platform/android/CHANGELOG.md @@ -2,7 +2,22 @@ Mapbox welcomes participation and contributions from everyone. If you'd like to do so please see the [`Contributing Guide`](https://github.com/mapbox/mapbox-gl-native/blob/master/CONTRIBUTING.md) first to get started. -## master +## 5.4.0 - January 30, 2018 + - Blacklist Adreno 2xx GPU for VAO support [#11047](https://github.com/mapbox/mapbox-gl-native/pull/11047) + - Bearing tracking mode GPS_NORTH_FACING [#11095](https://github.com/mapbox/mapbox-gl-native/pull/11095) + - Disable logging missing location permissons [#11084](https://github.com/mapbox/mapbox-gl-native/pull/11084) + - Create offline handler using the main thread looper [#11021](https://github.com/mapbox/mapbox-gl-native/pull/11021) + +## 6.0.0-beta.1 - January 26, 2018 + - Binding integration for expressions [#10654](https://github.com/mapbox/mapbox-gl-native/pull/10654) + - CustomGeometrySource [#9983](https://github.com/mapbox/mapbox-gl-native/pull/9983) + - HillshadeLayer and RasterDemSource [#11031](https://github.com/mapbox/mapbox-gl-native/pull/11031) + - Revisit marker placement for snapshot [#11029](https://github.com/mapbox/mapbox-gl-native/pull/11029) + - SafeVarargs annotation for expressions [#11027](https://github.com/mapbox/mapbox-gl-native/pull/11027) + - Expression#toString [#11024](https://github.com/mapbox/mapbox-gl-native/pull/11024) + - Rename initRenderSurface to onSurfaceCreated [#11023](https://github.com/mapbox/mapbox-gl-native/pull/11023) + - Expose attribution manager as public API [#10942](https://github.com/mapbox/mapbox-gl-native/pull/10942) + - Replace Mapzen vector source example with Mapillary [#10931](https://github.com/mapbox/mapbox-gl-native/pull/10931) - Add Hebrew localization [#10967](https://github.com/mapbox/mapbox-gl-native/pull/10967) ## 5.3.2 - January 22, 2018 diff --git a/platform/android/MapboxGLAndroidSDK/gradle.properties b/platform/android/MapboxGLAndroidSDK/gradle.properties index 20d6f60ba..011052b52 100644 --- a/platform/android/MapboxGLAndroidSDK/gradle.properties +++ b/platform/android/MapboxGLAndroidSDK/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.mapbox.mapboxsdk -VERSION_NAME=5.3.3-SNAPSHOT +VERSION_NAME=5.4.1-SNAPSHOT POM_DESCRIPTION=Mapbox GL Android SDK POM_URL=https://github.com/mapbox/mapbox-gl-native From 332dad2a4e002b1856ea072d6af22c9e501a55fd Mon Sep 17 00:00:00 2001 From: Randall C Lee Date: Mon, 5 Feb 2018 16:17:57 -0500 Subject: [PATCH 06/22] [ios] Add Radius Configuration (#11070) * Add Radius Configuration * Remove default config - removes default configuration - moves config setup to events init method - renames config class * Delete MGLConfig header file --- platform/ios/ios.xcodeproj/project.pbxproj | 12 ++++++++ platform/ios/src/MGLLocationManager.m | 6 ++-- platform/ios/src/MGLMapboxEvents.m | 3 ++ platform/ios/src/MGLTelemetryConfig.h | 18 +++++++++++ platform/ios/src/MGLTelemetryConfig.m | 35 ++++++++++++++++++++++ 5 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 platform/ios/src/MGLTelemetryConfig.h create mode 100644 platform/ios/src/MGLTelemetryConfig.m diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index 52f33a83b..2325f3d3c 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -247,6 +247,10 @@ 968F36B51E4D128D003A5522 /* MGLDistanceFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = 3557F7AE1E1D27D300CCA5E6 /* MGLDistanceFormatter.h */; settings = {ATTRIBUTES = (Public, ); }; }; 96E027231E57C76E004B8E66 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 96E027251E57C76E004B8E66 /* Localizable.strings */; }; 96F3F73C1F57124B003E2D2C /* MGLUserLocationHeadingIndicator.h in Headers */ = {isa = PBXBuildFile; fileRef = 96F3F73B1F5711F1003E2D2C /* MGLUserLocationHeadingIndicator.h */; }; + AC518DFF201BB55A00EBC820 /* MGLTelemetryConfig.h in Headers */ = {isa = PBXBuildFile; fileRef = AC518DFD201BB55A00EBC820 /* MGLTelemetryConfig.h */; }; + AC518E00201BB55A00EBC820 /* MGLTelemetryConfig.h in Headers */ = {isa = PBXBuildFile; fileRef = AC518DFD201BB55A00EBC820 /* MGLTelemetryConfig.h */; }; + AC518E03201BB56000EBC820 /* MGLTelemetryConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = AC518DFE201BB55A00EBC820 /* MGLTelemetryConfig.m */; }; + AC518E04201BB56100EBC820 /* MGLTelemetryConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = AC518DFE201BB55A00EBC820 /* MGLTelemetryConfig.m */; }; DA00FC8E1D5EEB0D009AABC8 /* MGLAttributionInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA00FC8C1D5EEB0D009AABC8 /* MGLAttributionInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; DA00FC8F1D5EEB0D009AABC8 /* MGLAttributionInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA00FC8C1D5EEB0D009AABC8 /* MGLAttributionInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; DA00FC901D5EEB0D009AABC8 /* MGLAttributionInfo.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA00FC8D1D5EEB0D009AABC8 /* MGLAttributionInfo.mm */; }; @@ -750,6 +754,8 @@ 96E0272D1E57C7E6004B8E66 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; 96E0272E1E57C7E7004B8E66 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; 96F3F73B1F5711F1003E2D2C /* MGLUserLocationHeadingIndicator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLUserLocationHeadingIndicator.h; sourceTree = ""; }; + AC518DFD201BB55A00EBC820 /* MGLTelemetryConfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLTelemetryConfig.h; sourceTree = ""; }; + AC518DFE201BB55A00EBC820 /* MGLTelemetryConfig.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLTelemetryConfig.m; sourceTree = ""; }; DA00FC8C1D5EEB0D009AABC8 /* MGLAttributionInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAttributionInfo.h; sourceTree = ""; }; DA00FC8D1D5EEB0D009AABC8 /* MGLAttributionInfo.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLAttributionInfo.mm; sourceTree = ""; }; DA0CD58F1CF56F6A00A5F5A5 /* MGLFeatureTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLFeatureTests.mm; path = ../../darwin/test/MGLFeatureTests.mm; sourceTree = ""; }; @@ -1724,6 +1730,8 @@ DA8848491CBAFB9800AB86E3 /* MGLMapboxEvents.m */, 9620BB361E69FE1700705A1D /* MGLSDKUpdateChecker.h */, 9620BB371E69FE1700705A1D /* MGLSDKUpdateChecker.mm */, + AC518DFD201BB55A00EBC820 /* MGLTelemetryConfig.h */, + AC518DFE201BB55A00EBC820 /* MGLTelemetryConfig.m */, ); name = Telemetry; sourceTree = ""; @@ -1840,6 +1848,7 @@ DA8847F51CBAFA5100AB86E3 /* MGLOfflineRegion.h in Headers */, DA737EE11D056A4E005BDA16 /* MGLMapViewDelegate.h in Headers */, DA8848851CBB033F00AB86E3 /* FABKitProtocol.h in Headers */, + AC518DFF201BB55A00EBC820 /* MGLTelemetryConfig.h in Headers */, DA88481B1CBAFA6200AB86E3 /* MGLGeometry_Private.h in Headers */, 3510FFF91D6DCC4700F413B2 /* NSCompoundPredicate+MGLAdditions.h in Headers */, 3557F7B01E1D27D300CCA5E6 /* MGLDistanceFormatter.h in Headers */, @@ -1906,6 +1915,7 @@ 7E016D7F1D9E86BE00A29A21 /* MGLPolyline+MGLAdditions.h in Headers */, DABFB8701CBE9A0F00D62B32 /* MGLMapView+IBAdditions.h in Headers */, 353AFA151D65AB17005A69F4 /* NSDate+MGLAdditions.h in Headers */, + AC518E00201BB55A00EBC820 /* MGLTelemetryConfig.h in Headers */, 3510FFFA1D6DCC4700F413B2 /* NSCompoundPredicate+MGLAdditions.h in Headers */, DA72620C1DEEE3480043BB89 /* MGLOpenGLStyleLayer.h in Headers */, 35CE61831D4165D9004F2359 /* UIColor+MGLAdditions.h in Headers */, @@ -2399,6 +2409,7 @@ DA72620D1DEEE3480043BB89 /* MGLOpenGLStyleLayer.mm in Sources */, DA88481A1CBAFA6200AB86E3 /* MGLAccountManager.m in Sources */, 3510FFFB1D6DCC4700F413B2 /* NSCompoundPredicate+MGLAdditions.mm in Sources */, + AC518E03201BB56000EBC820 /* MGLTelemetryConfig.m in Sources */, DA8848271CBAFA6200AB86E3 /* MGLPolyline.mm in Sources */, DA8848581CBAFB9800AB86E3 /* MGLMapboxEvents.m in Sources */, 35CE61841D4165D9004F2359 /* UIColor+MGLAdditions.mm in Sources */, @@ -2488,6 +2499,7 @@ DA72620E1DEEE3480043BB89 /* MGLOpenGLStyleLayer.mm in Sources */, DAA4E42F1CBB730400178DFB /* MGLCompactCalloutView.m in Sources */, 3510FFFC1D6DCC4700F413B2 /* NSCompoundPredicate+MGLAdditions.mm in Sources */, + AC518E04201BB56100EBC820 /* MGLTelemetryConfig.m in Sources */, DAA4E4271CBB730400178DFB /* MGLTilePyramidOfflineRegion.mm in Sources */, DAA4E41C1CBB730400178DFB /* MGLAccountManager.m in Sources */, 35CE61851D4165D9004F2359 /* UIColor+MGLAdditions.mm in Sources */, diff --git a/platform/ios/src/MGLLocationManager.m b/platform/ios/src/MGLLocationManager.m index b0d2e17d5..85ef4ca48 100644 --- a/platform/ios/src/MGLLocationManager.m +++ b/platform/ios/src/MGLLocationManager.m @@ -1,9 +1,9 @@ #import "MGLLocationManager.h" +#import "MGLTelemetryConfig.h" #import static const NSTimeInterval MGLLocationManagerHibernationTimeout = 300.0; static const NSTimeInterval MGLLocationManagerHibernationPollInterval = 5.0; -static const CLLocationDistance MGLLocationManagerHibernationRadius = 300.0; static const CLLocationDistance MGLLocationManagerDistanceFilter = 5.0; static NSString * const MGLLocationManagerRegionIdentifier = @"MGLLocationManagerRegionIdentifier.fence.center"; @@ -122,7 +122,7 @@ static NSString * const MGLLocationManagerRegionIdentifier = @"MGLLocationManage } - (void)establishRegionMonitoringForLocation:(CLLocation *)location { - CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:location.coordinate radius:MGLLocationManagerHibernationRadius identifier:MGLLocationManagerRegionIdentifier]; + CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:location.coordinate radius:MGLTelemetryConfig.sharedConfig.MGLLocationManagerHibernationRadius identifier:MGLLocationManagerRegionIdentifier]; region.notifyOnEntry = NO; region.notifyOnExit = YES; [self.standardLocationManager startMonitoringForRegion:region]; @@ -151,7 +151,7 @@ static NSString * const MGLLocationManagerRegionIdentifier = @"MGLLocationManage if (location.speed > 0.0) { [self startBackgroundTimeoutTimer]; } - if (self.standardLocationManager.monitoredRegions.count == 0 || location.horizontalAccuracy < MGLLocationManagerHibernationRadius) { + if (self.standardLocationManager.monitoredRegions.count == 0 || location.horizontalAccuracy < MGLTelemetryConfig.sharedConfig.MGLLocationManagerHibernationRadius) { [self establishRegionMonitoringForLocation:location]; } if ([self.delegate respondsToSelector:@selector(locationManager:didUpdateLocations:)]) { diff --git a/platform/ios/src/MGLMapboxEvents.m b/platform/ios/src/MGLMapboxEvents.m index d59972f5b..273af5b3b 100644 --- a/platform/ios/src/MGLMapboxEvents.m +++ b/platform/ios/src/MGLMapboxEvents.m @@ -6,6 +6,7 @@ #import "NSException+MGLAdditions.h" #import "MGLAPIClient.h" #import "MGLLocationManager.h" +#import "MGLTelemetryConfig.h" #include #include @@ -172,6 +173,8 @@ const NSTimeInterval MGLFlushInterval = 180; - (instancetype) init { self = [super init]; if (self) { + [MGLTelemetryConfig.sharedConfig configurationFromKey:[[NSUserDefaults standardUserDefaults] objectForKey:MGLMapboxMetricsProfile]]; + _currentAccountTypeValue = @0; _currentMetricsEnabledValue = YES; diff --git a/platform/ios/src/MGLTelemetryConfig.h b/platform/ios/src/MGLTelemetryConfig.h new file mode 100644 index 000000000..527d34429 --- /dev/null +++ b/platform/ios/src/MGLTelemetryConfig.h @@ -0,0 +1,18 @@ +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MGLTelemetryConfig : NSObject + +@property (nonatomic) CLLocationDistance MGLLocationManagerHibernationRadius; + +extern NSString *const MGLMapboxMetricsProfile; + ++ (nullable instancetype)sharedConfig; + +- (void)configurationFromKey:(NSString *)key; + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/ios/src/MGLTelemetryConfig.m b/platform/ios/src/MGLTelemetryConfig.m new file mode 100644 index 000000000..c45554414 --- /dev/null +++ b/platform/ios/src/MGLTelemetryConfig.m @@ -0,0 +1,35 @@ +#import "MGLTelemetryConfig.h" + +static const CLLocationDistance MGLConfigHibernationRadiusDefault = 300.0; +static const CLLocationDistance MGLConfigHibernationRadiusWide = 1200.0; + +NSString *const MGLMapboxMetricsProfile = @"MGLMapboxMetricsProfile"; + +static NSString *const MGLConfigHibernationRadiusWideKey = @"WideGeoFence"; + +@implementation MGLTelemetryConfig + +- (instancetype) init { + self = [super init]; + if (self) { + _MGLLocationManagerHibernationRadius = MGLConfigHibernationRadiusDefault; + } + return self; +} + ++ (nullable instancetype)sharedConfig { + static dispatch_once_t onceToken; + static MGLTelemetryConfig *_sharedConfig; + dispatch_once(&onceToken, ^{ + _sharedConfig = [[self alloc] init]; + }); + return _sharedConfig; +} + +- (void)configurationFromKey:(NSString *)key { + if ([key isEqualToString:MGLConfigHibernationRadiusWideKey]) { + _MGLLocationManagerHibernationRadius = MGLConfigHibernationRadiusWide; + } +} + +@end From 638ab799eda3d8eb474fb248469914f12e6a957a Mon Sep 17 00:00:00 2001 From: Randall C Lee Date: Tue, 6 Feb 2018 15:34:46 -0500 Subject: [PATCH 07/22] Adjust radius (#11132) --- platform/ios/src/MGLTelemetryConfig.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/ios/src/MGLTelemetryConfig.m b/platform/ios/src/MGLTelemetryConfig.m index c45554414..828bafb14 100644 --- a/platform/ios/src/MGLTelemetryConfig.m +++ b/platform/ios/src/MGLTelemetryConfig.m @@ -1,7 +1,7 @@ #import "MGLTelemetryConfig.h" static const CLLocationDistance MGLConfigHibernationRadiusDefault = 300.0; -static const CLLocationDistance MGLConfigHibernationRadiusWide = 1200.0; +static const CLLocationDistance MGLConfigHibernationRadiusWide = 600.0; NSString *const MGLMapboxMetricsProfile = @"MGLMapboxMetricsProfile"; From 057af7e75f78a7a8ddd12e4df0f1bd688eaf31fa Mon Sep 17 00:00:00 2001 From: Chris Loer Date: Tue, 6 Feb 2018 10:16:14 -0800 Subject: [PATCH 08/22] [core] Don't crash on line labels with 0 glyphs. Fixes issue #10956. --- src/mbgl/layout/symbol_projection.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mbgl/layout/symbol_projection.cpp b/src/mbgl/layout/symbol_projection.cpp index 279d251f8..d97bfb1ac 100644 --- a/src/mbgl/layout/symbol_projection.cpp +++ b/src/mbgl/layout/symbol_projection.cpp @@ -271,7 +271,7 @@ namespace mbgl { placedGlyphs.push_back(*placedGlyph); } placedGlyphs.push_back(*lastPlacedGlyph); - } else { + } else if (symbol.glyphOffsets.size() == 1) { // Only a single glyph to place // So, determine whether to flip based on projected angle of the line segment it's on if (keepUpright && !flip) { @@ -289,7 +289,6 @@ namespace mbgl { return PlacementResult::NeedsFlipping; } } - assert(symbol.glyphOffsets.size() == 1); // We are relying on SymbolInstance.hasText filtering out symbols without any glyphs at all const float glyphOffsetX = symbol.glyphOffsets.front(); optional singleGlyph = placeGlyphAlongLine(fontScale * glyphOffsetX, lineOffsetX, lineOffsetY, flip, projectedAnchorPoint, symbol.anchorPoint, symbol.segment, symbol.line, labelPlaneMatrix); @@ -299,6 +298,8 @@ namespace mbgl { placedGlyphs.push_back(*singleGlyph); } + // The number of placedGlyphs must equal the number of glyphOffsets, which must correspond to the number of glyph vertices + // There may be 0 glyphs here, if a label consists entirely of glyphs that have 0x0 dimensions for (auto& placedGlyph : placedGlyphs) { addDynamicAttributes(placedGlyph.point, placedGlyph.angle, symbol.placementZoom, dynamicVertexArray); } From 7bccc9d69555423218c97aab5b3ff2cf9ed3bd24 Mon Sep 17 00:00:00 2001 From: tobrun Date: Tue, 6 Feb 2018 14:57:45 +0100 Subject: [PATCH 09/22] [android] - introduce mapview weak reference in global layout listener --- .../com/mapbox/mapboxsdk/maps/MapView.java | 46 ++++++++++++------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java index c5b7159e8..4deea90cb 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java @@ -22,7 +22,6 @@ import android.view.ViewTreeObserver; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ZoomButtonsController; - import com.mapbox.mapboxsdk.R; import com.mapbox.mapboxsdk.annotations.Annotation; import com.mapbox.mapboxsdk.annotations.MarkerViewManager; @@ -37,19 +36,18 @@ import com.mapbox.mapboxsdk.maps.widgets.MyLocationViewSettings; import com.mapbox.mapboxsdk.net.ConnectivityReceiver; import com.mapbox.mapboxsdk.storage.FileSource; import com.mapbox.services.android.telemetry.MapboxTelemetry; +import timber.log.Timber; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.opengles.GL10; - -import timber.log.Timber; - import static com.mapbox.mapboxsdk.maps.widgets.CompassView.TIME_MAP_NORTH_ANIMATION; import static com.mapbox.mapboxsdk.maps.widgets.CompassView.TIME_WAIT_IDLE; @@ -133,17 +131,7 @@ public class MapView extends FrameLayout { setContentDescription(context.getString(R.string.mapbox_mapActionDescription)); setWillNotDraw(false); - getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { - @Override - public void onGlobalLayout() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - getViewTreeObserver().removeOnGlobalLayoutListener(this); - } else { - getViewTreeObserver().removeGlobalOnLayoutListener(this); - } - initialiseDrawingSurface(options); - } - }); + getViewTreeObserver().addOnGlobalLayoutListener(new MapViewLayoutListener(this, options)); } private void initialiseMap() { @@ -882,6 +870,30 @@ public class MapView extends FrameLayout { void onMapChanged(@MapChange int change); } + private static class MapViewLayoutListener implements ViewTreeObserver.OnGlobalLayoutListener { + + private WeakReference mapViewWeakReference; + private MapboxMapOptions options; + + MapViewLayoutListener(MapView mapView, MapboxMapOptions options) { + this.mapViewWeakReference = new WeakReference<>(mapView); + this.options = options; + } + + @Override + public void onGlobalLayout() { + MapView mapView = mapViewWeakReference.get(); + if (mapView != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + mapView.getViewTreeObserver().removeOnGlobalLayoutListener(this); + } else { + mapView.getViewTreeObserver().removeGlobalOnLayoutListener(this); + } + mapView.initialiseDrawingSurface(options); + } + } + } + private class FocalPointInvalidator implements FocalPointChangeListener { private final List focalPointChangeListeners = new ArrayList<>(); From aeba621573d77e1ce4f72b1e9b39bde92bf174a2 Mon Sep 17 00:00:00 2001 From: tobrun Date: Tue, 6 Feb 2018 13:41:16 +0100 Subject: [PATCH 10/22] [android] - programmatically create GlSurfaceView --- .../src/main/java/com/mapbox/mapboxsdk/maps/MapView.java | 4 ++-- .../src/main/res/layout/mapbox_mapview_internal.xml | 7 ------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java index 4deea90cb..6db11db49 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java @@ -292,7 +292,7 @@ public class MapView extends FrameLayout { addView(textureView, 0); } else { - GLSurfaceView glSurfaceView = (GLSurfaceView) findViewById(R.id.surfaceView); + GLSurfaceView glSurfaceView = new GLSurfaceView(getContext()); glSurfaceView.setZOrderMediaOverlay(mapboxMapOptions.getRenderSurfaceOnTop()); mapRenderer = new GLSurfaceViewMapRenderer(getContext(), glSurfaceView, options.getLocalIdeographFontFamily()) { @Override @@ -302,7 +302,7 @@ public class MapView extends FrameLayout { } }; - glSurfaceView.setVisibility(View.VISIBLE); + addView(glSurfaceView, 0); } nativeMapView = new NativeMapView(this, mapRenderer); diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_mapview_internal.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_mapview_internal.xml index df7ccaaca..29ff49f47 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_mapview_internal.xml +++ b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_mapview_internal.xml @@ -1,13 +1,6 @@ - - Date: Fri, 5 Jan 2018 06:35:31 -0800 Subject: [PATCH 11/22] Support TileJSON bounds property (#10701) * [core] Parse TileJSON bounds property * [core] Add TileRange and LatLngBounds::contains(CanonicalTileID) Move LatLngBounds::contains impl to cpp file * [core] Skip tile creation outside of tileset bounds * [core] Fix TileRange for wrapped bounds and use for CustomTileLoader instead of LatLngBounds comparisons for tiles. --- cmake/core-files.cmake | 1 + cmake/test-files.cmake | 2 + include/mbgl/style/conversion/tileset.hpp | 31 ++++++ include/mbgl/util/geo.hpp | 18 ++- include/mbgl/util/projection.hpp | 4 + include/mbgl/util/tileset.hpp | 14 ++- src/mbgl/algorithm/update_renderables.hpp | 6 +- .../annotation/render_annotation_source.cpp | 1 + .../sources/render_geojson_source.cpp | 1 + .../renderer/sources/render_raster_source.cpp | 1 + .../renderer/sources/render_vector_source.cpp | 1 + src/mbgl/renderer/tile_pyramid.cpp | 11 ++ src/mbgl/renderer/tile_pyramid.hpp | 1 + src/mbgl/style/conversion/tileset.cpp | 104 ++++++++++++++++++ src/mbgl/util/geo.cpp | 82 ++++++++++++++ src/mbgl/util/tile_range.hpp | 47 ++++++++ test/style/conversion/tileset.test.cpp | 72 ++++++++++++ test/util/tile_range.test.cpp | 56 ++++++++++ 18 files changed, 436 insertions(+), 17 deletions(-) create mode 100644 src/mbgl/style/conversion/tileset.cpp create mode 100644 src/mbgl/util/tile_range.hpp create mode 100644 test/style/conversion/tileset.test.cpp create mode 100644 test/util/tile_range.test.cpp diff --git a/cmake/core-files.cmake b/cmake/core-files.cmake index 33b3072f3..0c109d080 100644 --- a/cmake/core-files.cmake +++ b/cmake/core-files.cmake @@ -614,6 +614,7 @@ set(MBGL_CORE_FILES src/mbgl/util/tile_coordinate.hpp src/mbgl/util/tile_cover.cpp src/mbgl/util/tile_cover.hpp + src/mbgl/util/tile_range.hpp src/mbgl/util/tiny_sdf.cpp src/mbgl/util/tiny_sdf.hpp src/mbgl/util/token.hpp diff --git a/cmake/test-files.cmake b/cmake/test-files.cmake index 027c34f31..dfd646e62 100644 --- a/cmake/test-files.cmake +++ b/cmake/test-files.cmake @@ -87,6 +87,7 @@ set(MBGL_TEST_FILES test/style/conversion/layer.test.cpp test/style/conversion/light.test.cpp test/style/conversion/stringify.test.cpp + test/style/conversion/tileset.test.cpp # style test/style/filter.test.cpp @@ -138,6 +139,7 @@ set(MBGL_TEST_FILES test/util/thread.test.cpp test/util/thread_local.test.cpp test/util/tile_cover.test.cpp + test/util/tile_range.test.cpp test/util/timer.test.cpp test/util/token.test.cpp test/util/url.test.cpp diff --git a/include/mbgl/style/conversion/tileset.hpp b/include/mbgl/style/conversion/tileset.hpp index 377170aa6..6577e3957 100644 --- a/include/mbgl/style/conversion/tileset.hpp +++ b/include/mbgl/style/conversion/tileset.hpp @@ -10,6 +10,11 @@ namespace conversion { template <> struct Converter { public: + + bool validateLatitude(const double lat) const { + return lat < 90 && lat > -90; + } + template optional operator()(const V& value, Error& error) const { Tileset result; @@ -72,6 +77,32 @@ public: result.attribution = std::move(*attribution); } + auto boundsValue = objectMember(value, "bounds"); + if (boundsValue) { + if (!isArray(*boundsValue) || arrayLength(*boundsValue) != 4) { + error = { "bounds must be an array with left, bottom, top, and right values" }; + return {}; + } + optional left = toDouble(arrayMember(*boundsValue, 0)); + optional bottom = toDouble(arrayMember(*boundsValue, 1)); + optional right = toDouble(arrayMember(*boundsValue, 2)); + optional top = toDouble(arrayMember(*boundsValue, 3)); + + if (!left || !right || !bottom || !top) { + error = { "bounds array must contain numeric longitude and latitude values" }; + return {}; + } + if (!validateLatitude(*bottom) || !validateLatitude(*top) || top <= bottom){ + error = { "bounds latitude values must be between -90 and 90 with bottom less than top" }; + return {}; + } + if(*left >= *right) { + error = { "bounds left longitude should be less than right longitude" }; + return {}; + } + result.bounds = LatLngBounds::hull({ *bottom, *left }, { *top, *right }); + } + return result; } }; diff --git a/include/mbgl/util/geo.hpp b/include/mbgl/util/geo.hpp index 6d725b102..dacdb968f 100644 --- a/include/mbgl/util/geo.hpp +++ b/include/mbgl/util/geo.hpp @@ -154,19 +154,15 @@ public: sw.longitude() > ne.longitude(); } - bool contains(const LatLng& point) const { - return (point.latitude() >= sw.latitude() && - point.latitude() <= ne.latitude() && - point.longitude() >= sw.longitude() && - point.longitude() <= ne.longitude()); + bool crossesAntimeridian() const { + return (sw.wrapped().longitude() > ne.wrapped().longitude()); } - bool intersects(const LatLngBounds area) const { - return (area.ne.latitude() > sw.latitude() && - area.sw.latitude() < ne.latitude() && - area.ne.longitude() > sw.longitude() && - area.sw.longitude() < ne.longitude()); - } + bool contains(const CanonicalTileID& tileID) const; + bool contains(const LatLng& point, LatLng::WrapMode wrap = LatLng::Unwrapped) const; + bool contains(const LatLngBounds& area, LatLng::WrapMode wrap = LatLng::Unwrapped) const; + + bool intersects(const LatLngBounds area, LatLng::WrapMode wrap = LatLng::Unwrapped) const; private: LatLng sw; diff --git a/include/mbgl/util/projection.hpp b/include/mbgl/util/projection.hpp index f64502c5b..a9c12e595 100644 --- a/include/mbgl/util/projection.hpp +++ b/include/mbgl/util/projection.hpp @@ -78,6 +78,10 @@ public: return project_(latLng, worldSize(scale)); } + static Point project(const LatLng& latLng, uint8_t zoom) { + return project_(latLng, std::pow(2.0, zoom)); + } + static LatLng unproject(const Point& p, double scale, LatLng::WrapMode wrapMode = LatLng::Unwrapped) { auto p2 = p * util::DEGREES_MAX / worldSize(scale); return LatLng { diff --git a/include/mbgl/util/tileset.hpp b/include/mbgl/util/tileset.hpp index 61aa47d4e..7bef0e89e 100644 --- a/include/mbgl/util/tileset.hpp +++ b/include/mbgl/util/tileset.hpp @@ -2,7 +2,9 @@ #include #include - +#include +#include +#include #include #include #include @@ -17,6 +19,7 @@ public: Range zoomRange; std::string attribution; Scheme scheme; + optional bounds; Tileset(std::vector tiles_ = std::vector(), Range zoomRange_ = { 0, util::DEFAULT_MAX_ZOOM }, @@ -25,13 +28,14 @@ public: : tiles(std::move(tiles_)), zoomRange(std::move(zoomRange_)), attribution(std::move(attribution_)), - scheme(scheme_) {} + scheme(scheme_), + bounds() {} - // TileJSON also includes center, zoom, and bounds, but they are not used by mbgl. + // TileJSON also includes center and zoom but they are not used by mbgl. friend bool operator==(const Tileset& lhs, const Tileset& rhs) { - return std::tie(lhs.tiles, lhs.zoomRange, lhs.attribution, lhs.scheme) - == std::tie(rhs.tiles, rhs.zoomRange, rhs.attribution, rhs.scheme); + return std::tie(lhs.tiles, lhs.zoomRange, lhs.attribution, lhs.scheme, lhs.bounds) + == std::tie(rhs.tiles, rhs.zoomRange, rhs.attribution, rhs.scheme, rhs.bounds); } }; diff --git a/src/mbgl/algorithm/update_renderables.hpp b/src/mbgl/algorithm/update_renderables.hpp index c583b6b2b..5fbe0d943 100644 --- a/src/mbgl/algorithm/update_renderables.hpp +++ b/src/mbgl/algorithm/update_renderables.hpp @@ -35,7 +35,11 @@ void updateRenderables(GetTileFn getTile, auto tile = getTile(idealDataTileID); if (!tile) { tile = createTile(idealDataTileID); - assert(tile); + // For source types where TileJSON.bounds is set, tiles outside the + // bounds are not created + if(tile == nullptr) { + continue; + } } // if (source has the tile and bucket is loaded) { diff --git a/src/mbgl/annotation/render_annotation_source.cpp b/src/mbgl/annotation/render_annotation_source.cpp index ba80be0da..0aff64583 100644 --- a/src/mbgl/annotation/render_annotation_source.cpp +++ b/src/mbgl/annotation/render_annotation_source.cpp @@ -41,6 +41,7 @@ void RenderAnnotationSource::update(Immutable baseImpl_, // Zoom level 16 is typically sufficient for annotations. // See https://github.com/mapbox/mapbox-gl-native/issues/10197 { 0, 16 }, + optional {}, [&] (const OverscaledTileID& tileID) { return std::make_unique(tileID, parameters); }); diff --git a/src/mbgl/renderer/sources/render_geojson_source.cpp b/src/mbgl/renderer/sources/render_geojson_source.cpp index 504db78ea..c13cd49f4 100644 --- a/src/mbgl/renderer/sources/render_geojson_source.cpp +++ b/src/mbgl/renderer/sources/render_geojson_source.cpp @@ -62,6 +62,7 @@ void RenderGeoJSONSource::update(Immutable baseImpl_, SourceType::GeoJSON, util::tileSize, impl().getZoomRange(), + optional{}, [&] (const OverscaledTileID& tileID) { return std::make_unique(tileID, impl().id, parameters, data->getTile(tileID.canonical)); }); diff --git a/src/mbgl/renderer/sources/render_raster_source.cpp b/src/mbgl/renderer/sources/render_raster_source.cpp index bcd719365..5e63e929e 100644 --- a/src/mbgl/renderer/sources/render_raster_source.cpp +++ b/src/mbgl/renderer/sources/render_raster_source.cpp @@ -52,6 +52,7 @@ void RenderRasterSource::update(Immutable baseImpl_, SourceType::Raster, impl().getTileSize(), tileset->zoomRange, + tileset->bounds, [&] (const OverscaledTileID& tileID) { return std::make_unique(tileID, parameters, *tileset); }); diff --git a/src/mbgl/renderer/sources/render_vector_source.cpp b/src/mbgl/renderer/sources/render_vector_source.cpp index ca3071c6b..a3da3e7cb 100644 --- a/src/mbgl/renderer/sources/render_vector_source.cpp +++ b/src/mbgl/renderer/sources/render_vector_source.cpp @@ -55,6 +55,7 @@ void RenderVectorSource::update(Immutable baseImpl_, SourceType::Vector, util::tileSize, tileset->zoomRange, + tileset->bounds, [&] (const OverscaledTileID& tileID) { return std::make_unique(tileID, impl().id, parameters, *tileset); }); diff --git a/src/mbgl/renderer/tile_pyramid.cpp b/src/mbgl/renderer/tile_pyramid.cpp index 3e2311089..c9e3b0630 100644 --- a/src/mbgl/renderer/tile_pyramid.cpp +++ b/src/mbgl/renderer/tile_pyramid.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -15,6 +16,7 @@ #include +#include #include namespace mbgl { @@ -62,6 +64,7 @@ void TilePyramid::update(const std::vector>& layer const SourceType type, const uint16_t tileSize, const Range zoomRange, + optional bounds, std::function (const OverscaledTileID&)> createTile) { // If we need a relayout, abandon any cached tiles; they're now stale. if (needsRelayout) { @@ -134,7 +137,15 @@ void TilePyramid::update(const std::vector>& layer auto it = tiles.find(tileID); return it == tiles.end() ? nullptr : it->second.get(); }; + + optional tileRange = {}; + if (bounds) { + tileRange = util::TileRange::fromLatLngBounds(*bounds, std::min(tileZoom, (int32_t)zoomRange.max)); + } auto createTileFn = [&](const OverscaledTileID& tileID) -> Tile* { + if (tileRange && !tileRange->contains(tileID.canonical)) { + return nullptr; + } std::unique_ptr tile = cache.get(tileID); if (!tile) { tile = createTile(tileID); diff --git a/src/mbgl/renderer/tile_pyramid.hpp b/src/mbgl/renderer/tile_pyramid.hpp index 73a8d34c1..e34b05027 100644 --- a/src/mbgl/renderer/tile_pyramid.hpp +++ b/src/mbgl/renderer/tile_pyramid.hpp @@ -40,6 +40,7 @@ public: SourceType type, uint16_t tileSize, Range zoomRange, + optional bounds, std::function (const OverscaledTileID&)> createTile); void startRender(PaintParameters&); diff --git a/src/mbgl/style/conversion/tileset.cpp b/src/mbgl/style/conversion/tileset.cpp new file mode 100644 index 000000000..6e559c0ca --- /dev/null +++ b/src/mbgl/style/conversion/tileset.cpp @@ -0,0 +1,104 @@ +#include +#include + +namespace mbgl { +namespace style { +namespace conversion { + +bool validateLatitude(const double lat) { + return lat < 90 && lat > -90; +} + +optional Converter::operator()(const Convertible& value, Error& error) const { + Tileset result; + + auto tiles = objectMember(value, "tiles"); + if (!tiles) { + error = { "source must have tiles" }; + return {}; + } + + if (!isArray(*tiles)) { + error = { "source tiles must be an array" }; + return {}; + } + + for (std::size_t i = 0; i < arrayLength(*tiles); i++) { + optional urlTemplate = toString(arrayMember(*tiles, i)); + if (!urlTemplate) { + error = { "source tiles member must be a string" }; + return {}; + } + result.tiles.push_back(std::move(*urlTemplate)); + } + + auto schemeValue = objectMember(value, "scheme"); + if (schemeValue) { + optional scheme = toString(*schemeValue); + if (scheme && *scheme == "tms") { + result.scheme = Tileset::Scheme::TMS; + } + } + + auto minzoomValue = objectMember(value, "minzoom"); + if (minzoomValue) { + optional minzoom = toNumber(*minzoomValue); + if (!minzoom || *minzoom < 0 || *minzoom > std::numeric_limits::max()) { + error = { "invalid minzoom" }; + return {}; + } + result.zoomRange.min = *minzoom; + } + + auto maxzoomValue = objectMember(value, "maxzoom"); + if (maxzoomValue) { + optional maxzoom = toNumber(*maxzoomValue); + if (!maxzoom || *maxzoom < 0 || *maxzoom > std::numeric_limits::max()) { + error = { "invalid maxzoom" }; + return {}; + } + result.zoomRange.max = *maxzoom; + } + + auto attributionValue = objectMember(value, "attribution"); + if (attributionValue) { + optional attribution = toString(*attributionValue); + if (!attribution) { + error = { "source attribution must be a string" }; + return {}; + } + result.attribution = std::move(*attribution); + } + + auto boundsValue = objectMember(value, "bounds"); + if (boundsValue) { + if (!isArray(*boundsValue) || arrayLength(*boundsValue) != 4) { + error = { "bounds must be an array with left, bottom, top, and right values" }; + return {}; + } + optional left = toDouble(arrayMember(*boundsValue, 0)); + optional bottom = toDouble(arrayMember(*boundsValue, 1)); + optional right = toDouble(arrayMember(*boundsValue, 2)); + optional top = toDouble(arrayMember(*boundsValue, 3)); + + if (!left || !right || !bottom || !top) { + error = { "bounds array must contain numeric longitude and latitude values" }; + return {}; + } + if (!validateLatitude(*bottom) || !validateLatitude(*top) || top <= bottom){ + error = { "bounds latitude values must be between -90 and 90 with bottom less than top" }; + return {}; + } + if(*left >= *right) { + error = { "bounds left longitude should be less than right longitude" }; + return {}; + } + result.bounds = LatLngBounds::hull({ *bottom, *left }, { *top, *right }); + } + + return result; +} + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/util/geo.cpp b/src/mbgl/util/geo.cpp index f38aba20c..a04e2c535 100644 --- a/src/mbgl/util/geo.cpp +++ b/src/mbgl/util/geo.cpp @@ -1,6 +1,8 @@ #include #include #include +#include +#include #include @@ -32,6 +34,86 @@ LatLngBounds::LatLngBounds(const CanonicalTileID& id) ne({ lat_(id.z, id.y), lon_(id.z, id.x + 1) }) { } +bool LatLngBounds::contains(const CanonicalTileID& tileID) const { + return util::TileRange::fromLatLngBounds(*this, tileID.z).contains(tileID); +} + +bool LatLngBounds::contains(const LatLng& point, LatLng::WrapMode wrap /*= LatLng::Unwrapped*/) const { + bool containsLatitude = point.latitude() >= sw.latitude() && + point.latitude() <= ne.latitude(); + if (!containsLatitude) { + return false; + } + + bool containsUnwrappedLongitude = point.longitude() >= sw.longitude() && + point.longitude() <= ne.longitude(); + if (containsUnwrappedLongitude) { + return true; + } else if (wrap == LatLng::Wrapped) { + LatLngBounds wrapped(sw.wrapped(), ne.wrapped()); + auto ptLon = point.wrapped().longitude(); + if (crossesAntimeridian()) { + return (ptLon >= wrapped.sw.longitude() && + ptLon <= util::LONGITUDE_MAX) || + (ptLon <= wrapped.ne.longitude() && + ptLon >= -util::LONGITUDE_MAX); + } else { + return (ptLon >= wrapped.sw.longitude() && + ptLon <= wrapped.ne.longitude()); + } + } + return false; +} + +bool LatLngBounds::contains(const LatLngBounds& area, LatLng::WrapMode wrap /*= LatLng::Unwrapped*/) const { + bool containsLatitude = area.north() <= north() && area.south() >= south(); + if (!containsLatitude) { + return false; + } + + bool containsUnwrapped = area.east() <= east() && area.west() >= west(); + if(containsUnwrapped) { + return true; + } else if (wrap == LatLng::Wrapped) { + LatLngBounds wrapped(sw.wrapped(), ne.wrapped()); + LatLngBounds other(area.sw.wrapped(), area.ne.wrapped()); + if (crossesAntimeridian() & !area.crossesAntimeridian()) { + return (other.east() <= util::LONGITUDE_MAX && other.west() >= wrapped.west()) || + (other.east() <= wrapped.east() && other.west() >= -util::LONGITUDE_MAX); + } else { + return other.east() <= wrapped.east() && other.west() >= wrapped.west(); + } + } + return false; +} + +bool LatLngBounds::intersects(const LatLngBounds area, LatLng::WrapMode wrap /*= LatLng::Unwrapped*/) const { + bool latitudeIntersects = area.north() > south() && area.south() < north(); + if (!latitudeIntersects) { + return false; + } + + bool longitudeIntersects = area.east() > west() && area.west() < east(); + if (longitudeIntersects) { + return true; + } else if (wrap == LatLng::Wrapped) { + LatLngBounds wrapped(sw.wrapped(), ne.wrapped()); + LatLngBounds other(area.sw.wrapped(), area.ne.wrapped()); + if (crossesAntimeridian()) { + return area.crossesAntimeridian() || + other.east() > wrapped.west() || + other.west() < wrapped.east(); + } else if (other.crossesAntimeridian()){ + return other.east() > wrapped.west() || + other.west() < wrapped.east(); + } else { + return other.east() > wrapped.west() && + other.west() < wrapped.east(); + } + } + return false; +} + ScreenCoordinate EdgeInsets::getCenter(uint16_t width, uint16_t height) const { return { (width - left() - right()) / 2.0 + left(), diff --git a/src/mbgl/util/tile_range.hpp b/src/mbgl/util/tile_range.hpp new file mode 100644 index 000000000..f630a4907 --- /dev/null +++ b/src/mbgl/util/tile_range.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include +#include + +namespace mbgl { + +namespace util { + +class TileRange { +public: + Range> range; + uint8_t z; + + // Compute the range of tiles covered by the bounds. + static TileRange fromLatLngBounds(const LatLngBounds& bounds, uint8_t z) { + auto swProj = Projection::project(bounds.southwest().wrapped(), z); + auto ne = bounds.northeast(); + auto neProj = Projection::project(ne.longitude() > util::LONGITUDE_MAX ? ne.wrapped() : ne , z); + const auto minX = std::floor(swProj.x); + const auto maxX = std::ceil(neProj.x); + const auto minY = std::floor(neProj.y); + const auto maxY = std::ceil(swProj.y); + return TileRange({ {minX, minY}, {maxX, maxY} }, z); + } + + bool contains(const CanonicalTileID& tileID) { + return z == tileID.z && + (range.min.x >= range.max.x ? //For wrapped bounds + tileID.x >= range.min.x || tileID.x < range.max.x : + tileID.x < range.max.x && tileID.x >= range.min.x) && + tileID.y < range.max.y && + tileID.y >= range.min.y; + } + +private: + TileRange(Range> range_, uint8_t z_) + : range(range_), + z(z_) { + } + +}; + +} // namespace util +} // namespace mbgl diff --git a/test/style/conversion/tileset.test.cpp b/test/style/conversion/tileset.test.cpp new file mode 100644 index 000000000..8002cd038 --- /dev/null +++ b/test/style/conversion/tileset.test.cpp @@ -0,0 +1,72 @@ +#include + +#include +#include + +#include + +using namespace mbgl; +using namespace mbgl::style::conversion; + +TEST(Tileset, Empty) { + Error error; + mbgl::optional converted = convertJSON("{}", error); + EXPECT_FALSE((bool) converted); +} + +TEST(Tileset, ErrorHandling) { + Error error; + mbgl::optional converted = convertJSON(R"JSON({ + "tiles": "should not be a string" + })JSON", error); + EXPECT_FALSE((bool) converted); +} + +TEST(Tileset, InvalidBounds) { + { + Error error; + mbgl::optional converted = convertJSON(R"JSON({ + "tiles": ["http://mytiles"], + "bounds": [73, -180, -73, -120] + })JSON", error); + + EXPECT_FALSE((bool) converted); + } + { + Error error; + mbgl::optional converted = convertJSON(R"JSON({ + "tiles": ["http://mytiles"], + "bounds": [-120] + })JSON", error); + + EXPECT_FALSE((bool) converted); + } + { + Error error; + mbgl::optional converted = convertJSON(R"JSON({ + "tiles": ["http://mytiles"], + "bounds": "should not be a string" + })JSON", error); + + EXPECT_FALSE((bool) converted); + } +} + +TEST(Tileset, FullConversion) { + Error error; + Tileset converted = *convertJSON(R"JSON({ + "tiles": ["http://mytiles"], + "scheme": "xyz", + "minzoom": 1, + "maxzoom": 2, + "attribution": "mapbox", + "bounds": [-180, -73, -120, 73] + })JSON", error); + + EXPECT_EQ(converted.tiles[0], "http://mytiles"); + EXPECT_EQ(converted.scheme, Tileset::Scheme::XYZ); + EXPECT_EQ(converted.zoomRange.min, 1); + EXPECT_EQ(converted.zoomRange.max, 2); + EXPECT_EQ(converted.attribution, "mapbox"); + EXPECT_EQ(converted.bounds, LatLngBounds::hull({73, -180}, {-73, -120})); +} diff --git a/test/util/tile_range.test.cpp b/test/util/tile_range.test.cpp new file mode 100644 index 000000000..dc8ae2870 --- /dev/null +++ b/test/util/tile_range.test.cpp @@ -0,0 +1,56 @@ + +#include +#include +#include + +#include + +using namespace mbgl; + +TEST(TileRange, ContainsWorld) { + auto range = util::TileRange::fromLatLngBounds(LatLngBounds::world(), 0); + EXPECT_TRUE(range.contains(CanonicalTileID(0, 0, 0))); + EXPECT_FALSE(range.contains(CanonicalTileID(10, 0, 0))); +} + +TEST(TileRange, ContainsBoundsFromTile) { + { + const LatLngBounds bounds{ CanonicalTileID(3, 7, 0) }; + auto range = util::TileRange::fromLatLngBounds(bounds, 3); + EXPECT_TRUE(range.contains(CanonicalTileID(3, 7, 0))); + } + { + const LatLngBounds bounds{ CanonicalTileID(10, 162, 395) }; + auto range = util::TileRange::fromLatLngBounds(bounds, 10); + EXPECT_TRUE(range.contains(CanonicalTileID(10, 162, 395))); + } +} +TEST(TileRange, ContainsIntersectingTiles) { + auto bounds = LatLngBounds::hull({ 37.6609, -122.5744 }, { 37.8271, -122.3204 }); + auto range = util::TileRange::fromLatLngBounds(bounds, 13); + EXPECT_FALSE(range.contains(CanonicalTileID(13, 1316, 3100))); + EXPECT_TRUE(range.contains(CanonicalTileID(13, 1310, 3166))); +} + +TEST(TileRange, ContainsWrappedBounds) { + auto wrappedBounds = LatLngBounds::hull({ 37.6609, 237.6796 }, { 37.8271, 237.4256 }); + auto range = util::TileRange::fromLatLngBounds(wrappedBounds, 13); + EXPECT_FALSE(range.contains(CanonicalTileID(13, 1316, 3100))); + EXPECT_TRUE(range.contains(CanonicalTileID(13, 1310, 3166))); +} + +TEST(TileRange, ContainsBoundsCrossingAntimeridian) { + { + auto cossingBounds = LatLngBounds::hull({-20.9615, -214.309}, {19.477, -155.830}); + auto range = util::TileRange::fromLatLngBounds(cossingBounds, 1); + EXPECT_TRUE(range.contains(CanonicalTileID(1, 1, 1))); + EXPECT_TRUE(range.contains(CanonicalTileID(1, 0, 0))); + } + { + auto cossingBounds = LatLngBounds::hull({-20.9615, -214.309}, {19.477, -155.830}); + auto range = util::TileRange::fromLatLngBounds(cossingBounds, 6); + EXPECT_FALSE(range.contains(CanonicalTileID(6, 55, 34))); + EXPECT_FALSE(range.contains(CanonicalTileID(6, 5, 28))); + EXPECT_TRUE(range.contains(CanonicalTileID(6, 63, 28))); + } +} From 4ab07f5be73f802e1f8f9d88eafa7077dbd5cac8 Mon Sep 17 00:00:00 2001 From: Asheem Mamoowala Date: Mon, 5 Feb 2018 19:31:03 -0500 Subject: [PATCH 12/22] Reset tileset-based render sources when any tileset properties changed. (#11042) --- .../renderer/sources/render_raster_source.cpp | 15 ++++++++------- .../renderer/sources/render_raster_source.hpp | 2 +- .../renderer/sources/render_vector_source.cpp | 15 ++++++++------- .../renderer/sources/render_vector_source.hpp | 2 +- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/mbgl/renderer/sources/render_raster_source.cpp b/src/mbgl/renderer/sources/render_raster_source.cpp index 5e63e929e..46e3c2eff 100644 --- a/src/mbgl/renderer/sources/render_raster_source.cpp +++ b/src/mbgl/renderer/sources/render_raster_source.cpp @@ -29,14 +29,10 @@ void RenderRasterSource::update(Immutable baseImpl_, enabled = needsRendering; - optional tileset = impl().getTileset(); + optional _tileset = impl().getTileset(); - if (!tileset) { - return; - } - - if (tileURLTemplates != tileset->tiles) { - tileURLTemplates = tileset->tiles; + if (tileset != _tileset) { + tileset = _tileset; // TODO: this removes existing buckets, and will cause flickering. // Should instead refresh tile data in place. @@ -44,6 +40,11 @@ void RenderRasterSource::update(Immutable baseImpl_, tilePyramid.renderTiles.clear(); tilePyramid.cache.clear(); } + // Allow clearing the tile pyramid first, before the early return in case + // the new tileset is not yet available or has an error in loading + if (!_tileset) { + return; + } tilePyramid.update(layers, needsRendering, diff --git a/src/mbgl/renderer/sources/render_raster_source.hpp b/src/mbgl/renderer/sources/render_raster_source.hpp index 01de81230..aebc49bf8 100644 --- a/src/mbgl/renderer/sources/render_raster_source.hpp +++ b/src/mbgl/renderer/sources/render_raster_source.hpp @@ -39,7 +39,7 @@ private: const style::RasterSource::Impl& impl() const; TilePyramid tilePyramid; - optional> tileURLTemplates; + optional tileset; }; template <> diff --git a/src/mbgl/renderer/sources/render_vector_source.cpp b/src/mbgl/renderer/sources/render_vector_source.cpp index a3da3e7cb..310dfe68b 100644 --- a/src/mbgl/renderer/sources/render_vector_source.cpp +++ b/src/mbgl/renderer/sources/render_vector_source.cpp @@ -32,14 +32,10 @@ void RenderVectorSource::update(Immutable baseImpl_, enabled = needsRendering; - optional tileset = impl().getTileset(); + optional _tileset = impl().getTileset(); - if (!tileset) { - return; - } - - if (tileURLTemplates != tileset->tiles) { - tileURLTemplates = tileset->tiles; + if (tileset != _tileset) { + tileset = _tileset; // TODO: this removes existing buckets, and will cause flickering. // Should instead refresh tile data in place. @@ -47,6 +43,11 @@ void RenderVectorSource::update(Immutable baseImpl_, tilePyramid.renderTiles.clear(); tilePyramid.cache.clear(); } + // Allow clearing the tile pyramid first, before the early return in case + // the new tileset is not yet available or has an error in loading + if (!_tileset) { + return; + } tilePyramid.update(layers, needsRendering, diff --git a/src/mbgl/renderer/sources/render_vector_source.hpp b/src/mbgl/renderer/sources/render_vector_source.hpp index 5e5c6d110..f237b4b82 100644 --- a/src/mbgl/renderer/sources/render_vector_source.hpp +++ b/src/mbgl/renderer/sources/render_vector_source.hpp @@ -39,7 +39,7 @@ private: const style::VectorSource::Impl& impl() const; TilePyramid tilePyramid; - optional> tileURLTemplates; + optional tileset; }; template <> From 101b55b0f070ba86c8d5df089dc03393c9abe8da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Wed, 7 Feb 2018 12:27:01 -0800 Subject: [PATCH 13/22] [ios, macos] Added option to restrict tile source to bounds Cherry-picked from c3bf7c55a1f648e57c3853d555ff5f63c989f8c4. --- .../darwin/docs/guides/For Style Authors.md.ejs | 2 ++ platform/darwin/src/MGLTileSource.h | 14 ++++++++++++++ platform/darwin/src/MGLTileSource.mm | 12 ++++++++++++ platform/darwin/test/MGLTileSetTests.mm | 14 ++++++++++++++ platform/ios/CHANGELOG.md | 6 +++++- platform/ios/docs/guides/For Style Authors.md | 2 ++ platform/macos/CHANGELOG.md | 4 ++++ platform/macos/docs/guides/For Style Authors.md | 2 ++ 8 files changed, 55 insertions(+), 1 deletion(-) diff --git a/platform/darwin/docs/guides/For Style Authors.md.ejs b/platform/darwin/docs/guides/For Style Authors.md.ejs index 06a890770..ba6f14647 100644 --- a/platform/darwin/docs/guides/For Style Authors.md.ejs +++ b/platform/darwin/docs/guides/For Style Authors.md.ejs @@ -160,6 +160,7 @@ the following terms for concepts defined in the style specification: In the style specification | In the SDK ---------------------------|--------- +bounds | coordinate bounds filter | predicate function type | interpolation mode id | identifier @@ -200,6 +201,7 @@ In style JSON | In TileJSON | In the SDK `tiles` | `tiles` | `tileURLTemplates` parameter in `-[MGLTileSource initWithIdentifier:tileURLTemplates:options:]` `minzoom` | `minzoom` | `MGLTileSourceOptionMinimumZoomLevel` `maxzoom` | `maxzoom` | `MGLTileSourceOptionMaximumZoomLevel` +`bounds` | `bounds` | `MGLTileSourceOptionCoordinateBounds` `tileSize` | — | `MGLTileSourceOptionTileSize` `attribution` | `attribution` | `MGLTileSourceOptionAttributionHTMLString` (but consider specifying `MGLTileSourceOptionAttributionInfos` instead for improved security) `scheme` | `scheme` | `MGLTileSourceOptionTileCoordinateSystem` diff --git a/platform/darwin/src/MGLTileSource.h b/platform/darwin/src/MGLTileSource.h index caeafcd2f..3dc268db6 100644 --- a/platform/darwin/src/MGLTileSource.h +++ b/platform/darwin/src/MGLTileSource.h @@ -41,6 +41,20 @@ extern MGL_EXPORT const MGLTileSourceOption MGLTileSourceOptionMinimumZoomLevel; */ extern MGL_EXPORT const MGLTileSourceOption MGLTileSourceOptionMaximumZoomLevel; +/** + An `NSValue` object containing an `MGLCoordinateBounds` struct that specifies + the geographic extent of the source. + + If this option is specified, the SDK avoids requesting any tile that falls + outside of the coordinate bounds. Otherwise, the SDK requests any tile needed + to cover the viewport, as it does by default. + + This option corresponds to the `bounds` key in the + TileJSON + specification. + */ +extern MGL_EXPORT const MGLTileSourceOption MGLTileSourceOptionCoordinateBounds; + #if TARGET_OS_IPHONE /** An HTML string defining the buttons to be displayed in an action sheet when the diff --git a/platform/darwin/src/MGLTileSource.mm b/platform/darwin/src/MGLTileSource.mm index 5644ad9a0..bc985bd72 100644 --- a/platform/darwin/src/MGLTileSource.mm +++ b/platform/darwin/src/MGLTileSource.mm @@ -1,7 +1,9 @@ #import "MGLTileSource_Private.h" #import "MGLAttributionInfo_Private.h" +#import "MGLGeometry_Private.h" #import "NSString+MGLAdditions.h" +#import "NSValue+MGLAdditions.h" #if TARGET_OS_IPHONE #import @@ -13,6 +15,7 @@ const MGLTileSourceOption MGLTileSourceOptionMinimumZoomLevel = @"MGLTileSourceOptionMinimumZoomLevel"; const MGLTileSourceOption MGLTileSourceOptionMaximumZoomLevel = @"MGLTileSourceOptionMaximumZoomLevel"; +const MGLTileSourceOption MGLTileSourceOptionCoordinateBounds = @"MGLTileSourceOptionCoordinateBounds"; const MGLTileSourceOption MGLTileSourceOptionAttributionHTMLString = @"MGLTileSourceOptionAttributionHTMLString"; const MGLTileSourceOption MGLTileSourceOptionAttributionInfos = @"MGLTileSourceOptionAttributionInfos"; const MGLTileSourceOption MGLTileSourceOptionTileCoordinateSystem = @"MGLTileSourceOptionTileCoordinateSystem"; @@ -70,6 +73,15 @@ mbgl::Tileset MGLTileSetFromTileURLTemplates(NS_ARRAY_OF(NSString *) *tileURLTem [NSException raise:NSInvalidArgumentException format:@"MGLTileSourceOptionMinimumZoomLevel must be less than MGLTileSourceOptionMaximumZoomLevel."]; } + + if (NSValue *coordinateBounds = options[MGLTileSourceOptionCoordinateBounds]) { + if (![coordinateBounds isKindOfClass:[NSValue class]] + && strcmp(coordinateBounds.objCType, @encode(MGLCoordinateBounds)) == 0) { + [NSException raise:NSInvalidArgumentException + format:@"MGLTileSourceOptionCoordinateBounds must be set to an NSValue containing an MGLCoordinateBounds."]; + } + tileSet.bounds = MGLLatLngBoundsFromCoordinateBounds(coordinateBounds.MGLCoordinateBoundsValue); + } if (NSString *attribution = options[MGLTileSourceOptionAttributionHTMLString]) { if (![attribution isKindOfClass:[NSString class]]) { diff --git a/platform/darwin/test/MGLTileSetTests.mm b/platform/darwin/test/MGLTileSetTests.mm index 40eab5f97..4d5e1fcd0 100644 --- a/platform/darwin/test/MGLTileSetTests.mm +++ b/platform/darwin/test/MGLTileSetTests.mm @@ -2,6 +2,7 @@ #import #import "MGLTileSource_Private.h" +#import "MGLGeometry_Private.h" #include @@ -40,6 +41,19 @@ XCTAssertEqual(tileSet.zoomRange.min, 1); XCTAssertEqual(tileSet.zoomRange.max, 2); + // when the tile set has a bounds set + MGLCoordinateBounds bounds = MGLCoordinateBoundsMake(CLLocationCoordinate2DMake(12, 34), CLLocationCoordinate2DMake(56, 78)); + tileSet = MGLTileSetFromTileURLTemplates(@[@"tile.1"], @{ + MGLTileSourceOptionCoordinateBounds: @(bounds), + }); + + // the mbgl object reflects the set values for the bounds + XCTAssert(!!tileSet.bounds, @"The bounds are set after setting the bounds"); + if (tileSet.bounds) { + MGLCoordinateBounds actual = MGLCoordinateBoundsFromLatLngBounds(*tileSet.bounds); + XCTAssert(MGLCoordinateBoundsEqualToCoordinateBounds(bounds, actual), @"The bounds round-trip"); + } + // when the tile set has an attribution NSString *attribution = @"my tileset © ©️🎈"; tileSet = MGLTileSetFromTileURLTemplates(tileURLTemplates, @{ diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 292075f0c..7eb3fd8bf 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -2,7 +2,11 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONTRIBUTING.md](../../CONTRIBUTING.md) to get started. -## 3.7.3 +## 3.7.4 + +* Added the `MGLTileSourceOptionTileCoordinateBounds` option to create an `MGLTileSource` that only supplies tiles within a specific geographic bounding box. ([#11141](https://github.com/mapbox/mapbox-gl-native/pull/11141)) + +## 3.7.3 - January 10, 2018 * Fixed a crash while zooming while annotations are present on the map. ([#10791](https://github.com/mapbox/mapbox-gl-native/pull/10791)) * CJK characters can be displayed in a locally installed font or a custom font bundled with the application, reducing map download times. Specify the font name using the `MGLIdeographicFontFamilyName` key in the application’s Info.plist file. ([#10522](https://github.com/mapbox/mapbox-gl-native/pull/10522)) diff --git a/platform/ios/docs/guides/For Style Authors.md b/platform/ios/docs/guides/For Style Authors.md index 7eabfed77..ebf4eb23d 100644 --- a/platform/ios/docs/guides/For Style Authors.md +++ b/platform/ios/docs/guides/For Style Authors.md @@ -109,6 +109,7 @@ the following terms for concepts defined in the style specification: In the style specification | In the SDK ---------------------------|--------- +bounds | coordinate bounds filter | predicate function type | interpolation mode id | identifier @@ -149,6 +150,7 @@ In style JSON | In TileJSON | In the SDK `tiles` | `tiles` | `tileURLTemplates` parameter in `-[MGLTileSource initWithIdentifier:tileURLTemplates:options:]` `minzoom` | `minzoom` | `MGLTileSourceOptionMinimumZoomLevel` `maxzoom` | `maxzoom` | `MGLTileSourceOptionMaximumZoomLevel` +`bounds` | `bounds` | `MGLTileSourceOptionCoordinateBounds` `tileSize` | — | `MGLTileSourceOptionTileSize` `attribution` | `attribution` | `MGLTileSourceOptionAttributionHTMLString` (but consider specifying `MGLTileSourceOptionAttributionInfos` instead for improved security) `scheme` | `scheme` | `MGLTileSourceOptionTileCoordinateSystem` diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md index 7a2c47ced..24abafdaa 100644 --- a/platform/macos/CHANGELOG.md +++ b/platform/macos/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog for Mapbox Maps SDK for macOS +## v0.6.2 + +* Added the `MGLTileSourceOptionTileCoordinateBounds` option to create an `MGLTileSource` that only supplies tiles within a specific geographic bounding box. ([#11141](https://github.com/mapbox/mapbox-gl-native/pull/11141)) + ## v0.6.1 - January 16, 2018 This version of the Mapbox macOS SDK corresponds to version 3.7.3 of the Mapbox Maps SDK for iOS. diff --git a/platform/macos/docs/guides/For Style Authors.md b/platform/macos/docs/guides/For Style Authors.md index 3cacc8137..d1b08f74a 100644 --- a/platform/macos/docs/guides/For Style Authors.md +++ b/platform/macos/docs/guides/For Style Authors.md @@ -96,6 +96,7 @@ the following terms for concepts defined in the style specification: In the style specification | In the SDK ---------------------------|--------- +bounds | coordinate bounds filter | predicate function type | interpolation mode id | identifier @@ -136,6 +137,7 @@ In style JSON | In TileJSON | In the SDK `tiles` | `tiles` | `tileURLTemplates` parameter in `-[MGLTileSource initWithIdentifier:tileURLTemplates:options:]` `minzoom` | `minzoom` | `MGLTileSourceOptionMinimumZoomLevel` `maxzoom` | `maxzoom` | `MGLTileSourceOptionMaximumZoomLevel` +`bounds` | `bounds` | `MGLTileSourceOptionCoordinateBounds` `tileSize` | — | `MGLTileSourceOptionTileSize` `attribution` | `attribution` | `MGLTileSourceOptionAttributionHTMLString` (but consider specifying `MGLTileSourceOptionAttributionInfos` instead for improved security) `scheme` | `scheme` | `MGLTileSourceOptionTileCoordinateSystem` From 5a799117ec580155b9192dbb3a1df6406f57f175 Mon Sep 17 00:00:00 2001 From: tobrun Date: Tue, 6 Feb 2018 14:42:25 +0100 Subject: [PATCH 14/22] [android] - add optional location provider lost to proguard configuration --- platform/android/MapboxGLAndroidSDK/proguard-rules.pro | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/platform/android/MapboxGLAndroidSDK/proguard-rules.pro b/platform/android/MapboxGLAndroidSDK/proguard-rules.pro index b5a1d82c8..3b8adac5a 100644 --- a/platform/android/MapboxGLAndroidSDK/proguard-rules.pro +++ b/platform/android/MapboxGLAndroidSDK/proguard-rules.pro @@ -12,4 +12,7 @@ # config for okhttp 3.8.0, https://github.com/square/okhttp/pull/3354 -dontwarn okio.** -dontwarn javax.annotation.Nullable --dontwarn javax.annotation.ParametersAreNonnullByDefault \ No newline at end of file +-dontwarn javax.annotation.ParametersAreNonnullByDefault + +# config for optional location provider https://github.com/mapbox/mapbox-gl-native/issues/10960 +-dontwarn com.mapzen.android.lost.api** \ No newline at end of file From cab2086e1c74eeb1fe23c2d4a5e03776be1592b9 Mon Sep 17 00:00:00 2001 From: tobrun Date: Thu, 8 Feb 2018 13:37:37 +0100 Subject: [PATCH 15/22] [android] - don't recreate surface as part of view resize --- .../textureview/TextureViewRenderThread.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/textureview/TextureViewRenderThread.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/textureview/TextureViewRenderThread.java index c34833e9c..1e76ffe3f 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/textureview/TextureViewRenderThread.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/renderer/textureview/TextureViewRenderThread.java @@ -4,7 +4,6 @@ import android.graphics.SurfaceTexture; import android.support.annotation.NonNull; import android.support.annotation.UiThread; import android.view.TextureView; - import com.mapbox.mapboxsdk.maps.renderer.egl.EGLConfigChooser; import java.lang.ref.WeakReference; @@ -219,13 +218,6 @@ class TextureViewRenderThread extends Thread implements TextureView.SurfaceTextu break; } - // Check if the size has changed - if (sizeChanged) { - recreateSurface = true; - sizeChanged = false; - break; - } - // Reset the request render flag now, so we can catch new requests // while rendering requestRender = false; @@ -273,6 +265,12 @@ class TextureViewRenderThread extends Thread implements TextureView.SurfaceTextu continue; } + if (sizeChanged) { + mapRenderer.onSurfaceChanged(gl, w, h); + sizeChanged = false; + continue; + } + // Don't continue without a surface if (eglHolder.eglSurface == EGL10.EGL_NO_SURFACE) { continue; From 9941b562389aac04a6bcf926bef176ef15b1148e Mon Sep 17 00:00:00 2001 From: Tobrun Date: Fri, 2 Feb 2018 10:30:05 +0100 Subject: [PATCH 16/22] [android] - don't invoke onLowMemory on map when the map isn't fully created yet --- .../src/main/java/com/mapbox/mapboxsdk/maps/MapView.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java index 6db11db49..a8e065c45 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java @@ -477,7 +477,9 @@ public class MapView extends FrameLayout { */ @UiThread public void onLowMemory() { - nativeMapView.onLowMemory(); + if (nativeMapView != null) { + nativeMapView.onLowMemory(); + } } /** From f422f794467dd4a43e2ed2d79c298141769defc9 Mon Sep 17 00:00:00 2001 From: Tobrun Date: Fri, 9 Feb 2018 14:48:14 +0100 Subject: [PATCH 17/22] [android] - update changelog for 5.4.1 --- platform/android/CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/platform/android/CHANGELOG.md b/platform/android/CHANGELOG.md index 1e51725ed..7827bc129 100644 --- a/platform/android/CHANGELOG.md +++ b/platform/android/CHANGELOG.md @@ -2,6 +2,13 @@ Mapbox welcomes participation and contributions from everyone. If you'd like to do so please see the [`Contributing Guide`](https://github.com/mapbox/mapbox-gl-native/blob/master/CONTRIBUTING.md) first to get started. +## 5.4.1 - February 9, 2018 + - Don't recreate TextureView surface as part of view resizing, solves OOM crashes [#11148](https://github.com/mapbox/mapbox-gl-native/pull/11148) + - Don't invoke OnLowMemory before map is ready, solves startup crash on low memory devices [#11109](https://github.com/mapbox/mapbox-gl-native/pull/11109) + - Programmatically create GLSurfaceView, solves fragment bug [#11124](https://github.com/mapbox/mapbox-gl-native/pull/11124) + - Proguard config for optional location provider, solves obfuscation warnings [#11127](https://github.com/mapbox/mapbox-gl-native/pull/11127) + - MapView weak reference in global layout listener, solves memory leak [#11128](https://github.com/mapbox/mapbox-gl-native/pull/11128) + ## 5.4.0 - January 30, 2018 - Blacklist Adreno 2xx GPU for VAO support [#11047](https://github.com/mapbox/mapbox-gl-native/pull/11047) - Bearing tracking mode GPS_NORTH_FACING [#11095](https://github.com/mapbox/mapbox-gl-native/pull/11095) From cdaf90d2e75a8d8f6924caf657ca5ff3d1ae6adb Mon Sep 17 00:00:00 2001 From: Tobrun Date: Fri, 9 Feb 2018 15:07:15 +0100 Subject: [PATCH 18/22] [android] - update SNAPSHOT version to v5.4.2 --- platform/android/MapboxGLAndroidSDK/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/android/MapboxGLAndroidSDK/gradle.properties b/platform/android/MapboxGLAndroidSDK/gradle.properties index 011052b52..8839f21d8 100644 --- a/platform/android/MapboxGLAndroidSDK/gradle.properties +++ b/platform/android/MapboxGLAndroidSDK/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.mapbox.mapboxsdk -VERSION_NAME=5.4.1-SNAPSHOT +VERSION_NAME=5.4.2-SNAPSHOT POM_DESCRIPTION=Mapbox GL Android SDK POM_URL=https://github.com/mapbox/mapbox-gl-native From 560afaf1fb8f1cd20cef261da4992cd8a5478f88 Mon Sep 17 00:00:00 2001 From: Asheem Mamoowala Date: Mon, 12 Feb 2018 12:32:22 -0800 Subject: [PATCH 19/22] Cherry pick #11176 for Agua patch --- include/mbgl/style/conversion/tileset.hpp | 2 +- test/style/conversion/tileset.test.cpp | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/include/mbgl/style/conversion/tileset.hpp b/include/mbgl/style/conversion/tileset.hpp index 6577e3957..6ec46aa7b 100644 --- a/include/mbgl/style/conversion/tileset.hpp +++ b/include/mbgl/style/conversion/tileset.hpp @@ -12,7 +12,7 @@ struct Converter { public: bool validateLatitude(const double lat) const { - return lat < 90 && lat > -90; + return lat <= 90 && lat >= -90; } template diff --git a/test/style/conversion/tileset.test.cpp b/test/style/conversion/tileset.test.cpp index 8002cd038..9487277cc 100644 --- a/test/style/conversion/tileset.test.cpp +++ b/test/style/conversion/tileset.test.cpp @@ -52,6 +52,16 @@ TEST(Tileset, InvalidBounds) { } } +TEST(Tileset, ValidWorldBounds) { + Error error; + mbgl::optional converted = convertJSON(R"JSON({ + "tiles": ["http://mytiles"], + "bounds": [-180, -90, 180, 90] + })JSON", error); + EXPECT_TRUE((bool) converted); + EXPECT_EQ(converted->bounds, LatLngBounds::hull({90, -180}, {-90, 180})); +} + TEST(Tileset, FullConversion) { Error error; Tileset converted = *convertJSON(R"JSON({ From 9ee99c91bbf995302ead77a3f6fa22443f50a910 Mon Sep 17 00:00:00 2001 From: Julian Rex Date: Mon, 12 Feb 2018 16:37:21 -0500 Subject: [PATCH 20/22] [ios] Adds camera change delegate methods with reason parameter. (#11151) Added missing notification handler for UIApplicationWillResignActiveNotification. --- platform/ios/app/MBXViewController.m | 4 +- platform/ios/ios.xcodeproj/project.pbxproj | 26 ++- platform/ios/src/MGLCameraChangeReason.h | 61 ++++++ platform/ios/src/MGLMapView.mm | 178 +++++++++++++----- platform/ios/src/MGLMapViewDelegate.h | 119 ++++++++++-- .../MGLMapViewDelegateIntegrationTests.swift | 11 ++ platform/ios/uitest/MapViewTests.m | 7 +- 7 files changed, 329 insertions(+), 77 deletions(-) create mode 100644 platform/ios/src/MGLCameraChangeReason.h diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m index 2c3d26b48..1046644f8 100644 --- a/platform/ios/app/MBXViewController.m +++ b/platform/ios/app/MBXViewController.m @@ -1279,6 +1279,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { - (void)styleDynamicPointCollection { [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(36.9979, -109.0441) zoomLevel:14 animated:NO]; + CLLocationCoordinate2D coordinates[] = { {37.00145594210082, -109.04960632324219}, {37.00173012609867, -109.0404224395752}, @@ -1884,7 +1885,8 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { [self updateHUD]; } -- (void)mapView:(MGLMapView *)mapView regionDidChangeAnimated:(BOOL)animated { +- (void)mapView:(MGLMapView *)mapView regionDidChangeWithReason:(MGLCameraChangeReason)reason animated:(BOOL)animated +{ [self updateHUD]; } diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index 2325f3d3c..bbd406753 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -251,6 +251,8 @@ AC518E00201BB55A00EBC820 /* MGLTelemetryConfig.h in Headers */ = {isa = PBXBuildFile; fileRef = AC518DFD201BB55A00EBC820 /* MGLTelemetryConfig.h */; }; AC518E03201BB56000EBC820 /* MGLTelemetryConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = AC518DFE201BB55A00EBC820 /* MGLTelemetryConfig.m */; }; AC518E04201BB56100EBC820 /* MGLTelemetryConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = AC518DFE201BB55A00EBC820 /* MGLTelemetryConfig.m */; }; + CA55CD41202C16AA00CE7095 /* MGLCameraChangeReason.h in Headers */ = {isa = PBXBuildFile; fileRef = CA55CD3E202C16AA00CE7095 /* MGLCameraChangeReason.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CA55CD42202C16AA00CE7095 /* MGLCameraChangeReason.h in Headers */ = {isa = PBXBuildFile; fileRef = CA55CD3E202C16AA00CE7095 /* MGLCameraChangeReason.h */; settings = {ATTRIBUTES = (Public, ); }; }; DA00FC8E1D5EEB0D009AABC8 /* MGLAttributionInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA00FC8C1D5EEB0D009AABC8 /* MGLAttributionInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; DA00FC8F1D5EEB0D009AABC8 /* MGLAttributionInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA00FC8C1D5EEB0D009AABC8 /* MGLAttributionInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; DA00FC901D5EEB0D009AABC8 /* MGLAttributionInfo.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA00FC8D1D5EEB0D009AABC8 /* MGLAttributionInfo.mm */; }; @@ -756,6 +758,7 @@ 96F3F73B1F5711F1003E2D2C /* MGLUserLocationHeadingIndicator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLUserLocationHeadingIndicator.h; sourceTree = ""; }; AC518DFD201BB55A00EBC820 /* MGLTelemetryConfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLTelemetryConfig.h; sourceTree = ""; }; AC518DFE201BB55A00EBC820 /* MGLTelemetryConfig.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLTelemetryConfig.m; sourceTree = ""; }; + CA55CD3E202C16AA00CE7095 /* MGLCameraChangeReason.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLCameraChangeReason.h; sourceTree = ""; }; DA00FC8C1D5EEB0D009AABC8 /* MGLAttributionInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAttributionInfo.h; sourceTree = ""; }; DA00FC8D1D5EEB0D009AABC8 /* MGLAttributionInfo.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLAttributionInfo.mm; sourceTree = ""; }; DA0CD58F1CF56F6A00A5F5A5 /* MGLFeatureTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLFeatureTests.mm; path = ../../darwin/test/MGLFeatureTests.mm; sourceTree = ""; }; @@ -1449,19 +1452,20 @@ DA8848331CBAFB2A00AB86E3 /* Kit */ = { isa = PBXGroup; children = ( - 355ADFF91E9281C300F3939D /* Views */, - 35CE617F1D4165C2004F2359 /* Categories */, DAD165841CF4D06B001FF4B9 /* Annotations */, - DAD165851CF4D08B001FF4B9 /* Telemetry */, - DA704CC01F65A475004B3F28 /* MGLMapAccessibilityElement.h */, - DA704CC11F65A475004B3F28 /* MGLMapAccessibilityElement.mm */, - DA8848361CBAFB8500AB86E3 /* MGLMapView.h */, - DA17BE2F1CC4BAC300402C41 /* MGLMapView_Private.h */, - DA8848371CBAFB8500AB86E3 /* MGLMapView+IBAdditions.h */, - DA737EE01D056A4E005BDA16 /* MGLMapViewDelegate.h */, - DA88484A1CBAFB9800AB86E3 /* MGLMapView.mm */, + 35CE617F1D4165C2004F2359 /* Categories */, DA88487F1CBB033F00AB86E3 /* Fabric */, DA8848881CBB036000AB86E3 /* SMCalloutView */, + DAD165851CF4D08B001FF4B9 /* Telemetry */, + 355ADFF91E9281C300F3939D /* Views */, + CA55CD3E202C16AA00CE7095 /* MGLCameraChangeReason.h */, + DA704CC01F65A475004B3F28 /* MGLMapAccessibilityElement.h */, + DA704CC11F65A475004B3F28 /* MGLMapAccessibilityElement.mm */, + DA17BE2F1CC4BAC300402C41 /* MGLMapView_Private.h */, + DA8848361CBAFB8500AB86E3 /* MGLMapView.h */, + DA88484A1CBAFB9800AB86E3 /* MGLMapView.mm */, + DA8848371CBAFB8500AB86E3 /* MGLMapView+IBAdditions.h */, + DA737EE01D056A4E005BDA16 /* MGLMapViewDelegate.h */, ); name = Kit; path = src; @@ -1764,6 +1768,7 @@ 350098DC1D484E60004B2AF0 /* NSValue+MGLStyleAttributeAdditions.h in Headers */, DA8848231CBAFA6200AB86E3 /* MGLOfflineStorage_Private.h in Headers */, 404326891D5B9B27007111BD /* MGLAnnotationContainerView_Private.h in Headers */, + CA55CD41202C16AA00CE7095 /* MGLCameraChangeReason.h in Headers */, 1FB7DAAF1F2A4DBD00410606 /* MGLVectorSource+MGLAdditions.h in Headers */, DA88483B1CBAFB8500AB86E3 /* MGLCalloutView.h in Headers */, 35E0CFE61D3E501500188327 /* MGLStyle_Private.h in Headers */, @@ -1896,6 +1901,7 @@ 3510FFF11D6D9D8C00F413B2 /* NSExpression+MGLAdditions.h in Headers */, 35D3A1E71E9BE7EC002B38EE /* MGLScaleBar.h in Headers */, 35E0CFE71D3E501500188327 /* MGLStyle_Private.h in Headers */, + CA55CD42202C16AA00CE7095 /* MGLCameraChangeReason.h in Headers */, DABFB86D1CBE9A0F00D62B32 /* MGLAnnotationImage.h in Headers */, DABFB8721CBE9A0F00D62B32 /* MGLUserLocation.h in Headers */, 927FBD001F4DB05500F8BF1F /* MGLMapSnapshotter.h in Headers */, diff --git a/platform/ios/src/MGLCameraChangeReason.h b/platform/ios/src/MGLCameraChangeReason.h new file mode 100644 index 000000000..6c6b3636b --- /dev/null +++ b/platform/ios/src/MGLCameraChangeReason.h @@ -0,0 +1,61 @@ +#import "MGLFoundation.h" + +/** + :nodoc: + Bitmask values that describe why a camera move occurred. + + Values of this type are passed to the `MGLMapView`'s delegate in the following methods: + + - `-mapView:shouldChangeFromCamera:toCamera:reason:` + - `-mapView:regionWillChangeWithReason:animated:` + - `-mapView:regionIsChangingWithReason:` + - `-mapView:regionDidChangeWithReason:animated:` + + It's important to note that it's almost impossible to perform a rotate without zooming (in or out), + so if you'll often find `MGLCameraChangeReasonGesturePinch` set alongside `MGLCameraChangeReasonGestureRotate`. + + Since there are several reasons why a zoom or rotation has occurred, it is worth considering + creating a combined constant, for example: + + ``` + static const MGLCameraChangeReason anyZoom = MGLCameraChangeReasonGesturePinch | + MGLCameraChangeReasonGestureZoomIn | + MGLCameraChangeReasonGestureZoomOut | + MGLCameraChangeReasonGestureOneFingerZoom; + + static const MGLCameraChangeReason anyRotation = MGLCameraChangeReasonResetNorth | MGLCameraChangeReasonGestureRotate; + ``` + */ +typedef NS_OPTIONS(NSUInteger, MGLCameraChangeReason) +{ + /// :nodoc: The reason for the camera change has not be specified. + MGLCameraChangeReasonNone = 0, + + /// :nodoc: Set when a public API that moves the camera is called. This may be set for some gestures, + /// for example MGLCameraChangeReasonResetNorth. + MGLCameraChangeReasonProgrammatic = 1 << 0, + + /// :nodoc: The user tapped the compass to reset the map orientation so North is up. + MGLCameraChangeReasonResetNorth = 1 << 1, + + /// :nodoc: The user panned the map. + MGLCameraChangeReasonGesturePan = 1 << 2, + + /// :nodoc: The user pinched to zoom in/out. + MGLCameraChangeReasonGesturePinch = 1 << 3, + + // :nodoc: The user rotated the map. + MGLCameraChangeReasonGestureRotate = 1 << 4, + + /// :nodoc: The user zoomed the map in (one finger double tap). + MGLCameraChangeReasonGestureZoomIn = 1 << 5, + + /// :nodoc: The user zoomed the map out (two finger single tap). + MGLCameraChangeReasonGestureZoomOut = 1 << 6, + + /// :nodoc: The user long pressed on the map for a quick zoom (single tap, then long press and drag up/down). + MGLCameraChangeReasonGestureOneFingerZoom = 1 << 7, + + // :nodoc: The user panned with two fingers to tilt the map (two finger drag). + MGLCameraChangeReasonGestureTilt = 1 << 8 +}; diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm index 0849cbd12..f2141c384 100644 --- a/platform/ios/src/MGLMapView.mm +++ b/platform/ios/src/MGLMapView.mm @@ -209,6 +209,8 @@ public: @property (nonatomic) UILongPressGestureRecognizer *quickZoom; @property (nonatomic) UIPanGestureRecognizer *twoFingerDrag; +@property (nonatomic) MGLCameraChangeReason cameraChangeReasonBitmask; + /// Mapping from reusable identifiers to annotation images. @property (nonatomic) NS_MUTABLE_DICTIONARY_OF(NSString *, MGLAnnotationImage *) *annotationImagesByIdentifier; @@ -502,10 +504,6 @@ public: _doubleTap.numberOfTapsRequired = 2; [self addGestureRecognizer:_doubleTap]; - _singleTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTapGesture:)]; - [_singleTapGestureRecognizer requireGestureRecognizerToFail:_doubleTap]; - _singleTapGestureRecognizer.delegate = self; - [self addGestureRecognizer:_singleTapGestureRecognizer]; _twoFingerDrag = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleTwoFingerDragGesture:)]; _twoFingerDrag.minimumNumberOfTouches = 2; @@ -530,16 +528,25 @@ public: [_quickZoom requireGestureRecognizerToFail:_doubleTap]; [self addGestureRecognizer:_quickZoom]; + _singleTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTapGesture:)]; + [_singleTapGestureRecognizer requireGestureRecognizerToFail:_doubleTap]; + _singleTapGestureRecognizer.delegate = self; + [_singleTapGestureRecognizer requireGestureRecognizerToFail:_quickZoom]; + [self addGestureRecognizer:_singleTapGestureRecognizer]; + // observe app activity // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willTerminate) name:UIApplicationWillTerminateNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sleepGL:) name:UIApplicationDidEnterBackgroundNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(wakeGL:) name:UIApplicationWillEnterForegroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sleepGL:) name:UIApplicationWillResignActiveNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(wakeGL:) name:UIApplicationDidBecomeActiveNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceOrientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil]; [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; + // set initial position // mbgl::CameraOptions options; @@ -547,6 +554,9 @@ public: mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(self.contentInset); options.padding = padding; options.zoom = 0; + + _cameraChangeReasonBitmask = MGLCameraChangeReasonNone; + _mbglMap->jumpTo(options); _pendingLatitude = NAN; _pendingLongitude = NAN; @@ -1233,6 +1243,8 @@ public: - (void)handleCompassTapGesture:(__unused id)sender { + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonResetNorth; + [self resetNorthAnimated:YES]; if (self.userTrackingMode == MGLUserTrackingModeFollowWithHeading || @@ -1255,6 +1267,7 @@ public: - (void)notifyGestureDidBegin { BOOL animated = NO; + [self cameraWillChangeAnimated:animated]; _mbglMap->setGestureInProgress(true); _changeDelimiterSuppressionDepth++; @@ -1278,6 +1291,23 @@ public: return _changeDelimiterSuppressionDepth > 0; } +- (BOOL)_shouldChangeFromCamera:(nonnull MGLMapCamera *)oldCamera toCamera:(nonnull MGLMapCamera *)newCamera +{ + // Check delegates first + if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:reason:)]) + { + return [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:newCamera reason:self.cameraChangeReasonBitmask]; + } + else if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)]) + { + return [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:newCamera]; + } + else + { + return YES; + } +} + - (void)handlePanGesture:(UIPanGestureRecognizer *)pan { if ( ! self.isScrollEnabled) return; @@ -1285,7 +1315,9 @@ public: _mbglMap->cancelTransitions(); MGLMapCamera *oldCamera = self.camera; - + + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGesturePan; + if (pan.state == UIGestureRecognizerStateBegan) { [self trackGestureEvent:MGLEventGesturePanStart forRecognizer:pan]; @@ -1299,9 +1331,8 @@ public: CGPoint delta = [pan translationInView:pan.view]; MGLMapCamera *toCamera = [self cameraByPanningWithTranslation:delta panGesture:pan]; - - if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] || - [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera]) + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { _mbglMap->moveBy({ delta.x, delta.y }); [pan setTranslation:CGPointZero inView:pan.view]; @@ -1323,9 +1354,8 @@ public: { CGPoint offset = CGPointMake(velocity.x * self.decelerationRate / 4, velocity.y * self.decelerationRate / 4); MGLMapCamera *toCamera = [self cameraByPanningWithTranslation:offset panGesture:pan]; - - if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] || - [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera]) + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { _mbglMap->moveBy({ offset.x, offset.y }, MGLDurationFromTimeInterval(self.decelerationRate)); } @@ -1356,6 +1386,8 @@ public: CGPoint centerPoint = [self anchorPointForGesture:pinch]; MGLMapCamera *oldCamera = self.camera; + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGesturePinch; + if (pinch.state == UIGestureRecognizerStateBegan) { [self trackGestureEvent:MGLEventGesturePinchStart forRecognizer:pinch]; @@ -1372,9 +1404,8 @@ public: // Calculates the final camera zoom, has no effect within current map camera. MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom aroundAnchorPoint:centerPoint]; - - if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] || - [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera]) + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { _mbglMap->setZoom(newZoom, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }); // The gesture recognizer only reports the gesture’s current center @@ -1426,9 +1457,8 @@ public: // Calculates the final camera zoom, this has no effect within current map camera. double zoom = log2(newScale); MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:zoom aroundAnchorPoint:centerPoint]; - - if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] - && ![self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera]) + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { drift = NO; } else { @@ -1454,7 +1484,9 @@ public: CGPoint centerPoint = [self anchorPointForGesture:rotate]; MGLMapCamera *oldCamera = self.camera; - + + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGestureRotate; + if (rotate.state == UIGestureRecognizerStateBegan) { [self trackGestureEvent:MGLEventGestureRotateStart forRecognizer:rotate]; @@ -1481,9 +1513,8 @@ public: } MGLMapCamera *toCamera = [self cameraByRotatingToDirection:newDegrees aroundAnchorPoint:centerPoint]; - - if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] || - [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera]) + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { _mbglMap->setBearing(newDegrees, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }); } @@ -1501,9 +1532,8 @@ public: CGFloat newDegrees = MGLDegreesFromRadians(newRadians) * -1; MGLMapCamera *toCamera = [self cameraByRotatingToDirection:newDegrees aroundAnchorPoint:centerPoint]; - - if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] || - [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera]) + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { _mbglMap->setBearing(newDegrees, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }, MGLDurationFromTimeInterval(decelerationRate)); @@ -1548,6 +1578,7 @@ public: } [self deselectAnnotation:self.selectedAnnotation animated:YES]; UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nextElement); + return; } @@ -1558,7 +1589,7 @@ public: CGRect positionRect = [self positioningRectForAnnotation:annotation defaultCalloutPoint:calloutPoint]; [self selectAnnotation:annotation animated:YES calloutPositioningRect:positionRect]; } - else + else if (self.selectedAnnotation) { [self deselectAnnotation:self.selectedAnnotation animated:YES]; } @@ -1640,6 +1671,8 @@ public: if (doubleTap.state == UIGestureRecognizerStateEnded) { + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGestureZoomIn; + MGLMapCamera *oldCamera = self.camera; double newZoom = round(self.zoomLevel) + 1.0; @@ -1647,9 +1680,8 @@ public: CGPoint gesturePoint = [self anchorPointForGesture:doubleTap]; MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom aroundAnchorPoint:gesturePoint]; - - if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] || - [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera]) + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { [self trackGestureEvent:MGLEventGestureDoubleTap forRecognizer:doubleTap]; @@ -1676,9 +1708,13 @@ public: _mbglMap->cancelTransitions(); + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGestureZoomOut; + if (twoFingerTap.state == UIGestureRecognizerStateBegan) { [self trackGestureEvent:MGLEventGestureTwoFingerSingleTap forRecognizer:twoFingerTap]; + + [self notifyGestureDidBegin]; } else if (twoFingerTap.state == UIGestureRecognizerStateEnded) { @@ -1689,9 +1725,8 @@ public: CGPoint gesturePoint = [self anchorPointForGesture:twoFingerTap]; MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom aroundAnchorPoint:gesturePoint]; - - if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] || - [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera]) + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { mbgl::ScreenCoordinate center(gesturePoint.x, gesturePoint.y); _mbglMap->setZoom(newZoom, center, MGLDurationFromTimeInterval(MGLAnimationDuration)); @@ -1711,7 +1746,9 @@ public: if ( ! self.isZoomEnabled) return; _mbglMap->cancelTransitions(); - + + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGestureOneFingerZoom; + if (quickZoom.state == UIGestureRecognizerStateBegan) { [self trackGestureEvent:MGLEventGestureQuickZoom forRecognizer:quickZoom]; @@ -1734,9 +1771,8 @@ public: MGLMapCamera *oldCamera = self.camera; MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom aroundAnchorPoint:centerPoint]; - - if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] || - [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera]) + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { _mbglMap->setZoom(newZoom, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }); } @@ -1756,6 +1792,8 @@ public: _mbglMap->cancelTransitions(); + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGestureTilt; + if (twoFingerDrag.state == UIGestureRecognizerStateBegan) { [self trackGestureEvent:MGLEventGesturePitchStart forRecognizer:twoFingerDrag]; @@ -1775,8 +1813,7 @@ public: MGLMapCamera *oldCamera = self.camera; MGLMapCamera *toCamera = [self cameraByTiltingToPitch:pitchNew]; - if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] || - [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera]) + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { _mbglMap->setPitch(pitchNew, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }); } @@ -2852,6 +2889,8 @@ public: { self.userTrackingMode = MGLUserTrackingModeNone; + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic; + [self _setCenterCoordinate:centerCoordinate edgePadding:self.contentInset zoomLevel:zoomLevel direction:direction duration:animated ? MGLAnimationDuration : 0 animationTimingFunction:nil completionHandler:completion]; } @@ -2901,6 +2940,9 @@ public: } _mbglMap->cancelTransitions(); + + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic; + _mbglMap->easeTo(cameraOptions, animationOptions); } @@ -2924,6 +2966,8 @@ public: if (zoomLevel == self.zoomLevel) return; _mbglMap->cancelTransitions(); + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic; + CGFloat duration = animated ? MGLAnimationDuration : 0; _mbglMap->setZoom(zoomLevel, @@ -3012,6 +3056,9 @@ public: - (void)setVisibleCoordinates:(const CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets direction:(CLLocationDirection)direction duration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion { self.userTrackingMode = MGLUserTrackingModeNone; + + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic; + [self _setVisibleCoordinates:coordinates count:count edgePadding:insets direction:direction duration:duration animationTimingFunction:function completionHandler:completion]; } @@ -3061,6 +3108,9 @@ public: [self willChangeValueForKey:@"visibleCoordinateBounds"]; _mbglMap->cancelTransitions(); + + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic; + _mbglMap->easeTo(cameraOptions, animationOptions); [self didChangeValueForKey:@"visibleCoordinateBounds"]; } @@ -3094,6 +3144,8 @@ public: CGFloat duration = animated ? MGLAnimationDuration : 0; + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic; + if (self.userTrackingMode == MGLUserTrackingModeNone) { _mbglMap->setBearing(direction, @@ -3178,6 +3230,9 @@ public: [self willChangeValueForKey:@"camera"]; _mbglMap->cancelTransitions(); + + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic; + mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera edgePadding:edgePadding]; _mbglMap->easeTo(cameraOptions, animationOptions); [self didChangeValueForKey:@"camera"]; @@ -3234,6 +3289,9 @@ public: [self willChangeValueForKey:@"camera"]; _mbglMap->cancelTransitions(); + + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic; + mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera edgePadding:insets]; _mbglMap->flyTo(cameraOptions, animationOptions); [self didChangeValueForKey:@"camera"]; @@ -3394,6 +3452,13 @@ public: return [self metersPerPointAtLatitude:latitude]; } +#pragma mark - Camera Change Reason - + +- (void)resetCameraChangeReason +{ + self.cameraChangeReasonBitmask = MGLCameraChangeReasonNone; +} + #pragma mark - Styling - - (NS_ARRAY_OF(NSURL *) *)bundledStyleURLs @@ -5340,9 +5405,16 @@ public: } } - if ( ! [self isSuppressingChangeDelimiters] && [self.delegate respondsToSelector:@selector(mapView:regionWillChangeAnimated:)]) + if ( ! [self isSuppressingChangeDelimiters] ) { - [self.delegate mapView:self regionWillChangeAnimated:animated]; + if ([self.delegate respondsToSelector:@selector(mapView:regionWillChangeWithReason:animated:)]) + { + [self.delegate mapView:self regionWillChangeWithReason:self.cameraChangeReasonBitmask animated:animated]; + } + else if ([self.delegate respondsToSelector:@selector(mapView:regionWillChangeAnimated:)]) + { + [self.delegate mapView:self regionWillChangeAnimated:animated]; + } } } @@ -5356,8 +5428,12 @@ public: if (!self.scaleBar.hidden) { [(MGLScaleBar *)self.scaleBar setMetersPerPoint:[self metersPerPointAtLatitude:self.centerCoordinate.latitude]]; } - - if ([self.delegate respondsToSelector:@selector(mapViewRegionIsChanging:)]) + + if ([self.delegate respondsToSelector:@selector(mapView:regionIsChangingWithReason:)]) + { + [self.delegate mapView:self regionIsChangingWithReason:self.cameraChangeReasonBitmask]; + } + else if ([self.delegate respondsToSelector:@selector(mapViewRegionIsChanging:)]) { [self.delegate mapViewRegionIsChanging:self]; } @@ -5370,9 +5446,13 @@ public: [self updateCompass]; - if ( ! [self isSuppressingChangeDelimiters] && [self.delegate respondsToSelector:@selector(mapView:regionDidChangeAnimated:)]) + if ( ! [self isSuppressingChangeDelimiters]) { - if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) + BOOL respondsToSelector = [self.delegate respondsToSelector:@selector(mapView:regionDidChangeAnimated:)]; + BOOL respondsToSelectorWithReason = [self.delegate respondsToSelector:@selector(mapView:regionDidChangeWithReason:animated:)]; + + if ((respondsToSelector || respondsToSelectorWithReason) && + ([UIApplication sharedApplication].applicationState == UIApplicationStateActive)) { _featureAccessibilityElements = nil; _visiblePlaceFeatures = nil; @@ -5384,7 +5464,17 @@ public: UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil); } } - [self.delegate mapView:self regionDidChangeAnimated:animated]; + + if (respondsToSelectorWithReason) + { + [self.delegate mapView:self regionDidChangeWithReason:self.cameraChangeReasonBitmask animated:animated]; + } + else if (respondsToSelector) + { + [self.delegate mapView:self regionDidChangeAnimated:animated]; + } + + [self resetCameraChangeReason]; } } diff --git a/platform/ios/src/MGLMapViewDelegate.h b/platform/ios/src/MGLMapViewDelegate.h index 096711fcb..0368d8413 100644 --- a/platform/ios/src/MGLMapViewDelegate.h +++ b/platform/ios/src/MGLMapViewDelegate.h @@ -1,6 +1,7 @@ #import #import "MGLTypes.h" +#import "MGLCameraChangeReason.h" NS_ASSUME_NONNULL_BEGIN @@ -21,17 +22,80 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark Responding to Map Position Changes +/** + Asks the delegate whether the map view should be allowed to change from the + existing camera to the new camera in response to a user gesture. + + This method is called as soon as the user gesture is recognized. It is not + called in response to a programmatic camera change, such as by setting the + `centerCoordinate` property or calling `-flyToCamera:completionHandler:`. + + This method is called many times during gesturing, so you should avoid performing + complex or performance-intensive tasks in your implementation. + + @param mapView The map view that the user is manipulating. + @param oldCamera The camera representing the viewpoint at the moment the + gesture is recognized. If this method returns `NO`, the map view’s camera + continues to be this camera. + @param newCamera The expected camera after the gesture completes. If this + method returns `YES`, this camera becomes the map view’s camera. + @return A Boolean value indicating whether the map view should stay at + `oldCamera` or change to `newCamera`. + */ +- (BOOL)mapView:(MGLMapView *)mapView shouldChangeFromCamera:(MGLMapCamera *)oldCamera toCamera:(MGLMapCamera *)newCamera; + +/** + :nodoc: + Asks the delegate whether the map view should be allowed to change from the + existing camera to the new camera in response to a user gesture. + + This method is called as soon as the user gesture is recognized. It is not + called in response to a programmatic camera change, such as by setting the + `centerCoordinate` property or calling `-flyToCamera:completionHandler:`. + + This method is called many times during gesturing, so you should avoid performing + complex or performance-intensive tasks in your implementation. + + @param mapView The map view that the user is manipulating. + @param oldCamera The camera representing the viewpoint at the moment the + gesture is recognized. If this method returns `NO`, the map view’s camera + continues to be this camera. + @param newCamera The expected camera after the gesture completes. If this + method returns `YES`, this camera becomes the map view’s camera. + @param reason The reason for the camera change. + @return A Boolean value indicating whether the map view should stay at + `oldCamera` or change to `newCamera`. + + @note If this method is implemented `-mapView:shouldChangeFromCamera:toCamera:` will not be called. + */ +- (BOOL)mapView:(MGLMapView *)mapView shouldChangeFromCamera:(MGLMapCamera *)oldCamera toCamera:(MGLMapCamera *)newCamera reason:(MGLCameraChangeReason)reason; + /** Tells the delegate that the viewpoint depicted by the map view is about to change. This method is called whenever the currently displayed map camera will start changing for any reason. - + @param mapView The map view whose viewpoint will change. @param animated Whether the change will cause an animated effect on the map. */ - (void)mapView:(MGLMapView *)mapView regionWillChangeAnimated:(BOOL)animated; +/** + :nodoc: + Tells the delegate that the viewpoint depicted by the map view is about to change. + + This method is called whenever the currently displayed map camera will start + changing for any reason. + + @param mapView The map view whose viewpoint will change. + @param animated Whether the change will cause an animated effect on the map. + @param reason The reason for the camera change. + + @note If this method is implemented `-mapView:regionWillChangeAnimated:` will not be called. + */ +- (void)mapView:(MGLMapView *)mapView regionWillChangeWithReason:(MGLCameraChangeReason)reason animated:(BOOL)animated; + /** Tells the delegate that the viewpoint depicted by the map view is changing. @@ -48,6 +112,26 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)mapViewRegionIsChanging:(MGLMapView *)mapView; +/** + :nodoc: + Tells the delegate that the viewpoint depicted by the map view is changing. + + This method is called as the currently displayed map camera changes as part of + an animation, whether due to a user gesture or due to a call to a method such + as `-[MGLMapView setCamera:animated:]`. This method can be called before + `-mapViewDidFinishLoadingMap:` is called. + + During the animation, this method may be called many times to report updates to + the viewpoint. Therefore, your implementation of this method should be as lightweight + as possible to avoid affecting performance. + + @param mapView The map view whose viewpoint is changing. + @param reason The reason for the camera change. + + @note If this method is implemented `-mapViewRegionIsChanging:` will not be called. + */ +- (void)mapView:(MGLMapView *)mapView regionIsChangingWithReason:(MGLCameraChangeReason)reason; + /** Tells the delegate that the viewpoint depicted by the map view has finished changing. @@ -62,26 +146,21 @@ NS_ASSUME_NONNULL_BEGIN - (void)mapView:(MGLMapView *)mapView regionDidChangeAnimated:(BOOL)animated; /** - Asks the delegate whether the map view should be allowed to change from the - existing camera to the new camera in response to a user gesture. - - This method is called as soon as the user gesture is recognized. It is not - called in response to a programmatic camera change, such as by setting the - `centerCoordinate` property or calling `-flyToCamera:completionHandler:`. - - This method is called many times during gesturing, so you should avoid performing - complex or performance-intensive tasks in your implementation. - - @param mapView The map view that the user is manipulating. - @param oldCamera The camera representing the viewpoint at the moment the - gesture is recognized. If this method returns `NO`, the map view’s camera - continues to be this camera. - @param newCamera The expected camera after the gesture completes. If this - method returns `YES`, this camera becomes the map view’s camera. - @return A Boolean value indicating whether the map view should stay at - `oldCamera` or change to `newCamera`. + :nodoc: + Tells the delegate that the viewpoint depicted by the map view has finished + changing. + + This method is called whenever the currently displayed map camera has finished + changing, after any calls to `-mapViewRegionIsChanging:` due to animation. Therefore, + this method can be called before `-mapViewDidFinishLoadingMap:` is called. + + @param mapView The map view whose viewpoint has changed. + @param animated Whether the change caused an animated effect on the map. + @param reason The reason for the camera change. + + @note If this method is implemented `-mapView:regionDidChangeAnimated:` will not be called. */ -- (BOOL)mapView:(MGLMapView *)mapView shouldChangeFromCamera:(MGLMapCamera *)oldCamera toCamera:(MGLMapCamera *)newCamera; +- (void)mapView:(MGLMapView *)mapView regionDidChangeWithReason:(MGLCameraChangeReason)reason animated:(BOOL)animated; #pragma mark Loading the Map diff --git a/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift b/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift index 50f101e86..4d11b000b 100644 --- a/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift +++ b/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift @@ -13,6 +13,10 @@ extension MGLMapViewDelegateIntegrationTests: MGLMapViewDelegate { func mapViewRegionIsChanging(_ mapView: MGLMapView) {} + func mapViewRegionIsChanging(_ mapView: MGLMapView, reason: MGLCameraChangeReason) {} + + func mapView(_ mapView: MGLMapView, regionIsChangingWith reason: MGLCameraChangeReason) {} + func mapView(_ mapView: MGLMapView, didChange mode: MGLUserTrackingMode, animated: Bool) {} func mapViewDidFinishLoadingMap(_ mapView: MGLMapView) {} @@ -33,10 +37,16 @@ extension MGLMapViewDelegateIntegrationTests: MGLMapViewDelegate { func mapView(_ mapView: MGLMapView, didDeselect annotation: MGLAnnotation) {} + func mapView(_ mapView: MGLMapView, didSingleTapAt coordinate: CLLocationCoordinate2D) {} + func mapView(_ mapView: MGLMapView, regionDidChangeAnimated animated: Bool) {} + func mapView(_ mapView: MGLMapView, regionDidChangeWith reason: MGLCameraChangeReason, animated: Bool) {} + func mapView(_ mapView: MGLMapView, regionWillChangeAnimated animated: Bool) {} + func mapView(_ mapView: MGLMapView, regionWillChangeWith reason: MGLCameraChangeReason, animated: Bool) {} + func mapViewDidFailLoadingMap(_ mapView: MGLMapView, withError error: Error) {} func mapView(_ mapView: MGLMapView, didUpdate userLocation: MGLUserLocation?) {} @@ -79,4 +89,5 @@ extension MGLMapViewDelegateIntegrationTests: MGLMapViewDelegate { func mapView(_ mapView: MGLMapView, shouldChangeFrom oldCamera: MGLMapCamera, to newCamera: MGLMapCamera) -> Bool { return false } + func mapView(_ mapView: MGLMapView, shouldChangeFrom oldCamera: MGLMapCamera, to newCamera: MGLMapCamera, reason: MGLCameraChangeReason) -> Bool { return false } } diff --git a/platform/ios/uitest/MapViewTests.m b/platform/ios/uitest/MapViewTests.m index 4ed3d8939..ba15af918 100644 --- a/platform/ios/uitest/MapViewTests.m +++ b/platform/ios/uitest/MapViewTests.m @@ -538,10 +538,13 @@ userInfo:@{ @"animated" : @(animated) }]; } -- (void)mapView:(MGLMapView *)mapView regionDidChangeAnimated:(BOOL)animated { +- (void)mapView:(MGLMapView *)mapView regionDidChangeWithReason:(MGLCameraChangeReason)reason animated:(BOOL)animated { + [[NSNotificationCenter defaultCenter] postNotificationName:@"regionDidChangeAnimated" object:mapView - userInfo:@{ @"animated" : @(animated) }]; + userInfo:@{ @"animated" : @(animated), + @"reason" : @(reason) + }]; } - (void)testDelegatesStartStopLocatingUser { From 024e75bd560cdf2be58322e127f92c81f06cfe0c Mon Sep 17 00:00:00 2001 From: Fabian Guerra Soto Date: Mon, 12 Feb 2018 16:47:16 -0500 Subject: [PATCH 21/22] [ios, macos] Fix memory leaks in MGLMapSnapshotter. (#11133) --- platform/darwin/src/MGLMapSnapshotter.mm | 353 +++++++++--------- platform/ios/app/MBXSnapshotsViewController.m | 3 +- 2 files changed, 184 insertions(+), 172 deletions(-) diff --git a/platform/darwin/src/MGLMapSnapshotter.mm b/platform/darwin/src/MGLMapSnapshotter.mm index 8b4ae7977..db236a8ae 100644 --- a/platform/darwin/src/MGLMapSnapshotter.mm +++ b/platform/darwin/src/MGLMapSnapshotter.mm @@ -81,7 +81,7 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64; @end @interface MGLMapSnapshotter() - +@property (nonatomic) BOOL loading; @end @implementation MGLMapSnapshotter { @@ -90,6 +90,7 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64; std::unique_ptr _mbglMapSnapshotter; std::unique_ptr> _snapshotCallback; NS_ARRAY_OF(MGLAttributionInfo *) *_attributionInfo; + } - (instancetype)initWithOptions:(MGLMapSnapshotOptions *)options @@ -115,182 +116,192 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64; format:@"Already started this snapshotter."]; } - _loading = true; + self.loading = true; - dispatch_async(queue, ^{ - _snapshotCallback = std::make_unique>(*mbgl::Scheduler::GetCurrent(), [=](std::exception_ptr mbglError, mbgl::PremultipliedImage image, mbgl::MapSnapshotter::Attributions attributions, mbgl::MapSnapshotter::PointForFn pointForFn) { - _loading = false; + __weak __typeof__(self) weakSelf = self; + _snapshotCallback = std::make_unique>(*mbgl::Scheduler::GetCurrent(), [=](std::exception_ptr mbglError, mbgl::PremultipliedImage image, mbgl::MapSnapshotter::Attributions attributions, mbgl::MapSnapshotter::PointForFn pointForFn) { + __typeof__(self) strongSelf = weakSelf; + strongSelf.loading = false; + + + if (mbglError) { + NSString *description = @(mbgl::util::toString(mbglError).c_str()); + NSDictionary *userInfo = @{NSLocalizedDescriptionKey: description}; + NSError *error = [NSError errorWithDomain:MGLErrorDomain code:MGLErrorCodeSnapshotFailed userInfo:userInfo]; - NSMutableArray *infos = [NSMutableArray array]; - + // Dispatch result to origin queue + dispatch_async(queue, ^{ + completion(nil, error); + }); + } else { #if TARGET_OS_IPHONE - CGFloat fontSize = [UIFont smallSystemFontSize]; - UIColor *attributeFontColor = [UIColor blackColor]; + MGLImage *mglImage = [[MGLImage alloc] initWithMGLPremultipliedImage:std::move(image) scale:strongSelf.options.scale]; #else - CGFloat fontSize = [NSFont systemFontSizeForControlSize:NSMiniControlSize]; - NSColor *attributeFontColor = [NSColor blackColor]; + MGLImage *mglImage = [[MGLImage alloc] initWithMGLPremultipliedImage:std::move(image)]; + mglImage.size = NSMakeSize(mglImage.size.width / strongSelf.options.scale, + mglImage.size.height / strongSelf.options.scale); #endif - for (auto attribution = attributions.begin(); attribution != attributions.end(); ++attribution) { - NSString *attributionHTMLString = @(attribution->c_str()); - NSArray *tileSetInfos = [MGLAttributionInfo attributionInfosFromHTMLString:attributionHTMLString - fontSize:fontSize - linkColor:attributeFontColor]; - [infos growArrayByAddingAttributionInfosFromArray:tileSetInfos]; - } - - _attributionInfo = infos; - - if (mbglError) { - NSString *description = @(mbgl::util::toString(mbglError).c_str()); - NSDictionary *userInfo = @{NSLocalizedDescriptionKey: description}; - NSError *error = [NSError errorWithDomain:MGLErrorDomain code:MGLErrorCodeSnapshotFailed userInfo:userInfo]; - - // Dispatch result to origin queue - dispatch_async(queue, ^{ - completion(nil, error); - }); - } else { -#if TARGET_OS_IPHONE - MGLImage *mglImage = [[MGLImage alloc] initWithMGLPremultipliedImage:std::move(image) scale:self.options.scale]; -#else - MGLImage *mglImage = [[MGLImage alloc] initWithMGLPremultipliedImage:std::move(image)]; - mglImage.size = NSMakeSize(mglImage.size.width / self.options.scale, - mglImage.size.height / self.options.scale); -#endif - - // Process image watermark in a work queue - dispatch_queue_t workQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - dispatch_async(workQueue, ^{ -#if TARGET_OS_IPHONE - MGLAttributionInfoStyle attributionInfoStyle = MGLAttributionInfoStyleLong; - for (NSUInteger styleValue = MGLAttributionInfoStyleLong; styleValue >= MGLAttributionInfoStyleShort; styleValue--) { - attributionInfoStyle = (MGLAttributionInfoStyle)styleValue; - CGSize attributionSize = [self attributionSizeWithLogoStyle:attributionInfoStyle sourceAttributionStyle:attributionInfoStyle]; - if (attributionSize.width <= mglImage.size.width) { - break; - } - } - - UIImage *logoImage = [self logoImageWithStyle:attributionInfoStyle]; - CGSize attributionBackgroundSize = [self attributionTextSizeWithStyle:attributionInfoStyle]; - - CGRect logoImageRect = CGRectMake(MGLLogoImagePosition.x, mglImage.size.height - (MGLLogoImagePosition.y + logoImage.size.height), logoImage.size.width, logoImage.size.height); - CGPoint attributionOrigin = CGPointMake(mglImage.size.width - 10 - attributionBackgroundSize.width, - logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2) + 1); - if (!logoImage) { - CGSize defaultLogoSize = [self mapboxLongStyleLogo].size; - logoImageRect = CGRectMake(0, mglImage.size.height - (MGLLogoImagePosition.y + defaultLogoSize.height), 0, defaultLogoSize.height); - attributionOrigin = CGPointMake(10, logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2) + 1); - } - - CGRect attributionBackgroundFrame = CGRectMake(attributionOrigin.x, - attributionOrigin.y, - attributionBackgroundSize.width, - attributionBackgroundSize.height); - CGPoint attributionTextPosition = CGPointMake(attributionBackgroundFrame.origin.x + 10, - attributionBackgroundFrame.origin.y - 1); - - CGRect cropRect = CGRectMake(attributionBackgroundFrame.origin.x * mglImage.scale, - attributionBackgroundFrame.origin.y * mglImage.scale, - attributionBackgroundSize.width * mglImage.scale, - attributionBackgroundSize.height * mglImage.scale); - - - UIGraphicsBeginImageContextWithOptions(mglImage.size, NO, self.options.scale); - - [mglImage drawInRect:CGRectMake(0, 0, mglImage.size.width, mglImage.size.height)]; - - [logoImage drawInRect:logoImageRect]; - - UIImage *currentImage = UIGraphicsGetImageFromCurrentImageContext(); - CGImageRef attributionImageRef = CGImageCreateWithImageInRect([currentImage CGImage], cropRect); - UIImage *attributionImage = [UIImage imageWithCGImage:attributionImageRef]; - CGImageRelease(attributionImageRef); - - CIImage *ciAttributionImage = [[CIImage alloc] initWithCGImage:attributionImage.CGImage]; - - UIImage *blurredAttributionBackground = [self blurredAttributionBackground:ciAttributionImage]; - - [blurredAttributionBackground drawInRect:attributionBackgroundFrame]; - - [self drawAttributionTextWithStyle:attributionInfoStyle origin:attributionTextPosition]; - - UIImage *compositedImage = UIGraphicsGetImageFromCurrentImageContext(); - - UIGraphicsEndImageContext(); -#else - NSSize targetSize = NSMakeSize(self.options.size.width, self.options.size.height); - NSRect targetFrame = NSMakeRect(0, 0, targetSize.width, targetSize.height); - - MGLAttributionInfoStyle attributionInfoStyle = MGLAttributionInfoStyleLong; - for (NSUInteger styleValue = MGLAttributionInfoStyleLong; styleValue >= MGLAttributionInfoStyleShort; styleValue--) { - attributionInfoStyle = (MGLAttributionInfoStyle)styleValue; - CGSize attributionSize = [self attributionSizeWithLogoStyle:attributionInfoStyle sourceAttributionStyle:attributionInfoStyle]; - if (attributionSize.width <= mglImage.size.width) { - break; - } - } - - NSImage *logoImage = [self logoImageWithStyle:attributionInfoStyle]; - CGSize attributionBackgroundSize = [self attributionTextSizeWithStyle:attributionInfoStyle]; - NSImage *sourceImage = mglImage; - - CGRect logoImageRect = CGRectMake(MGLLogoImagePosition.x, MGLLogoImagePosition.y, logoImage.size.width, logoImage.size.height); - CGPoint attributionOrigin = CGPointMake(targetFrame.size.width - 10 - attributionBackgroundSize.width, - MGLLogoImagePosition.y + 1); - if (!logoImage) { - CGSize defaultLogoSize = [self mapboxLongStyleLogo].size; - logoImageRect = CGRectMake(0, MGLLogoImagePosition.y, 0, defaultLogoSize.height); - attributionOrigin = CGPointMake(10, attributionOrigin.y); - } - - CGRect attributionBackgroundFrame = CGRectMake(attributionOrigin.x, - attributionOrigin.y, - attributionBackgroundSize.width, - attributionBackgroundSize.height); - CGPoint attributionTextPosition = CGPointMake(attributionBackgroundFrame.origin.x + 10, - logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2)); - - - NSImage *compositedImage = nil; - NSImageRep *sourceImageRep = [sourceImage bestRepresentationForRect:targetFrame - context:nil - hints:nil]; - compositedImage = [[NSImage alloc] initWithSize:targetSize]; - - [compositedImage lockFocus]; - - [sourceImageRep drawInRect: targetFrame]; - - if (logoImage) { - [logoImage drawInRect:logoImageRect]; - } - - NSBitmapImageRep *attributionBackground = [[NSBitmapImageRep alloc] initWithFocusedViewRect:attributionBackgroundFrame]; - - CIImage *attributionBackgroundImage = [[CIImage alloc] initWithCGImage:[attributionBackground CGImage]]; - - NSImage *blurredAttributionBackground = [self blurredAttributionBackground:attributionBackgroundImage]; - - [blurredAttributionBackground drawInRect:attributionBackgroundFrame]; - - [self drawAttributionTextWithStyle:attributionInfoStyle origin:attributionTextPosition]; - - [compositedImage unlockFocus]; - - -#endif - - // Dispatch result to origin queue - dispatch_async(queue, ^{ - MGLMapSnapshot* snapshot = [[MGLMapSnapshot alloc] initWithImage:compositedImage scale:self.options.scale pointForFn:pointForFn]; - completion(snapshot, nil); - }); - }); - } - }); - _mbglMapSnapshotter->snapshot(_snapshotCallback->self()); + [strongSelf drawAttributedSnapshot:attributions snapshotImage:mglImage pointForFn:pointForFn queue:queue completionHandler:completion]; + } + _snapshotCallback = NULL; }); + dispatch_async(queue, ^{ + _mbglMapSnapshotter->snapshot(_snapshotCallback->self()); + + }); +} + +- (MGLImage *)drawAttributedSnapshot:(mbgl::MapSnapshotter::Attributions)attributions snapshotImage:(MGLImage *)mglImage pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn queue:(dispatch_queue_t)queue completionHandler:(MGLMapSnapshotCompletionHandler)completion { + + NSMutableArray *infos = [NSMutableArray array]; + +#if TARGET_OS_IPHONE + CGFloat fontSize = [UIFont smallSystemFontSize]; + UIColor *attributeFontColor = [UIColor blackColor]; +#else + CGFloat fontSize = [NSFont systemFontSizeForControlSize:NSMiniControlSize]; + NSColor *attributeFontColor = [NSColor blackColor]; +#endif + for (auto attribution = attributions.begin(); attribution != attributions.end(); ++attribution) { + NSString *attributionHTMLString = @(attribution->c_str()); + NSArray *tileSetInfos = [MGLAttributionInfo attributionInfosFromHTMLString:attributionHTMLString + fontSize:fontSize + linkColor:attributeFontColor]; + [infos growArrayByAddingAttributionInfosFromArray:tileSetInfos]; + } + + _attributionInfo = infos; + + // Process image watermark in a work queue + dispatch_queue_t workQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + dispatch_async(workQueue, ^{ +#if TARGET_OS_IPHONE + MGLAttributionInfoStyle attributionInfoStyle = MGLAttributionInfoStyleLong; + for (NSUInteger styleValue = MGLAttributionInfoStyleLong; styleValue >= MGLAttributionInfoStyleShort; styleValue--) { + attributionInfoStyle = (MGLAttributionInfoStyle)styleValue; + CGSize attributionSize = [self attributionSizeWithLogoStyle:attributionInfoStyle sourceAttributionStyle:attributionInfoStyle]; + if (attributionSize.width <= mglImage.size.width) { + break; + } + } + + UIImage *logoImage = [self logoImageWithStyle:attributionInfoStyle]; + CGSize attributionBackgroundSize = [self attributionTextSizeWithStyle:attributionInfoStyle]; + + CGRect logoImageRect = CGRectMake(MGLLogoImagePosition.x, mglImage.size.height - (MGLLogoImagePosition.y + logoImage.size.height), logoImage.size.width, logoImage.size.height); + CGPoint attributionOrigin = CGPointMake(mglImage.size.width - 10 - attributionBackgroundSize.width, + logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2) + 1); + if (!logoImage) { + CGSize defaultLogoSize = [self mapboxLongStyleLogo].size; + logoImageRect = CGRectMake(0, mglImage.size.height - (MGLLogoImagePosition.y + defaultLogoSize.height), 0, defaultLogoSize.height); + attributionOrigin = CGPointMake(10, logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2) + 1); + } + + CGRect attributionBackgroundFrame = CGRectMake(attributionOrigin.x, + attributionOrigin.y, + attributionBackgroundSize.width, + attributionBackgroundSize.height); + CGPoint attributionTextPosition = CGPointMake(attributionBackgroundFrame.origin.x + 10, + attributionBackgroundFrame.origin.y - 1); + + CGRect cropRect = CGRectMake(attributionBackgroundFrame.origin.x * mglImage.scale, + attributionBackgroundFrame.origin.y * mglImage.scale, + attributionBackgroundSize.width * mglImage.scale, + attributionBackgroundSize.height * mglImage.scale); + + + UIGraphicsBeginImageContextWithOptions(mglImage.size, NO, self.options.scale); + + [mglImage drawInRect:CGRectMake(0, 0, mglImage.size.width, mglImage.size.height)]; + + [logoImage drawInRect:logoImageRect]; + + UIImage *currentImage = UIGraphicsGetImageFromCurrentImageContext(); + CGImageRef attributionImageRef = CGImageCreateWithImageInRect([currentImage CGImage], cropRect); + UIImage *attributionImage = [UIImage imageWithCGImage:attributionImageRef]; + CGImageRelease(attributionImageRef); + + CIImage *ciAttributionImage = [[CIImage alloc] initWithCGImage:attributionImage.CGImage]; + + UIImage *blurredAttributionBackground = [self blurredAttributionBackground:ciAttributionImage]; + + [blurredAttributionBackground drawInRect:attributionBackgroundFrame]; + + [self drawAttributionTextWithStyle:attributionInfoStyle origin:attributionTextPosition]; + + UIImage *compositedImage = UIGraphicsGetImageFromCurrentImageContext(); + + UIGraphicsEndImageContext(); +#else + NSSize targetSize = NSMakeSize(self.options.size.width, self.options.size.height); + NSRect targetFrame = NSMakeRect(0, 0, targetSize.width, targetSize.height); + + MGLAttributionInfoStyle attributionInfoStyle = MGLAttributionInfoStyleLong; + for (NSUInteger styleValue = MGLAttributionInfoStyleLong; styleValue >= MGLAttributionInfoStyleShort; styleValue--) { + attributionInfoStyle = (MGLAttributionInfoStyle)styleValue; + CGSize attributionSize = [self attributionSizeWithLogoStyle:attributionInfoStyle sourceAttributionStyle:attributionInfoStyle]; + if (attributionSize.width <= mglImage.size.width) { + break; + } + } + + NSImage *logoImage = [self logoImageWithStyle:attributionInfoStyle]; + CGSize attributionBackgroundSize = [self attributionTextSizeWithStyle:attributionInfoStyle]; + NSImage *sourceImage = mglImage; + + CGRect logoImageRect = CGRectMake(MGLLogoImagePosition.x, MGLLogoImagePosition.y, logoImage.size.width, logoImage.size.height); + CGPoint attributionOrigin = CGPointMake(targetFrame.size.width - 10 - attributionBackgroundSize.width, + MGLLogoImagePosition.y + 1); + if (!logoImage) { + CGSize defaultLogoSize = [self mapboxLongStyleLogo].size; + logoImageRect = CGRectMake(0, MGLLogoImagePosition.y, 0, defaultLogoSize.height); + attributionOrigin = CGPointMake(10, attributionOrigin.y); + } + + CGRect attributionBackgroundFrame = CGRectMake(attributionOrigin.x, + attributionOrigin.y, + attributionBackgroundSize.width, + attributionBackgroundSize.height); + CGPoint attributionTextPosition = CGPointMake(attributionBackgroundFrame.origin.x + 10, + logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2)); + + + NSImage *compositedImage = nil; + NSImageRep *sourceImageRep = [sourceImage bestRepresentationForRect:targetFrame + context:nil + hints:nil]; + compositedImage = [[NSImage alloc] initWithSize:targetSize]; + + [compositedImage lockFocus]; + + [sourceImageRep drawInRect: targetFrame]; + + if (logoImage) { + [logoImage drawInRect:logoImageRect]; + } + + NSBitmapImageRep *attributionBackground = [[NSBitmapImageRep alloc] initWithFocusedViewRect:attributionBackgroundFrame]; + + CIImage *attributionBackgroundImage = [[CIImage alloc] initWithCGImage:[attributionBackground CGImage]]; + + NSImage *blurredAttributionBackground = [self blurredAttributionBackground:attributionBackgroundImage]; + + [blurredAttributionBackground drawInRect:attributionBackgroundFrame]; + + [self drawAttributionTextWithStyle:attributionInfoStyle origin:attributionTextPosition]; + + [compositedImage unlockFocus]; + + +#endif + + // Dispatch result to origin queue + dispatch_async(queue, ^{ + MGLMapSnapshot* snapshot = [[MGLMapSnapshot alloc] initWithImage:compositedImage scale:self.options.scale pointForFn:pointForFn]; + completion(snapshot, nil); + }); + }); + return nil; } - (void)drawAttributionTextWithStyle:(MGLAttributionInfoStyle)attributionInfoStyle origin:(CGPoint)origin diff --git a/platform/ios/app/MBXSnapshotsViewController.m b/platform/ios/app/MBXSnapshotsViewController.m index 3bf93d872..95d3251e2 100644 --- a/platform/ios/app/MBXSnapshotsViewController.m +++ b/platform/ios/app/MBXSnapshotsViewController.m @@ -50,12 +50,13 @@ options.zoomLevel = 10; // Create and start the snapshotter + __weak UIImageView *weakImageView = imageView; MGLMapSnapshotter* snapshotter = [[MGLMapSnapshotter alloc] initWithOptions:options]; [snapshotter startWithCompletionHandler: ^(MGLMapSnapshot* snapshot, NSError *error) { if (error) { NSLog(@"Could not load snapshot: %@", [error localizedDescription]); } else { - imageView.image = snapshot.image; + weakImageView.image = snapshot.image; } }]; From 3a0c87b1b4d4f2789a2ca5e995a79a7bad4f761f Mon Sep 17 00:00:00 2001 From: Fabian Guerra Soto Date: Mon, 12 Feb 2018 17:37:00 -0500 Subject: [PATCH 22/22] [ios] Bump podspec to 3.7.4 (#11179) * [ios] Bump podspec to 3.7.4 * [ios, macos] Update changelogs. --- platform/ios/CHANGELOG.md | 1 + platform/ios/Mapbox-iOS-SDK-nightly-dynamic.podspec | 2 +- platform/ios/Mapbox-iOS-SDK-symbols.podspec | 2 +- platform/ios/Mapbox-iOS-SDK.podspec | 2 +- platform/macos/CHANGELOG.md | 1 + 5 files changed, 5 insertions(+), 3 deletions(-) diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 7eb3fd8bf..19d6d36ac 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -5,6 +5,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT ## 3.7.4 * Added the `MGLTileSourceOptionTileCoordinateBounds` option to create an `MGLTileSource` that only supplies tiles within a specific geographic bounding box. ([#11141](https://github.com/mapbox/mapbox-gl-native/pull/11141)) +* Fixed an issue that caused `-[MGLMapSnapshotter pointForCoordinate:]` to return the wrong point. ([#11035](https://github.com/mapbox/mapbox-gl-native/pull/11035)) ## 3.7.3 - January 10, 2018 diff --git a/platform/ios/Mapbox-iOS-SDK-nightly-dynamic.podspec b/platform/ios/Mapbox-iOS-SDK-nightly-dynamic.podspec index fb9511e70..65adcf8d3 100644 --- a/platform/ios/Mapbox-iOS-SDK-nightly-dynamic.podspec +++ b/platform/ios/Mapbox-iOS-SDK-nightly-dynamic.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |m| - version = '3.7.3' + version = '3.7.4' m.name = 'Mapbox-iOS-SDK-nightly-dynamic' m.version = "#{version}-nightly" diff --git a/platform/ios/Mapbox-iOS-SDK-symbols.podspec b/platform/ios/Mapbox-iOS-SDK-symbols.podspec index ea8ae667f..fff485ebb 100644 --- a/platform/ios/Mapbox-iOS-SDK-symbols.podspec +++ b/platform/ios/Mapbox-iOS-SDK-symbols.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |m| - version = '3.7.3' + version = '3.7.4' m.name = 'Mapbox-iOS-SDK-symbols' m.version = "#{version}-symbols" diff --git a/platform/ios/Mapbox-iOS-SDK.podspec b/platform/ios/Mapbox-iOS-SDK.podspec index 1e81272d6..3181b6691 100644 --- a/platform/ios/Mapbox-iOS-SDK.podspec +++ b/platform/ios/Mapbox-iOS-SDK.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |m| - version = '3.7.3' + version = '3.7.4' m.name = 'Mapbox-iOS-SDK' m.version = version diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md index 24abafdaa..620c6f81e 100644 --- a/platform/macos/CHANGELOG.md +++ b/platform/macos/CHANGELOG.md @@ -3,6 +3,7 @@ ## v0.6.2 * Added the `MGLTileSourceOptionTileCoordinateBounds` option to create an `MGLTileSource` that only supplies tiles within a specific geographic bounding box. ([#11141](https://github.com/mapbox/mapbox-gl-native/pull/11141)) +* Fixed an issue that caused `-[MGLMapSnapshotter pointForCoordinate:]` to return the wrong point. ([#11035](https://github.com/mapbox/mapbox-gl-native/pull/11035)) ## v0.6.1 - January 16, 2018