From cecfa1834658e4ed65ecf8656f0d32bbe678ad9d Mon Sep 17 00:00:00 2001 From: Martin Hellwig Date: Thu, 25 Jan 2018 17:04:53 +0100 Subject: Implement new bearingTrackingMode GPS_NORTH_FACING --- .../mapboxsdk/constants/MyBearingTracking.java | 7 ++++++- .../mapboxsdk/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 ceac862f39..c042b00577 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 4f27e0ada8..1cdd91028d 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); -- cgit v1.2.1 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: [android] - log lack of permissions only if enabling location --- .../src/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 6eacbbaeaf..81fd091c90 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; -- cgit v1.2.1 From c8a96bb24ff23513b67c258e60f5820ee3134802 Mon Sep 17 00:00:00 2001 From: Tobrun Date: Thu, 25 Jan 2018 14:04:13 +0100 Subject: [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 d8ade8b8b8..d1a37a861a 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 && -- cgit v1.2.1 From ff29a0cb99d5e8aedc66207080d7e83a7e2dc0dd Mon Sep 17 00:00:00 2001 From: Tobrun Date: Wed, 24 Jan 2018 11:28:47 +0100 Subject: [android] - create Handler using the main thread looper --- .../src/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 c033b1cbcb..987756876b 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 -- cgit v1.2.1 From 0742f7fb66f832ffae4d96fa430a691e53a0c32d Mon Sep 17 00:00:00 2001 From: Tobrun Date: Wed, 31 Jan 2018 13:18:49 +0100 Subject: [android] - update changelog for 5.4.0 --- platform/android/CHANGELOG.md | 17 ++++++++++++++++- platform/android/MapboxGLAndroidSDK/gradle.properties | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/platform/android/CHANGELOG.md b/platform/android/CHANGELOG.md index e98ed491aa..1e51725ed0 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 20d6f60baf..011052b521 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 -- cgit v1.2.1 From 332dad2a4e002b1856ea072d6af22c9e501a55fd Mon Sep 17 00:00:00 2001 From: Randall C Lee Date: Mon, 5 Feb 2018 16:17:57 -0500 Subject: [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 52f33a83b6..2325f3d3ce 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 b0d2e17d5d..85ef4ca489 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 d59972f5bf..273af5b3bc 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 0000000000..527d344291 --- /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 0000000000..c45554414a --- /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 -- cgit v1.2.1 From 638ab799eda3d8eb474fb248469914f12e6a957a Mon Sep 17 00:00:00 2001 From: Randall C Lee Date: Tue, 6 Feb 2018 15:34:46 -0500 Subject: 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 c45554414a..828bafb14f 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"; -- cgit v1.2.1 From 057af7e75f78a7a8ddd12e4df0f1bd688eaf31fa Mon Sep 17 00:00:00 2001 From: Chris Loer Date: Tue, 6 Feb 2018 10:16:14 -0800 Subject: [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 279d251f8f..d97bfb1ac0 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); } -- cgit v1.2.1 From 7bccc9d69555423218c97aab5b3ff2cf9ed3bd24 Mon Sep 17 00:00:00 2001 From: tobrun Date: Tue, 6 Feb 2018 14:57:45 +0100 Subject: [android] - introduce mapview weak reference in global layout listener --- .../java/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 c5b7159e8c..4deea90cb5 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<>(); -- cgit v1.2.1 From aeba621573d77e1ce4f72b1e9b39bde92bf174a2 Mon Sep 17 00:00:00 2001 From: tobrun Date: Tue, 6 Feb 2018 13:41:16 +0100 Subject: [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 4deea90cb5..6db11db49f 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 df7ccaaca9..29ff49f47e 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: 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 +- src/mbgl/annotation/render_annotation_source.cpp | 1 + .../renderer/sources/render_geojson_source.cpp | 1 + src/mbgl/renderer/sources/render_raster_source.cpp | 1 + src/mbgl/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 33b3072f3a..0c109d080d 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 027c34f31e..dfd646e624 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 377170aa6a..6577e39576 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 6d725b102b..dacdb968f3 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 f64502c5bc..a9c12e595b 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 61aa47d4ea..7bef0e89ed 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 c583b6b2b6..5fbe0d943f 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 ba80be0da0..0aff64583d 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 504db78ea3..c13cd49f46 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 bcd719365d..5e63e929e2 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 ca3071c6b0..a3da3e7cbd 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 3e2311089d..c9e3b0630a 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 73a8d34c1c..e34b050273 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 0000000000..6e559c0cac --- /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 f38aba20c4..a04e2c5355 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 0000000000..f630a49078 --- /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 0000000000..8002cd038f --- /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 0000000000..dc8ae28705 --- /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))); + } +} -- cgit v1.2.1 From 4ab07f5be73f802e1f8f9d88eafa7077dbd5cac8 Mon Sep 17 00:00:00 2001 From: Asheem Mamoowala Date: Mon, 5 Feb 2018 19:31:03 -0500 Subject: Reset tileset-based render sources when any tileset properties changed. (#11042) --- src/mbgl/renderer/sources/render_raster_source.cpp | 15 ++++++++------- src/mbgl/renderer/sources/render_raster_source.hpp | 2 +- src/mbgl/renderer/sources/render_vector_source.cpp | 15 ++++++++------- src/mbgl/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 5e63e929e2..46e3c2eff6 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 01de812309..aebc49bf8a 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 a3da3e7cbd..310dfe68b8 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 5e5c6d1108..f237b4b825 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 <> -- cgit v1.2.1 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: [ios, macos] Added option to restrict tile source to bounds Cherry-picked from c3bf7c55a1f648e57c3853d555ff5f63c989f8c4. --- platform/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 06a8907704..ba6f14647c 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 caeafcd2f6..3dc268db60 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 5644ad9a06..bc985bd728 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 40eab5f974..4d5e1fcd05 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 292075f0c0..7eb3fd8bf9 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 7eabfed777..ebf4eb23d6 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 7a2c47cede..24abafdaaf 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 3cacc81376..d1b08f74a9 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` -- cgit v1.2.1 From 5a799117ec580155b9192dbb3a1df6406f57f175 Mon Sep 17 00:00:00 2001 From: tobrun Date: Tue, 6 Feb 2018 14:42:25 +0100 Subject: [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 b5a1d82c81..3b8adac5a8 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 -- cgit v1.2.1 From cab2086e1c74eeb1fe23c2d4a5e03776be1592b9 Mon Sep 17 00:00:00 2001 From: tobrun Date: Thu, 8 Feb 2018 13:37:37 +0100 Subject: [android] - don't recreate surface as part of view resize --- .../maps/renderer/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 c34833e9ce..1e76ffe3fb 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; -- cgit v1.2.1 From 9941b562389aac04a6bcf926bef176ef15b1148e Mon Sep 17 00:00:00 2001 From: Tobrun Date: Fri, 2 Feb 2018 10:30:05 +0100 Subject: [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 6db11db49f..a8e065c45e 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(); + } } /** -- cgit v1.2.1 From f422f794467dd4a43e2ed2d79c298141769defc9 Mon Sep 17 00:00:00 2001 From: Tobrun Date: Fri, 9 Feb 2018 14:48:14 +0100 Subject: [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 1e51725ed0..7827bc129f 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) -- cgit v1.2.1 From cdaf90d2e75a8d8f6924caf657ca5ff3d1ae6adb Mon Sep 17 00:00:00 2001 From: Tobrun Date: Fri, 9 Feb 2018 15:07:15 +0100 Subject: [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 011052b521..8839f21d8f 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 -- cgit v1.2.1 From 560afaf1fb8f1cd20cef261da4992cd8a5478f88 Mon Sep 17 00:00:00 2001 From: Asheem Mamoowala Date: Mon, 12 Feb 2018 12:32:22 -0800 Subject: 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 6577e39576..6ec46aa7b6 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 8002cd038f..9487277cca 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({ -- cgit v1.2.1 From 9ee99c91bbf995302ead77a3f6fa22443f50a910 Mon Sep 17 00:00:00 2001 From: Julian Rex Date: Mon, 12 Feb 2018 16:37:21 -0500 Subject: [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 | 18 ++- platform/ios/src/MGLCameraChangeReason.h | 61 +++++++ platform/ios/src/MGLMapView.mm | 178 ++++++++++++++++----- platform/ios/src/MGLMapViewDelegate.h | 119 +++++++++++--- .../test/MGLMapViewDelegateIntegrationTests.swift | 11 ++ platform/ios/uitest/MapViewTests.m | 7 +- 7 files changed, 325 insertions(+), 73 deletions(-) create mode 100644 platform/ios/src/MGLCameraChangeReason.h diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m index 2c3d26b489..1046644f8c 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 2325f3d3ce..bbd4067534 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 */, + 35CE617F1D4165C2004F2359 /* Categories */, + DA88487F1CBB033F00AB86E3 /* Fabric */, + DA8848881CBB036000AB86E3 /* SMCalloutView */, DAD165851CF4D08B001FF4B9 /* Telemetry */, + 355ADFF91E9281C300F3939D /* Views */, + CA55CD3E202C16AA00CE7095 /* MGLCameraChangeReason.h */, DA704CC01F65A475004B3F28 /* MGLMapAccessibilityElement.h */, DA704CC11F65A475004B3F28 /* MGLMapAccessibilityElement.mm */, - DA8848361CBAFB8500AB86E3 /* MGLMapView.h */, DA17BE2F1CC4BAC300402C41 /* MGLMapView_Private.h */, + DA8848361CBAFB8500AB86E3 /* MGLMapView.h */, + DA88484A1CBAFB9800AB86E3 /* MGLMapView.mm */, DA8848371CBAFB8500AB86E3 /* MGLMapView+IBAdditions.h */, DA737EE01D056A4E005BDA16 /* MGLMapViewDelegate.h */, - DA88484A1CBAFB9800AB86E3 /* MGLMapView.mm */, - DA88487F1CBB033F00AB86E3 /* Fabric */, - DA8848881CBB036000AB86E3 /* SMCalloutView */, ); 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 0000000000..6c6b3636ba --- /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 0849cbd12f..f2141c3840 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 096711fcbb..0368d8413c 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 50f101e86b..4d11b000b9 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 4ed3d89399..ba15af918a 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 { -- cgit v1.2.1 From 024e75bd560cdf2be58322e127f92c81f06cfe0c Mon Sep 17 00:00:00 2001 From: Fabian Guerra Soto Date: Mon, 12 Feb 2018 16:47:16 -0500 Subject: [ios, macos] Fix memory leaks in MGLMapSnapshotter. (#11133) --- platform/darwin/src/MGLMapSnapshotter.mm | 339 +++++++++++++------------- platform/ios/app/MBXSnapshotsViewController.m | 3 +- 2 files changed, 177 insertions(+), 165 deletions(-) diff --git a/platform/darwin/src/MGLMapSnapshotter.mm b/platform/darwin/src/MGLMapSnapshotter.mm index 8b4ae7977b..db236a8aeb 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 { + [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 - MGLImage *mglImage = [[MGLImage alloc] initWithMGLPremultipliedImage:std::move(image) scale:self.options.scale]; + CGFloat fontSize = [UIFont smallSystemFontSize]; + UIColor *attributeFontColor = [UIColor blackColor]; #else - MGLImage *mglImage = [[MGLImage alloc] initWithMGLPremultipliedImage:std::move(image)]; - mglImage.size = NSMakeSize(mglImage.size.width / self.options.scale, - mglImage.size.height / self.options.scale); + CGFloat fontSize = [NSFont systemFontSizeForControlSize:NSMiniControlSize]; + NSColor *attributeFontColor = [NSColor blackColor]; #endif - - // Process image watermark in a work queue - dispatch_queue_t workQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - dispatch_async(workQueue, ^{ + 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(); + 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); - }); - }); + 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()); }); + return nil; } - (void)drawAttributionTextWithStyle:(MGLAttributionInfoStyle)attributionInfoStyle origin:(CGPoint)origin diff --git a/platform/ios/app/MBXSnapshotsViewController.m b/platform/ios/app/MBXSnapshotsViewController.m index 3bf93d8721..95d3251e2e 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; } }]; -- cgit v1.2.1 From 3a0c87b1b4d4f2789a2ca5e995a79a7bad4f761f Mon Sep 17 00:00:00 2001 From: Fabian Guerra Soto Date: Mon, 12 Feb 2018 17:37:00 -0500 Subject: [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 7eb3fd8bf9..19d6d36ace 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 fb9511e707..65adcf8d3c 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 ea8ae667f6..fff485ebb4 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 1e81272d6d..3181b66918 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 24abafdaaf..620c6f81ed 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 -- cgit v1.2.1