From eb7926d20572c19363568f53c117cf8eb1b373d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Paczos?= Date: Wed, 5 Jul 2017 17:32:23 +0200 Subject: [PATCH 01/71] [android] - invible marker views performance fix #9419 (#9420) --- .../com/mapbox/mapboxsdk/annotations/MarkerViewManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewManager.java index 6d42842b7..dce6f6b27 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewManager.java @@ -198,7 +198,7 @@ public class MarkerViewManager implements MapView.OnMapChangedListener { // ensure view is measured first // #6805 invalidate marker views to ensure convertView width and height // values are properly measured and up to date - if (marker.getWidth() == 0) { + if (marker.getWidth() == 0 && marker.isVisible()) { convertView.getViewTreeObserver().addOnPreDrawListener(markerViewPreDrawObserver); } } From c966c0e12f828e0e442b8a360314bd69627a228d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguy=E1=BB=85n?= Date: Tue, 20 Jun 2017 18:04:10 -0700 Subject: [PATCH 02/71] [ios] Update SMCalloutView to fix iOS 10 build warnings Reapplied 76a2a2db28ce70d06b64b747cfc59ed493eefc67, which was partially rolled back in f6e79d70735361438655f279c8699a786d25458c. --- platform/ios/vendor/SMCalloutView | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/ios/vendor/SMCalloutView b/platform/ios/vendor/SMCalloutView index 2aede5d8d..d6ecaba37 160000 --- a/platform/ios/vendor/SMCalloutView +++ b/platform/ios/vendor/SMCalloutView @@ -1 +1 @@ -Subproject commit 2aede5d8d1577101bf18405246220e7a710df607 +Subproject commit d6ecaba377c9f963aef630faf86e3b8f8cdb88d1 From 3fce13349ccdcc1af0efd333020949c355376282 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Mon, 10 Jul 2017 17:32:43 -0400 Subject: [PATCH 03/71] [darwin] Include polylabel via cmake --- platform/ios/config.cmake | 1 + platform/ios/ios.xcodeproj/project.pbxproj | 30 ++++--------------- platform/macos/config.cmake | 1 + .../macos/macos.xcodeproj/project.pbxproj | 20 +++---------- scripts/config.xcconfig.in | 1 - 5 files changed, 12 insertions(+), 41 deletions(-) diff --git a/platform/ios/config.cmake b/platform/ios/config.cmake index fdb286a6d..e9c56f245 100644 --- a/platform/ios/config.cmake +++ b/platform/ios/config.cmake @@ -65,6 +65,7 @@ macro(mbgl_platform_core) ) target_add_mason_package(mbgl-core PUBLIC geojson) + target_add_mason_package(mbgl-core PUBLIC polylabel) target_add_mason_package(mbgl-core PRIVATE icu) target_compile_options(mbgl-core diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index ab110bca5..82e385bbb 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -2676,10 +2676,7 @@ baseConfigurationReference = 55D8C9941D0F133500F42F10 /* config.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; - HEADER_SEARCH_PATHS = ( - "$(mbgl_core_INCLUDE_DIRECTORIES)", - "$(polylabel_INCLUDE_DIRECTORIES)", - ); + HEADER_SEARCH_PATHS = "$(mbgl_core_INCLUDE_DIRECTORIES)"; INFOPLIST_FILE = test/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; @@ -2703,10 +2700,7 @@ baseConfigurationReference = 55D8C9941D0F133500F42F10 /* config.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; - HEADER_SEARCH_PATHS = ( - "$(mbgl_core_INCLUDE_DIRECTORIES)", - "$(polylabel_INCLUDE_DIRECTORIES)", - ); + HEADER_SEARCH_PATHS = "$(mbgl_core_INCLUDE_DIRECTORIES)"; INFOPLIST_FILE = test/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; @@ -2734,10 +2728,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - HEADER_SEARCH_PATHS = ( - "$(mbgl_core_INCLUDE_DIRECTORIES)", - "$(polylabel_INCLUDE_DIRECTORIES)", - ); + HEADER_SEARCH_PATHS = "$(mbgl_core_INCLUDE_DIRECTORIES)"; INFOPLIST_FILE = framework/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; @@ -2771,10 +2762,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - HEADER_SEARCH_PATHS = ( - "$(mbgl_core_INCLUDE_DIRECTORIES)", - "$(polylabel_INCLUDE_DIRECTORIES)", - ); + HEADER_SEARCH_PATHS = "$(mbgl_core_INCLUDE_DIRECTORIES)"; INFOPLIST_FILE = framework/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; @@ -2825,10 +2813,7 @@ baseConfigurationReference = 55D8C9941D0F133500F42F10 /* config.xcconfig */; buildSettings = { BITCODE_GENERATION_MODE = bitcode; - HEADER_SEARCH_PATHS = ( - "$(mbgl_core_INCLUDE_DIRECTORIES)", - "$(polylabel_INCLUDE_DIRECTORIES)", - ); + HEADER_SEARCH_PATHS = "$(mbgl_core_INCLUDE_DIRECTORIES)"; OTHER_CPLUSPLUSFLAGS = ( "$(OTHER_CFLAGS)", "$(sqlite_cflags)", @@ -2854,10 +2839,7 @@ baseConfigurationReference = 55D8C9941D0F133500F42F10 /* config.xcconfig */; buildSettings = { BITCODE_GENERATION_MODE = bitcode; - HEADER_SEARCH_PATHS = ( - "$(mbgl_core_INCLUDE_DIRECTORIES)", - "$(polylabel_INCLUDE_DIRECTORIES)", - ); + HEADER_SEARCH_PATHS = "$(mbgl_core_INCLUDE_DIRECTORIES)"; OTHER_CPLUSPLUSFLAGS = ( "$(OTHER_CFLAGS)", "$(sqlite_cflags)", diff --git a/platform/macos/config.cmake b/platform/macos/config.cmake index 8dc3c3824..c6ccdf8ca 100644 --- a/platform/macos/config.cmake +++ b/platform/macos/config.cmake @@ -61,6 +61,7 @@ macro(mbgl_platform_core) ) target_add_mason_package(mbgl-core PUBLIC geojson) + target_add_mason_package(mbgl-core PUBLIC polylabel) target_add_mason_package(mbgl-core PRIVATE icu) target_compile_options(mbgl-core diff --git a/platform/macos/macos.xcodeproj/project.pbxproj b/platform/macos/macos.xcodeproj/project.pbxproj index 564d81afb..112752409 100644 --- a/platform/macos/macos.xcodeproj/project.pbxproj +++ b/platform/macos/macos.xcodeproj/project.pbxproj @@ -1779,10 +1779,7 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_VERSION = A; - HEADER_SEARCH_PATHS = ( - "$(mbgl_core_INCLUDE_DIRECTORIES)", - "$(polylabel_INCLUDE_DIRECTORIES)", - ); + HEADER_SEARCH_PATHS = "$(mbgl_core_INCLUDE_DIRECTORIES)"; INFOPLIST_FILE = "$(SRCROOT)/sdk/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; @@ -1816,10 +1813,7 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_VERSION = A; - HEADER_SEARCH_PATHS = ( - "$(mbgl_core_INCLUDE_DIRECTORIES)", - "$(polylabel_INCLUDE_DIRECTORIES)", - ); + HEADER_SEARCH_PATHS = "$(mbgl_core_INCLUDE_DIRECTORIES)"; INFOPLIST_FILE = "$(SRCROOT)/sdk/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; @@ -1843,10 +1837,7 @@ buildSettings = { CLANG_ENABLE_MODULES = YES; COMBINE_HIDPI_IMAGES = YES; - HEADER_SEARCH_PATHS = ( - "$(mbgl_core_INCLUDE_DIRECTORIES)", - "$(polylabel_INCLUDE_DIRECTORIES)", - ); + HEADER_SEARCH_PATHS = "$(mbgl_core_INCLUDE_DIRECTORIES)"; INFOPLIST_FILE = test/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; LIBRARY_SEARCH_PATHS = ( @@ -1875,10 +1866,7 @@ buildSettings = { CLANG_ENABLE_MODULES = YES; COMBINE_HIDPI_IMAGES = YES; - HEADER_SEARCH_PATHS = ( - "$(mbgl_core_INCLUDE_DIRECTORIES)", - "$(polylabel_INCLUDE_DIRECTORIES)", - ); + HEADER_SEARCH_PATHS = "$(mbgl_core_INCLUDE_DIRECTORIES)"; INFOPLIST_FILE = test/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; OTHER_CFLAGS = "-fvisibility=hidden"; diff --git a/scripts/config.xcconfig.in b/scripts/config.xcconfig.in index b70ff9d67..eb6bc71b8 100644 --- a/scripts/config.xcconfig.in +++ b/scripts/config.xcconfig.in @@ -3,4 +3,3 @@ // mbgl-core mbgl_core_INCLUDE_DIRECTORIES = "@mbgl_core_INCLUDE_DIRECTORIES@" mbgl_core_LINK_LIBRARIES = "@mbgl_core_LINK_LIBRARIES@" -polylabel_INCLUDE_DIRECTORIES = "@MASON_PACKAGE_polylabel_INCLUDE_DIRS@" From 629922f5570abc46011db092d204c2e7642812c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Thu, 5 Jan 2017 14:37:29 +0100 Subject: [PATCH 04/71] [build] enable -fvisibility=hidden for iOS --- Makefile | 2 +- platform/ios/config.cmake | 1 + platform/ios/ios.xcodeproj/project.pbxproj | 8 ++++++++ platform/ios/src/MGLAnnotationImage.h | 3 +++ platform/ios/src/MGLAnnotationView.h | 3 +++ platform/ios/src/MGLMapView.h | 9 +++++---- platform/ios/src/MGLUserLocation.h | 2 ++ platform/ios/src/MGLUserLocationAnnotationView.h | 2 ++ 8 files changed, 25 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 1a635cdd3..b09d1a9ba 100644 --- a/Makefile +++ b/Makefile @@ -273,7 +273,7 @@ darwin-update-examples: .PHONY: check-public-symbols check-public-symbols: - node platform/darwin/scripts/check-public-symbols.js macOS + node platform/darwin/scripts/check-public-symbols.js macOS iOS endif #### Linux targets ##################################################### diff --git a/platform/ios/config.cmake b/platform/ios/config.cmake index e9c56f245..0adacddd3 100644 --- a/platform/ios/config.cmake +++ b/platform/ios/config.cmake @@ -70,6 +70,7 @@ macro(mbgl_platform_core) target_compile_options(mbgl-core PRIVATE -fobjc-arc + PRIVATE -fvisibility=hidden ) # TODO: Remove this by converting to ARC diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index 82e385bbb..7c6f1115c 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -186,6 +186,7 @@ 40F887701D7A1E58008ECB67 /* MGLShapeSource_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 40F8876F1D7A1DB8008ECB67 /* MGLShapeSource_Private.h */; }; 40F887711D7A1E59008ECB67 /* MGLShapeSource_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 40F8876F1D7A1DB8008ECB67 /* MGLShapeSource_Private.h */; }; 40FDA76B1CCAAA6800442548 /* MBXAnnotationView.m in Sources */ = {isa = PBXBuildFile; fileRef = 40FDA76A1CCAAA6800442548 /* MBXAnnotationView.m */; }; + 5549A0381EF1D86B00073113 /* libmbgl-core.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5549A0371EF1D86B00073113 /* libmbgl-core.a */; }; 556660CA1E1BF3A900E2C41B /* MGLFoundation.h in Headers */ = {isa = PBXBuildFile; fileRef = 556660C91E1BF3A900E2C41B /* MGLFoundation.h */; settings = {ATTRIBUTES = (Public, ); }; }; 556660D81E1D085500E2C41B /* MGLVersionNumber.m in Sources */ = {isa = PBXBuildFile; fileRef = 556660D71E1D085500E2C41B /* MGLVersionNumber.m */; }; 556660DB1E1D8E8D00E2C41B /* MGLFoundation.h in Headers */ = {isa = PBXBuildFile; fileRef = 556660C91E1BF3A900E2C41B /* MGLFoundation.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -656,6 +657,7 @@ 40FDA7691CCAAA6800442548 /* MBXAnnotationView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MBXAnnotationView.h; sourceTree = ""; }; 40FDA76A1CCAAA6800442548 /* MBXAnnotationView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MBXAnnotationView.m; sourceTree = ""; }; 554180411D2E97DE00012372 /* OpenGLES.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGLES.framework; path = System/Library/Frameworks/OpenGLES.framework; sourceTree = SDKROOT; }; + 5549A0371EF1D86B00073113 /* libmbgl-core.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libmbgl-core.a"; path = "../../build/ios/Debug-iphonesimulator/libmbgl-core.a"; sourceTree = ""; }; 556660C91E1BF3A900E2C41B /* MGLFoundation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLFoundation.h; sourceTree = ""; }; 556660D71E1D085500E2C41B /* MGLVersionNumber.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = MGLVersionNumber.m; path = ../../darwin/test/MGLVersionNumber.m; sourceTree = ""; }; 558DE79E1E5615E400C7916D /* MGLFoundation_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLFoundation_Private.h; sourceTree = ""; }; @@ -945,6 +947,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 5549A0381EF1D86B00073113 /* libmbgl-core.a in Frameworks */, DA2E88561CC036F400F24E7B /* Mapbox.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1219,6 +1222,7 @@ DA1DC9921CB6DF24006E619F /* Frameworks */ = { isa = PBXGroup; children = ( + 5549A0371EF1D86B00073113 /* libmbgl-core.a */, 36F1153B1D46080700878E1A /* libmbgl-core.a */, 36F1153C1D46080700878E1A /* libmbgl-platform-ios.a */, 554180411D2E97DE00012372 /* OpenGLES.framework */, @@ -2733,6 +2737,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + OTHER_CFLAGS = "-fvisibility=hidden"; OTHER_CPLUSPLUSFLAGS = ( "$(OTHER_CFLAGS)", "$(sqlite_cflags)", @@ -2767,6 +2772,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + OTHER_CFLAGS = "-fvisibility=hidden"; OTHER_CPLUSPLUSFLAGS = ( "$(OTHER_CFLAGS)", "$(sqlite_cflags)", @@ -2814,6 +2820,7 @@ buildSettings = { BITCODE_GENERATION_MODE = bitcode; HEADER_SEARCH_PATHS = "$(mbgl_core_INCLUDE_DIRECTORIES)"; + OTHER_CFLAGS = "-fvisibility=hidden"; OTHER_CPLUSPLUSFLAGS = ( "$(OTHER_CFLAGS)", "$(sqlite_cflags)", @@ -2840,6 +2847,7 @@ buildSettings = { BITCODE_GENERATION_MODE = bitcode; HEADER_SEARCH_PATHS = "$(mbgl_core_INCLUDE_DIRECTORIES)"; + OTHER_CFLAGS = "-fvisibility=hidden"; OTHER_CPLUSPLUSFLAGS = ( "$(OTHER_CFLAGS)", "$(sqlite_cflags)", diff --git a/platform/ios/src/MGLAnnotationImage.h b/platform/ios/src/MGLAnnotationImage.h index fbeee1862..0b5a11184 100644 --- a/platform/ios/src/MGLAnnotationImage.h +++ b/platform/ios/src/MGLAnnotationImage.h @@ -1,5 +1,7 @@ #import +#import "MGLFoundation.h" + NS_ASSUME_NONNULL_BEGIN /** @@ -8,6 +10,7 @@ NS_ASSUME_NONNULL_BEGIN objects and may be recycled later and put into a reuse queue that is maintained by the map view. */ +MGL_EXPORT @interface MGLAnnotationImage : NSObject #pragma mark Initializing and Preparing the Image Object diff --git a/platform/ios/src/MGLAnnotationView.h b/platform/ios/src/MGLAnnotationView.h index 2802d31b0..4fa0f196a 100644 --- a/platform/ios/src/MGLAnnotationView.h +++ b/platform/ios/src/MGLAnnotationView.h @@ -1,5 +1,7 @@ #import +#import "MGLFoundation.h" + NS_ASSUME_NONNULL_BEGIN @protocol MGLAnnotation; @@ -50,6 +52,7 @@ typedef NS_ENUM(NSUInteger, MGLAnnotationViewDragState) { interactivity such as dragging, you can use an `MGLAnnotationImage` instead to conserve memory and optimize drawing performance. */ +MGL_EXPORT @interface MGLAnnotationView : UIView #pragma mark Initializing and Preparing the View diff --git a/platform/ios/src/MGLMapView.h b/platform/ios/src/MGLMapView.h index 31320ac97..a93b8e851 100644 --- a/platform/ios/src/MGLMapView.h +++ b/platform/ios/src/MGLMapView.h @@ -4,6 +4,7 @@ #import #import +#import "MGLFoundation.h" #import "MGLTypes.h" NS_ASSUME_NONNULL_BEGIN @@ -23,13 +24,13 @@ NS_ASSUME_NONNULL_BEGIN @protocol MGLFeature; /** The default deceleration rate for a map view. */ -extern const CGFloat MGLMapViewDecelerationRateNormal; +extern MGL_EXPORT const CGFloat MGLMapViewDecelerationRateNormal; /** A fast deceleration rate for a map view. */ -extern const CGFloat MGLMapViewDecelerationRateFast; +extern MGL_EXPORT const CGFloat MGLMapViewDecelerationRateFast; /** Disables deceleration in a map view. */ -extern const CGFloat MGLMapViewDecelerationRateImmediate; +extern MGL_EXPORT const CGFloat MGLMapViewDecelerationRateImmediate; /** The vertical alignment of an annotation within a map view. Used with @@ -125,7 +126,7 @@ typedef NS_ENUM(NSUInteger, MGLUserTrackingMode) { ensuring that your use adheres to the relevant terms of use. */ -IB_DESIGNABLE +MGL_EXPORT IB_DESIGNABLE @interface MGLMapView : UIView #pragma mark Creating Instances diff --git a/platform/ios/src/MGLUserLocation.h b/platform/ios/src/MGLUserLocation.h index 30bfc592c..8c6fe4613 100644 --- a/platform/ios/src/MGLUserLocation.h +++ b/platform/ios/src/MGLUserLocation.h @@ -1,6 +1,7 @@ #import #import +#import "MGLFoundation.h" #import "MGLAnnotation.h" NS_ASSUME_NONNULL_BEGIN @@ -11,6 +12,7 @@ NS_ASSUME_NONNULL_BEGIN directly. Instead, you retrieve an existing `MGLUserLocation` object from the `userLocation` property of the map view displayed in your application. */ +MGL_EXPORT @interface MGLUserLocation : NSObject #pragma mark Determining the User’s Position diff --git a/platform/ios/src/MGLUserLocationAnnotationView.h b/platform/ios/src/MGLUserLocationAnnotationView.h index 4b36236b8..4d95f39cf 100644 --- a/platform/ios/src/MGLUserLocationAnnotationView.h +++ b/platform/ios/src/MGLUserLocationAnnotationView.h @@ -1,6 +1,7 @@ #import #import +#import "MGLFoundation.h" #import "MGLAnnotationView.h" NS_ASSUME_NONNULL_BEGIN @@ -9,6 +10,7 @@ NS_ASSUME_NONNULL_BEGIN @class MGLUserLocation; /** View representing an `MGLUserLocation` on screen. */ +MGL_EXPORT @interface MGLUserLocationAnnotationView : MGLAnnotationView /** From f16a4e5da6609a17a29fde5295f19ca78adfeb7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Wed, 14 Jun 2017 21:47:01 +0200 Subject: [PATCH 05/71] [all] make default_styles header-only --- cmake/glfw.cmake | 1 - cmake/offline.cmake | 1 - platform/default/mbgl/util/default_styles.cpp | 18 ------------------ platform/default/mbgl/util/default_styles.hpp | 16 ++++++++-------- platform/ios/config.cmake | 1 - platform/macos/config.cmake | 1 - platform/qt/qt.cmake | 1 - 7 files changed, 8 insertions(+), 31 deletions(-) delete mode 100644 platform/default/mbgl/util/default_styles.cpp diff --git a/cmake/glfw.cmake b/cmake/glfw.cmake index f1ace9e6e..c14bd6f2c 100644 --- a/cmake/glfw.cmake +++ b/cmake/glfw.cmake @@ -8,7 +8,6 @@ target_sources(mbgl-glfw PRIVATE platform/glfw/settings_json.hpp PRIVATE platform/glfw/settings_json.cpp PRIVATE platform/default/mbgl/util/default_styles.hpp - PRIVATE platform/default/mbgl/util/default_styles.cpp ) # Our GL implementation is internal to mbgl-core, which causes the GL header to diff --git a/cmake/offline.cmake b/cmake/offline.cmake index d0124e661..a79b22db5 100644 --- a/cmake/offline.cmake +++ b/cmake/offline.cmake @@ -4,7 +4,6 @@ add_executable(mbgl-offline target_sources(mbgl-offline PRIVATE platform/default/mbgl/util/default_styles.hpp - PRIVATE platform/default/mbgl/util/default_styles.cpp ) target_compile_options(mbgl-offline diff --git a/platform/default/mbgl/util/default_styles.cpp b/platform/default/mbgl/util/default_styles.cpp deleted file mode 100644 index 5f4ca862f..000000000 --- a/platform/default/mbgl/util/default_styles.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include - -namespace mbgl { -namespace util { -namespace default_styles { - -const DefaultStyle streets = { "mapbox://styles/mapbox/streets-v10", "Streets", 10 }; -const DefaultStyle outdoors = { "mapbox://styles/mapbox/outdoors-v10", "Outdoors", 10 }; -const DefaultStyle light = { "mapbox://styles/mapbox/light-v9", "Light", 9 }; -const DefaultStyle dark = { "mapbox://styles/mapbox/dark-v9", "Dark", 9 }; -const DefaultStyle satellite = { "mapbox://styles/mapbox/satellite-v9", "Satellite", 9 }; -const DefaultStyle satelliteStreets = { "mapbox://styles/mapbox/satellite-streets-v10", "Satellite Streets", 10 }; -const DefaultStyle trafficDay = { "mapbox://styles/mapbox/traffic-day-v2", "Traffic Day", 2 }; -const DefaultStyle trafficNight = { "mapbox://styles/mapbox/traffic-night-v2", "Traffic Night", 2 }; - -} // namespace default_styles -} // end namespace util -} // end namespace mbgl diff --git a/platform/default/mbgl/util/default_styles.hpp b/platform/default/mbgl/util/default_styles.hpp index 466102d62..43dafb808 100644 --- a/platform/default/mbgl/util/default_styles.hpp +++ b/platform/default/mbgl/util/default_styles.hpp @@ -13,14 +13,14 @@ struct DefaultStyle { const unsigned currentVersion; }; -extern const DefaultStyle streets; -extern const DefaultStyle outdoors; -extern const DefaultStyle light; -extern const DefaultStyle dark; -extern const DefaultStyle satellite; -extern const DefaultStyle satelliteStreets; -extern const DefaultStyle trafficDay; -extern const DefaultStyle trafficNight; +constexpr const DefaultStyle streets = { "mapbox://styles/mapbox/streets-v10", "Streets", 10 }; +constexpr const DefaultStyle outdoors = { "mapbox://styles/mapbox/outdoors-v10", "Outdoors", 10 }; +constexpr const DefaultStyle light = { "mapbox://styles/mapbox/light-v9", "Light", 9 }; +constexpr const DefaultStyle dark = { "mapbox://styles/mapbox/dark-v9", "Dark", 9 }; +constexpr const DefaultStyle satellite = { "mapbox://styles/mapbox/satellite-v9", "Satellite", 9 }; +constexpr const DefaultStyle satelliteStreets = { "mapbox://styles/mapbox/satellite-streets-v10", "Satellite Streets", 10 }; +constexpr const DefaultStyle trafficDay = { "mapbox://styles/mapbox/traffic-day-v2", "Traffic Day", 2 }; +constexpr const DefaultStyle trafficNight = { "mapbox://styles/mapbox/traffic-night-v2", "Traffic Night", 2 }; const DefaultStyle orderedStyles[] = { streets, outdoors, light, dark, satellite, satelliteStreets, diff --git a/platform/ios/config.cmake b/platform/ios/config.cmake index 0adacddd3..836d0678c 100644 --- a/platform/ios/config.cmake +++ b/platform/ios/config.cmake @@ -23,7 +23,6 @@ macro(mbgl_platform_core) # Default styles PRIVATE platform/default/mbgl/util/default_styles.hpp - PRIVATE platform/default/mbgl/util/default_styles.cpp # Offline PRIVATE platform/default/mbgl/storage/offline.cpp diff --git a/platform/macos/config.cmake b/platform/macos/config.cmake index c6ccdf8ca..1b8ea3581 100644 --- a/platform/macos/config.cmake +++ b/platform/macos/config.cmake @@ -19,7 +19,6 @@ macro(mbgl_platform_core) # Default styles PRIVATE platform/default/mbgl/util/default_styles.hpp - PRIVATE platform/default/mbgl/util/default_styles.cpp # Offline PRIVATE platform/default/mbgl/storage/offline.cpp diff --git a/platform/qt/qt.cmake b/platform/qt/qt.cmake index cee0d1080..67622efca 100644 --- a/platform/qt/qt.cmake +++ b/platform/qt/qt.cmake @@ -65,7 +65,6 @@ add_library(qmapboxgl SHARED platform/qt/src/qmapboxgl.cpp platform/qt/src/qmapboxgl_p.hpp platform/default/mbgl/util/default_styles.hpp - platform/default/mbgl/util/default_styles.cpp ) # C++ app From 76caec25c73ef3ddbf2d1673ecfe2c4d9e4f1b45 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Thu, 13 Jul 2017 01:52:55 -0400 Subject: [PATCH 06/71] [ios] Update changelog; kick-off 3.6.1 --- platform/ios/CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index c08952a66..93da2ff37 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.6.0 +## 3.6.1 + +* Reduced the size of the dynamic framework by optimizing symbol visibility. ([#7604](https://github.com/mapbox/mapbox-gl-native/pull/7604)) + +## 3.6.0 - June 29, 2017 ### Packaging From ccb525553786e11fbb76af2caf931ba539713761 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 13 Jun 2017 16:04:13 -0700 Subject: [PATCH 07/71] [core] Don't use "current" icon/text-size when binding The current value is not passed to `SymbolSizeBinder::uniformValues`, so we shouldn't check `currentValue.isConstant()` in `SymbolSizeBinder::attributeBindings`. If it were true, then we might end up using attribute bindings that are appropriate only for a constant property, but uniform bindings that are appropriate only for a source or composite function. Instead, just wait for a new bucket to be generated. This will happen automatically, since icon/text-size are layout properties. --- src/mbgl/programs/symbol_program.hpp | 21 +++++---------------- src/mbgl/renderer/painter_symbol.cpp | 1 - 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/src/mbgl/programs/symbol_program.hpp b/src/mbgl/programs/symbol_program.hpp index 01e95f456..abcba1d03 100644 --- a/src/mbgl/programs/symbol_program.hpp +++ b/src/mbgl/programs/symbol_program.hpp @@ -107,7 +107,7 @@ public: const style::DataDrivenPropertyValue& sizeProperty, const float defaultValue); - virtual SymbolSizeAttributes::Bindings attributeBindings(const PossiblyEvaluatedPropertyValue currentValue) const = 0; + virtual SymbolSizeAttributes::Bindings attributeBindings() const = 0; virtual void populateVertexVector(const GeometryTileFeature& feature) = 0; virtual UniformValues uniformValues(float currentZoom) const = 0; virtual void upload(gl::Context&) = 0; @@ -133,8 +133,6 @@ Range getCoveringStops(Stops s, float lowerZoom, float upperZoom) { class ConstantSymbolSizeBinder final : public SymbolSizeBinder { public: - using PropertyValue = variant>; - ConstantSymbolSizeBinder(const float /*tileZoom*/, const float& size, const float /*defaultValue*/) : layoutSize(size) {} @@ -157,7 +155,7 @@ public: ); } - SymbolSizeAttributes::Bindings attributeBindings(const PossiblyEvaluatedPropertyValue) const override { + SymbolSizeAttributes::Bindings attributeBindings() const override { return SymbolSizeAttributes::Bindings { SymbolSizeAttributes::Attribute::ConstantBinding {{{0, 0, 0}}} }; } void upload(gl::Context&) override {} @@ -211,11 +209,7 @@ public: defaultValue(defaultValue_) { } - SymbolSizeAttributes::Bindings attributeBindings(const PossiblyEvaluatedPropertyValue currentValue) const override { - if (currentValue.isConstant()) { - return SymbolSizeAttributes::Bindings { SymbolSizeAttributes::Attribute::ConstantBinding {{{0, 0, 0}}} }; - } - + SymbolSizeAttributes::Bindings attributeBindings() const override { return SymbolSizeAttributes::Bindings { SymbolSizeAttributes::Attribute::variableBinding(*buffer, 0, 1) }; } @@ -268,11 +262,7 @@ public: return getCoveringStops(stops, tileZoom, tileZoom + 1); })) {} - SymbolSizeAttributes::Bindings attributeBindings(const PossiblyEvaluatedPropertyValue currentValue) const override { - if (currentValue.isConstant()) { - return SymbolSizeAttributes::Bindings { SymbolSizeAttributes::Attribute::ConstantBinding {{{0, 0, 0}}} }; - } - + SymbolSizeAttributes::Bindings attributeBindings() const override { return SymbolSizeAttributes::Bindings { SymbolSizeAttributes::Attribute::variableBinding(*buffer, 0) }; } @@ -364,7 +354,6 @@ public: UniformValues&& uniformValues, const gl::VertexBuffer& layoutVertexBuffer, const SymbolSizeBinder& symbolSizeBinder, - const PossiblyEvaluatedPropertyValue& currentSizeValue, const gl::IndexBuffer& indexBuffer, const gl::SegmentVector& segments, const PaintPropertyBinders& paintPropertyBinders, @@ -380,7 +369,7 @@ public: .concat(symbolSizeBinder.uniformValues(currentZoom)) .concat(paintPropertyBinders.uniformValues(currentZoom, currentProperties)), LayoutAttributes::allVariableBindings(layoutVertexBuffer) - .concat(symbolSizeBinder.attributeBindings(currentSizeValue)) + .concat(symbolSizeBinder.attributeBindings()) .concat(paintPropertyBinders.attributeBindings(currentProperties)), indexBuffer, segments diff --git a/src/mbgl/renderer/painter_symbol.cpp b/src/mbgl/renderer/painter_symbol.cpp index 86b2146b9..c9ed8cc57 100644 --- a/src/mbgl/renderer/painter_symbol.cpp +++ b/src/mbgl/renderer/painter_symbol.cpp @@ -54,7 +54,6 @@ void Painter::renderSymbol(PaintParameters& parameters, std::move(uniformValues), *buffers.vertexBuffer, *symbolSizeBinder, - values_.layoutSize, *buffers.indexBuffer, buffers.segments, binders, From 874244a65680c4f99b1b549b979e511f37c1542b Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 13 Jun 2017 16:45:12 -0700 Subject: [PATCH 08/71] [core] Eliminate constant attribute bindings Rather than binding constant attributes that will never be used, just disable the attribute. --- src/mbgl/gl/attribute.cpp | 176 +++----------------- src/mbgl/gl/attribute.hpp | 91 +++++----- src/mbgl/gl/segment.hpp | 6 +- src/mbgl/programs/program.hpp | 2 +- src/mbgl/programs/symbol_program.hpp | 11 +- src/mbgl/renderer/paint_property_binder.hpp | 21 +-- 6 files changed, 77 insertions(+), 230 deletions(-) diff --git a/src/mbgl/gl/attribute.cpp b/src/mbgl/gl/attribute.cpp index 8c52121f6..e05ca7586 100644 --- a/src/mbgl/gl/attribute.cpp +++ b/src/mbgl/gl/attribute.cpp @@ -10,6 +10,10 @@ AttributeLocation bindAttributeLocation(ProgramID id, AttributeLocation location return location; } +void DisabledAttribute::bind(Context&, AttributeLocation location, std::size_t) const { + MBGL_CHECK_ERROR(glDisableVertexAttribArray(location)); +} + template DataType DataTypeOf = static_cast(0); template <> DataType DataTypeOf< int8_t> = DataType::Byte; template <> DataType DataTypeOf = DataType::UnsignedByte; @@ -20,16 +24,9 @@ template <> DataType DataTypeOf = DataType::UnsignedInteger; template <> DataType DataTypeOf = DataType::Float; template -void VariableAttributeBinding::bind(Context& context, - AttributeLocation location, - optional>& oldBinding, - std::size_t vertexOffset) const { - if (oldBinding == *this) { - return; - } +void AttributeBinding::bind(Context& context, AttributeLocation location, std::size_t vertexOffset) const { context.vertexBuffer = vertexBuffer; MBGL_CHECK_ERROR(glEnableVertexAttribArray(location)); - oldBinding = *this; MBGL_CHECK_ERROR(glVertexAttribPointer( location, static_cast(attributeSize), @@ -39,156 +36,25 @@ void VariableAttributeBinding::bind(Context& context, reinterpret_cast(attributeOffset + (vertexSize * vertexOffset)))); } -template class VariableAttributeBinding; -template class VariableAttributeBinding; -template class VariableAttributeBinding; -template class VariableAttributeBinding; +template class AttributeBinding; +template class AttributeBinding; +template class AttributeBinding; +template class AttributeBinding; -template class VariableAttributeBinding; -template class VariableAttributeBinding; -template class VariableAttributeBinding; -template class VariableAttributeBinding; +template class AttributeBinding; +template class AttributeBinding; +template class AttributeBinding; +template class AttributeBinding; -template class VariableAttributeBinding; -template class VariableAttributeBinding; -template class VariableAttributeBinding; -template class VariableAttributeBinding; +template class AttributeBinding; +template class AttributeBinding; +template class AttributeBinding; +template class AttributeBinding; -template class VariableAttributeBinding; -template class VariableAttributeBinding; -template class VariableAttributeBinding; -template class VariableAttributeBinding; - -template <> -void ConstantAttributeBinding::bind(Context&, AttributeLocation location, optional>& oldBinding, std::size_t) const { - assert(location != 0); - oldBinding = {}; - MBGL_CHECK_ERROR(glDisableVertexAttribArray(location)); - MBGL_CHECK_ERROR(glVertexAttrib1f(location, value[0])); -} - -template <> -void ConstantAttributeBinding::bind(Context&, AttributeLocation location, optional>& oldBinding, std::size_t) const { - assert(location != 0); - oldBinding = {}; - MBGL_CHECK_ERROR(glDisableVertexAttribArray(location)); - MBGL_CHECK_ERROR(glVertexAttrib2f(location, value[0], value[1])); -} - -template <> -void ConstantAttributeBinding::bind(Context&, AttributeLocation location, optional>& oldBinding, std::size_t) const { - assert(location != 0); - oldBinding = {}; - MBGL_CHECK_ERROR(glDisableVertexAttribArray(location)); - MBGL_CHECK_ERROR(glVertexAttrib3f(location, value[0], value[1], value[2])); -} - -template <> -void ConstantAttributeBinding::bind(Context&, AttributeLocation location, optional>& oldBinding, std::size_t) const { - assert(location != 0); - oldBinding = {}; - MBGL_CHECK_ERROR(glDisableVertexAttribArray(location)); - MBGL_CHECK_ERROR(glVertexAttrib4f(location, value[0], value[1], value[2], value[3])); -} - - -template <> -void ConstantAttributeBinding::bind(Context&, AttributeLocation location, optional>& oldBinding, std::size_t) const { - assert(location != 0); - oldBinding = {}; - MBGL_CHECK_ERROR(glDisableVertexAttribArray(location)); - MBGL_CHECK_ERROR(glVertexAttrib1f(location, value[0])); -} - -template <> -void ConstantAttributeBinding::bind(Context&, AttributeLocation location, optional>& oldBinding, std::size_t) const { - assert(location != 0); - oldBinding = {}; - MBGL_CHECK_ERROR(glDisableVertexAttribArray(location)); - MBGL_CHECK_ERROR(glVertexAttrib2f(location, value[0], value[1])); -} - -template <> -void ConstantAttributeBinding::bind(Context&, AttributeLocation location, optional>& oldBinding, std::size_t) const { - assert(location != 0); - oldBinding = {}; - MBGL_CHECK_ERROR(glDisableVertexAttribArray(location)); - MBGL_CHECK_ERROR(glVertexAttrib3f(location, value[0], value[1], value[2])); -} - -template <> -void ConstantAttributeBinding::bind(Context&, AttributeLocation location, optional>& oldBinding, std::size_t) const { - assert(location != 0); - oldBinding = {}; - MBGL_CHECK_ERROR(glDisableVertexAttribArray(location)); - MBGL_CHECK_ERROR(glVertexAttrib4f(location, value[0], value[1], value[2], value[3])); -} - - -template <> -void ConstantAttributeBinding::bind(Context&, AttributeLocation location, optional>& oldBinding, std::size_t) const { - assert(location != 0); - oldBinding = {}; - MBGL_CHECK_ERROR(glDisableVertexAttribArray(location)); - MBGL_CHECK_ERROR(glVertexAttrib1f(location, value[0])); -} - -template <> -void ConstantAttributeBinding::bind(Context&, AttributeLocation location, optional>& oldBinding, std::size_t) const { - assert(location != 0); - oldBinding = {}; - MBGL_CHECK_ERROR(glDisableVertexAttribArray(location)); - MBGL_CHECK_ERROR(glVertexAttrib2f(location, value[0], value[1])); -} - -template <> -void ConstantAttributeBinding::bind(Context&, AttributeLocation location, optional>& oldBinding, std::size_t) const { - assert(location != 0); - oldBinding = {}; - MBGL_CHECK_ERROR(glDisableVertexAttribArray(location)); - MBGL_CHECK_ERROR(glVertexAttrib3f(location, value[0], value[1], value[2])); -} - -template <> -void ConstantAttributeBinding::bind(Context&, AttributeLocation location, optional>& oldBinding, std::size_t) const { - assert(location != 0); - oldBinding = {}; - MBGL_CHECK_ERROR(glDisableVertexAttribArray(location)); - MBGL_CHECK_ERROR(glVertexAttrib4f(location, value[0], value[1], value[2], value[3])); -} - - -template <> -void ConstantAttributeBinding::bind(Context&, AttributeLocation location, optional>& oldBinding, std::size_t) const { - assert(location != 0); - oldBinding = {}; - MBGL_CHECK_ERROR(glDisableVertexAttribArray(location)); - MBGL_CHECK_ERROR(glVertexAttrib1f(location, value[0])); -} - -template <> -void ConstantAttributeBinding::bind(Context&, AttributeLocation location, optional>& oldBinding, std::size_t) const { - assert(location != 0); - oldBinding = {}; - MBGL_CHECK_ERROR(glDisableVertexAttribArray(location)); - MBGL_CHECK_ERROR(glVertexAttrib2f(location, value[0], value[1])); -} - -template <> -void ConstantAttributeBinding::bind(Context&, AttributeLocation location, optional>& oldBinding, std::size_t) const { - assert(location != 0); - oldBinding = {}; - MBGL_CHECK_ERROR(glDisableVertexAttribArray(location)); - MBGL_CHECK_ERROR(glVertexAttrib3f(location, value[0], value[1], value[2])); -} - -template <> -void ConstantAttributeBinding::bind(Context&, AttributeLocation location, optional>& oldBinding, std::size_t) const { - assert(location != 0); - oldBinding = {}; - MBGL_CHECK_ERROR(glDisableVertexAttribArray(location)); - MBGL_CHECK_ERROR(glVertexAttrib4f(location, value[0], value[1], value[2], value[3])); -} +template class AttributeBinding; +template class AttributeBinding; +template class AttributeBinding; +template class AttributeBinding; } // namespace gl } // namespace mbgl diff --git a/src/mbgl/gl/attribute.hpp b/src/mbgl/gl/attribute.hpp index d5e8edfc7..48222146f 100644 --- a/src/mbgl/gl/attribute.hpp +++ b/src/mbgl/gl/attribute.hpp @@ -13,23 +13,33 @@ namespace mbgl { namespace gl { -template -class VariableAttributeBinding { +class DisabledAttribute { public: - VariableAttributeBinding(BufferID vertexBuffer_, - std::size_t vertexSize_, - std::size_t attributeOffset_, - std::size_t attributeSize_ = N) + void bind(Context&, AttributeLocation, std::size_t vertexOffset) const; + + friend bool operator==(const DisabledAttribute&, + const DisabledAttribute&) { + return true; + } +}; + +template +class AttributeBinding { +public: + AttributeBinding(BufferID vertexBuffer_, + std::size_t vertexSize_, + std::size_t attributeOffset_, + std::size_t attributeSize_ = N) : vertexBuffer(vertexBuffer_), vertexSize(vertexSize_), attributeOffset(attributeOffset_), attributeSize(attributeSize_) {} - void bind(Context&, AttributeLocation, optional>&, std::size_t vertexOffset) const; + void bind(Context&, AttributeLocation, std::size_t vertexOffset) const; - friend bool operator==(const VariableAttributeBinding& lhs, - const VariableAttributeBinding& rhs) { + friend bool operator==(const AttributeBinding& lhs, + const AttributeBinding& rhs) { return lhs.vertexBuffer == rhs.vertexBuffer && lhs.vertexSize == rhs.vertexSize && lhs.attributeOffset == rhs.attributeOffset @@ -43,28 +53,8 @@ private: std::size_t attributeSize; }; -template -class ConstantAttributeBinding { -public: - ConstantAttributeBinding() { value.fill(T()); } - - explicit ConstantAttributeBinding(std::array value_) - : value(std::move(value_)) - {} - - void bind(Context&, AttributeLocation, optional>&, std::size_t) const; - - friend bool operator==(const ConstantAttributeBinding& lhs, - const ConstantAttributeBinding& rhs) { - return lhs.value == rhs.value; - } - -private: - std::array value; -}; - /* - gl::Attribute manages the binding of a constant value or vertex buffer to a GL program attribute. + gl::Attribute manages the binding of a vertex buffer to a GL program attribute. - T is the underlying primitive type (exposed as Attribute::ValueType) - N is the number of components in the attribute declared in the shader (exposed as Attribute::Dimensions) */ @@ -75,27 +65,23 @@ public: static constexpr size_t Dimensions = N; using Value = std::array; - using VariableBinding = VariableAttributeBinding; - using ConstantBinding = ConstantAttributeBinding; - using Location = AttributeLocation; using Binding = variant< - ConstantBinding, - VariableBinding>; + DisabledAttribute, + AttributeBinding>; /* - Create a variable (i.e. data-driven) binding for this attribute. The `attributeSize` - parameter may be used to override the number of components available in the buffer for - each vertex. Thus, a buffer with only one float for each vertex can be bound to a - `vec2` attribute + Create a binding for this attribute. The `attributeSize` parameter may be used to + override the number of components available in the buffer for each vertex. Thus, + a buffer with only one float for each vertex can be bound to a `vec2` attribute */ template - static VariableBinding variableBinding(const VertexBuffer& buffer, - std::size_t attributeIndex, - std::size_t attributeSize = N) { + static Binding binding(const VertexBuffer& buffer, + std::size_t attributeIndex, + std::size_t attributeSize = N) { static_assert(std::is_standard_layout::value, "vertex type must use standard layout"); - return VariableBinding { + return AttributeBinding { buffer.buffer, sizeof(Vertex), Vertex::attributeOffsets[attributeIndex], @@ -105,12 +91,18 @@ public: static void bind(Context& context, const Location& location, - optional& oldBinding, + Binding& oldBinding, const Binding& newBinding, std::size_t vertexOffset) { + if (oldBinding == newBinding) { + return; + } + Binding::visit(newBinding, [&] (const auto& binding) { - binding.bind(context, location, oldBinding, vertexOffset); + binding.bind(context, location, vertexOffset); }); + + oldBinding = newBinding; } }; @@ -242,9 +234,6 @@ public: using Bindings = IndexedTuple< TypeList, TypeList>; - using VariableBindings = IndexedTuple< - TypeList, - TypeList...>>; using NamedLocations = std::vector>; using Vertex = detail::Vertex; @@ -266,13 +255,13 @@ public: } template - static Bindings allVariableBindings(const VertexBuffer& buffer) { - return Bindings { As::Type::variableBinding(buffer, Index)... }; + static Bindings bindings(const VertexBuffer& buffer) { + return Bindings { As::Type::binding(buffer, Index)... }; } static void bind(Context& context, const Locations& locations, - VariableBindings& oldBindings, + Bindings& oldBindings, const Bindings& newBindings, std::size_t vertexOffset) { util::ignore({ (As::Type::bind(context, diff --git a/src/mbgl/gl/segment.hpp b/src/mbgl/gl/segment.hpp index 45c81973f..fe0658bf8 100644 --- a/src/mbgl/gl/segment.hpp +++ b/src/mbgl/gl/segment.hpp @@ -47,12 +47,12 @@ public: } else { // No VAO support. Force attributes to be rebound. context.elementBuffer = indexBuffer_; - variableBindings = {}; + attributeBindings = {}; } Attributes::bind(context, attributeLocations, - variableBindings, + attributeBindings, attributeBindings_, vertexOffset); } @@ -60,7 +60,7 @@ public: private: mutable optional vao; mutable optional indexBuffer; - mutable typename Attributes::VariableBindings variableBindings; + mutable typename Attributes::Bindings attributeBindings; }; template diff --git a/src/mbgl/programs/program.hpp b/src/mbgl/programs/program.hpp index bbe488574..65f17e5bd 100644 --- a/src/mbgl/programs/program.hpp +++ b/src/mbgl/programs/program.hpp @@ -66,7 +66,7 @@ public: std::move(colorMode), uniformValues .concat(paintPropertyBinders.uniformValues(currentZoom, currentProperties)), - LayoutAttributes::allVariableBindings(layoutVertexBuffer) + LayoutAttributes::bindings(layoutVertexBuffer) .concat(paintPropertyBinders.attributeBindings(currentProperties)), indexBuffer, segments diff --git a/src/mbgl/programs/symbol_program.hpp b/src/mbgl/programs/symbol_program.hpp index abcba1d03..47d92f912 100644 --- a/src/mbgl/programs/symbol_program.hpp +++ b/src/mbgl/programs/symbol_program.hpp @@ -156,8 +156,9 @@ public: } SymbolSizeAttributes::Bindings attributeBindings() const override { - return SymbolSizeAttributes::Bindings { SymbolSizeAttributes::Attribute::ConstantBinding {{{0, 0, 0}}} }; + return SymbolSizeAttributes::Bindings { gl::DisabledAttribute() }; } + void upload(gl::Context&) override {} void populateVertexVector(const GeometryTileFeature&) override {}; @@ -210,9 +211,9 @@ public: } SymbolSizeAttributes::Bindings attributeBindings() const override { - return SymbolSizeAttributes::Bindings { SymbolSizeAttributes::Attribute::variableBinding(*buffer, 0, 1) }; + return SymbolSizeAttributes::Bindings { SymbolSizeAttributes::Attribute::binding(*buffer, 0, 1) }; } - + void populateVertexVector(const GeometryTileFeature& feature) override { const auto sizeVertex = Vertex { {{ @@ -263,7 +264,7 @@ public: {} SymbolSizeAttributes::Bindings attributeBindings() const override { - return SymbolSizeAttributes::Bindings { SymbolSizeAttributes::Attribute::variableBinding(*buffer, 0) }; + return SymbolSizeAttributes::Bindings { SymbolSizeAttributes::Attribute::binding(*buffer, 0) }; } void populateVertexVector(const GeometryTileFeature& feature) override { @@ -368,7 +369,7 @@ public: uniformValues .concat(symbolSizeBinder.uniformValues(currentZoom)) .concat(paintPropertyBinders.uniformValues(currentZoom, currentProperties)), - LayoutAttributes::allVariableBindings(layoutVertexBuffer) + LayoutAttributes::bindings(layoutVertexBuffer) .concat(symbolSizeBinder.attributeBindings()) .concat(paintPropertyBinders.attributeBindings(currentProperties)), indexBuffer, diff --git a/src/mbgl/renderer/paint_property_binder.hpp b/src/mbgl/renderer/paint_property_binder.hpp index bcbc7c287..906a2ea07 100644 --- a/src/mbgl/renderer/paint_property_binder.hpp +++ b/src/mbgl/renderer/paint_property_binder.hpp @@ -102,11 +102,8 @@ public: void populateVertexVector(const GeometryTileFeature&, std::size_t) override {} void upload(gl::Context&) override {} - AttributeBinding attributeBinding(const PossiblyEvaluatedPropertyValue& currentValue) const override { - auto value = attributeValue(currentValue.constantOr(constant)); - return typename Attribute::ConstantBinding { - zoomInterpolatedAttributeValue(value, value) - }; + AttributeBinding attributeBinding(const PossiblyEvaluatedPropertyValue&) const override { + return gl::DisabledAttribute(); } float interpolationFactor(float) const override { @@ -151,12 +148,9 @@ public: AttributeBinding attributeBinding(const PossiblyEvaluatedPropertyValue& currentValue) const override { if (currentValue.isConstant()) { - BaseAttributeValue value = attributeValue(*currentValue.constant()); - return typename Attribute::ConstantBinding { - zoomInterpolatedAttributeValue(value, value) - }; + return gl::DisabledAttribute(); } else { - return Attribute::variableBinding(*vertexBuffer, 0, BaseAttribute::Dimensions); + return Attribute::binding(*vertexBuffer, 0, BaseAttribute::Dimensions); } } @@ -215,12 +209,9 @@ public: AttributeBinding attributeBinding(const PossiblyEvaluatedPropertyValue& currentValue) const override { if (currentValue.isConstant()) { - BaseAttributeValue value = attributeValue(*currentValue.constant()); - return typename Attribute::ConstantBinding { - zoomInterpolatedAttributeValue(value, value) - }; + return gl::DisabledAttribute(); } else { - return Attribute::variableBinding(*vertexBuffer, 0); + return Attribute::binding(*vertexBuffer, 0); } } From e8272ebdbb6e4788e82e4c4555f8ef8225d4013c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Wed, 14 Jun 2017 13:07:32 +0200 Subject: [PATCH 09/71] [core] rename a_gap_width => a_gapwidth, u_gap_width => u_gapwidth to reflect naming in the shader not sure how this ever worked --- src/mbgl/programs/attributes.hpp | 2 +- src/mbgl/programs/uniforms.hpp | 2 +- src/mbgl/style/layers/line_layer_properties.hpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mbgl/programs/attributes.hpp b/src/mbgl/programs/attributes.hpp index cfd6a629d..a0b2b93e1 100644 --- a/src/mbgl/programs/attributes.hpp +++ b/src/mbgl/programs/attributes.hpp @@ -106,7 +106,7 @@ struct a_base { using Type = gl::Attribute; }; -struct a_gap_width { +struct a_gapwidth { static auto name() { return "a_gapwidth"; } using Type = gl::Attribute; }; diff --git a/src/mbgl/programs/uniforms.hpp b/src/mbgl/programs/uniforms.hpp index 60a50a7cb..32d857a94 100644 --- a/src/mbgl/programs/uniforms.hpp +++ b/src/mbgl/programs/uniforms.hpp @@ -27,7 +27,7 @@ MBGL_DEFINE_UNIFORM_SCALAR(float, u_halo_blur); MBGL_DEFINE_UNIFORM_SCALAR(Color, u_outline_color); MBGL_DEFINE_UNIFORM_SCALAR(float, u_height); MBGL_DEFINE_UNIFORM_SCALAR(float, u_base); -MBGL_DEFINE_UNIFORM_SCALAR(float, u_gap_width); +MBGL_DEFINE_UNIFORM_SCALAR(float, u_gapwidth); MBGL_DEFINE_UNIFORM_SCALAR(float, u_offset); MBGL_DEFINE_UNIFORM_SCALAR(Size, u_world); diff --git a/src/mbgl/style/layers/line_layer_properties.hpp b/src/mbgl/style/layers/line_layer_properties.hpp index 0b234921a..6c301e6a0 100644 --- a/src/mbgl/style/layers/line_layer_properties.hpp +++ b/src/mbgl/style/layers/line_layer_properties.hpp @@ -51,7 +51,7 @@ struct LineWidth : PaintProperty { static float defaultValue() { return 1; } }; -struct LineGapWidth : DataDrivenPaintProperty { +struct LineGapWidth : DataDrivenPaintProperty { static float defaultValue() { return 0; } }; From eed89fcf9d099266aa793375ad63493e880f8a80 Mon Sep 17 00:00:00 2001 From: Lauren Budorick Date: Thu, 29 Jun 2017 11:29:48 -0700 Subject: [PATCH 10/71] [core] Bind only active attributes in order to avoid exceeding attribute limits (#9373) Introducing two new attributes to enable property functions for line-width (#9250) pushed the attribute count over GL_MAX_VERTEX_ATTRIBS on some devices. Now we selectively bind only attributes that are used, making it unlikely to surpass GL_MAX_VERTEX_ATTRIBS. --- src/mbgl/gl/attribute.cpp | 46 +++++++++++++++++++++++++++++++++++++++ src/mbgl/gl/attribute.hpp | 12 +++++++++- src/mbgl/gl/program.hpp | 12 +++++----- 3 files changed, 64 insertions(+), 6 deletions(-) diff --git a/src/mbgl/gl/attribute.cpp b/src/mbgl/gl/attribute.cpp index e05ca7586..4e6f78e68 100644 --- a/src/mbgl/gl/attribute.cpp +++ b/src/mbgl/gl/attribute.cpp @@ -2,14 +2,55 @@ #include #include +#include + namespace mbgl { namespace gl { AttributeLocation bindAttributeLocation(ProgramID id, AttributeLocation location, const char* name) { + assert(location < 8); MBGL_CHECK_ERROR(glBindAttribLocation(id, location, name)); return location; } +int32_t getActiveAttributeCount(ProgramID id) { + GLint numAttributes; + MBGL_CHECK_ERROR(glGetProgramiv(id, GL_ACTIVE_ATTRIBUTES, &numAttributes)); + return numAttributes; +} + +int32_t getMaxAttributeNameLength(ProgramID id) { + GLint nameLength; + MBGL_CHECK_ERROR(glGetProgramiv(id, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &nameLength)); + return nameLength; +} + +std::string getAttributeName(ProgramID id, int32_t maxLength, AttributeLocation location) { + std::string attributeName; + attributeName.resize(maxLength); + GLsizei actualLength; + GLint size; + GLenum type; + MBGL_CHECK_ERROR(glGetActiveAttrib(id, static_cast(location), + static_cast(maxLength), &actualLength, &size, &type, + const_cast(attributeName.data()))); + attributeName.resize(actualLength); + return attributeName; +} + +std::set getActiveAttributes(ProgramID id) { + std::set activeAttributes; + + GLint attributeCount = getActiveAttributeCount(id); + GLint maxAttributeLength = getMaxAttributeNameLength(id); + + for (int32_t i = 0; i < attributeCount; i++) { + activeAttributes.emplace(getAttributeName(id, maxAttributeLength, i)); + } + + return activeAttributes; +} + void DisabledAttribute::bind(Context&, AttributeLocation location, std::size_t) const { MBGL_CHECK_ERROR(glDisableVertexAttribArray(location)); } @@ -25,6 +66,11 @@ template <> DataType DataTypeOf = DataType::Float; template void AttributeBinding::bind(Context& context, AttributeLocation location, std::size_t vertexOffset) const { + // FillProgram will attempt to bind at location -1 because it includes + // a fill-outline-color paint property but does not use it or have an + // a_outline_color shader attribute - in this case, we have nothing to bind. + if (location == -1) return; + context.vertexBuffer = vertexBuffer; MBGL_CHECK_ERROR(glEnableVertexAttribArray(location)); MBGL_CHECK_ERROR(glVertexAttribPointer( diff --git a/src/mbgl/gl/attribute.hpp b/src/mbgl/gl/attribute.hpp index 48222146f..f018a1d26 100644 --- a/src/mbgl/gl/attribute.hpp +++ b/src/mbgl/gl/attribute.hpp @@ -8,6 +8,7 @@ #include #include +#include #include namespace mbgl { @@ -223,6 +224,7 @@ const std::size_t Vertex::attributeOffsets[5] = { } // namespace detail AttributeLocation bindAttributeLocation(ProgramID, AttributeLocation, const char * name); +std::set getActiveAttributes(ProgramID); template class Attributes { @@ -242,7 +244,15 @@ public: static constexpr std::size_t Index = TypeIndex::value; static Locations bindLocations(const ProgramID& id) { - return Locations { bindAttributeLocation(id, Index, As::name())... }; + std::set activeAttributes = getActiveAttributes(id); + + AttributeLocation location = -1; + auto bindAndIncrement = [&](const char* name) { + location++; + return bindAttributeLocation(id, location, name); + }; + return Locations{ (activeAttributes.count(As::name()) ? bindAndIncrement(As::name()) + : -1)... }; } template diff --git a/src/mbgl/gl/program.hpp b/src/mbgl/gl/program.hpp index 47ad39de7..583d53e4e 100644 --- a/src/mbgl/gl/program.hpp +++ b/src/mbgl/gl/program.hpp @@ -33,15 +33,17 @@ public: : program( context.createProgram(context.createShader(ShaderType::Vertex, vertexSource), context.createShader(ShaderType::Fragment, fragmentSource))), - attributeLocations(Attributes::bindLocations(program)), - uniformsState((context.linkProgram(program), Uniforms::bindLocations(program))) { + uniformsState((context.linkProgram(program), Uniforms::bindLocations(program))), + attributeLocations(Attributes::bindLocations(program)) { + // Re-link program after manually binding only active attributes in Attributes::bindLocations + context.linkProgram(program); } template Program(Context& context, const BinaryProgram& binaryProgram) : program(context.createProgram(binaryProgram.format(), binaryProgram.code())), - attributeLocations(Attributes::loadNamedLocations(binaryProgram)), - uniformsState(Uniforms::loadNamedLocations(binaryProgram)) { + uniformsState(Uniforms::loadNamedLocations(binaryProgram)), + attributeLocations(Attributes::loadNamedLocations(binaryProgram)) { } static Program createProgram(gl::Context& context, @@ -144,8 +146,8 @@ public: private: UniqueProgram program; - typename Attributes::Locations attributeLocations; typename Uniforms::State uniformsState; + typename Attributes::Locations attributeLocations; }; } // namespace gl From f9fe0de806a29cae4e7a2071940949ebffaf3140 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 6 Jul 2017 13:45:52 -0700 Subject: [PATCH 11/71] [core] Include what you use --- src/mbgl/gl/program.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mbgl/gl/program.hpp b/src/mbgl/gl/program.hpp index 583d53e4e..ce2b5a335 100644 --- a/src/mbgl/gl/program.hpp +++ b/src/mbgl/gl/program.hpp @@ -9,11 +9,11 @@ #include #include +#include #include #include #include - #include namespace mbgl { From fbd437689901d725ce5e8df056abab3dc6c104ec Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 3 Jul 2017 14:05:12 -0700 Subject: [PATCH 12/71] [core] Inline getActiveAttributes details --- src/mbgl/gl/attribute.cpp | 42 +++++++++++++-------------------------- 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/src/mbgl/gl/attribute.cpp b/src/mbgl/gl/attribute.cpp index 4e6f78e68..2f817cdcd 100644 --- a/src/mbgl/gl/attribute.cpp +++ b/src/mbgl/gl/attribute.cpp @@ -13,39 +13,25 @@ AttributeLocation bindAttributeLocation(ProgramID id, AttributeLocation location return location; } -int32_t getActiveAttributeCount(ProgramID id) { - GLint numAttributes; - MBGL_CHECK_ERROR(glGetProgramiv(id, GL_ACTIVE_ATTRIBUTES, &numAttributes)); - return numAttributes; -} - -int32_t getMaxAttributeNameLength(ProgramID id) { - GLint nameLength; - MBGL_CHECK_ERROR(glGetProgramiv(id, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &nameLength)); - return nameLength; -} - -std::string getAttributeName(ProgramID id, int32_t maxLength, AttributeLocation location) { - std::string attributeName; - attributeName.resize(maxLength); - GLsizei actualLength; - GLint size; - GLenum type; - MBGL_CHECK_ERROR(glGetActiveAttrib(id, static_cast(location), - static_cast(maxLength), &actualLength, &size, &type, - const_cast(attributeName.data()))); - attributeName.resize(actualLength); - return attributeName; -} - std::set getActiveAttributes(ProgramID id) { std::set activeAttributes; - GLint attributeCount = getActiveAttributeCount(id); - GLint maxAttributeLength = getMaxAttributeNameLength(id); + GLint attributeCount; + MBGL_CHECK_ERROR(glGetProgramiv(id, GL_ACTIVE_ATTRIBUTES, &attributeCount)); + + GLint maxAttributeLength; + MBGL_CHECK_ERROR(glGetProgramiv(id, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &maxAttributeLength)); + + std::string attributeName; + attributeName.resize(maxAttributeLength); + + GLsizei actualLength; + GLint size; + GLenum type; for (int32_t i = 0; i < attributeCount; i++) { - activeAttributes.emplace(getAttributeName(id, maxAttributeLength, i)); + MBGL_CHECK_ERROR(glGetActiveAttrib(id, i, maxAttributeLength, &actualLength, &size, &type, &attributeName[0])); + activeAttributes.emplace(std::string(attributeName, 0, actualLength)); } return activeAttributes; From d7bdc7a60d330094dd8af8431e0b1d6f588d9f07 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 3 Jul 2017 14:05:12 -0700 Subject: [PATCH 13/71] [core] Rework attribute binding (again) These changes are necessary for programs whose set of active attributes is not fixed at compile time by a template parameter pack, but rather varies based on the generated shader text at runtime. In such cases, the attribute location of a given named attribute may vary between instances of the same Program. Previously, attribute bindings were implicitly associated with a location based on template parameter order, and -1 was used to indicate an inactive attribute. This left us unable to disable the appropriate attribute when it went from active to inactive. Now, the state tracker for bindings explicitly associates locations and state, and an empty optional is used to indicate an inactive attribute. In addition, a gl::VertexArray class is now exposed, allowing more flexibility in the relationship between Programs, Segments, and attribute bindings. In this commit, that relationship does not change, but the subsequent commit adjusts it to match gl-js, reduce rebinds, and work around buggy VAO implementations. VertexArray uses a pimpl idiom in order to support implementations that lack the VAO extension. In that case, all VertexArrays share global binding state, reflecting the platform reality in the absence of VAOs, while still providing a uniform API. --- cmake/core-files.cmake | 6 +- src/mbgl/gl/attribute.cpp | 61 +------- src/mbgl/gl/attribute.hpp | 148 ++++++++++---------- src/mbgl/gl/context.cpp | 31 ++-- src/mbgl/gl/context.hpp | 12 +- src/mbgl/gl/object.cpp | 4 +- src/mbgl/gl/program.hpp | 26 ++-- src/mbgl/gl/segment.hpp | 73 ---------- src/mbgl/gl/types.hpp | 2 +- src/mbgl/gl/uniform.hpp | 2 +- src/mbgl/gl/value.cpp | 18 +++ src/mbgl/gl/value.hpp | 7 + src/mbgl/gl/vertex_array.cpp | 18 +++ src/mbgl/gl/vertex_array.hpp | 58 ++++++++ src/mbgl/programs/binary_program.cpp | 4 +- src/mbgl/programs/binary_program.hpp | 4 +- src/mbgl/programs/program.hpp | 42 ++++-- src/mbgl/{gl => programs}/segment.cpp | 2 +- src/mbgl/programs/segment.hpp | 40 ++++++ src/mbgl/programs/symbol_program.hpp | 48 ++++--- src/mbgl/renderer/circle_bucket.hpp | 4 +- src/mbgl/renderer/debug_bucket.hpp | 2 +- src/mbgl/renderer/fill_bucket.hpp | 6 +- src/mbgl/renderer/fill_extrusion_bucket.hpp | 4 +- src/mbgl/renderer/line_bucket.hpp | 4 +- src/mbgl/renderer/paint_property_binder.hpp | 15 +- src/mbgl/renderer/painter.cpp | 4 +- src/mbgl/renderer/painter.hpp | 8 +- src/mbgl/renderer/symbol_bucket.hpp | 8 +- test/programs/binary_program.test.cpp | 16 +-- 30 files changed, 368 insertions(+), 309 deletions(-) delete mode 100644 src/mbgl/gl/segment.hpp create mode 100644 src/mbgl/gl/vertex_array.cpp create mode 100644 src/mbgl/gl/vertex_array.hpp rename src/mbgl/{gl => programs}/segment.cpp (66%) create mode 100644 src/mbgl/programs/segment.hpp diff --git a/cmake/core-files.cmake b/cmake/core-files.cmake index 069b6cb4c..8a3c42a1f 100644 --- a/cmake/core-files.cmake +++ b/cmake/core-files.cmake @@ -76,8 +76,6 @@ set(MBGL_CORE_FILES src/mbgl/gl/program.hpp src/mbgl/gl/program_binary_extension.hpp src/mbgl/gl/renderbuffer.hpp - src/mbgl/gl/segment.cpp - src/mbgl/gl/segment.hpp src/mbgl/gl/state.hpp src/mbgl/gl/stencil_mode.cpp src/mbgl/gl/stencil_mode.hpp @@ -87,6 +85,8 @@ set(MBGL_CORE_FILES src/mbgl/gl/uniform.hpp src/mbgl/gl/value.cpp src/mbgl/gl/value.hpp + src/mbgl/gl/vertex_array.cpp + src/mbgl/gl/vertex_array.hpp src/mbgl/gl/vertex_array_extension.hpp src/mbgl/gl/vertex_buffer.hpp @@ -155,6 +155,8 @@ set(MBGL_CORE_FILES src/mbgl/programs/programs.hpp src/mbgl/programs/raster_program.cpp src/mbgl/programs/raster_program.hpp + src/mbgl/programs/segment.cpp + src/mbgl/programs/segment.hpp src/mbgl/programs/symbol_program.cpp src/mbgl/programs/symbol_program.hpp src/mbgl/programs/uniforms.hpp diff --git a/src/mbgl/gl/attribute.cpp b/src/mbgl/gl/attribute.cpp index 2f817cdcd..bb5b2ddc3 100644 --- a/src/mbgl/gl/attribute.cpp +++ b/src/mbgl/gl/attribute.cpp @@ -1,16 +1,14 @@ #include -#include #include -#include - namespace mbgl { namespace gl { -AttributeLocation bindAttributeLocation(ProgramID id, AttributeLocation location, const char* name) { - assert(location < 8); +void bindAttributeLocation(ProgramID id, AttributeLocation location, const char* name) { + if (location >= MAX_ATTRIBUTES) { + throw gl::Error("too many vertex attributes"); + } MBGL_CHECK_ERROR(glBindAttribLocation(id, location, name)); - return location; } std::set getActiveAttributes(ProgramID id) { @@ -37,56 +35,5 @@ std::set getActiveAttributes(ProgramID id) { return activeAttributes; } -void DisabledAttribute::bind(Context&, AttributeLocation location, std::size_t) const { - MBGL_CHECK_ERROR(glDisableVertexAttribArray(location)); -} - -template DataType DataTypeOf = static_cast(0); -template <> DataType DataTypeOf< int8_t> = DataType::Byte; -template <> DataType DataTypeOf = DataType::UnsignedByte; -template <> DataType DataTypeOf< int16_t> = DataType::Short; -template <> DataType DataTypeOf = DataType::UnsignedShort; -template <> DataType DataTypeOf< int32_t> = DataType::Integer; -template <> DataType DataTypeOf = DataType::UnsignedInteger; -template <> DataType DataTypeOf = DataType::Float; - -template -void AttributeBinding::bind(Context& context, AttributeLocation location, std::size_t vertexOffset) const { - // FillProgram will attempt to bind at location -1 because it includes - // a fill-outline-color paint property but does not use it or have an - // a_outline_color shader attribute - in this case, we have nothing to bind. - if (location == -1) return; - - context.vertexBuffer = vertexBuffer; - MBGL_CHECK_ERROR(glEnableVertexAttribArray(location)); - MBGL_CHECK_ERROR(glVertexAttribPointer( - location, - static_cast(attributeSize), - static_cast(DataTypeOf), - static_cast(false), - static_cast(vertexSize), - reinterpret_cast(attributeOffset + (vertexSize * vertexOffset)))); -} - -template class AttributeBinding; -template class AttributeBinding; -template class AttributeBinding; -template class AttributeBinding; - -template class AttributeBinding; -template class AttributeBinding; -template class AttributeBinding; -template class AttributeBinding; - -template class AttributeBinding; -template class AttributeBinding; -template class AttributeBinding; -template class AttributeBinding; - -template class AttributeBinding; -template class AttributeBinding; -template class AttributeBinding; -template class AttributeBinding; - } // namespace gl } // namespace mbgl diff --git a/src/mbgl/gl/attribute.hpp b/src/mbgl/gl/attribute.hpp index f018a1d26..ed4168c21 100644 --- a/src/mbgl/gl/attribute.hpp +++ b/src/mbgl/gl/attribute.hpp @@ -1,59 +1,51 @@ #pragma once #include -#include +#include #include #include -#include +#include #include #include #include #include +#include +#include namespace mbgl { namespace gl { -class DisabledAttribute { -public: - void bind(Context&, AttributeLocation, std::size_t vertexOffset) const; +static constexpr std::size_t MAX_ATTRIBUTES = 8; - friend bool operator==(const DisabledAttribute&, - const DisabledAttribute&) { - return true; - } -}; +template struct DataTypeOf; +template <> struct DataTypeOf< int8_t> : std::integral_constant {}; +template <> struct DataTypeOf : std::integral_constant {}; +template <> struct DataTypeOf< int16_t> : std::integral_constant {}; +template <> struct DataTypeOf : std::integral_constant {}; +template <> struct DataTypeOf< int32_t> : std::integral_constant {}; +template <> struct DataTypeOf : std::integral_constant {}; +template <> struct DataTypeOf : std::integral_constant {}; -template class AttributeBinding { public: - AttributeBinding(BufferID vertexBuffer_, - std::size_t vertexSize_, - std::size_t attributeOffset_, - std::size_t attributeSize_ = N) - : vertexBuffer(vertexBuffer_), - vertexSize(vertexSize_), - attributeOffset(attributeOffset_), - attributeSize(attributeSize_) - {} + DataType attributeType; + std::size_t attributeSize; + std::size_t attributeOffset; - void bind(Context&, AttributeLocation, std::size_t vertexOffset) const; + BufferID vertexBuffer; + std::size_t vertexSize; + std::size_t vertexOffset; friend bool operator==(const AttributeBinding& lhs, const AttributeBinding& rhs) { - return lhs.vertexBuffer == rhs.vertexBuffer - && lhs.vertexSize == rhs.vertexSize - && lhs.attributeOffset == rhs.attributeOffset - && lhs.attributeSize == rhs.attributeSize; + return std::tie(lhs.attributeType, lhs.attributeSize, lhs.attributeOffset, lhs.vertexBuffer, lhs.vertexSize, lhs.vertexOffset) + == std::tie(rhs.attributeType, rhs.attributeSize, rhs.attributeOffset, rhs.vertexBuffer, rhs.vertexSize, rhs.vertexOffset); } - -private: - BufferID vertexBuffer; - std::size_t vertexSize; - std::size_t attributeOffset; - std::size_t attributeSize; }; +using AttributeBindingArray = std::array, MAX_ATTRIBUTES>; + /* gl::Attribute manages the binding of a vertex buffer to a GL program attribute. - T is the underlying primitive type (exposed as Attribute::ValueType) @@ -67,10 +59,7 @@ public: using Value = std::array; using Location = AttributeLocation; - - using Binding = variant< - DisabledAttribute, - AttributeBinding>; + using Binding = AttributeBinding; /* Create a binding for this attribute. The `attributeSize` parameter may be used to @@ -82,28 +71,24 @@ public: std::size_t attributeIndex, std::size_t attributeSize = N) { static_assert(std::is_standard_layout::value, "vertex type must use standard layout"); - return AttributeBinding { + return AttributeBinding { + DataTypeOf::value, + attributeSize, + Vertex::attributeOffsets[attributeIndex], buffer.buffer, sizeof(Vertex), - Vertex::attributeOffsets[attributeIndex], - attributeSize + 0, }; } - static void bind(Context& context, - const Location& location, - Binding& oldBinding, - const Binding& newBinding, - std::size_t vertexOffset) { - if (oldBinding == newBinding) { - return; + static optional offsetBinding(const optional& binding, std::size_t vertexOffset) { + if (binding) { + AttributeBinding result = *binding; + result.vertexOffset = vertexOffset; + return result; + } else { + return binding; } - - Binding::visit(newBinding, [&] (const auto& binding) { - binding.bind(context, location, vertexOffset); - }); - - oldBinding = newBinding; } }; @@ -223,7 +208,7 @@ const std::size_t Vertex::attributeOffsets[5] = { } // namespace detail -AttributeLocation bindAttributeLocation(ProgramID, AttributeLocation, const char * name); +void bindAttributeLocation(ProgramID, AttributeLocation, const char * name); std::set getActiveAttributes(ProgramID); template @@ -232,10 +217,10 @@ public: using Types = TypeList; using Locations = IndexedTuple< TypeList, - TypeList>; + TypeList...>>; using Bindings = IndexedTuple< TypeList, - TypeList>; + TypeList...>>; using NamedLocations = std::vector>; using Vertex = detail::Vertex; @@ -246,13 +231,17 @@ public: static Locations bindLocations(const ProgramID& id) { std::set activeAttributes = getActiveAttributes(id); - AttributeLocation location = -1; - auto bindAndIncrement = [&](const char* name) { - location++; - return bindAttributeLocation(id, location, name); + AttributeLocation location = 0; + auto maybeBindLocation = [&](const char* name) -> optional { + if (activeAttributes.count(name)) { + bindAttributeLocation(id, location, name); + return location++; + } else { + return {}; + } }; - return Locations{ (activeAttributes.count(As::name()) ? bindAndIncrement(As::name()) - : -1)... }; + + return Locations { maybeBindLocation(As::name())... }; } template @@ -261,7 +250,17 @@ public: } static NamedLocations getNamedLocations(const Locations& locations) { - return NamedLocations{ { As::name(), locations.template get() }... }; + NamedLocations result; + + auto maybeAddLocation = [&] (const std::string& name, const optional& location) { + if (location) { + result.emplace_back(name, *location); + } + }; + + util::ignore({ (maybeAddLocation(As::name(), locations.template get()), 0)... }); + + return result; } template @@ -269,16 +268,23 @@ public: return Bindings { As::Type::binding(buffer, Index)... }; } - static void bind(Context& context, - const Locations& locations, - Bindings& oldBindings, - const Bindings& newBindings, - std::size_t vertexOffset) { - util::ignore({ (As::Type::bind(context, - locations.template get(), - oldBindings.template get(), - newBindings.template get(), - vertexOffset), 0)... }); + static Bindings offsetBindings(const Bindings& bindings, std::size_t vertexOffset) { + return Bindings { As::Type::offsetBinding(bindings.template get(), vertexOffset)... }; + } + + static AttributeBindingArray toBindingArray(const Locations& locations, const Bindings& bindings) { + AttributeBindingArray result; + + auto maybeAddBinding = [&] (const optional& location, + const optional& binding) { + if (location) { + result.at(*location) = binding; + } + }; + + util::ignore({ (maybeAddBinding(locations.template get(), bindings.template get()), 0)... }); + + return result; } }; diff --git a/src/mbgl/gl/context.cpp b/src/mbgl/gl/context.cpp index 3ab1260d2..2d7354a64 100644 --- a/src/mbgl/gl/context.cpp +++ b/src/mbgl/gl/context.cpp @@ -177,8 +177,8 @@ UniqueBuffer Context::createIndexBuffer(const void* data, std::size_t size) { BufferID id = 0; MBGL_CHECK_ERROR(glGenBuffers(1, &id)); UniqueBuffer result { std::move(id), { this } }; - vertexArrayObject = 0; - elementBuffer = result; + bindVertexArray = 0; + globalVertexArrayState.indexBuffer = result; MBGL_CHECK_ERROR(glBufferData(GL_ELEMENT_ARRAY_BUFFER, size, data, GL_STATIC_DRAW)); return result; } @@ -229,11 +229,16 @@ optional> Context::getBinaryProgram( } #endif -UniqueVertexArray Context::createVertexArray() { - assert(supportsVertexArrays()); - VertexArrayID id = 0; - MBGL_CHECK_ERROR(vertexArray->genVertexArrays(1, &id)); - return UniqueVertexArray(std::move(id), { this }); +VertexArray Context::createVertexArray() { + if (supportsVertexArrays()) { + VertexArrayID id = 0; + MBGL_CHECK_ERROR(vertexArray->genVertexArrays(1, &id)); + return { std::make_unique(UniqueVertexArray(std::move(id), { this }), *this) }; + } else { + // On GL implementations which do not support vertex arrays, attribute bindings are global state. + // So return a VertexArray which shares our global state tracking and whose deleter is a no-op. + return { UniqueVertexArrayState(&globalVertexArrayState, [] (VertexArrayState*) {}) }; + } } UniqueFramebuffer Context::createFramebuffer() { @@ -502,8 +507,8 @@ void Context::setDirtyState() { tex.setDirty(); } vertexBuffer.setDirty(); - elementBuffer.setDirty(); - vertexArrayObject.setDirty(); + bindVertexArray.setDirty(); + globalVertexArrayState.setDirty(); } void Context::clear(optional color, @@ -622,8 +627,8 @@ void Context::performCleanup() { for (const auto id : abandonedBuffers) { if (vertexBuffer == id) { vertexBuffer.setDirty(); - } else if (elementBuffer == id) { - elementBuffer.setDirty(); + } else if (globalVertexArrayState.indexBuffer == id) { + globalVertexArrayState.indexBuffer.setDirty(); } } MBGL_CHECK_ERROR(glDeleteBuffers(int(abandonedBuffers.size()), abandonedBuffers.data())); @@ -643,8 +648,8 @@ void Context::performCleanup() { if (!abandonedVertexArrays.empty()) { assert(supportsVertexArrays()); for (const auto id : abandonedVertexArrays) { - if (vertexArrayObject == id) { - vertexArrayObject.setDirty(); + if (bindVertexArray == id) { + bindVertexArray.setDirty(); } } MBGL_CHECK_ERROR(vertexArray->deleteVertexArrays(int(abandonedVertexArrays.size()), diff --git a/src/mbgl/gl/context.hpp b/src/mbgl/gl/context.hpp index 56c061898..8929d24e5 100644 --- a/src/mbgl/gl/context.hpp +++ b/src/mbgl/gl/context.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -53,9 +54,7 @@ public: void verifyProgramLinkage(ProgramID); void linkProgram(ProgramID); UniqueTexture createTexture(); - - bool supportsVertexArrays() const; - UniqueVertexArray createVertexArray(); + VertexArray createVertexArray(); #if MBGL_HAS_BINARY_PROGRAMS bool supportsProgramBinaries() const; @@ -200,10 +199,11 @@ public: State bindFramebuffer; State viewport; std::array, 2> texture; - State vertexArrayObject { *this }; State program; State vertexBuffer; - State elementBuffer; + + State bindVertexArray { *this }; + VertexArrayState globalVertexArrayState { UniqueVertexArray(0, { this }), *this }; #if not MBGL_USE_GLES2 State pixelZoom; @@ -248,6 +248,8 @@ private: void drawPixels(Size size, const void* data, TextureFormat); #endif // MBGL_USE_GLES2 + bool supportsVertexArrays() const; + friend detail::ProgramDeleter; friend detail::ShaderDeleter; friend detail::BufferDeleter; diff --git a/src/mbgl/gl/object.cpp b/src/mbgl/gl/object.cpp index e2d476e0c..2c5f1bca1 100644 --- a/src/mbgl/gl/object.cpp +++ b/src/mbgl/gl/object.cpp @@ -33,7 +33,9 @@ void TextureDeleter::operator()(TextureID id) const { void VertexArrayDeleter::operator()(VertexArrayID id) const { assert(context); - context->abandonedVertexArrays.push_back(id); + if (id != 0) { + context->abandonedVertexArrays.push_back(id); + } } void FramebufferDeleter::operator()(FramebufferID id) const { diff --git a/src/mbgl/gl/program.hpp b/src/mbgl/gl/program.hpp index ce2b5a335..9d8b0a5b0 100644 --- a/src/mbgl/gl/program.hpp +++ b/src/mbgl/gl/program.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -116,10 +117,12 @@ public: DepthMode depthMode, StencilMode stencilMode, ColorMode colorMode, - UniformValues&& uniformValues, - AttributeBindings&& attributeBindings, + const UniformValues& uniformValues, + VertexArray& vertexArray, + const AttributeBindings& attributeBindings, const IndexBuffer& indexBuffer, - const SegmentVector& segments) { + std::size_t indexOffset, + std::size_t indexLength) { static_assert(std::is_same::value, "incompatible draw mode"); context.setDrawMode(drawMode); @@ -129,18 +132,15 @@ public: context.program = program; - Uniforms::bind(uniformsState, std::move(uniformValues)); + Uniforms::bind(uniformsState, uniformValues); - for (const auto& segment : segments) { - segment.bind(context, - indexBuffer.buffer, - attributeLocations, - attributeBindings); + vertexArray.bind(context, + indexBuffer.buffer, + Attributes::toBindingArray(attributeLocations, attributeBindings)); - context.draw(drawMode.primitiveType, - segment.indexOffset, - segment.indexLength); - } + context.draw(drawMode.primitiveType, + indexOffset, + indexLength); } private: diff --git a/src/mbgl/gl/segment.hpp b/src/mbgl/gl/segment.hpp deleted file mode 100644 index fe0658bf8..000000000 --- a/src/mbgl/gl/segment.hpp +++ /dev/null @@ -1,73 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include -#include - -namespace mbgl { -namespace gl { - -template -class Segment { -public: - Segment(std::size_t vertexOffset_, - std::size_t indexOffset_, - std::size_t vertexLength_ = 0, - std::size_t indexLength_ = 0) - : vertexOffset(vertexOffset_), - indexOffset(indexOffset_), - vertexLength(vertexLength_), - indexLength(indexLength_) {} - - const std::size_t vertexOffset; - const std::size_t indexOffset; - - std::size_t vertexLength; - std::size_t indexLength; - - void bind(Context& context, - BufferID indexBuffer_, - const typename Attributes::Locations& attributeLocations, - const typename Attributes::Bindings& attributeBindings_) const { - if (context.supportsVertexArrays()) { - if (!vao) { - vao = context.createVertexArray(); - context.vertexBuffer.setDirty(); - } - context.vertexArrayObject = *vao; - if (indexBuffer != indexBuffer_) { - indexBuffer = indexBuffer_; - context.elementBuffer.setDirty(); - context.elementBuffer = indexBuffer_; - } - } else { - // No VAO support. Force attributes to be rebound. - context.elementBuffer = indexBuffer_; - attributeBindings = {}; - } - - Attributes::bind(context, - attributeLocations, - attributeBindings, - attributeBindings_, - vertexOffset); - } - -private: - mutable optional vao; - mutable optional indexBuffer; - mutable typename Attributes::Bindings attributeBindings; -}; - -template -class SegmentVector : public std::vector> { -public: - SegmentVector() = default; -}; - -} // namespace gl -} // namespace mbgl diff --git a/src/mbgl/gl/types.hpp b/src/mbgl/gl/types.hpp index 059541967..c80399016 100644 --- a/src/mbgl/gl/types.hpp +++ b/src/mbgl/gl/types.hpp @@ -15,7 +15,7 @@ using VertexArrayID = uint32_t; using FramebufferID = uint32_t; using RenderbufferID = uint32_t; -using AttributeLocation = int32_t; +using AttributeLocation = uint32_t; using UniformLocation = int32_t; using TextureUnit = uint8_t; diff --git a/src/mbgl/gl/uniform.hpp b/src/mbgl/gl/uniform.hpp index bb3453b2d..3cac09c52 100644 --- a/src/mbgl/gl/uniform.hpp +++ b/src/mbgl/gl/uniform.hpp @@ -82,7 +82,7 @@ public: return NamedLocations{ { Us::name(), state.template get().location }... }; } - static void bind(State& state, Values&& values) { + static void bind(State& state, const Values& values) { util::ignore({ (state.template get() = values.template get(), 0)... }); } }; diff --git a/src/mbgl/gl/value.cpp b/src/mbgl/gl/value.cpp index c081c941f..10818e849 100644 --- a/src/mbgl/gl/value.cpp +++ b/src/mbgl/gl/value.cpp @@ -353,6 +353,24 @@ BindVertexArray::Type BindVertexArray::Get(const Context& context) { return binding; } +const optional VertexAttribute::Default {}; + +void VertexAttribute::Set(const optional& binding, Context& context, AttributeLocation location) { + if (binding) { + context.vertexBuffer = binding->vertexBuffer; + MBGL_CHECK_ERROR(glEnableVertexAttribArray(location)); + MBGL_CHECK_ERROR(glVertexAttribPointer( + location, + static_cast(binding->attributeSize), + static_cast(binding->attributeType), + static_cast(false), + static_cast(binding->vertexSize), + reinterpret_cast(binding->attributeOffset + (binding->vertexSize * binding->vertexOffset)))); + } else { + MBGL_CHECK_ERROR(glDisableVertexAttribArray(location)); + } +} + #if not MBGL_USE_GLES2 const constexpr PointSize::Type PointSize::Default; diff --git a/src/mbgl/gl/value.hpp b/src/mbgl/gl/value.hpp index aa5cca6fe..62fe88a2f 100644 --- a/src/mbgl/gl/value.hpp +++ b/src/mbgl/gl/value.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -232,6 +233,12 @@ struct BindVertexArray { static Type Get(const Context&); }; +struct VertexAttribute { + using Type = optional; + static const Type Default; + static void Set(const Type&, Context&, AttributeLocation); +}; + #if not MBGL_USE_GLES2 struct PointSize { diff --git a/src/mbgl/gl/vertex_array.cpp b/src/mbgl/gl/vertex_array.cpp new file mode 100644 index 000000000..68a500ac4 --- /dev/null +++ b/src/mbgl/gl/vertex_array.cpp @@ -0,0 +1,18 @@ +#include +#include +#include + +namespace mbgl { +namespace gl { + +void VertexArray::bind(Context& context, BufferID indexBuffer, const AttributeBindingArray& bindings) { + context.bindVertexArray = state->vertexArray; + state->indexBuffer = indexBuffer; + + for (AttributeLocation location = 0; location < MAX_ATTRIBUTES; ++location) { + state->bindings[location] = bindings[location]; + } +} + +} // namespace gl +} // namespace mbgl diff --git a/src/mbgl/gl/vertex_array.hpp b/src/mbgl/gl/vertex_array.hpp new file mode 100644 index 000000000..9ccf48d9b --- /dev/null +++ b/src/mbgl/gl/vertex_array.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include + +namespace mbgl { +namespace gl { + +class Context; + +class VertexArrayState { +public: + VertexArrayState(UniqueVertexArray vertexArray_, Context& context) + : vertexArray(std::move(vertexArray_)), + bindings(makeBindings(context, std::make_index_sequence())) { + } + + void setDirty() { + indexBuffer.setDirty(); + for (auto& binding : bindings) { + binding.setDirty(); + } + } + + UniqueVertexArray vertexArray; + State indexBuffer; + + using AttributeState = State; + std::array bindings; + +private: + template + std::array makeBindings(Context& context, std::index_sequence) { + return {{ AttributeState { context, I }... }}; + } +}; + +using UniqueVertexArrayState = std::unique_ptr>; + +class VertexArray { +public: + VertexArray(UniqueVertexArrayState state_) + : state(std::move(state_)) { + } + + void bind(Context&, BufferID indexBuffer, const AttributeBindingArray&); + +private: + UniqueVertexArrayState state; +}; + +} // namespace gl +} // namespace mbgl diff --git a/src/mbgl/programs/binary_program.cpp b/src/mbgl/programs/binary_program.cpp index 3b37cfa44..1cad0a269 100644 --- a/src/mbgl/programs/binary_program.cpp +++ b/src/mbgl/programs/binary_program.cpp @@ -96,7 +96,7 @@ std::string BinaryProgram::serialize() const { return data; } -gl::AttributeLocation BinaryProgram::attributeLocation(const std::string& name) const { +optional BinaryProgram::attributeLocation(const std::string& name) const { for (const auto& pair : attributes) { if (pair.first == name) { return pair.second; @@ -111,7 +111,7 @@ gl::UniformLocation BinaryProgram::uniformLocation(const std::string& name) cons return pair.second; } } - return {}; + return -1; } } // namespace mbgl diff --git a/src/mbgl/programs/binary_program.hpp b/src/mbgl/programs/binary_program.hpp index 8ff3863dc..2806f4fb3 100644 --- a/src/mbgl/programs/binary_program.hpp +++ b/src/mbgl/programs/binary_program.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -29,7 +30,8 @@ public: const std::string& identifier() const { return binaryIdentifier; } - gl::AttributeLocation attributeLocation(const std::string& name) const; + + optional attributeLocation(const std::string& name) const; gl::UniformLocation uniformLocation(const std::string& name) const; private: diff --git a/src/mbgl/programs/program.hpp b/src/mbgl/programs/program.hpp index 65f17e5bd..3ff6e59c6 100644 --- a/src/mbgl/programs/program.hpp +++ b/src/mbgl/programs/program.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -51,26 +52,37 @@ public: gl::DepthMode depthMode, gl::StencilMode stencilMode, gl::ColorMode colorMode, - UniformValues&& uniformValues, + const UniformValues& uniformValues, const gl::VertexBuffer& layoutVertexBuffer, const gl::IndexBuffer& indexBuffer, - const gl::SegmentVector& segments, + const SegmentVector& segments, const PaintPropertyBinders& paintPropertyBinders, const typename PaintProperties::Evaluated& currentProperties, float currentZoom) { - program.draw( - context, - std::move(drawMode), - std::move(depthMode), - std::move(stencilMode), - std::move(colorMode), - uniformValues - .concat(paintPropertyBinders.uniformValues(currentZoom, currentProperties)), - LayoutAttributes::bindings(layoutVertexBuffer) - .concat(paintPropertyBinders.attributeBindings(currentProperties)), - indexBuffer, - segments - ); + typename AllUniforms::Values allUniformValues = uniformValues + .concat(paintPropertyBinders.uniformValues(currentZoom, currentProperties)); + + typename Attributes::Bindings allAttributeBindings = LayoutAttributes::bindings(layoutVertexBuffer) + .concat(paintPropertyBinders.attributeBindings(currentProperties)); + + for (auto& segment : segments) { + if (!segment.vertexArray) { + segment.vertexArray = context.createVertexArray(); + } + + program.draw( + context, + std::move(drawMode), + std::move(depthMode), + std::move(stencilMode), + std::move(colorMode), + allUniformValues, + *segment.vertexArray, + Attributes::offsetBindings(allAttributeBindings, segment.vertexOffset), + indexBuffer, + segment.indexOffset, + segment.indexLength); + } } }; diff --git a/src/mbgl/gl/segment.cpp b/src/mbgl/programs/segment.cpp similarity index 66% rename from src/mbgl/gl/segment.cpp rename to src/mbgl/programs/segment.cpp index aabdc83cd..bb09843e2 100644 --- a/src/mbgl/gl/segment.cpp +++ b/src/mbgl/programs/segment.cpp @@ -1,4 +1,4 @@ -#include +#include namespace mbgl { namespace gl { diff --git a/src/mbgl/programs/segment.hpp b/src/mbgl/programs/segment.hpp new file mode 100644 index 000000000..d8cc9679d --- /dev/null +++ b/src/mbgl/programs/segment.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include + +namespace mbgl { + +template +class Segment { +public: + Segment(std::size_t vertexOffset_, + std::size_t indexOffset_, + std::size_t vertexLength_ = 0, + std::size_t indexLength_ = 0) + : vertexOffset(vertexOffset_), + indexOffset(indexOffset_), + vertexLength(vertexLength_), + indexLength(indexLength_) {} + + const std::size_t vertexOffset; + const std::size_t indexOffset; + + std::size_t vertexLength; + std::size_t indexLength; + + mutable optional vertexArray; +}; + +template +class SegmentVector : public std::vector> { +public: + SegmentVector() = default; +}; + +} // namespace mbgl diff --git a/src/mbgl/programs/symbol_program.hpp b/src/mbgl/programs/symbol_program.hpp index 47d92f912..2fddebde0 100644 --- a/src/mbgl/programs/symbol_program.hpp +++ b/src/mbgl/programs/symbol_program.hpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -156,7 +157,7 @@ public: } SymbolSizeAttributes::Bindings attributeBindings() const override { - return SymbolSizeAttributes::Bindings { gl::DisabledAttribute() }; + return SymbolSizeAttributes::Bindings { {} }; } void upload(gl::Context&) override {} @@ -352,29 +353,40 @@ public: gl::DepthMode depthMode, gl::StencilMode stencilMode, gl::ColorMode colorMode, - UniformValues&& uniformValues, + const UniformValues& uniformValues, const gl::VertexBuffer& layoutVertexBuffer, const SymbolSizeBinder& symbolSizeBinder, const gl::IndexBuffer& indexBuffer, - const gl::SegmentVector& segments, + const SegmentVector& segments, const PaintPropertyBinders& paintPropertyBinders, const typename PaintProperties::Evaluated& currentProperties, float currentZoom) { - program.draw( - context, - std::move(drawMode), - std::move(depthMode), - std::move(stencilMode), - std::move(colorMode), - uniformValues - .concat(symbolSizeBinder.uniformValues(currentZoom)) - .concat(paintPropertyBinders.uniformValues(currentZoom, currentProperties)), - LayoutAttributes::bindings(layoutVertexBuffer) - .concat(symbolSizeBinder.attributeBindings()) - .concat(paintPropertyBinders.attributeBindings(currentProperties)), - indexBuffer, - segments - ); + typename AllUniforms::Values allUniformValues = uniformValues + .concat(symbolSizeBinder.uniformValues(currentZoom)) + .concat(paintPropertyBinders.uniformValues(currentZoom, currentProperties)); + + typename Attributes::Bindings allAttributeBindings = LayoutAttributes::bindings(layoutVertexBuffer) + .concat(symbolSizeBinder.attributeBindings()) + .concat(paintPropertyBinders.attributeBindings(currentProperties)); + + for (auto& segment : segments) { + if (!segment.vertexArray) { + segment.vertexArray = context.createVertexArray(); + } + + program.draw( + context, + std::move(drawMode), + std::move(depthMode), + std::move(stencilMode), + std::move(colorMode), + allUniformValues, + *segment.vertexArray, + Attributes::offsetBindings(allAttributeBindings, segment.vertexOffset), + indexBuffer, + segment.indexOffset, + segment.indexLength); + } } }; diff --git a/src/mbgl/renderer/circle_bucket.hpp b/src/mbgl/renderer/circle_bucket.hpp index b048fd767..0f27e2a7e 100644 --- a/src/mbgl/renderer/circle_bucket.hpp +++ b/src/mbgl/renderer/circle_bucket.hpp @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include #include @@ -29,7 +29,7 @@ public: gl::VertexVector vertices; gl::IndexVector triangles; - gl::SegmentVector segments; + SegmentVector segments; optional> vertexBuffer; optional> indexBuffer; diff --git a/src/mbgl/renderer/debug_bucket.hpp b/src/mbgl/renderer/debug_bucket.hpp index 756e58a6d..fc3128e94 100644 --- a/src/mbgl/renderer/debug_bucket.hpp +++ b/src/mbgl/renderer/debug_bucket.hpp @@ -33,7 +33,7 @@ public: const optional expires; const MapDebugOptions debugMode; - gl::SegmentVector segments; + SegmentVector segments; optional> vertexBuffer; optional> indexBuffer; }; diff --git a/src/mbgl/renderer/fill_bucket.hpp b/src/mbgl/renderer/fill_bucket.hpp index 421d8b332..d3cd92d45 100644 --- a/src/mbgl/renderer/fill_bucket.hpp +++ b/src/mbgl/renderer/fill_bucket.hpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include @@ -30,8 +30,8 @@ public: gl::VertexVector vertices; gl::IndexVector lines; gl::IndexVector triangles; - gl::SegmentVector lineSegments; - gl::SegmentVector triangleSegments; + SegmentVector lineSegments; + SegmentVector triangleSegments; optional> vertexBuffer; optional> lineIndexBuffer; diff --git a/src/mbgl/renderer/fill_extrusion_bucket.hpp b/src/mbgl/renderer/fill_extrusion_bucket.hpp index c54805d74..d1e695c5a 100644 --- a/src/mbgl/renderer/fill_extrusion_bucket.hpp +++ b/src/mbgl/renderer/fill_extrusion_bucket.hpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include @@ -27,7 +27,7 @@ public: gl::VertexVector vertices; gl::IndexVector triangles; - gl::SegmentVector triangleSegments; + SegmentVector triangleSegments; optional> vertexBuffer; optional> indexBuffer; diff --git a/src/mbgl/renderer/line_bucket.hpp b/src/mbgl/renderer/line_bucket.hpp index c31954871..95ef2f9a6 100644 --- a/src/mbgl/renderer/line_bucket.hpp +++ b/src/mbgl/renderer/line_bucket.hpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include @@ -34,7 +34,7 @@ public: gl::VertexVector vertices; gl::IndexVector triangles; - gl::SegmentVector segments; + SegmentVector segments; optional> vertexBuffer; optional> indexBuffer; diff --git a/src/mbgl/renderer/paint_property_binder.hpp b/src/mbgl/renderer/paint_property_binder.hpp index 906a2ea07..36d2e9808 100644 --- a/src/mbgl/renderer/paint_property_binder.hpp +++ b/src/mbgl/renderer/paint_property_binder.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -80,7 +81,7 @@ public: virtual void populateVertexVector(const GeometryTileFeature& feature, std::size_t length) = 0; virtual void upload(gl::Context& context) = 0; - virtual AttributeBinding attributeBinding(const PossiblyEvaluatedPropertyValue& currentValue) const = 0; + virtual optional attributeBinding(const PossiblyEvaluatedPropertyValue& currentValue) const = 0; virtual float interpolationFactor(float currentZoom) const = 0; virtual T uniformValue(const PossiblyEvaluatedPropertyValue& currentValue) const = 0; @@ -102,8 +103,8 @@ public: void populateVertexVector(const GeometryTileFeature&, std::size_t) override {} void upload(gl::Context&) override {} - AttributeBinding attributeBinding(const PossiblyEvaluatedPropertyValue&) const override { - return gl::DisabledAttribute(); + optional attributeBinding(const PossiblyEvaluatedPropertyValue&) const override { + return {}; } float interpolationFactor(float) const override { @@ -146,9 +147,9 @@ public: vertexBuffer = context.createVertexBuffer(std::move(vertexVector)); } - AttributeBinding attributeBinding(const PossiblyEvaluatedPropertyValue& currentValue) const override { + optional attributeBinding(const PossiblyEvaluatedPropertyValue& currentValue) const override { if (currentValue.isConstant()) { - return gl::DisabledAttribute(); + return {}; } else { return Attribute::binding(*vertexBuffer, 0, BaseAttribute::Dimensions); } @@ -207,9 +208,9 @@ public: vertexBuffer = context.createVertexBuffer(std::move(vertexVector)); } - AttributeBinding attributeBinding(const PossiblyEvaluatedPropertyValue& currentValue) const override { + optional attributeBinding(const PossiblyEvaluatedPropertyValue& currentValue) const override { if (currentValue.isConstant()) { - return gl::DisabledAttribute(); + return {}; } else { return Attribute::binding(*vertexBuffer, 0); } diff --git a/src/mbgl/renderer/painter.cpp b/src/mbgl/renderer/painter.cpp index da4903864..da3268ed7 100644 --- a/src/mbgl/renderer/painter.cpp +++ b/src/mbgl/renderer/painter.cpp @@ -278,7 +278,7 @@ void Painter::render(const Style& style, const FrameData& frame_, View& view, Sp context.activeTexture = 0; context.texture[0] = 0; - context.vertexArrayObject = 0; + context.bindVertexArray = 0; } } @@ -312,7 +312,7 @@ void Painter::renderPass(PaintParameters& parameters, MBGL_DEBUG_GROUP(context, layer.baseImpl.id + " - custom"); // Reset GL state to a known state so the CustomLayer always has a clean slate. - context.vertexArrayObject = 0; + context.bindVertexArray = 0; context.setDepthMode(depthModeForSublayer(0, gl::DepthMode::ReadOnly)); context.setStencilMode(gl::StencilMode::disabled()); context.setColorMode(colorModeForRenderPass()); diff --git a/src/mbgl/renderer/painter.hpp b/src/mbgl/renderer/painter.hpp index 4658f0c20..1919ad924 100644 --- a/src/mbgl/renderer/painter.hpp +++ b/src/mbgl/renderer/painter.hpp @@ -177,10 +177,10 @@ private: gl::IndexBuffer quadTriangleIndexBuffer; gl::IndexBuffer tileBorderIndexBuffer; - gl::SegmentVector tileTriangleSegments; - gl::SegmentVector tileBorderSegments; - gl::SegmentVector rasterSegments; - gl::SegmentVector extrusionTextureSegments; + SegmentVector tileTriangleSegments; + SegmentVector tileBorderSegments; + SegmentVector rasterSegments; + SegmentVector extrusionTextureSegments; }; } // namespace mbgl diff --git a/src/mbgl/renderer/symbol_bucket.hpp b/src/mbgl/renderer/symbol_bucket.hpp index f7e4bcfa2..a9bc868db 100644 --- a/src/mbgl/renderer/symbol_bucket.hpp +++ b/src/mbgl/renderer/symbol_bucket.hpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include #include @@ -45,7 +45,7 @@ public: struct TextBuffer { gl::VertexVector vertices; gl::IndexVector triangles; - gl::SegmentVector segments; + SegmentVector segments; optional> vertexBuffer; optional> indexBuffer; @@ -56,7 +56,7 @@ public: struct IconBuffer { gl::VertexVector vertices; gl::IndexVector triangles; - gl::SegmentVector segments; + SegmentVector segments; optional> vertexBuffer; optional> indexBuffer; @@ -65,7 +65,7 @@ public: struct CollisionBoxBuffer { gl::VertexVector vertices; gl::IndexVector lines; - gl::SegmentVector segments; + SegmentVector segments; optional> vertexBuffer; optional> indexBuffer; diff --git a/test/programs/binary_program.test.cpp b/test/programs/binary_program.test.cpp index ce544e765..a5cf7b6e3 100644 --- a/test/programs/binary_program.test.cpp +++ b/test/programs/binary_program.test.cpp @@ -14,12 +14,12 @@ TEST(BinaryProgram, ObtainValues) { EXPECT_EQ(42u, binaryProgram.format()); EXPECT_EQ("binary code", binaryProgram.code()); EXPECT_EQ("identifier", binaryProgram.identifier()); - EXPECT_EQ(1, binaryProgram.attributeLocation("a_pos")); - EXPECT_EQ(0, binaryProgram.attributeLocation("u_world")); - EXPECT_EQ(4, binaryProgram.attributeLocation("a_data")); + EXPECT_EQ(1u, binaryProgram.attributeLocation("a_pos")); + EXPECT_FALSE(binaryProgram.attributeLocation("u_world")); + EXPECT_EQ(4u, binaryProgram.attributeLocation("a_data")); EXPECT_EQ(1, binaryProgram.uniformLocation("u_world")); EXPECT_EQ(3, binaryProgram.uniformLocation("u_ratio")); - EXPECT_EQ(0, binaryProgram.uniformLocation("a_data")); + EXPECT_EQ(-1, binaryProgram.uniformLocation("a_data")); auto serialized = binaryProgram.serialize(); @@ -28,12 +28,12 @@ TEST(BinaryProgram, ObtainValues) { EXPECT_EQ(42u, binaryProgram2.format()); EXPECT_EQ("binary code", binaryProgram2.code()); EXPECT_EQ("identifier", binaryProgram2.identifier()); - EXPECT_EQ(1, binaryProgram2.attributeLocation("a_pos")); - EXPECT_EQ(0, binaryProgram2.attributeLocation("u_world")); - EXPECT_EQ(4, binaryProgram2.attributeLocation("a_data")); + EXPECT_EQ(1u, binaryProgram2.attributeLocation("a_pos")); + EXPECT_FALSE(binaryProgram2.attributeLocation("u_world")); + EXPECT_EQ(4u, binaryProgram2.attributeLocation("a_data")); EXPECT_EQ(1, binaryProgram2.uniformLocation("u_world")); EXPECT_EQ(3, binaryProgram2.uniformLocation("u_ratio")); - EXPECT_EQ(0, binaryProgram2.uniformLocation("a_data")); + EXPECT_EQ(-1, binaryProgram2.uniformLocation("a_data")); EXPECT_THROW(BinaryProgram(""), std::runtime_error); } From de91e68090a8b246eb161cc8dfdc5b0f40e5bdc0 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 6 Jul 2017 08:06:12 -0700 Subject: [PATCH 14/71] [core] Per-segment-per-layer vertex arrays Reduces rebinding, matches gl-js, and works around the buggy VAO implementation on PowerVR SGX544 GPUs. --- src/mbgl/programs/program.hpp | 11 +++++++---- src/mbgl/programs/segment.hpp | 9 ++++++++- src/mbgl/programs/symbol_program.hpp | 11 +++++++---- src/mbgl/renderer/painter.cpp | 15 +++++++++++---- src/mbgl/renderer/painter_background.cpp | 6 ++++-- src/mbgl/renderer/painter_circle.cpp | 3 ++- src/mbgl/renderer/painter_clipping.cpp | 3 ++- src/mbgl/renderer/painter_debug.cpp | 3 ++- src/mbgl/renderer/painter_fill.cpp | 6 ++++-- src/mbgl/renderer/painter_fill_extrusion.cpp | 6 ++++-- src/mbgl/renderer/painter_line.cpp | 3 ++- src/mbgl/renderer/painter_raster.cpp | 3 ++- src/mbgl/renderer/painter_symbol.cpp | 6 ++++-- 13 files changed, 59 insertions(+), 26 deletions(-) diff --git a/src/mbgl/programs/program.hpp b/src/mbgl/programs/program.hpp index 3ff6e59c6..8cd6c0732 100644 --- a/src/mbgl/programs/program.hpp +++ b/src/mbgl/programs/program.hpp @@ -58,7 +58,8 @@ public: const SegmentVector& segments, const PaintPropertyBinders& paintPropertyBinders, const typename PaintProperties::Evaluated& currentProperties, - float currentZoom) { + float currentZoom, + const std::string& layerID) { typename AllUniforms::Values allUniformValues = uniformValues .concat(paintPropertyBinders.uniformValues(currentZoom, currentProperties)); @@ -66,8 +67,10 @@ public: .concat(paintPropertyBinders.attributeBindings(currentProperties)); for (auto& segment : segments) { - if (!segment.vertexArray) { - segment.vertexArray = context.createVertexArray(); + optional& vertexArray = segment.vertexArrays[layerID]; + + if (!vertexArray) { + vertexArray = context.createVertexArray(); } program.draw( @@ -77,7 +80,7 @@ public: std::move(stencilMode), std::move(colorMode), allUniformValues, - *segment.vertexArray, + *vertexArray, Attributes::offsetBindings(allAttributeBindings, segment.vertexOffset), indexBuffer, segment.indexOffset, diff --git a/src/mbgl/programs/segment.hpp b/src/mbgl/programs/segment.hpp index d8cc9679d..74bf4a75c 100644 --- a/src/mbgl/programs/segment.hpp +++ b/src/mbgl/programs/segment.hpp @@ -28,7 +28,14 @@ public: std::size_t vertexLength; std::size_t indexLength; - mutable optional vertexArray; + // One VertexArray per layer ID. This minimizes rebinding in cases where + // several layers share buckets but have different sets of active attributes. + // This can happen: + // * when two layers have the same layout properties, but differing + // data-driven paint properties + // * when two fill layers have the same layout properties, but one + // uses fill-color and the other uses fill-pattern + mutable std::map> vertexArrays; }; template diff --git a/src/mbgl/programs/symbol_program.hpp b/src/mbgl/programs/symbol_program.hpp index 2fddebde0..3bf82009e 100644 --- a/src/mbgl/programs/symbol_program.hpp +++ b/src/mbgl/programs/symbol_program.hpp @@ -360,7 +360,8 @@ public: const SegmentVector& segments, const PaintPropertyBinders& paintPropertyBinders, const typename PaintProperties::Evaluated& currentProperties, - float currentZoom) { + float currentZoom, + const std::string& layerID) { typename AllUniforms::Values allUniformValues = uniformValues .concat(symbolSizeBinder.uniformValues(currentZoom)) .concat(paintPropertyBinders.uniformValues(currentZoom, currentProperties)); @@ -370,8 +371,10 @@ public: .concat(paintPropertyBinders.attributeBindings(currentProperties)); for (auto& segment : segments) { - if (!segment.vertexArray) { - segment.vertexArray = context.createVertexArray(); + optional& vertexArray = segment.vertexArrays[layerID]; + + if (!vertexArray) { + vertexArray = context.createVertexArray(); } program.draw( @@ -381,7 +384,7 @@ public: std::move(stencilMode), std::move(colorMode), allUniformValues, - *segment.vertexArray, + *vertexArray, Attributes::offsetBindings(allAttributeBindings, segment.vertexOffset), indexBuffer, segment.indexOffset, diff --git a/src/mbgl/renderer/painter.cpp b/src/mbgl/renderer/painter.cpp index da3268ed7..fbaf40d5c 100644 --- a/src/mbgl/renderer/painter.cpp +++ b/src/mbgl/renderer/painter.cpp @@ -353,16 +353,23 @@ void Painter::renderPass(PaintParameters& parameters, const PaintProperties<>::Evaluated properties{}; parameters.programs.extrusionTexture.draw( - context, gl::Triangles(), gl::DepthMode::disabled(), gl::StencilMode::disabled(), + context, + gl::Triangles(), + gl::DepthMode::disabled(), + gl::StencilMode::disabled(), colorModeForRenderPass(), ExtrusionTextureProgram::UniformValues{ uniforms::u_matrix::Value{ viewportMat }, uniforms::u_world::Value{ size }, uniforms::u_image::Value{ 0 }, uniforms::u_opacity::Value{ layer.as() ->evaluated.get() } }, - extrusionTextureVertexBuffer, quadTriangleIndexBuffer, extrusionTextureSegments, - ExtrusionTextureProgram::PaintPropertyBinders{ properties, 0 }, properties, - state.getZoom()); + extrusionTextureVertexBuffer, + quadTriangleIndexBuffer, + extrusionTextureSegments, + ExtrusionTextureProgram::PaintPropertyBinders{ properties, 0 }, + properties, + state.getZoom(), + layer.getID()); } else { for (auto& tileRef : item.tiles) { auto& tile = tileRef.get(); diff --git a/src/mbgl/renderer/painter_background.cpp b/src/mbgl/renderer/painter_background.cpp index d01696ee3..9bd943108 100644 --- a/src/mbgl/renderer/painter_background.cpp +++ b/src/mbgl/renderer/painter_background.cpp @@ -53,7 +53,8 @@ void Painter::renderBackground(PaintParameters& parameters, const RenderBackgrou tileTriangleSegments, paintAttibuteData, properties, - state.getZoom() + state.getZoom(), + layer.getID() ); } } else { @@ -73,7 +74,8 @@ void Painter::renderBackground(PaintParameters& parameters, const RenderBackgrou tileTriangleSegments, paintAttibuteData, properties, - state.getZoom() + state.getZoom(), + layer.getID() ); } } diff --git a/src/mbgl/renderer/painter_circle.cpp b/src/mbgl/renderer/painter_circle.cpp index 13acb5f7f..ecd7598de 100644 --- a/src/mbgl/renderer/painter_circle.cpp +++ b/src/mbgl/renderer/painter_circle.cpp @@ -50,7 +50,8 @@ void Painter::renderCircle(PaintParameters& parameters, bucket.segments, bucket.paintPropertyBinders.at(layer.getID()), properties, - state.getZoom() + state.getZoom(), + layer.getID() ); } diff --git a/src/mbgl/renderer/painter_clipping.cpp b/src/mbgl/renderer/painter_clipping.cpp index b3a2d77b1..162f3b1d9 100644 --- a/src/mbgl/renderer/painter_clipping.cpp +++ b/src/mbgl/renderer/painter_clipping.cpp @@ -30,7 +30,8 @@ void Painter::renderClippingMask(const UnwrappedTileID& tileID, const ClipID& cl tileTriangleSegments, paintAttibuteData, properties, - state.getZoom() + state.getZoom(), + "clipping" ); } diff --git a/src/mbgl/renderer/painter_debug.cpp b/src/mbgl/renderer/painter_debug.cpp index 37fa3bcb1..9a24ab542 100644 --- a/src/mbgl/renderer/painter_debug.cpp +++ b/src/mbgl/renderer/painter_debug.cpp @@ -39,7 +39,8 @@ void Painter::renderTileDebug(const RenderTile& renderTile) { segments, paintAttibuteData, properties, - state.getZoom() + state.getZoom(), + "debug" ); }; diff --git a/src/mbgl/renderer/painter_fill.cpp b/src/mbgl/renderer/painter_fill.cpp index e1b59d883..cad76ace4 100644 --- a/src/mbgl/renderer/painter_fill.cpp +++ b/src/mbgl/renderer/painter_fill.cpp @@ -60,7 +60,8 @@ void Painter::renderFill(PaintParameters& parameters, segments, bucket.paintPropertyBinders.at(layer.getID()), properties, - state.getZoom() + state.getZoom(), + layer.getID() ); }; @@ -104,7 +105,8 @@ void Painter::renderFill(PaintParameters& parameters, segments, bucket.paintPropertyBinders.at(layer.getID()), properties, - state.getZoom() + state.getZoom(), + layer.getID() ); }; diff --git a/src/mbgl/renderer/painter_fill_extrusion.cpp b/src/mbgl/renderer/painter_fill_extrusion.cpp index 95617525c..5581cfe98 100644 --- a/src/mbgl/renderer/painter_fill_extrusion.cpp +++ b/src/mbgl/renderer/painter_fill_extrusion.cpp @@ -59,7 +59,8 @@ void Painter::renderFillExtrusion(PaintParameters& parameters, bucket.triangleSegments, bucket.paintPropertyBinders.at(layer.getID()), properties, - state.getZoom()); + state.getZoom(), + layer.getID()); } else { parameters.programs.fillExtrusion.get(properties).draw( @@ -80,7 +81,8 @@ void Painter::renderFillExtrusion(PaintParameters& parameters, bucket.triangleSegments, bucket.paintPropertyBinders.at(layer.getID()), properties, - state.getZoom()); + state.getZoom(), + layer.getID()); }; } diff --git a/src/mbgl/renderer/painter_line.cpp b/src/mbgl/renderer/painter_line.cpp index 9152ac851..209b1447d 100644 --- a/src/mbgl/renderer/painter_line.cpp +++ b/src/mbgl/renderer/painter_line.cpp @@ -36,7 +36,8 @@ void Painter::renderLine(PaintParameters& parameters, bucket.segments, bucket.paintPropertyBinders.at(layer.getID()), properties, - state.getZoom() + state.getZoom(), + layer.getID() ); }; diff --git a/src/mbgl/renderer/painter_raster.cpp b/src/mbgl/renderer/painter_raster.cpp index fbe025b5b..f0e5399f4 100644 --- a/src/mbgl/renderer/painter_raster.cpp +++ b/src/mbgl/renderer/painter_raster.cpp @@ -81,7 +81,8 @@ void Painter::renderRaster(PaintParameters& parameters, rasterSegments, paintAttributeData, properties, - state.getZoom() + state.getZoom(), + layer.getID() ); } diff --git a/src/mbgl/renderer/painter_symbol.cpp b/src/mbgl/renderer/painter_symbol.cpp index c9ed8cc57..6462eebdc 100644 --- a/src/mbgl/renderer/painter_symbol.cpp +++ b/src/mbgl/renderer/painter_symbol.cpp @@ -58,7 +58,8 @@ void Painter::renderSymbol(PaintParameters& parameters, buffers.segments, binders, paintProperties, - state.getZoom() + state.getZoom(), + layer.getID() ); }; @@ -156,7 +157,8 @@ void Painter::renderSymbol(PaintParameters& parameters, bucket.collisionBox.segments, paintAttributeData, properties, - state.getZoom() + state.getZoom(), + layer.getID() ); } } From cf83f117f0e3e338454ba1bc0e5a13b3b9565228 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 10 Jul 2017 12:19:23 -0700 Subject: [PATCH 15/71] [core] Reduce memory requirements of VertexArrays --- src/mbgl/gl/attribute.hpp | 22 ++++++++++++++-------- src/mbgl/gl/context.cpp | 5 +++-- src/mbgl/gl/types.hpp | 2 +- src/mbgl/gl/vertex_array.hpp | 17 ++++++++++++++++- 4 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/mbgl/gl/attribute.hpp b/src/mbgl/gl/attribute.hpp index ed4168c21..a85b49c6a 100644 --- a/src/mbgl/gl/attribute.hpp +++ b/src/mbgl/gl/attribute.hpp @@ -12,6 +12,7 @@ #include #include #include +#include namespace mbgl { namespace gl { @@ -30,12 +31,12 @@ template <> struct DataTypeOf : std::integral_constant::value, "vertex type must use standard layout"); + assert(attributeSize >= 1); + assert(attributeSize <= 4); + assert(Vertex::attributeOffsets[attributeIndex] <= std::numeric_limits::max()); + static_assert(sizeof(Vertex) <= std::numeric_limits::max(), "vertex too large"); return AttributeBinding { DataTypeOf::value, - attributeSize, - Vertex::attributeOffsets[attributeIndex], + static_cast(attributeSize), + static_cast(Vertex::attributeOffsets[attributeIndex]), buffer.buffer, - sizeof(Vertex), + static_cast(sizeof(Vertex)), 0, }; } static optional offsetBinding(const optional& binding, std::size_t vertexOffset) { + assert(vertexOffset <= std::numeric_limits::max()); if (binding) { AttributeBinding result = *binding; - result.vertexOffset = vertexOffset; + result.vertexOffset = static_cast(vertexOffset); return result; } else { return binding; diff --git a/src/mbgl/gl/context.cpp b/src/mbgl/gl/context.cpp index 2d7354a64..19ea9a514 100644 --- a/src/mbgl/gl/context.cpp +++ b/src/mbgl/gl/context.cpp @@ -233,11 +233,12 @@ VertexArray Context::createVertexArray() { if (supportsVertexArrays()) { VertexArrayID id = 0; MBGL_CHECK_ERROR(vertexArray->genVertexArrays(1, &id)); - return { std::make_unique(UniqueVertexArray(std::move(id), { this }), *this) }; + UniqueVertexArray vao(std::move(id), { this }); + return { UniqueVertexArrayState(new VertexArrayState(std::move(vao), *this), VertexArrayStateDeleter { true })}; } else { // On GL implementations which do not support vertex arrays, attribute bindings are global state. // So return a VertexArray which shares our global state tracking and whose deleter is a no-op. - return { UniqueVertexArrayState(&globalVertexArrayState, [] (VertexArrayState*) {}) }; + return { UniqueVertexArrayState(&globalVertexArrayState, VertexArrayStateDeleter { false }) }; } } diff --git a/src/mbgl/gl/types.hpp b/src/mbgl/gl/types.hpp index c80399016..e7d2ca449 100644 --- a/src/mbgl/gl/types.hpp +++ b/src/mbgl/gl/types.hpp @@ -24,7 +24,7 @@ enum class ShaderType : uint32_t { Fragment = 0x8B30 }; -enum class DataType : uint32_t { +enum class DataType : uint16_t { Byte = 0x1400, UnsignedByte = 0x1401, Short = 0x1402, diff --git a/src/mbgl/gl/vertex_array.hpp b/src/mbgl/gl/vertex_array.hpp index 9ccf48d9b..46c67017b 100644 --- a/src/mbgl/gl/vertex_array.hpp +++ b/src/mbgl/gl/vertex_array.hpp @@ -40,7 +40,22 @@ private: } }; -using UniqueVertexArrayState = std::unique_ptr>; +class VertexArrayStateDeleter { +public: + VertexArrayStateDeleter(bool destroy_) + : destroy(destroy_) {} + + void operator()(VertexArrayState* ptr) const { + if (destroy) { + delete ptr; + } + } + +private: + bool destroy; +}; + +using UniqueVertexArrayState = std::unique_ptr; class VertexArray { public: From 902d9442da0f2f2c74530a4b770c9ff05f5d4d1e Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 10 Jul 2017 12:31:46 -0700 Subject: [PATCH 16/71] [core] No need for optional in map of VertexArrays --- src/mbgl/programs/program.hpp | 8 ++++---- src/mbgl/programs/segment.hpp | 3 +-- src/mbgl/programs/symbol_program.hpp | 8 ++++---- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/mbgl/programs/program.hpp b/src/mbgl/programs/program.hpp index 8cd6c0732..ca8434cf0 100644 --- a/src/mbgl/programs/program.hpp +++ b/src/mbgl/programs/program.hpp @@ -67,10 +67,10 @@ public: .concat(paintPropertyBinders.attributeBindings(currentProperties)); for (auto& segment : segments) { - optional& vertexArray = segment.vertexArrays[layerID]; + auto vertexArrayIt = segment.vertexArrays.find(layerID); - if (!vertexArray) { - vertexArray = context.createVertexArray(); + if (vertexArrayIt == segment.vertexArrays.end()) { + vertexArrayIt = segment.vertexArrays.emplace(layerID, context.createVertexArray()).first; } program.draw( @@ -80,7 +80,7 @@ public: std::move(stencilMode), std::move(colorMode), allUniformValues, - *vertexArray, + vertexArrayIt->second, Attributes::offsetBindings(allAttributeBindings, segment.vertexOffset), indexBuffer, segment.indexOffset, diff --git a/src/mbgl/programs/segment.hpp b/src/mbgl/programs/segment.hpp index 74bf4a75c..937df4dec 100644 --- a/src/mbgl/programs/segment.hpp +++ b/src/mbgl/programs/segment.hpp @@ -2,7 +2,6 @@ #include #include -#include #include #include @@ -35,7 +34,7 @@ public: // data-driven paint properties // * when two fill layers have the same layout properties, but one // uses fill-color and the other uses fill-pattern - mutable std::map> vertexArrays; + mutable std::map vertexArrays; }; template diff --git a/src/mbgl/programs/symbol_program.hpp b/src/mbgl/programs/symbol_program.hpp index 3bf82009e..5fb0c4f15 100644 --- a/src/mbgl/programs/symbol_program.hpp +++ b/src/mbgl/programs/symbol_program.hpp @@ -371,10 +371,10 @@ public: .concat(paintPropertyBinders.attributeBindings(currentProperties)); for (auto& segment : segments) { - optional& vertexArray = segment.vertexArrays[layerID]; + auto vertexArrayIt = segment.vertexArrays.find(layerID); - if (!vertexArray) { - vertexArray = context.createVertexArray(); + if (vertexArrayIt == segment.vertexArrays.end()) { + vertexArrayIt = segment.vertexArrays.emplace(layerID, context.createVertexArray()).first; } program.draw( @@ -384,7 +384,7 @@ public: std::move(stencilMode), std::move(colorMode), allUniformValues, - *vertexArray, + vertexArrayIt->second, Attributes::offsetBindings(allAttributeBindings, segment.vertexOffset), indexBuffer, segment.indexOffset, From 550da70cf78e700deea4d298e90afe134fda27e2 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 11 Jul 2017 14:11:59 -0700 Subject: [PATCH 17/71] [core] Pass correct sources to programIdentifier --- src/mbgl/gl/program.hpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/mbgl/gl/program.hpp b/src/mbgl/gl/program.hpp index 9d8b0a5b0..3b54ec194 100644 --- a/src/mbgl/gl/program.hpp +++ b/src/mbgl/gl/program.hpp @@ -52,15 +52,13 @@ public: const char* name, const char* vertexSource_, const char* fragmentSource_) { + const std::string vertexSource = shaders::vertexSource(programParameters, vertexSource_); + const std::string fragmentSource = shaders::fragmentSource(programParameters, fragmentSource_); + #if MBGL_HAS_BINARY_PROGRAMS optional cachePath = programParameters.cachePath(name); if (cachePath && context.supportsProgramBinaries()) { - const std::string vertexSource = - shaders::vertexSource(programParameters, vertexSource_); - const std::string fragmentSource = - shaders::fragmentSource(programParameters, fragmentSource_); - const std::string identifier = - shaders::programIdentifier(vertexSource, fragmentSource_); + const std::string identifier = shaders::programIdentifier(vertexSource, fragmentSource); try { if (auto cachedBinaryProgram = util::readFile(*cachePath)) { @@ -94,11 +92,9 @@ public: return std::move(result); } #endif + (void)name; - return Program { - context, shaders::vertexSource(programParameters, vertexSource_), - shaders::fragmentSource(programParameters, fragmentSource_) - }; + return Program { context, vertexSource, fragmentSource }; } template From 2d147470a5f982afb0c1a63ff632497f85ea85bf Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 11 Jul 2017 14:11:02 -0700 Subject: [PATCH 18/71] [android] Force previously cached program binaries to be regenerated --- src/mbgl/shaders/shaders.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mbgl/shaders/shaders.cpp b/src/mbgl/shaders/shaders.cpp index 31ff405f0..2e5a31802 100644 --- a/src/mbgl/shaders/shaders.cpp +++ b/src/mbgl/shaders/shaders.cpp @@ -22,6 +22,7 @@ std::string programIdentifier(const std::string& vertexSource, const std::string ss << std::setfill('0') << std::setw(sizeof(size_t) * 2) << std::hex; ss << std::hash()(vertexSource); ss << std::hash()(fragmentSource); + ss << "v2"; return ss.str(); } From 733661948c004dad70e634aa9f2b41402e460a6e Mon Sep 17 00:00:00 2001 From: Ivo van Dongen Date: Thu, 13 Jul 2017 15:35:02 +0300 Subject: [PATCH 19/71] [core] BackendScope prevent double (de-)activation - Guards against duplicate activations by checking wether the backend of the prior scope is the same as the current ones - Makes sure that only the most outer backend scope deactivates by tracking activation state --- cmake/test-files.cmake | 1 + include/mbgl/map/backend.hpp | 5 ++ include/mbgl/map/backend_scope.hpp | 4 + src/mbgl/map/backend_scope.cpp | 35 +++++++-- test/renderer/backend_scope.test.cpp | 113 +++++++++++++++++++++++++++ 5 files changed, 150 insertions(+), 8 deletions(-) create mode 100644 test/renderer/backend_scope.test.cpp diff --git a/cmake/test-files.cmake b/cmake/test-files.cmake index 6026374de..c29bd284d 100644 --- a/cmake/test-files.cmake +++ b/cmake/test-files.cmake @@ -42,6 +42,7 @@ set(MBGL_TEST_FILES test/programs/binary_program.test.cpp # renderer + test/renderer/backend_scope.test.cpp test/renderer/group_by_layout.test.cpp # sprite diff --git a/include/mbgl/map/backend.hpp b/include/mbgl/map/backend.hpp index 884c4b525..69a998709 100644 --- a/include/mbgl/map/backend.hpp +++ b/include/mbgl/map/backend.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -80,4 +81,8 @@ private: friend class BackendScope; }; +constexpr bool operator==(const Backend& a, const Backend& b) { + return &a == &b; +} + } // namespace mbgl diff --git a/include/mbgl/map/backend_scope.hpp b/include/mbgl/map/backend_scope.hpp index 4985cd197..322c17b3f 100644 --- a/include/mbgl/map/backend_scope.hpp +++ b/include/mbgl/map/backend_scope.hpp @@ -22,10 +22,14 @@ public: static bool exists(); private: + void activate(); + void deactivate(); + BackendScope* priorScope; BackendScope* nextScope; Backend& backend; const ScopeType scopeType; + bool activated = false; }; } // namespace mbgl diff --git a/src/mbgl/map/backend_scope.cpp b/src/mbgl/map/backend_scope.cpp index 824ad4498..dac90346d 100644 --- a/src/mbgl/map/backend_scope.cpp +++ b/src/mbgl/map/backend_scope.cpp @@ -16,30 +16,49 @@ BackendScope::BackendScope(Backend& backend_, ScopeType scopeType_) if (priorScope) { assert(priorScope->nextScope == nullptr); priorScope->nextScope = this; + priorScope->deactivate(); } - if (scopeType == ScopeType::Explicit) { - backend.activate(); - } + + activate(); currentScope.set(this); } BackendScope::~BackendScope() { assert(nextScope == nullptr); + deactivate(); + if (priorScope) { - priorScope->backend.activate(); + priorScope->activate(); currentScope.set(priorScope); assert(priorScope->nextScope == this); priorScope->nextScope = nullptr; } else { - if (scopeType == ScopeType::Explicit) { - backend.deactivate(); - } - currentScope.set(nullptr); } } +void BackendScope::activate() { + if (scopeType == ScopeType::Explicit && + !(priorScope && this->backend == priorScope->backend) && + !(nextScope && this->backend == nextScope->backend)) { + // Only activate when set to Explicit and + // only once per RenderBackend + backend.activate(); + activated = true; + } +} + +void BackendScope::deactivate() { + if (activated && + !(nextScope && this->backend == nextScope->backend)) { + // Only deactivate when set to Explicit and + // only once per RenderBackend + backend.deactivate(); + activated = false; + } +} + bool BackendScope::exists() { return currentScope.get(); } diff --git a/test/renderer/backend_scope.test.cpp b/test/renderer/backend_scope.test.cpp new file mode 100644 index 000000000..6afd8f12e --- /dev/null +++ b/test/renderer/backend_scope.test.cpp @@ -0,0 +1,113 @@ +#include + +#include +#include + +#include + +using namespace mbgl; + +class StubBackend: public Backend { +public: + + void activate() override { + if (activateFunction) activateFunction(); + } + + void deactivate() override { + if (deactivateFunction) deactivateFunction(); + } + + void updateAssumedState() override { + if (updateAssumedStateFunction) updateAssumedStateFunction(); + } + + void invalidate() override {} + + gl::ProcAddress initializeExtension(const char* ext) override { + if (initializeExtensionFunction) { + return initializeExtensionFunction(ext); + } else { + return {}; + } + } + + std::function activateFunction; + std::function deactivateFunction; + std::function updateAssumedStateFunction; + std::function initializeExtensionFunction; +}; + +// A scope should activate on construction +// and deactivate on descruction (going out +// of scope) +TEST(BackendScope, SingleScope) { + bool activated; + bool deactivated; + + StubBackend backend; + backend.activateFunction = [&] { activated = true; }; + backend.deactivateFunction = [&] { deactivated = true; }; + + { + BackendScope test { backend }; + } + + ASSERT_TRUE(activated); + ASSERT_TRUE(deactivated); +} + +// With nested scopes, only the outer scope +// should activate/deactivate +TEST(BackendScope, NestedScopes) { + int activated = 0; + int deactivated = 0; + + StubBackend backend; + backend.activateFunction = [&] { activated++; }; + backend.deactivateFunction = [&] { deactivated++; }; + + { + BackendScope outer { backend }; + ASSERT_EQ(1, activated); + { + BackendScope inner { backend }; + ASSERT_EQ(1, activated); + } + ASSERT_EQ(0, deactivated); + } + + ASSERT_EQ(1, deactivated); +} + +// With chained scopes, where scopes have +// different backends, the scopes should each +// activate the scope on entering and de-activating +// on leaving the scope +TEST(BackendScope, ChainedScopes) { + bool activatedA = false; + bool activatedB = false; + + StubBackend backendA; + backendA.activateFunction = [&] { activatedA = true; }; + backendA.deactivateFunction = [&] { activatedA = false; }; + + StubBackend backendB; + backendB.activateFunction = [&] { activatedB = true; }; + backendB.deactivateFunction = [&] { activatedB = false; }; + + { + BackendScope scopeA { backendA }; + ASSERT_TRUE(activatedA); + { + BackendScope scopeB { backendB }; + ASSERT_FALSE(activatedA); + ASSERT_TRUE(activatedB); + } + ASSERT_FALSE(activatedB); + ASSERT_TRUE(activatedA); + } + + ASSERT_FALSE(activatedA); + ASSERT_FALSE(activatedB); +} From e7f45e0b19eaa86b587e5269f85ecd0eb8a55de7 Mon Sep 17 00:00:00 2001 From: Ivo van Dongen Date: Thu, 13 Jul 2017 12:38:48 +0300 Subject: [PATCH 20/71] [android] remove manual ref counting on activation/deactivation --- platform/android/src/native_map_view.cpp | 7 ------- platform/android/src/native_map_view.hpp | 2 -- 2 files changed, 9 deletions(-) diff --git a/platform/android/src/native_map_view.cpp b/platform/android/src/native_map_view.cpp index 0a57d3e6b..5b0825fa0 100755 --- a/platform/android/src/native_map_view.cpp +++ b/platform/android/src/native_map_view.cpp @@ -120,9 +120,6 @@ gl::ProcAddress NativeMapView::initializeExtension(const char* name) { } void NativeMapView::activate() { - if (active++) { - return; - } oldDisplay = eglGetCurrentDisplay(); oldReadSurface = eglGetCurrentSurface(EGL_READ); @@ -151,10 +148,6 @@ void NativeMapView::activate() { * From mbgl::Backend. */ void NativeMapView::deactivate() { - if (--active) { - return; - } - assert(vm != nullptr); if (oldContext != context && oldContext != EGL_NO_CONTEXT) { diff --git a/platform/android/src/native_map_view.hpp b/platform/android/src/native_map_view.hpp index df43ad08b..c638de44f 100755 --- a/platform/android/src/native_map_view.hpp +++ b/platform/android/src/native_map_view.hpp @@ -334,8 +334,6 @@ private: std::shared_ptr threadPool; std::unique_ptr map; mbgl::EdgeInsets insets; - - unsigned active = 0; }; } // namespace android From 441644166310314b5e8016405691110e69a0a3cd Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 19 Jul 2017 10:03:59 -0700 Subject: [PATCH 21/71] [core] Revert conditional attribute binding Reverts eed89fcf9d099266aa793375ad63493e880f8a80, which causes issues on certain Android devices. We don't actually need this change on this branch, I brought it over only to make the subsequent commits cleanly cherry-pickable. --- src/mbgl/gl/attribute.cpp | 30 ++---------------------------- src/mbgl/gl/attribute.hpp | 18 ++---------------- src/mbgl/gl/program.hpp | 12 +++++------- 3 files changed, 9 insertions(+), 51 deletions(-) diff --git a/src/mbgl/gl/attribute.cpp b/src/mbgl/gl/attribute.cpp index bb5b2ddc3..4dd102400 100644 --- a/src/mbgl/gl/attribute.cpp +++ b/src/mbgl/gl/attribute.cpp @@ -4,35 +4,9 @@ namespace mbgl { namespace gl { -void bindAttributeLocation(ProgramID id, AttributeLocation location, const char* name) { - if (location >= MAX_ATTRIBUTES) { - throw gl::Error("too many vertex attributes"); - } +AttributeLocation bindAttributeLocation(ProgramID id, AttributeLocation location, const char* name) { MBGL_CHECK_ERROR(glBindAttribLocation(id, location, name)); -} - -std::set getActiveAttributes(ProgramID id) { - std::set activeAttributes; - - GLint attributeCount; - MBGL_CHECK_ERROR(glGetProgramiv(id, GL_ACTIVE_ATTRIBUTES, &attributeCount)); - - GLint maxAttributeLength; - MBGL_CHECK_ERROR(glGetProgramiv(id, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &maxAttributeLength)); - - std::string attributeName; - attributeName.resize(maxAttributeLength); - - GLsizei actualLength; - GLint size; - GLenum type; - - for (int32_t i = 0; i < attributeCount; i++) { - MBGL_CHECK_ERROR(glGetActiveAttrib(id, i, maxAttributeLength, &actualLength, &size, &type, &attributeName[0])); - activeAttributes.emplace(std::string(attributeName, 0, actualLength)); - } - - return activeAttributes; + return location; } } // namespace gl diff --git a/src/mbgl/gl/attribute.hpp b/src/mbgl/gl/attribute.hpp index a85b49c6a..2d1d2386e 100644 --- a/src/mbgl/gl/attribute.hpp +++ b/src/mbgl/gl/attribute.hpp @@ -8,7 +8,6 @@ #include #include -#include #include #include #include @@ -214,8 +213,7 @@ const std::size_t Vertex::attributeOffsets[5] = { } // namespace detail -void bindAttributeLocation(ProgramID, AttributeLocation, const char * name); -std::set getActiveAttributes(ProgramID); +AttributeLocation bindAttributeLocation(ProgramID, AttributeLocation, const char * name); template class Attributes { @@ -235,19 +233,7 @@ public: static constexpr std::size_t Index = TypeIndex::value; static Locations bindLocations(const ProgramID& id) { - std::set activeAttributes = getActiveAttributes(id); - - AttributeLocation location = 0; - auto maybeBindLocation = [&](const char* name) -> optional { - if (activeAttributes.count(name)) { - bindAttributeLocation(id, location, name); - return location++; - } else { - return {}; - } - }; - - return Locations { maybeBindLocation(As::name())... }; + return Locations { bindAttributeLocation(id, Index, As::name())... }; } template diff --git a/src/mbgl/gl/program.hpp b/src/mbgl/gl/program.hpp index 3b54ec194..1a429c663 100644 --- a/src/mbgl/gl/program.hpp +++ b/src/mbgl/gl/program.hpp @@ -34,17 +34,15 @@ public: : program( context.createProgram(context.createShader(ShaderType::Vertex, vertexSource), context.createShader(ShaderType::Fragment, fragmentSource))), - uniformsState((context.linkProgram(program), Uniforms::bindLocations(program))), - attributeLocations(Attributes::bindLocations(program)) { - // Re-link program after manually binding only active attributes in Attributes::bindLocations - context.linkProgram(program); + attributeLocations(Attributes::bindLocations(program)), + uniformsState((context.linkProgram(program), Uniforms::bindLocations(program))) { } template Program(Context& context, const BinaryProgram& binaryProgram) : program(context.createProgram(binaryProgram.format(), binaryProgram.code())), - uniformsState(Uniforms::loadNamedLocations(binaryProgram)), - attributeLocations(Attributes::loadNamedLocations(binaryProgram)) { + attributeLocations(Attributes::loadNamedLocations(binaryProgram)), + uniformsState(Uniforms::loadNamedLocations(binaryProgram)) { } static Program createProgram(gl::Context& context, @@ -142,8 +140,8 @@ public: private: UniqueProgram program; - typename Uniforms::State uniformsState; typename Attributes::Locations attributeLocations; + typename Uniforms::State uniformsState; }; } // namespace gl From f94d81c305230a1f54ac5e024c98cfeb1b87739f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Wed, 19 Jul 2017 00:51:13 -0700 Subject: [PATCH 22/71] [android, ios, macos] Updated French localization Updated French localization on iOS and macOS and added French localization on Android. --- platform/android/CHANGELOG.md | 4 + .../src/main/res/values-fr/strings.xml | 15 + .../resources/fr.lproj/Foundation.strings | 291 ++++++++++++++++++ .../resources/fr.lproj/Localizable.strings | 18 ++ .../macos/macos.xcodeproj/project.pbxproj | 2 + .../macos/sdk/fr.lproj/Localizable.strings | 14 +- 6 files changed, 343 insertions(+), 1 deletion(-) create mode 100644 platform/android/MapboxGLAndroidSDK/src/main/res/values-fr/strings.xml create mode 100644 platform/darwin/resources/fr.lproj/Foundation.strings diff --git a/platform/android/CHANGELOG.md b/platform/android/CHANGELOG.md index 6d4bb4635..0a3615d80 100644 --- a/platform/android/CHANGELOG.md +++ b/platform/android/CHANGELOG.md @@ -2,6 +2,10 @@ 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.1.1 - TBA + +* Add French localization [#9545](https://github.com/mapbox/mapbox-gl-native/pull/9545) + ## 5.1.0 - June 30, 2017 * Update to MAS 2.1.3 [#9402](https://github.com/mapbox/mapbox-gl-native/pull/9402) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values-fr/strings.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values-fr/strings.xml new file mode 100644 index 000000000..48d90c332 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/res/values-fr/strings.xml @@ -0,0 +1,15 @@ + + + Boussole. Activer pour rétablir l\'orientation de la carte vers le nord. + Icone d\'attribution. Activer pour montrer le dialogue d\'attribution. + Vue de géolocalisation. Ceci affiche votre localisation sur la carte. + Affichage d\'une carte créée avec Mapbox. Faites la glisser en traînant deux doigts. Zoomez ou dézoomez en écartant ou rapprochant deux doigts. + SDK Mapbox pour Android + Faire de meilleures cartes Mapbox + Vous aidez à améliorer les cartes OpenStreetMap et Mapbox en contribuant des données d\'utilisation anonymes. + D\'accord + Pas d\'accord + Plus d\'informations + Le cadre OfflineRegionDefinition pour définir la région de navigation ne tient pas dans les limites du monde : %s + + diff --git a/platform/darwin/resources/fr.lproj/Foundation.strings b/platform/darwin/resources/fr.lproj/Foundation.strings new file mode 100644 index 000000000..d2f6c1f6d --- /dev/null +++ b/platform/darwin/resources/fr.lproj/Foundation.strings @@ -0,0 +1,291 @@ +/* Clock position format, long: {hours} o’clock */ +"CLOCK_FMT_LONG" = "%@ heures"; + +/* Clock position format, medium: {hours} o’clock */ +"CLOCK_FMT_MEDIUM" = "%@ heures"; + +/* Clock position format, short: {hours}:00 */ +"CLOCK_FMT_SHORT" = "%@h 00"; + +/* East, long */ +"COMPASS_E_LONG" = "est"; + +/* East, short */ +"COMPASS_E_SHORT" = "E"; + +/* East by north, long */ +"COMPASS_EbN_LONG" = "est par nord"; + +/* East by north, short */ +"COMPASS_EbN_SHORT" = "EpN"; + +/* East by south, long */ +"COMPASS_EbS_LONG" = "est par sud"; + +/* East by south, short */ +"COMPASS_EbS_SHORT" = "EpS"; + +/* East-northeast, long */ +"COMPASS_ENE_LONG" = "est-nord-est"; + +/* East-northeast, short */ +"COMPASS_ENE_SHORT" = "ENE"; + +/* East-southeast, long */ +"COMPASS_ESE_LONG" = "est-sud-est"; + +/* East-southeast, short */ +"COMPASS_ESE_SHORT" = "ESE"; + +/* North, long */ +"COMPASS_N_LONG" = "nord"; + +/* North, short */ +"COMPASS_N_SHORT" = "N"; + +/* North by east, long */ +"COMPASS_NbE_LONG" = "nord par est"; + +/* North by east, short */ +"COMPASS_NbE_SHORT" = "NpE"; + +/* North by west, long */ +"COMPASS_NbW_LONG" = "nord par ouest"; + +/* North by west, short */ +"COMPASS_NbW_SHORT" = "NpO"; + +/* Northeast, long */ +"COMPASS_NE_LONG" = "nord-est"; + +/* Northeast, short */ +"COMPASS_NE_SHORT" = "NE"; + +/* Northeast by east, long */ +"COMPASS_NEbE_LONG" = "nord-est par est"; + +/* Northeast by east, short */ +"COMPASS_NEbE_SHORT" = "NEpE"; + +/* Northeast by north, long */ +"COMPASS_NEbN_LONG" = "nord-est par nord"; + +/* Northeast by north, short */ +"COMPASS_NEbN_SHORT" = "NEpN"; + +/* North-northeast, long */ +"COMPASS_NNE_LONG" = "nord-nord-est"; + +/* North-northeast, short */ +"COMPASS_NNE_SHORT" = "NNE"; + +/* North-northwest, long */ +"COMPASS_NNW_LONG" = "nord-nord-ouest"; + +/* North-northwest, short */ +"COMPASS_NNW_SHORT" = "NNO"; + +/* Northwest, long */ +"COMPASS_NW_LONG" = "nord-ouest"; + +/* Northwest, short */ +"COMPASS_NW_SHORT" = "NO"; + +/* Northwest by north, long */ +"COMPASS_NWbN_LONG" = "nord-ouest par nord"; + +/* Northwest by north, short */ +"COMPASS_NWbN_SHORT" = "NOpN"; + +/* Northwest by west, long */ +"COMPASS_NWbW_LONG" = "nord-ouest par ouest"; + +/* Northwest by west, short */ +"COMPASS_NWbW_SHORT" = "NOpO"; + +/* South, long */ +"COMPASS_S_LONG" = "sud"; + +/* South, short */ +"COMPASS_S_SHORT" = "S"; + +/* South by east, long */ +"COMPASS_SbE_LONG" = "sud par est"; + +/* South by east, short */ +"COMPASS_SbE_SHORT" = "SpE"; + +/* South by west, long */ +"COMPASS_SbW_LONG" = "sud par ouest"; + +/* South by west, short */ +"COMPASS_SbW_SHORT" = "SpO"; + +/* Southeast, long */ +"COMPASS_SE_LONG" = "sud-est"; + +/* Southeast, short */ +"COMPASS_SE_SHORT" = "SE"; + +/* Southeast by east, long */ +"COMPASS_SEbE_LONG" = "sud-est par est"; + +/* Southeast by east, short */ +"COMPASS_SEbE_SHORT" = "SEpE"; + +/* Southeast by south, long */ +"COMPASS_SEbS_LONG" = "sud-est par sud"; + +/* Southeast by south, short */ +"COMPASS_SEbS_SHORT" = "SEpS"; + +/* South-southeast, long */ +"COMPASS_SSE_LONG" = "sud-sud-est"; + +/* South-southeast, short */ +"COMPASS_SSE_SHORT" = "SSE"; + +/* South-southwest, long */ +"COMPASS_SSW_LONG" = "sud-sud-ouest"; + +/* South-southwest, short */ +"COMPASS_SSW_SHORT" = "SSO"; + +/* Southwest, long */ +"COMPASS_SW_LONG" = "sud-ouest"; + +/* Southwest, short */ +"COMPASS_SW_SHORT" = "SO"; + +/* Southwest by south, long */ +"COMPASS_SWbS_LONG" = "sud-ouest par sud"; + +/* Southwest by south, short */ +"COMPASS_SWbS_SHORT" = "SOpS"; + +/* Southwest by west, long */ +"COMPASS_SWbW_LONG" = "sud-ouest par ouest"; + +/* Southwest by west, short */ +"COMPASS_SWbW_SHORT" = "SOpO"; + +/* West, long */ +"COMPASS_W_LONG" = "ouest"; + +/* West, short */ +"COMPASS_W_SHORT" = "O"; + +/* West by north, long */ +"COMPASS_WbN_LONG" = "ouest par nord"; + +/* West by north, short */ +"COMPASS_WbN_SHORT" = "OpN"; + +/* West by south, long */ +"COMPASS_WbS_LONG" = "ouest par sud"; + +/* West by south, short */ +"COMPASS_WbS_SHORT" = "OpS"; + +/* West-northwest, long */ +"COMPASS_WNW_LONG" = "ouest-nord-ouest"; + +/* West-northwest, short */ +"COMPASS_WNW_SHORT" = "ONO"; + +/* West-southwest, long */ +"COMPASS_WSW_LONG" = "ouest-sud-ouest"; + +/* West-southwest, short */ +"COMPASS_WSW_SHORT" = "OSO"; + +/* Degrees format, long */ +"COORD_DEG_LONG" = "%d degré(s)"; + +/* Degrees format, medium: {degrees} */ +"COORD_DEG_MEDIUM" = "%d°"; + +/* Degrees format, short: {degrees} */ +"COORD_DEG_SHORT" = "%d°"; + +/* Coordinate format, long: {degrees}{minutes} */ +"COORD_DM_LONG" = "%1$@ et %2$@"; + +/* Coordinate format, medium: {degrees}{minutes} */ +"COORD_DM_MEDIUM" = "%1$@%2$@"; + +/* Coordinate format, short: {degrees}{minutes} */ +"COORD_DM_SHORT" = "%1$@%2$@"; + +/* Coordinate format, long: {degrees}{minutes}{seconds} */ +"COORD_DMS_LONG" = "%1$@, %2$@ et %3$@"; + +/* Coordinate format, medium: {degrees}{minutes}{seconds} */ +"COORD_DMS_MEDIUM" = "%1$@%2$@%3$@"; + +/* Coordinate format, short: {degrees}{minutes}{seconds} */ +"COORD_DMS_SHORT" = "%1$@%2$@%3$@"; + +/* East longitude format, long: {longitude} */ +"COORD_E_LONG" = "%@ est"; + +/* East longitude format, medium: {longitude} */ +"COORD_E_MEDIUM" = "%@ est"; + +/* East longitude format, short: {longitude} */ +"COORD_E_SHORT" = "%@E"; + +/* Coordinate pair format, long: {latitude}, {longitude} */ +"COORD_FMT_LONG" = "%1$@, %2$@"; + +/* Coordinate pair format, medium: {latitude}, {longitude} */ +"COORD_FMT_MEDIUM" = "%1$@, %2$@"; + +/* Coordinate pair format, short: {latitude}, {longitude} */ +"COORD_FMT_SHORT" = "%1$@, %2$@"; + +/* Minutes format, long */ +"COORD_MIN_LONG" = "%d minute(s)"; + +/* Minutes format, medium: {minutes} */ +"COORD_MIN_MEDIUM" = "%d′"; + +/* Minutes format, short: {minutes} */ +"COORD_MIN_SHORT" = "%d′"; + +/* North latitude format, long: {latitude} */ +"COORD_N_LONG" = "%@ nord"; + +/* North latitude format, medium: {latitude} */ +"COORD_N_MEDIUM" = "%@ nord"; + +/* North latitude format, short: {latitude} */ +"COORD_N_SHORT" = "%@N"; + +/* South latitude format, long: {latitude} */ +"COORD_S_LONG" = "%@ sud"; + +/* South latitude format, medium: {latitude} */ +"COORD_S_MEDIUM" = "%@ sud"; + +/* South latitude format, short: {latitude} */ +"COORD_S_SHORT" = "%@S"; + +/* Seconds format, long */ +"COORD_SEC_LONG" = "%d seconde(s)"; + +/* Seconds format, medium: {seconds} */ +"COORD_SEC_MEDIUM" = "%d″"; + +/* Seconds format, short: {seconds} */ +"COORD_SEC_SHORT" = "%d″"; + +/* West longitude format, long: {longitude} */ +"COORD_W_LONG" = "%@ ouest"; + +/* West longitude format, medium: {longitude} */ +"COORD_W_MEDIUM" = "%@ ouest"; + +/* West longitude format, short: {longitude} */ +"COORD_W_SHORT" = "%@O"; + diff --git a/platform/ios/resources/fr.lproj/Localizable.strings b/platform/ios/resources/fr.lproj/Localizable.strings index 075042c69..7fc85568c 100644 --- a/platform/ios/resources/fr.lproj/Localizable.strings +++ b/platform/ios/resources/fr.lproj/Localizable.strings @@ -10,6 +10,9 @@ /* No comment provided by engineer. */ "CANCEL" = "Annuler"; +/* Accessibility hint for closing the selected annotation’s callout view and returning to the map */ +"CLOSE_CALLOUT_A11Y_HINT" = "Retour à la carte"; + /* Accessibility hint */ "COMPASS_A11Y_HINT" = "Tourne la carte vers le nord"; @@ -31,6 +34,12 @@ /* Accessibility label */ "INFO_A11Y_LABEL" = "À propos de cette carte"; +/* User-friendly error description */ +"LOAD_MAP_FAILED_DESC" = "La carte n’a pas pu être chargée car une erreur inconnue est survenue."; + +/* User-friendly error description */ +"LOAD_STYLE_FAILED_DESC" = "La carte n’a pas pu être chargée car le style ne peut pas être chargé."; + /* Accessibility label */ "LOGO_A11Y_LABEL" = "Mapbox"; @@ -40,9 +49,18 @@ /* Map accessibility value */ "MAP_A11Y_VALUE" = "Zoom %1$dx\n%2$ld annotation(s) visible(s)"; +/* User-friendly error description */ +"PARSE_STYLE_FAILED_DESC" = "La carte n’a pas pu être chargée car le style est corrompu."; + /* Action sheet title */ "SDK_NAME" = "Mapbox iOS SDK"; +/* Developer-only SDK update notification; {latest version, in format x.x.x} */ +"SDK_UPDATE_AVAILABLE" = "La version %@ du SDK Mapbox pour iOS est maintenant disponible :"; + +/* User-friendly error description */ +"STYLE_NOT_FOUND_DESC" = "La carte n’a pas pu être chargée car le style n’a pas été trouvé ou est incompatible."; + /* Telemetry prompt message */ "TELEMETRY_DISABLED_MSG" = "Vous pouvez contribuer à OpenStreetMap et Mapbox en partageant des données d’utilisation anonymes."; diff --git a/platform/macos/macos.xcodeproj/project.pbxproj b/platform/macos/macos.xcodeproj/project.pbxproj index 112752409..be33b822f 100644 --- a/platform/macos/macos.xcodeproj/project.pbxproj +++ b/platform/macos/macos.xcodeproj/project.pbxproj @@ -475,6 +475,7 @@ DACC22121CF3D3E200D220D9 /* MGLFeature.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLFeature.h; sourceTree = ""; }; DACC22131CF3D3E200D220D9 /* MGLFeature.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLFeature.mm; sourceTree = ""; }; DACC22171CF3D4F700D220D9 /* MGLFeature_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLFeature_Private.h; sourceTree = ""; }; + DACCD9C71F1F443B00BB09A1 /* fr */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Foundation.strings; sourceTree = ""; }; DAD165721CF4CD7A001FF4B9 /* MGLShapeCollection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLShapeCollection.h; sourceTree = ""; }; DAD165731CF4CD7A001FF4B9 /* MGLShapeCollection.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLShapeCollection.mm; sourceTree = ""; }; DAE6C2E11CC304F900DB3429 /* Credits.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; path = Credits.rtf; sourceTree = ""; }; @@ -1598,6 +1599,7 @@ DA618B171E68876C00CB7F44 /* ca */, DA618B231E6891ED00CB7F44 /* lt */, DAE9E0F21EB7BF39001E8E8B /* es */, + DACCD9C71F1F443B00BB09A1 /* fr */, ); name = Foundation.strings; sourceTree = ""; diff --git a/platform/macos/sdk/fr.lproj/Localizable.strings b/platform/macos/sdk/fr.lproj/Localizable.strings index 34e085ef2..9d73f23d0 100644 --- a/platform/macos/sdk/fr.lproj/Localizable.strings +++ b/platform/macos/sdk/fr.lproj/Localizable.strings @@ -1,6 +1,18 @@ -/* Accessibility title */ +/* User-friendly error description */ +"LOAD_MAP_FAILED_DESC" = "La carte n’a pas pu être chargée car une erreur inconnue est survenue."; + +/* User-friendly error description */ +"LOAD_STYLE_FAILED_DESC" = "La carte n’a pas pu être chargée car le style ne peut pas être chargé."; + +/* Accessibility title */ "MAP_A11Y_TITLE" = "Mapbox"; +/* User-friendly error description */ +"PARSE_STYLE_FAILED_DESC" = "La carte n’a pas pu être chargée car le style est corrompu."; + +/* User-friendly error description */ +"STYLE_NOT_FOUND_DESC" = "La carte n’a pas pu être chargée car le style n’a pas été trouvé ou est incompatible."; + /* Label of Zoom In button */ "ZOOM_IN_LABEL" = "+"; From 9f2990d1677ad099cfb84eaca938de4132924868 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Wed, 19 Jul 2017 00:53:01 -0700 Subject: [PATCH 23/71] [ios] Added Hungarian settings bundle --- platform/ios/framework/Settings.bundle/hu.lproj/Root.strings | 3 +++ platform/ios/ios.xcodeproj/project.pbxproj | 2 ++ 2 files changed, 5 insertions(+) create mode 100644 platform/ios/framework/Settings.bundle/hu.lproj/Root.strings diff --git a/platform/ios/framework/Settings.bundle/hu.lproj/Root.strings b/platform/ios/framework/Settings.bundle/hu.lproj/Root.strings new file mode 100644 index 000000000..3d761f2b9 --- /dev/null +++ b/platform/ios/framework/Settings.bundle/hu.lproj/Root.strings @@ -0,0 +1,3 @@ +"TELEMETRY_GROUP_TITLE" = "Adatvédelmi beállítások"; +"TELEMETRY_SWITCH_TITLE" = "Mapbox Telemetria"; +"TELEMETRY_GROUP_FOOTER" = "Ez a beállítás megengedi az alkalmazásnak, hogy névtelen helyzeti és használati adatokat osszon meg a Mapbox-szal."; diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index 7c6f1115c..8ecbe0c6a 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -903,6 +903,7 @@ DABCABBF1CB80717000A7C39 /* locations.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = locations.cpp; sourceTree = ""; }; DABCABC01CB80717000A7C39 /* locations.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = locations.hpp; sourceTree = ""; }; DAC49C621CD07D74009E1AA3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; }; + DACCD9C81F1F473700BB09A1 /* hu */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Root.strings; sourceTree = ""; }; DAD165691CF41981001FF4B9 /* MGLFeature.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLFeature.h; sourceTree = ""; }; DAD1656A1CF41981001FF4B9 /* MGLFeature_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLFeature_Private.h; sourceTree = ""; }; DAD1656B1CF41981001FF4B9 /* MGLFeature.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLFeature.mm; sourceTree = ""; }; @@ -2440,6 +2441,7 @@ DA618B1E1E688A3700CB7F44 /* ca */, DA618B2C1E68933600CB7F44 /* fi */, DAE8CCAE1E6E8C76009B5CB0 /* nl */, + DACCD9C81F1F473700BB09A1 /* hu */, ); name = Root.strings; sourceTree = ""; From af76efac22905be06d23567465e67da97d381a7e Mon Sep 17 00:00:00 2001 From: Tobrun Date: Thu, 6 Jul 2017 21:04:30 +0200 Subject: [PATCH 24/71] [android] - validate if platform implementation doesn't return a null motion event (#9434) --- .../com/mapbox/mapboxsdk/maps/MapGestureDetector.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java index 3607703ab..17eb506b9 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java @@ -3,7 +3,6 @@ package com.mapbox.mapboxsdk.maps; import android.content.Context; import android.graphics.PointF; import android.location.Location; -import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.view.GestureDetectorCompat; import android.support.v4.view.ScaleGestureDetectorCompat; @@ -134,7 +133,12 @@ final class MapGestureDetector { * @param event the MotionEvent * @return True if touch event is handled */ - boolean onTouchEvent(@NonNull MotionEvent event) { + boolean onTouchEvent(MotionEvent event) { + // framework can return null motion events in edge cases #9432 + if (event == null) { + return false; + } + // Check and ignore non touch or left clicks if ((event.getButtonState() != 0) && (event.getButtonState() != MotionEvent.BUTTON_PRIMARY)) { return false; From 79f0cc3d812c95a02029749cb13900f31c6eb6b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Paczos?= Date: Thu, 6 Jul 2017 21:05:04 +0200 Subject: [PATCH 25/71] [android] - map's visible region fix #9427 (#9428) --- .../main/java/com/mapbox/mapboxsdk/maps/Projection.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java index ff466c436..e0a634521 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java @@ -80,10 +80,10 @@ public class Projection { public VisibleRegion getVisibleRegion() { LatLngBounds.Builder builder = new LatLngBounds.Builder(); - float left = contentPadding[0]; - float right = nativeMapView.getWidth() - contentPadding[2]; - float top = contentPadding[1]; - float bottom = nativeMapView.getHeight() - contentPadding[3]; + float left = 0; + float right = nativeMapView.getWidth(); + float top = 0; + float bottom = nativeMapView.getHeight(); LatLng topLeft = fromScreenLocation(new PointF(left, top)); LatLng topRight = fromScreenLocation(new PointF(right, top)); From 0ce62a20c45b3a43629306b7728d474343520025 Mon Sep 17 00:00:00 2001 From: Pablo Guardiola Date: Thu, 6 Jul 2017 11:26:03 +0200 Subject: [PATCH 26/71] [android] Fix my location drawable getting tinted (#9410) * fix my location drawable tinted when setting mapbox_myLocationTintColor in xml issue (linking my location foreground drawables with my location foreground tint color and adding the possibility of setting an undefined my location foreground tint color) * link my location background drawable with my location background tint color and add the possibility of setting an undefined my location background tint color --- .../mapboxsdk/maps/MapboxMapOptions.java | 21 +++++++------ .../maps/widgets/MyLocationView.java | 31 ++++++++++++++----- .../maps/widgets/MyLocationViewSettings.java | 10 ++++-- 3 files changed, 44 insertions(+), 18 deletions(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMapOptions.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMapOptions.java index 2efed1b32..80b25bf0d 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMapOptions.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMapOptions.java @@ -4,7 +4,6 @@ import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; -import android.graphics.Color; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Parcel; @@ -37,6 +36,7 @@ public class MapboxMapOptions implements Parcelable { private static final float FOUR_DP = 4f; private static final float NINETY_TWO_DP = 92f; + private static final int UNDEFINED_COLOR = -1; private CameraPosition cameraPosition; @@ -53,7 +53,7 @@ public class MapboxMapOptions implements Parcelable { private int[] logoMargins; @ColorInt - private int attributionTintColor = -1; + private int attributionTintColor = UNDEFINED_COLOR; private boolean attributionEnabled = true; private int attributionGravity = Gravity.BOTTOM; private int[] attributionMargins; @@ -72,8 +72,10 @@ public class MapboxMapOptions implements Parcelable { private Drawable myLocationForegroundDrawable; private Drawable myLocationForegroundBearingDrawable; private Drawable myLocationBackgroundDrawable; - private int myLocationForegroundTintColor; - private int myLocationBackgroundTintColor; + @ColorInt + private int myLocationForegroundTintColor = UNDEFINED_COLOR; + @ColorInt + private int myLocationBackgroundTintColor = UNDEFINED_COLOR; private int[] myLocationBackgroundPadding; private int myLocationAccuracyTintColor; private int myLocationAccuracyAlpha; @@ -234,7 +236,7 @@ public class MapboxMapOptions implements Parcelable { FOUR_DP * pxlRatio))}); mapboxMapOptions.attributionTintColor(typedArray.getColor( - R.styleable.mapbox_MapView_mapbox_uiAttributionTintColor, -1)); + R.styleable.mapbox_MapView_mapbox_uiAttributionTintColor, UNDEFINED_COLOR)); mapboxMapOptions.attributionEnabled(typedArray.getBoolean( R.styleable.mapbox_MapView_mapbox_uiAttribution, true)); mapboxMapOptions.attributionGravity(typedArray.getInt( @@ -251,10 +253,9 @@ public class MapboxMapOptions implements Parcelable { mapboxMapOptions.locationEnabled(typedArray.getBoolean(R.styleable.mapbox_MapView_mapbox_myLocation, false)); mapboxMapOptions.myLocationForegroundTintColor( - typedArray.getColor(R.styleable.mapbox_MapView_mapbox_myLocationTintColor, - ColorUtils.getPrimaryColor(context))); + typedArray.getColor(R.styleable.mapbox_MapView_mapbox_myLocationTintColor, UNDEFINED_COLOR)); mapboxMapOptions.myLocationBackgroundTintColor( - typedArray.getColor(R.styleable.mapbox_MapView_mapbox_myLocationBackgroundTintColor, Color.WHITE)); + typedArray.getColor(R.styleable.mapbox_MapView_mapbox_myLocationBackgroundTintColor, UNDEFINED_COLOR)); Drawable foregroundDrawable = typedArray.getDrawable(R.styleable.mapbox_MapView_mapbox_myLocationDrawable); if (foregroundDrawable == null) { @@ -638,7 +639,7 @@ public class MapboxMapOptions implements Parcelable { /** * Set the background tint color of MyLocationView. * - * @param myLocationBackgroundTintColor the color to tint the background + * @param myLocationBackgroundTintColor the color to tint the background drawable * @return This */ public MapboxMapOptions myLocationBackgroundTintColor(@ColorInt int myLocationBackgroundTintColor) { @@ -944,6 +945,7 @@ public class MapboxMapOptions implements Parcelable { * * @return the tint color */ + @ColorInt public int getMyLocationForegroundTintColor() { return myLocationForegroundTintColor; } @@ -953,6 +955,7 @@ public class MapboxMapOptions implements Parcelable { * * @return the tint color */ + @ColorInt public int getMyLocationBackgroundTintColor() { return myLocationBackgroundTintColor; } 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 8b6b93e03..24da59bb7 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 @@ -51,6 +51,7 @@ import timber.log.Timber; */ public class MyLocationView extends View { + private static final int UNDEFINED_TINT_COLOR = -1; private MyLocationBehavior myLocationBehavior; private MapboxMap mapboxMap; @@ -198,12 +199,8 @@ public class MyLocationView extends View { * @param color The color to tint the drawable with */ public final void setForegroundDrawableTint(@ColorInt int color) { - if (foregroundDrawable != null) { - foregroundDrawable.mutate().setColorFilter(color, PorterDuff.Mode.SRC_IN); - } - if (foregroundBearingDrawable != null) { - foregroundBearingDrawable.mutate().setColorFilter(color, PorterDuff.Mode.SRC_IN); - } + applyDrawableTint(foregroundDrawable, color); + applyDrawableTint(foregroundBearingDrawable, color); invalidate(); } @@ -247,7 +244,7 @@ public class MyLocationView extends View { if (backgroundDrawable == null) { return; } - backgroundDrawable.mutate().setColorFilter(color, PorterDuff.Mode.SRC_IN); + applyDrawableTint(backgroundDrawable, color); invalidate(); } @@ -737,6 +734,26 @@ public class MyLocationView extends View { setEnabled(isEnabled(), locationSource != null); } + private void applyDrawableTint(Drawable drawable, @ColorInt int color) { + if (color == UNDEFINED_TINT_COLOR) { + removeTintColorFilter(drawable); + } else { + applyTintColorFilter(drawable, color); + } + } + + private void removeTintColorFilter(Drawable drawable) { + if (drawable != null) { + drawable.mutate().setColorFilter(null); + } + } + + private void applyTintColorFilter(Drawable drawable, @ColorInt int color) { + if (drawable != null) { + drawable.mutate().setColorFilter(color, PorterDuff.Mode.SRC_IN); + } + } + private static class GpsLocationListener implements LocationEngineListener { private WeakReference userLocationView; diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationViewSettings.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationViewSettings.java index 2ad1bf7eb..fe2f18e4d 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationViewSettings.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationViewSettings.java @@ -119,6 +119,7 @@ public class MyLocationViewSettings { *

* The foreground drawable is the image visible on screen *

+ * It's linked with the foreground tint color * * @param foregroundDrawable the drawable to show as foreground without bearing * @param foregroundBearingDrawable the drawable to show as foreground when bearing is enabled @@ -127,6 +128,7 @@ public class MyLocationViewSettings { this.foregroundDrawable = foregroundDrawable; this.foregroundBearingDrawable = foregroundBearingDrawable; myLocationView.setForegroundDrawables(foregroundDrawable, foregroundBearingDrawable); + myLocationView.setForegroundDrawableTint(foregroundTintColor); } /** @@ -153,7 +155,8 @@ public class MyLocationViewSettings { * The color will tint both the foreground and the bearing foreground drawable. *

* - * @param foregroundTintColor the color to tint the foreground drawable + * @param foregroundTintColor the color to tint the foreground drawable or -1 (undefined color) to remove the + * existing foreground tint color */ public void setForegroundTintColor(@ColorInt int foregroundTintColor) { this.foregroundTintColor = foregroundTintColor; @@ -174,6 +177,7 @@ public class MyLocationViewSettings { *

* Padding can be added to provide an offset to the background *

+ * It's linked with the background tint color * * @param backgroundDrawable the drawable to show as background * @param padding the padding added to the background @@ -186,6 +190,7 @@ public class MyLocationViewSettings { } else { myLocationView.setShadowDrawable(backgroundDrawable); } + myLocationView.setShadowDrawableTint(backgroundTintColor); } /** @@ -200,7 +205,8 @@ public class MyLocationViewSettings { /** * Set the background tint color. * - * @param backgroundTintColor the color to tint the background + * @param backgroundTintColor the color to tint the background drawable or -1 (undefined color) to remove the + * existing background tint color */ public void setBackgroundTintColor(@ColorInt int backgroundTintColor) { this.backgroundTintColor = backgroundTintColor; From 23210cb15c39c3742defe11338d85edbcc2c8220 Mon Sep 17 00:00:00 2001 From: Tobrun Date: Tue, 18 Jul 2017 15:34:36 +0200 Subject: [PATCH 27/71] [android] - bump OkHttp dependency to avoid null pointer exceptions on Android 7.0 (#9522) --- platform/android/dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/android/dependencies.gradle b/platform/android/dependencies.gradle index 500b99def..3d8f48b46 100644 --- a/platform/android/dependencies.gradle +++ b/platform/android/dependencies.gradle @@ -47,7 +47,7 @@ ext { // square crew timber : 'com.jakewharton.timber:timber:4.5.1', - okhttp3 : 'com.squareup.okhttp3:okhttp:3.7.0', + okhttp3 : 'com.squareup.okhttp3:okhttp:3.8.0', leakCanaryDebug : "com.squareup.leakcanary:leakcanary-android:${leakCanaryVersion}", leakCanaryRelease : "com.squareup.leakcanary:leakcanary-android-no-op:${leakCanaryVersion}", leakCanaryTest : "com.squareup.leakcanary:leakcanary-android-no-op:${leakCanaryVersion}" From 655e23269542c63b2ca32692aea9f8782c09cf59 Mon Sep 17 00:00:00 2001 From: Tobrun Date: Tue, 18 Jul 2017 12:32:40 +0200 Subject: [PATCH 28/71] [android] - fine tune fling gesture (#9532) --- .../main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java | 2 +- platform/android/src/native_map_view.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java index 17eb506b9..c1e997c3a 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java @@ -370,7 +370,7 @@ final class MapGestureDetector { // tilt results in a bigger translation, limiting input for #5281 double tilt = transform.getTilt(); - double tiltFactor = 1 + ((tilt != 0) ? (tilt / 10) : 0); /* 1 -> 7 */ + double tiltFactor = 1.5 + ((tilt != 0) ? (tilt / 10) : 0); double offsetX = velocityX / tiltFactor / screenDensity; double offsetY = velocityY / tiltFactor / screenDensity; diff --git a/platform/android/src/native_map_view.cpp b/platform/android/src/native_map_view.cpp index 5b0825fa0..159ba7050 100755 --- a/platform/android/src/native_map_view.cpp +++ b/platform/android/src/native_map_view.cpp @@ -371,7 +371,7 @@ void NativeMapView::moveBy(jni::JNIEnv&, jni::jdouble dx, jni::jdouble dy, jni:: mbgl::AnimationOptions animationOptions; if (duration > 0) { animationOptions.duration.emplace(mbgl::Milliseconds(duration)); - animationOptions.easing.emplace(mbgl::util::UnitBezier { 0, 0.3, 0.6, 1.0 }); + animationOptions.easing.emplace(mbgl::util::UnitBezier {0.25, 0.46, 0.45, 0.94}); } map->moveBy({dx, dy}, animationOptions); } From 9c73d3458dfe70187209562c60a73081648e1015 Mon Sep 17 00:00:00 2001 From: Tobrun Date: Fri, 7 Jul 2017 12:28:19 +0200 Subject: [PATCH 29/71] Hit test Marker and MarkerViews (#9424) * [android] - hit test Marker and MarkerViews * fixup --- .../mapboxsdk/maps/AnnotationManager.java | 191 +++++++++++------- .../mapbox/mapboxsdk/maps/IconManager.java | 4 + .../mapboxsdk/maps/MapGestureDetector.java | 2 +- .../src/main/res/drawable/ic_check_box.xml | 9 + 4 files changed, 136 insertions(+), 70 deletions(-) create mode 100644 platform/android/MapboxGLAndroidSDKTestApp/src/main/res/drawable/ic_check_box.xml diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java index 3694668a7..392eededd 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java @@ -1,10 +1,13 @@ package com.mapbox.mapboxsdk.maps; +import android.graphics.Bitmap; import android.graphics.PointF; +import android.graphics.Rect; import android.graphics.RectF; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.util.LongSparseArray; +import android.view.View; import com.mapbox.mapboxsdk.annotations.Annotation; import com.mapbox.mapboxsdk.annotations.BaseMarkerOptions; @@ -19,7 +22,6 @@ import com.mapbox.mapboxsdk.annotations.Polyline; import com.mapbox.mapboxsdk.annotations.PolylineOptions; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import timber.log.Timber; @@ -55,7 +57,7 @@ class AnnotationManager { this.markerViewManager = markerViewManager; if (view != null) { // null checking needed for unit tests - nativeMapView.addOnMapChangedListener(markerViewManager); + view.addOnMapChangedListener(markerViewManager); } } @@ -616,84 +618,135 @@ class AnnotationManager { // Click event // - boolean onTap(PointF tapPoint, float screenDensity) { - float toleranceSides = 4 * screenDensity; - float toleranceTopBottom = 10 * screenDensity; - boolean handledDefaultClick = false; + boolean onTap(PointF tapPoint) { + MarkerHit markerHit = getMarkerHitFromTouchArea(tapPoint); + long markerId = new MarkerHitResolver(markerViewManager, mapboxMap.getProjection()).execute(markerHit); + return markerId >= 0 && isClickHandledForMarker(markerId); + } - RectF tapRect = new RectF(tapPoint.x - iconManager.getAverageIconWidth() / 2 - toleranceSides, - tapPoint.y - iconManager.getAverageIconHeight() / 2 - toleranceTopBottom, - tapPoint.x + iconManager.getAverageIconWidth() / 2 + toleranceSides, - tapPoint.y + iconManager.getAverageIconHeight() / 2 + toleranceTopBottom); + private MarkerHit getMarkerHitFromTouchArea(PointF tapPoint) { + int averageIconWidthOffset = iconManager.getAverageIconWidth() / 2; + int averageIconHeightOffset = iconManager.getAverageIconHeight() / 2; + final RectF tapRect = new RectF(tapPoint.x - averageIconWidthOffset, + tapPoint.y - averageIconHeightOffset, + tapPoint.x + averageIconWidthOffset, + tapPoint.y + averageIconHeightOffset + ); + return new MarkerHit(tapRect, getMarkersInRect(tapRect)); + } - List nearbyMarkers = getMarkersInRect(tapRect); - long newSelectedMarkerId = -1; + private boolean isClickHandledForMarker(long markerId) { + boolean handledDefaultClick; + Marker marker = (Marker) getAnnotation(markerId); + if (marker instanceof MarkerView) { + handledDefaultClick = markerViewManager.onClickMarkerView((MarkerView) marker); + } else { + handledDefaultClick = onClickMarker(marker); + } - // find a Marker that isn't selected yet - if (nearbyMarkers.size() > 0) { - Collections.sort(nearbyMarkers); - for (Marker nearbyMarker : nearbyMarkers) { - boolean found = false; - for (Marker selectedMarker : selectedMarkers) { - if (selectedMarker.equals(nearbyMarker)) { - found = true; - } - } - if (!found) { - newSelectedMarkerId = nearbyMarker.getId(); - break; + if (!handledDefaultClick) { + setMarkerSelectionState(marker); + } + return true; + } + + private boolean onClickMarker(Marker marker) { + return onMarkerClickListener != null && onMarkerClickListener.onMarkerClick(marker); + } + + private void setMarkerSelectionState(Marker marker) { + if (!selectedMarkers.contains(marker)) { + selectMarker(marker); + } else { + deselectMarker(marker); + } + } + + private static class MarkerHitResolver { + + private final MarkerViewManager markerViewManager; + private final Projection projection; + + private View view; + private Bitmap bitmap; + private PointF markerLocation; + + private Rect hitRectView = new Rect(); + private RectF hitRectMarker = new RectF(); + private RectF highestSurfaceIntersection = new RectF(); + + private long closestMarkerId = -1; + + MarkerHitResolver(@NonNull MarkerViewManager markerViewManager, @NonNull Projection projection) { + this.markerViewManager = markerViewManager; + this.projection = projection; + } + + public long execute(MarkerHit markerHit) { + resolveForMarkers(markerHit); + return closestMarkerId; + } + + private void resolveForMarkers(MarkerHit markerHit) { + for (Marker marker : markerHit.markers) { + if (marker instanceof MarkerView) { + resolveForMarkerView(markerHit, (MarkerView) marker); + } else { + resolveForMarker(markerHit, marker); } } } - // if unselected marker found - if (newSelectedMarkerId >= 0) { - List annotations = getAnnotations(); - int count = annotations.size(); - for (int i = 0; i < count; i++) { - Annotation annotation = annotations.get(i); - if (annotation instanceof Marker) { - if (annotation.getId() == newSelectedMarkerId) { - Marker marker = (Marker) annotation; - - if (marker instanceof MarkerView) { - handledDefaultClick = markerViewManager.onClickMarkerView((MarkerView) marker); - } else { - if (onMarkerClickListener != null) { - // end developer has provided a custom click listener - handledDefaultClick = onMarkerClickListener.onMarkerClick(marker); - } - } - - if (!handledDefaultClick) { - // only select marker if user didn't handle the click event themselves - selectMarker(marker); - } - - return true; - } - } + private void resolveForMarkerView(MarkerHit markerHit, MarkerView markerView) { + view = markerViewManager.getView(markerView); + if (view != null) { + view.getHitRect(hitRectView); + hitRectMarker = new RectF(hitRectView); + hitTestMarker(markerHit, markerView, hitRectMarker); } - } else if (nearbyMarkers.size() > 0) { - // we didn't find an unselected marker, check if we can close an already open markers - for (Marker nearbyMarker : nearbyMarkers) { - for (Marker selectedMarker : selectedMarkers) { - if (nearbyMarker.equals(selectedMarker)) { - if (nearbyMarker instanceof MarkerView) { - handledDefaultClick = markerViewManager.onClickMarkerView((MarkerView) nearbyMarker); - } else if (onMarkerClickListener != null) { - handledDefaultClick = onMarkerClickListener.onMarkerClick(nearbyMarker); - } + } - if (!handledDefaultClick) { - // only deselect marker if user didn't handle the click event themselves - deselectMarker(nearbyMarker); - } - return true; - } + private void resolveForMarker(MarkerHit markerHit, Marker marker) { + markerLocation = projection.toScreenLocation(marker.getPosition()); + bitmap = marker.getIcon().getBitmap(); + hitRectMarker.set(0, 0, bitmap.getWidth(), bitmap.getHeight()); + hitRectMarker.offsetTo( + markerLocation.x - bitmap.getWidth() / 2, + markerLocation.y - bitmap.getHeight() / 2 + ); + hitTestMarker(markerHit, marker, hitRectMarker); + } + + private void hitTestMarker(MarkerHit markerHit, Marker marker, RectF hitRectMarker) { + if (hitRectMarker.contains(markerHit.getTapPointX(), markerHit.getTapPointY())) { + hitRectMarker.intersect(markerHit.tapRect); + if (isRectangleHighestSurfaceIntersection(hitRectMarker)) { + highestSurfaceIntersection = new RectF(hitRectMarker); + closestMarkerId = marker.getId(); } } } - return false; + + private boolean isRectangleHighestSurfaceIntersection(RectF rectF) { + return rectF.width() * rectF.height() > highestSurfaceIntersection.width() * highestSurfaceIntersection.height(); + } + } + + private static class MarkerHit { + private final RectF tapRect; + private final List markers; + + MarkerHit(RectF tapRect, List markers) { + this.tapRect = tapRect; + this.markers = markers; + } + + float getTapPointX() { + return tapRect.centerX(); + } + + float getTapPointY() { + return tapRect.centerY(); + } } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/IconManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/IconManager.java index 9f4171aee..b5da5f59a 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/IconManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/IconManager.java @@ -12,6 +12,8 @@ import com.mapbox.mapboxsdk.exceptions.IconBitmapChangedException; import java.util.ArrayList; import java.util.List; +import timber.log.Timber; + /** * Responsible for managing icons added to the Map. *

@@ -105,6 +107,8 @@ class IconManager { int iconSize = icons.size() + 1; averageIconHeight = averageIconHeight + (height - averageIconHeight) / iconSize; averageIconWidth = averageIconWidth + (width - averageIconWidth) / iconSize; + Timber.e("OnUpdateAverageSizeIcon with: %s %s", width, height); + Timber.e("OnUpdateAverageSizeIcon now: %s %s", averageIconWidth, averageIconHeight); } private void loadIcon(Icon icon) { diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java index c1e997c3a..d2973bf55 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java @@ -315,7 +315,7 @@ final class MapGestureDetector { @Override public boolean onSingleTapConfirmed(MotionEvent motionEvent) { PointF tapPoint = new PointF(motionEvent.getX(), motionEvent.getY()); - boolean tapHandled = annotationManager.onTap(tapPoint, uiSettings.getPixelRatio()); + boolean tapHandled = annotationManager.onTap(tapPoint); if (!tapHandled) { if (uiSettings.isDeselectMarkersOnTap()) { diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/drawable/ic_check_box.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/drawable/ic_check_box.xml new file mode 100644 index 000000000..cf8bfa24b --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/drawable/ic_check_box.xml @@ -0,0 +1,9 @@ + + + From d7401f9e219e4607146cf1c34af23edb474348eb Mon Sep 17 00:00:00 2001 From: Tobrun Date: Tue, 18 Jul 2017 15:34:55 +0200 Subject: [PATCH 30/71] [android] - add OnPolygonClickListener and OnPolylineClickListener (#9443) --- .../mapboxsdk/maps/AnnotationManager.java | 96 +++++++++++++++++-- .../com/mapbox/mapboxsdk/maps/MapboxMap.java | 50 ++++++++++ .../activity/annotation/PolygonActivity.java | 14 +++ .../activity/annotation/PolylineActivity.java | 14 ++- 4 files changed, 164 insertions(+), 10 deletions(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java index 392eededd..404cc5f3b 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java @@ -9,6 +9,8 @@ import android.support.annotation.Nullable; import android.support.v4.util.LongSparseArray; import android.view.View; +import com.mapbox.mapboxsdk.Mapbox; +import com.mapbox.mapboxsdk.R; import com.mapbox.mapboxsdk.annotations.Annotation; import com.mapbox.mapboxsdk.annotations.BaseMarkerOptions; import com.mapbox.mapboxsdk.annotations.BaseMarkerViewOptions; @@ -20,6 +22,7 @@ import com.mapbox.mapboxsdk.annotations.Polygon; import com.mapbox.mapboxsdk.annotations.PolygonOptions; import com.mapbox.mapboxsdk.annotations.Polyline; import com.mapbox.mapboxsdk.annotations.PolylineOptions; +import com.mapbox.services.commons.geojson.Feature; import java.util.ArrayList; import java.util.List; @@ -39,6 +42,8 @@ import timber.log.Timber; */ class AnnotationManager { + private static final String LAYER_ID_SHAPE_ANNOTATIONS = "com.mapbox.annotations.shape."; + private final NativeMapView nativeMapView; private final MapView mapView; private final IconManager iconManager; @@ -46,9 +51,12 @@ class AnnotationManager { private final MarkerViewManager markerViewManager; private final LongSparseArray annotations = new LongSparseArray<>(); private final List selectedMarkers = new ArrayList<>(); + private final List shapeAnnotationIds = new ArrayList<>(); private MapboxMap mapboxMap; private MapboxMap.OnMarkerClickListener onMarkerClickListener; + private MapboxMap.OnPolygonClickListener onPolygonClickListener; + private MapboxMap.OnPolylineClickListener onPolylineClickListener; AnnotationManager(NativeMapView view, MapView mapView, MarkerViewManager markerViewManager) { this.nativeMapView = view; @@ -101,6 +109,9 @@ class AnnotationManager { if (marker instanceof MarkerView) { markerViewManager.removeMarkerView((MarkerView) marker); } + } else { + // instanceOf Polygon/Polyline + shapeAnnotationIds.remove(annotation.getId()); } long id = annotation.getId(); if (nativeMapView != null) { @@ -131,6 +142,9 @@ class AnnotationManager { if (marker instanceof MarkerView) { markerViewManager.removeMarkerView((MarkerView) marker); } + } else { + // instanceOf Polygon/Polyline + shapeAnnotationIds.remove(annotation.getId()); } ids[i] = annotationList.get(i).getId(); } @@ -158,6 +172,9 @@ class AnnotationManager { if (marker instanceof MarkerView) { markerViewManager.removeMarkerView((MarkerView) marker); } + } else { + // instanceOf Polygon/Polyline + shapeAnnotationIds.remove(annotation.getId()); } } @@ -244,7 +261,6 @@ class AnnotationManager { return marker; } - List addMarkerViews(@NonNull List markerViewOptions, @NonNull MapboxMap mapboxMap) { List markers = new ArrayList<>(); @@ -278,7 +294,6 @@ class AnnotationManager { Timber.w("Attempting to update non-added Marker with value %s", updatedMarker); return; } - ensureIconLoaded(updatedMarker); nativeMapView.updateMarker(updatedMarker); annotations.setValueAt(annotations.indexOfKey(updatedMarker.getId()), updatedMarker); @@ -310,6 +325,14 @@ class AnnotationManager { onMarkerClickListener = listener; } + void setOnPolygonClickListener(@Nullable MapboxMap.OnPolygonClickListener listener) { + onPolygonClickListener = listener; + } + + void setOnPolylineClickListener(@Nullable MapboxMap.OnPolylineClickListener listener) { + onPolylineClickListener = listener; + } + void selectMarker(@NonNull Marker marker) { if (selectedMarkers.contains(marker)) { return; @@ -374,7 +397,7 @@ class AnnotationManager { @NonNull List getMarkersInRect(@NonNull RectF rectangle) { - // convert Rectangle to be density depedent + // convert Rectangle to be density dependent float pixelRatio = nativeMapView.getPixelRatio(); RectF rect = new RectF(rectangle.left / pixelRatio, rectangle.top / pixelRatio, @@ -438,6 +461,7 @@ class AnnotationManager { long id = nativeMapView != null ? nativeMapView.addPolygon(polygon) : 0; polygon.setId(id); polygon.setMapboxMap(mapboxMap); + shapeAnnotationIds.add(LAYER_ID_SHAPE_ANNOTATIONS + id); annotations.put(id, polygon); } return polygon; @@ -472,6 +496,7 @@ class AnnotationManager { id++; } polygon.setId(id); + shapeAnnotationIds.add(LAYER_ID_SHAPE_ANNOTATIONS + id); annotations.put(id, polygon); } } @@ -510,6 +535,7 @@ class AnnotationManager { long id = nativeMapView != null ? nativeMapView.addPolyline(polyline) : 0; polyline.setMapboxMap(mapboxMap); polyline.setId(id); + shapeAnnotationIds.add(LAYER_ID_SHAPE_ANNOTATIONS + id); annotations.put(id, polyline); } return polyline; @@ -546,6 +572,7 @@ class AnnotationManager { id++; } p.setId(id); + shapeAnnotationIds.add(LAYER_ID_SHAPE_ANNOTATIONS + id); annotations.put(id, p); } } @@ -555,6 +582,7 @@ class AnnotationManager { void updatePolyline(@NonNull Polyline polyline) { if (!isAddedToMap(polyline)) { Timber.w("Attempting to update non-added Polyline with value %s", polyline); + return; } nativeMapView.updatePolyline(polyline); @@ -619,11 +647,37 @@ class AnnotationManager { // boolean onTap(PointF tapPoint) { + ShapeAnnotationHit shapeAnnotationHit = getShapeAnnotationHitFromTap(tapPoint); + long shapeAnnotationId = new ShapeAnnotationHitResolver(mapboxMap).execute(shapeAnnotationHit); + if (shapeAnnotationId >= 0) { + handleClickForShapeAnnotation(shapeAnnotationId); + } + MarkerHit markerHit = getMarkerHitFromTouchArea(tapPoint); - long markerId = new MarkerHitResolver(markerViewManager, mapboxMap.getProjection()).execute(markerHit); + long markerId = new MarkerHitResolver(mapboxMap).execute(markerHit); return markerId >= 0 && isClickHandledForMarker(markerId); } + private ShapeAnnotationHit getShapeAnnotationHitFromTap(PointF tapPoint) { + float touchTargetSide = Mapbox.getApplicationContext().getResources().getDimension(R.dimen.mapbox_eight_dp); + RectF tapRect = new RectF( + tapPoint.x - touchTargetSide, + tapPoint.y - touchTargetSide, + tapPoint.x + touchTargetSide, + tapPoint.y + touchTargetSide + ); + return new ShapeAnnotationHit(tapRect, shapeAnnotationIds.toArray(new String[shapeAnnotationIds.size()])); + } + + private void handleClickForShapeAnnotation(long shapeAnnotationId) { + Annotation annotation = getAnnotation(shapeAnnotationId); + if (annotation instanceof Polygon && onPolygonClickListener != null) { + onPolygonClickListener.onPolygonClick((Polygon) annotation); + } else if (annotation instanceof Polyline && onPolylineClickListener != null) { + onPolylineClickListener.onPolylineClick((Polyline) annotation); + } + } + private MarkerHit getMarkerHitFromTouchArea(PointF tapPoint) { int averageIconWidthOffset = iconManager.getAverageIconWidth() / 2; int averageIconHeightOffset = iconManager.getAverageIconHeight() / 2; @@ -645,7 +699,7 @@ class AnnotationManager { } if (!handledDefaultClick) { - setMarkerSelectionState(marker); + toggleMarkerSelectionState(marker); } return true; } @@ -654,7 +708,7 @@ class AnnotationManager { return onMarkerClickListener != null && onMarkerClickListener.onMarkerClick(marker); } - private void setMarkerSelectionState(Marker marker) { + private void toggleMarkerSelectionState(Marker marker) { if (!selectedMarkers.contains(marker)) { selectMarker(marker); } else { @@ -662,6 +716,20 @@ class AnnotationManager { } } + private static class ShapeAnnotationHitResolver { + + private MapboxMap mapboxMap; + + ShapeAnnotationHitResolver(MapboxMap mapboxMap) { + this.mapboxMap = mapboxMap; + } + + public long execute(ShapeAnnotationHit shapeHit) { + List features = mapboxMap.queryRenderedFeatures(shapeHit.tapPoint, shapeHit.layerIds); + return features.isEmpty() ? -1 : Long.valueOf(features.get(0).getId()); + } + } + private static class MarkerHitResolver { private final MarkerViewManager markerViewManager; @@ -677,9 +745,9 @@ class AnnotationManager { private long closestMarkerId = -1; - MarkerHitResolver(@NonNull MarkerViewManager markerViewManager, @NonNull Projection projection) { - this.markerViewManager = markerViewManager; - this.projection = projection; + MarkerHitResolver(@NonNull MapboxMap mapboxMap) { + this.markerViewManager = mapboxMap.getMarkerViewManager(); + this.projection = mapboxMap.getProjection(); } public long execute(MarkerHit markerHit) { @@ -732,6 +800,16 @@ class AnnotationManager { } } + private static class ShapeAnnotationHit { + private final RectF tapPoint; + private final String[] layerIds; + + ShapeAnnotationHit(RectF tapRect, String[] layerIds) { + this.tapPoint = tapRect; + this.layerIds = layerIds; + } + } + private static class MarkerHit { private final RectF tapRect; private final List markers; diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java index d672ab37c..c8725d8d8 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java @@ -1459,6 +1459,28 @@ public final class MapboxMap { annotationManager.setOnMarkerClickListener(listener); } + /** + * Sets a callback that's invoked when the user clicks on a polygon. + * + * @param listener The callback that's invoked when the user clicks on a polygon. + * To unset the callback, use null. + */ + @UiThread + public void setOnPolygonClickListener(@Nullable OnPolygonClickListener listener) { + annotationManager.setOnPolygonClickListener(listener); + } + + /** + * Sets a callback that's invoked when the user clicks on a polyline. + * + * @param listener The callback that's invoked when the user clicks on a polyline. + * To unset the callback, use null. + */ + @UiThread + public void setOnPolylineClickListener(@Nullable OnPolylineClickListener listener) { + annotationManager.setOnPolylineClickListener(listener); + } + /** *

* Selects a marker. The selected marker will have it's info window opened. @@ -2150,6 +2172,34 @@ public final class MapboxMap { boolean onMarkerClick(@NonNull Marker marker); } + /** + * Interface definition for a callback to be invoked when the user clicks on a polygon. + * + * @see MapboxMap#setOnPolygonClickListener(OnPolygonClickListener) + */ + public interface OnPolygonClickListener { + /** + * Called when the user clicks on a polygon. + * + * @param polygon The polygon the user clicked on. + */ + void onPolygonClick(@NonNull Polygon polygon); + } + + /** + * Interface definition for a callback to be invoked when the user clicks on a polyline. + * + * @see MapboxMap#setOnPolylineClickListener(OnPolylineClickListener) + */ + public interface OnPolylineClickListener { + /** + * Called when the user clicks on a polyline. + * + * @param polyline The polyline the user clicked on. + */ + void onPolylineClick(@NonNull Polyline polyline); + } + /** * Interface definition for a callback to be invoked when the user clicks on an info window. * diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/annotation/PolygonActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/annotation/PolygonActivity.java index b51d717f3..fecfe2a84 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/annotation/PolygonActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/annotation/PolygonActivity.java @@ -2,9 +2,11 @@ package com.mapbox.mapboxsdk.testapp.activity.annotation; import android.graphics.Color; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.v7.app.AppCompatActivity; import android.view.Menu; import android.view.MenuItem; +import android.widget.Toast; import com.mapbox.mapboxsdk.annotations.Polygon; import com.mapbox.mapboxsdk.annotations.PolygonOptions; @@ -77,6 +79,18 @@ public class PolygonActivity extends AppCompatActivity implements OnMapReadyCall @Override public void onMapReady(MapboxMap map) { mapboxMap = map; + + map.setOnPolygonClickListener(new MapboxMap.OnPolygonClickListener() { + @Override + public void onPolygonClick(@NonNull Polygon polygon) { + Toast.makeText( + PolygonActivity.this, + "You clicked on polygon with id = " + polygon.getId(), + Toast.LENGTH_SHORT + ).show(); + } + }); + polygon = mapboxMap.addPolygon(new PolygonOptions() .addAll(STAR_SHAPE_POINTS) .fillColor(BLUE_COLOR)); diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/annotation/PolylineActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/annotation/PolylineActivity.java index 0aaa6127f..b9dc39e5d 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/annotation/PolylineActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/annotation/PolylineActivity.java @@ -67,8 +67,20 @@ public class PolylineActivity extends AppCompatActivity { mapView.onCreate(savedInstanceState); mapView.getMapAsync(new OnMapReadyCallback() { @Override - public void onMapReady(@NonNull MapboxMap mapboxMap) { + public void onMapReady(@NonNull final MapboxMap mapboxMap) { PolylineActivity.this.mapboxMap = mapboxMap; + + mapboxMap.setOnPolylineClickListener(new MapboxMap.OnPolylineClickListener() { + @Override + public void onPolylineClick(@NonNull Polyline polyline) { + Toast.makeText( + PolylineActivity.this, + "You clicked on polygon with id = " + polyline.getId(), + Toast.LENGTH_SHORT + ).show(); + } + }); + polylines = mapboxMap.addPolylines(polylineOptions); } }); From 108cbc7ba58b3597b779029323e56b54f29546f0 Mon Sep 17 00:00:00 2001 From: Tobrun Van Nuland Date: Thu, 20 Jul 2017 11:44:53 +0200 Subject: [PATCH 31/71] [android] - update changelog for 5.1.1 --- platform/android/CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/platform/android/CHANGELOG.md b/platform/android/CHANGELOG.md index 0a3615d80..33ac48696 100644 --- a/platform/android/CHANGELOG.md +++ b/platform/android/CHANGELOG.md @@ -4,6 +4,16 @@ Mapbox welcomes participation and contributions from everyone. If you'd like to ## 5.1.1 - TBA +* Rework attribution binding [#9433](https://github.com/mapbox/mapbox-gl-native/pull/9433) +* BackendScope changes [#9538](https://github.com/mapbox/mapbox-gl-native/pull/9538) +* Invisible MarkerView performance fix [#9420](https://github.com/mapbox/mapbox-gl-native/pull/9420) +* Polyline/Polygon click listeners [#9443](https://github.com/mapbox/mapbox-gl-native/pull/9443) +* Hit test Marker/MarkerViews [#9424](https://github.com/mapbox/mapbox-gl-native/pull/9424) +* Fine tune fling gesture [#9532](https://github.com/mapbox/mapbox-gl-native/pull/9532) +* Bump OkHttp 3.8.0, Android 7.x crashes [#9522](https://github.com/mapbox/mapbox-gl-native/pull/9522) +* MyLocationView tint fix [#9410](https://github.com/mapbox/mapbox-gl-native/pull/9410) +* VisibleRegion logic [#9428](https://github.com/mapbox/mapbox-gl-native/pull/9428) +* Validate motion event [#9434](https://github.com/mapbox/mapbox-gl-native/pull/9434) * Add French localization [#9545](https://github.com/mapbox/mapbox-gl-native/pull/9545) ## 5.1.0 - June 30, 2017 From a39b4cfb00c567d93bb83390d9bbdccef12b1c65 Mon Sep 17 00:00:00 2001 From: Tobrun Date: Thu, 20 Jul 2017 17:26:28 +0200 Subject: [PATCH 32/71] [android] - manage InfoWindow selection in AnnotationManager (#9567) --- .../com/mapbox/mapboxsdk/annotations/MarkerViewManager.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewManager.java index dce6f6b27..8704e882e 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewManager.java @@ -570,11 +570,6 @@ public class MarkerViewManager implements MapView.OnMapChangedListener { clickHandled = onMarkerViewClickListener.onMarkerClick(markerView, view, adapter); } - if (!clickHandled) { - ensureInfoWindowOffset(markerView); - select(markerView, view, adapter); - } - return clickHandled; } From 820c04efbfa5c7e30bf5ff055538ccdd6e11b14d Mon Sep 17 00:00:00 2001 From: Tobrun Date: Thu, 20 Jul 2017 16:51:27 +0200 Subject: [PATCH 33/71] [android] - increase touch target to match largest Marker icon (#9565) --- .../mapboxsdk/maps/AnnotationManager.java | 12 +++--- .../mapbox/mapboxsdk/maps/IconManager.java | 42 +++++++++---------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java index 404cc5f3b..f35eb9b4f 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java @@ -679,12 +679,12 @@ class AnnotationManager { } private MarkerHit getMarkerHitFromTouchArea(PointF tapPoint) { - int averageIconWidthOffset = iconManager.getAverageIconWidth() / 2; - int averageIconHeightOffset = iconManager.getAverageIconHeight() / 2; - final RectF tapRect = new RectF(tapPoint.x - averageIconWidthOffset, - tapPoint.y - averageIconHeightOffset, - tapPoint.x + averageIconWidthOffset, - tapPoint.y + averageIconHeightOffset + int touchSurfaceWidth = (int) (iconManager.getHighestIconHeight() * 1.5); + int touchSurfaceHeight = (int) (iconManager.getHighestIconWidth() * 1.5); + final RectF tapRect = new RectF(tapPoint.x - touchSurfaceWidth, + tapPoint.y - touchSurfaceHeight, + tapPoint.x + touchSurfaceWidth, + tapPoint.y + touchSurfaceHeight ); return new MarkerHit(tapRect, getMarkersInRect(tapRect)); } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/IconManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/IconManager.java index b5da5f59a..18eecfd9c 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/IconManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/IconManager.java @@ -12,8 +12,6 @@ import com.mapbox.mapboxsdk.exceptions.IconBitmapChangedException; import java.util.ArrayList; import java.util.List; -import timber.log.Timber; - /** * Responsible for managing icons added to the Map. *

@@ -30,8 +28,8 @@ class IconManager { private NativeMapView nativeMapView; private List icons; - private int averageIconHeight; - private int averageIconWidth; + private int highestIconWidth; + private int highestIconHeight; IconManager(NativeMapView nativeMapView) { this.nativeMapView = nativeMapView; @@ -47,7 +45,7 @@ class IconManager { // TODO we can move this code afterwards to getIcon as with MarkerView.getIcon icon = loadDefaultIconForMarker(marker); } else { - updateAverageIconSize(icon); + updateHighestIconSize(icon); } addIcon(icon); return icon; @@ -56,7 +54,7 @@ class IconManager { void loadIconForMarkerView(MarkerView marker) { Icon icon = marker.getIcon(); Bitmap bitmap = icon.getBitmap(); - updateAverageIconSize(bitmap); + updateHighestIconSize(bitmap); addIcon(icon, false); } @@ -64,18 +62,18 @@ class IconManager { return (int) (nativeMapView.getTopOffsetPixelsForAnnotationSymbol(icon.getId()) * nativeMapView.getPixelRatio()); } - int getAverageIconHeight() { - return averageIconHeight; + int getHighestIconWidth() { + return highestIconWidth; } - int getAverageIconWidth() { - return averageIconWidth; + int getHighestIconHeight() { + return highestIconHeight; } private Icon loadDefaultIconForMarker(Marker marker) { Icon icon = IconFactory.getInstance(Mapbox.getApplicationContext()).defaultMarker(); Bitmap bitmap = icon.getBitmap(); - updateAverageIconSize(bitmap.getWidth(), bitmap.getHeight() / 2); + updateHighestIconSize(bitmap.getWidth(), bitmap.getHeight() / 2); marker.setIcon(icon); return icon; } @@ -95,20 +93,22 @@ class IconManager { } } - private void updateAverageIconSize(Icon icon) { - updateAverageIconSize(icon.getBitmap()); + private void updateHighestIconSize(Icon icon) { + updateHighestIconSize(icon.getBitmap()); } - private void updateAverageIconSize(Bitmap bitmap) { - updateAverageIconSize(bitmap.getWidth(), bitmap.getHeight()); + private void updateHighestIconSize(Bitmap bitmap) { + updateHighestIconSize(bitmap.getWidth(), bitmap.getHeight()); } - private void updateAverageIconSize(int width, int height) { - int iconSize = icons.size() + 1; - averageIconHeight = averageIconHeight + (height - averageIconHeight) / iconSize; - averageIconWidth = averageIconWidth + (width - averageIconWidth) / iconSize; - Timber.e("OnUpdateAverageSizeIcon with: %s %s", width, height); - Timber.e("OnUpdateAverageSizeIcon now: %s %s", averageIconWidth, averageIconHeight); + private void updateHighestIconSize(int width, int height) { + if (width > highestIconWidth) { + highestIconWidth = width; + } + + if (height > highestIconHeight) { + highestIconHeight = height; + } } private void loadIcon(Icon icon) { From b34a5ab276145dd5499133738caf0e8d6d30ddb7 Mon Sep 17 00:00:00 2001 From: Tobrun Van Nuland Date: Thu, 20 Jul 2017 17:32:23 +0200 Subject: [PATCH 34/71] [android] - update change log, fix typo public xml --- platform/android/CHANGELOG.md | 4 ++++ .../MapboxGLAndroidSDK/src/main/res-public/values/public.xml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/platform/android/CHANGELOG.md b/platform/android/CHANGELOG.md index 33ac48696..b2eaaa2a9 100644 --- a/platform/android/CHANGELOG.md +++ b/platform/android/CHANGELOG.md @@ -15,6 +15,10 @@ Mapbox welcomes participation and contributions from everyone. If you'd like to * VisibleRegion logic [#9428](https://github.com/mapbox/mapbox-gl-native/pull/9428) * Validate motion event [#9434](https://github.com/mapbox/mapbox-gl-native/pull/9434) * Add French localization [#9545](https://github.com/mapbox/mapbox-gl-native/pull/9545) +* Fix public.xml [#9525](https://github.com/mapbox/mapbox-gl-native/pull/9525/files) +* Manage InfoWindow selection in AnnotationManager [#9567](https://github.com/mapbox/mapbox-gl-native/pull/9567) +* Increase touch target size [#9565](https://github.com/mapbox/mapbox-gl-native/pull/9565) +* Work around Adreno 2xx GPU bugs [#9573](https://github.com/mapbox/mapbox-gl-native/pull/9573) ## 5.1.0 - June 30, 2017 diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml b/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml index 7294f43c0..f30cb7c27 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml +++ b/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml @@ -20,7 +20,7 @@ - + From ea337866e9870e95a977308f0926b15e25c7a0ce Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 21 Jul 2017 01:17:50 -0700 Subject: [PATCH 35/71] [core] Work around Adreno 2xx GPU bugs (#9573) --- src/mbgl/gl/context.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/mbgl/gl/context.cpp b/src/mbgl/gl/context.cpp index 19ea9a514..e5609db7b 100644 --- a/src/mbgl/gl/context.cpp +++ b/src/mbgl/gl/context.cpp @@ -564,6 +564,13 @@ void Context::setDrawMode(const TriangleStrip&) { void Context::setDepthMode(const DepthMode& depth) { if (depth.func == DepthMode::Always && !depth.mask) { depthTest = false; + + // Workaround for rendering errors on Adreno 2xx GPUs. Depth-related state should + // not matter when the depth test is disabled, but on these GPUs it apparently does. + // https://github.com/mapbox/mapbox-gl-native/issues/9164 + depthFunc = depth.func; + depthMask = depth.mask; + depthRange = depth.range; } else { depthTest = true; depthFunc = depth.func; From 7aad3613c3549ae5f4388fcbeef969e13274892b Mon Sep 17 00:00:00 2001 From: Tobrun Van Nuland Date: Fri, 21 Jul 2017 11:18:26 +0200 Subject: [PATCH 36/71] update release config --- circle.yml | 2 +- .../fabric/com.mapbox.mapboxsdk.mapbox-android-sdk.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/circle.yml b/circle.yml index 6bced990d..7d75427bc 100644 --- a/circle.yml +++ b/circle.yml @@ -133,4 +133,4 @@ jobs: - deploy: name: Publish to Maven command: | - if [ "${CIRCLE_BRANCH}" == "release-ios-v3.6.0-android-v5.1.0" ]; then make run-android-upload-archives ; fi + if [ "${CIRCLE_BRANCH}" == "master" ]; then make run-android-upload-archives ; fi diff --git a/platform/android/MapboxGLAndroidSDK/src/main/resources/fabric/com.mapbox.mapboxsdk.mapbox-android-sdk.properties b/platform/android/MapboxGLAndroidSDK/src/main/resources/fabric/com.mapbox.mapboxsdk.mapbox-android-sdk.properties index 029d25e04..a02b5d63a 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/resources/fabric/com.mapbox.mapboxsdk.mapbox-android-sdk.properties +++ b/platform/android/MapboxGLAndroidSDK/src/main/resources/fabric/com.mapbox.mapboxsdk.mapbox-android-sdk.properties @@ -1,3 +1,3 @@ fabric-identifier=com.mapbox.mapboxsdk.mapbox-android-sdk -fabric-version=5.1.0 +fabric-version=5.1.1 fabric-build-type=binary From dcd9a38388fe5fc4c70a3e39af4db10a3c09ed49 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Mon, 24 Jul 2017 19:24:24 -0400 Subject: [PATCH 37/71] [ios] Don't force the attribution button tint color to update --- platform/ios/CHANGELOG.md | 1 + platform/ios/src/MGLMapView.mm | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 93da2ff37..9ebf52311 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.6.1 * Reduced the size of the dynamic framework by optimizing symbol visibility. ([#7604](https://github.com/mapbox/mapbox-gl-native/pull/7604)) +* Fixed an issue where the attribution button would have its custom tint color reset when the map view received a tint color change notification, such as when an alert controller was presented. ([#9598](https://github.com/mapbox/mapbox-gl-native/pull/9598)) ## 3.6.0 - June 29, 2017 diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm index f3b0d8506..2de149699 100644 --- a/platform/ios/src/MGLMapView.mm +++ b/platform/ios/src/MGLMapView.mm @@ -1144,8 +1144,10 @@ public: - (void)updateTintColorForView:(UIView *)view { - // stop at recursing container & annotation views (#8522) - if ([view isEqual:self.annotationContainerView]) return; + // Don't update: + // - annotation views + // - attribution button (handled automatically) + if ([view isEqual:self.annotationContainerView] || [view isEqual:self.attributionButton]) return; if ([view respondsToSelector:@selector(setTintColor:)]) view.tintColor = self.tintColor; From 612a1e8377834211a273b30aff51839a219b5b73 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Fri, 14 Jul 2017 21:40:40 -0400 Subject: [PATCH 38/71] [build] Remove unused iOS 'self contained' static framework packaging This was a faux framework once required by Fabric. --- platform/ios/scripts/package.sh | 34 ++++++++++----------------------- 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/platform/ios/scripts/package.sh b/platform/ios/scripts/package.sh index e4403c465..6222ee0f9 100755 --- a/platform/ios/scripts/package.sh +++ b/platform/ios/scripts/package.sh @@ -21,33 +21,17 @@ elif [[ ${FORMAT} == "dynamic" ]]; then BUILD_STATIC=false fi -SELF_CONTAINED=${SELF_CONTAINED:-} -STATIC_BUNDLE_DIR= -if [[ ${SELF_CONTAINED} ]]; then - STATIC_BUNDLE_DIR="${OUTPUT}/static/${NAME}.framework" -else - STATIC_BUNDLE_DIR="${OUTPUT}/static" -fi - -STATIC_SETTINGS_DIR= -if [[ ${SELF_CONTAINED} ]]; then - STATIC_SETTINGS_DIR="${OUTPUT}/static/${NAME}.framework" -else - STATIC_SETTINGS_DIR="${OUTPUT}" -fi - SDK=iphonesimulator if [[ ${BUILD_FOR_DEVICE} == true ]]; then SDK=iphoneos fi IOS_SDK_VERSION=`xcrun --sdk ${SDK} --show-sdk-version` -echo "Configuring ${FORMAT:-dynamic and static} ${BUILDTYPE} framework for ${SDK}; symbols: ${SYMBOLS}; self-contained static framework: ${SELF_CONTAINED:-NO}" - function step { >&2 echo -e "\033[1m\033[36m* $@\033[0m"; } function finish { >&2 echo -en "\033[0m"; } trap finish EXIT +step "Configuring ${FORMAT:-dynamic and static} ${BUILDTYPE} framework for ${SDK}; symbols: ${SYMBOLS}" rm -rf ${OUTPUT} if [[ ${BUILD_STATIC} == true ]]; then @@ -69,7 +53,7 @@ PROJ_VERSION=$(git rev-list --count HEAD) SEM_VERSION=$( git describe --tags --match=ios-v*.*.* --abbrev=0 | sed 's/^ios-v//' ) SHORT_VERSION=${SEM_VERSION%-*} -step "Building targets (build ${PROJ_VERSION}, version ${SEM_VERSION})…" +step "Building targets (build ${PROJ_VERSION}, version ${SEM_VERSION})" SCHEME='dynamic' if [[ ${BUILD_DYNAMIC} == true && ${BUILD_STATIC} == true ]]; then @@ -78,6 +62,7 @@ elif [[ ${BUILD_STATIC} == true ]]; then SCHEME='static' fi +step "Building for iOS Simulator using scheme ${SCHEME}" xcodebuild \ CURRENT_PROJECT_VERSION=${PROJ_VERSION} \ CURRENT_SHORT_VERSION=${SHORT_VERSION} \ @@ -92,6 +77,7 @@ xcodebuild \ -jobs ${JOBS} | xcpretty if [[ ${BUILD_FOR_DEVICE} == true ]]; then + step "Building for iOS devices using scheme ${SCHEME}" xcodebuild \ CURRENT_PROJECT_VERSION=${PROJ_VERSION} \ CURRENT_SHORT_VERSION=${SHORT_VERSION} \ @@ -119,7 +105,7 @@ if [[ ${BUILD_FOR_DEVICE} == true ]]; then ${LIBS[@]/#/${PRODUCTS}/${BUILDTYPE}-iphonesimulator/lib} \ `cmake -LA -N ${DERIVED_DATA} | grep MASON_PACKAGE_icu_LIBRARIES | cut -d= -f2` - cp -rv ${PRODUCTS}/${BUILDTYPE}-iphoneos/${NAME}.bundle ${STATIC_BUNDLE_DIR} + cp -rv ${PRODUCTS}/${BUILDTYPE}-iphoneos/${NAME}.bundle ${OUTPUT}/static fi if [[ ${BUILD_DYNAMIC} == true ]]; then @@ -149,7 +135,7 @@ if [[ ${BUILD_FOR_DEVICE} == true ]]; then -create -output ${OUTPUT}/dynamic/${NAME}.framework/${NAME} | echo fi - cp -rv ${PRODUCTS}/${BUILDTYPE}-iphoneos/Settings.bundle ${STATIC_SETTINGS_DIR} + cp -rv ${PRODUCTS}/${BUILDTYPE}-iphoneos/Settings.bundle ${OUTPUT} else if [[ ${BUILD_STATIC} == true ]]; then step "Assembling static library for iOS Simulator…" @@ -159,7 +145,7 @@ else ${LIBS[@]/#/${PRODUCTS}/${BUILDTYPE}-iphonesimulator/lib} \ `cmake -LA -N ${DERIVED_DATA} | grep MASON_PACKAGE_icu_LIBRARIES | cut -d= -f2` - cp -rv ${PRODUCTS}/${BUILDTYPE}-iphonesimulator/${NAME}.bundle ${STATIC_BUNDLE_DIR} + cp -rv ${PRODUCTS}/${BUILDTYPE}-iphonesimulator/${NAME}.bundle ${OUTPUT}/static fi if [[ ${BUILD_DYNAMIC} == true ]]; then @@ -174,7 +160,7 @@ else fi fi - cp -rv ${PRODUCTS}/${BUILDTYPE}-iphonesimulator/Settings.bundle ${STATIC_SETTINGS_DIR} + cp -rv ${PRODUCTS}/${BUILDTYPE}-iphonesimulator/Settings.bundle ${OUTPUT} fi if [[ ${SYMBOLS} = NO ]]; then @@ -244,9 +230,9 @@ if [[ ${BUILD_STATIC} == true ]]; then fi step "Copying library resources…" -cp -pv LICENSE.md ${STATIC_SETTINGS_DIR} +cp -pv LICENSE.md ${OUTPUT} if [[ ${BUILD_STATIC} == true ]]; then - cp -pv "${STATIC_BUNDLE_DIR}/${NAME}.bundle/Info.plist" "${OUTPUT}/static/${NAME}.framework/Info.plist" + cp -pv "${OUTPUT}/static/${NAME}.bundle/Info.plist" "${OUTPUT}/static/${NAME}.framework/Info.plist" plutil -replace CFBundlePackageType -string FMWK "${OUTPUT}/static/${NAME}.framework/Info.plist" mkdir "${OUTPUT}/static/${NAME}.framework/Modules" cp -pv platform/ios/framework/modulemap "${OUTPUT}/static/${NAME}.framework/Modules/module.modulemap" From 82c2d4b3ca76c6b73564cd96d93ebd39969eb820 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Tue, 25 Jul 2017 23:58:21 -0400 Subject: [PATCH 39/71] [build] Include bcsymbolmap files in iOS dynamic framework package --- platform/ios/CHANGELOG.md | 1 + platform/ios/scripts/package.sh | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 9ebf52311..63e6a4d39 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -6,6 +6,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT * Reduced the size of the dynamic framework by optimizing symbol visibility. ([#7604](https://github.com/mapbox/mapbox-gl-native/pull/7604)) * Fixed an issue where the attribution button would have its custom tint color reset when the map view received a tint color change notification, such as when an alert controller was presented. ([#9598](https://github.com/mapbox/mapbox-gl-native/pull/9598)) +* Bitcode symbol maps (.bcsymbolmap files) are now included with the dynamic framework. ([#9613](https://github.com/mapbox/mapbox-gl-native/pull/9613)) ## 3.6.0 - June 29, 2017 diff --git a/platform/ios/scripts/package.sh b/platform/ios/scripts/package.sh index 6222ee0f9..a5e2f87e2 100755 --- a/platform/ios/scripts/package.sh +++ b/platform/ios/scripts/package.sh @@ -237,6 +237,10 @@ if [[ ${BUILD_STATIC} == true ]]; then mkdir "${OUTPUT}/static/${NAME}.framework/Modules" cp -pv platform/ios/framework/modulemap "${OUTPUT}/static/${NAME}.framework/Modules/module.modulemap" fi +if [[ ${BUILD_DYNAMIC} == true && ${BUILD_FOR_DEVICE} == true ]]; then + step "Copying bitcode symbol maps…" + find "${PRODUCTS}/${BUILDTYPE}-iphoneos" -name '*.bcsymbolmap' -type f -exec cp -pv {} "${OUTPUT}/dynamic/" \; +fi sed -n -e '/^## /,$p' platform/ios/CHANGELOG.md > "${OUTPUT}/CHANGELOG.md" rm -rf /tmp/mbgl From fa0b307680aa47537e04f48134c8a38c2cb7e17c Mon Sep 17 00:00:00 2001 From: Antonio Zugaldia Date: Thu, 27 Jul 2017 09:20:40 -0400 Subject: [PATCH 40/71] [android] Disable program caching on Adreno 3xx, 4xx, and 5xx GPUs due to known bugs (#9625) --- src/mbgl/gl/context.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/mbgl/gl/context.cpp b/src/mbgl/gl/context.cpp index e5609db7b..a530528da 100644 --- a/src/mbgl/gl/context.cpp +++ b/src/mbgl/gl/context.cpp @@ -203,7 +203,21 @@ bool Context::supportsVertexArrays() const { #if MBGL_HAS_BINARY_PROGRAMS bool Context::supportsProgramBinaries() const { - return programBinary && programBinary->programBinary && programBinary->getProgramBinary; + if (!programBinary || !programBinary->programBinary || !programBinary->getProgramBinary) { + return false; + } + + // Blacklist Adreno 3xx, 4xx, and 5xx GPUs due to known bugs: + // https://bugs.chromium.org/p/chromium/issues/detail?id=510637 + // https://chromium.googlesource.com/chromium/src/gpu/+/master/config/gpu_driver_bug_list.json#2316 + const std::string renderer = reinterpret_cast(glGetString(GL_RENDERER)); + if (renderer.find("Adreno (TM) 3") != std::string::npos + || renderer.find("Adreno (TM) 4") != std::string::npos + || renderer.find("Adreno (TM) 5") != std::string::npos) { + return false; + } + + return true; } optional> From 0cdef85798f7d2f78a732bb637f378560c1dce58 Mon Sep 17 00:00:00 2001 From: danielamitaysc Date: Mon, 24 Jul 2017 15:32:39 -0400 Subject: [PATCH 41/71] [ios] Proper limiting behavior when trying to zoom out fully - Bail from quick zoom if new zoom is same as old zoom - Allow pinch gesture at minZoom to still move the map --- platform/ios/CHANGELOG.md | 1 + platform/ios/src/MGLMapView.mm | 16 +++++++--------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 63e6a4d39..a8efb491d 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -6,6 +6,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT * Reduced the size of the dynamic framework by optimizing symbol visibility. ([#7604](https://github.com/mapbox/mapbox-gl-native/pull/7604)) * Fixed an issue where the attribution button would have its custom tint color reset when the map view received a tint color change notification, such as when an alert controller was presented. ([#9598](https://github.com/mapbox/mapbox-gl-native/pull/9598)) +* Improved the behavior of zoom gestures when the map reaches the minimum zoom limit. ([#9626](https://github.com/mapbox/mapbox-gl-native/pull/9626)) * Bitcode symbol maps (.bcsymbolmap files) are now included with the dynamic framework. ([#9613](https://github.com/mapbox/mapbox-gl-native/pull/9613)) ## 3.6.0 - June 29, 2017 diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm index 2de149699..3db0bc03b 100644 --- a/platform/ios/src/MGLMapView.mm +++ b/platform/ios/src/MGLMapView.mm @@ -1280,8 +1280,6 @@ public: { if ( ! self.isZoomEnabled) return; - if (_mbglMap->getZoom() <= _mbglMap->getMinZoom() && pinch.scale < 1) return; - _mbglMap->cancelTransitions(); CGPoint centerPoint = [self anchorPointForGesture:pinch]; @@ -1297,17 +1295,17 @@ public: } else if (pinch.state == UIGestureRecognizerStateChanged) { + // Zoom limiting happens at the core level. CGFloat newScale = self.scale * pinch.scale; - double zoom = log2(newScale); - if (zoom < _mbglMap->getMinZoom()) return; - + double newZoom = log2(newScale); + // Calculates the final camera zoom, has no effect within current map camera. - MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:zoom aroundAnchorPoint:centerPoint]; + MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom aroundAnchorPoint:centerPoint]; if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] || [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera]) { - _mbglMap->setZoom(zoom, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }); + _mbglMap->setZoom(newZoom, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }); // The gesture recognizer only reports the gesture’s current center // point, so use the previous center point to anchor the transition. // If the number of touches has changed, the remembered center point is @@ -1655,9 +1653,9 @@ public: { CGFloat distance = [quickZoom locationInView:quickZoom.view].y - self.quickZoomStart; - CGFloat newZoom = log2f(self.scale) + (distance / 75); + CGFloat newZoom = MAX(log2f(self.scale) + (distance / 75), _mbglMap->getMinZoom()); - if (newZoom < _mbglMap->getMinZoom()) return; + if (_mbglMap->getZoom() == newZoom) return; CGPoint centerPoint = [self anchorPointForGesture:quickZoom]; From 6d629597d7e05684ece16ae4239f5ab06ec386e2 Mon Sep 17 00:00:00 2001 From: Fabian Guerra Soto Date: Thu, 27 Jul 2017 15:16:39 -0700 Subject: [PATCH 42/71] [ios] Make tilt gesture recognizer recognizable only with two horizontal fingers (#9571) * [ios] Make tilt gesture recognizer recognizable only with two horizontal fingers * [ios] Make tilt gesture recognizer tolerance degree less restrictive --- platform/ios/src/MGLMapView.mm | 81 ++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 33 deletions(-) diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm index 3db0bc03b..8dbf26da5 100644 --- a/platform/ios/src/MGLMapView.mm +++ b/platform/ios/src/MGLMapView.mm @@ -1788,39 +1788,6 @@ public: return [gesture locationInView:gesture.view]; } -- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer -{ - if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) - { - UIPanGestureRecognizer *panGesture = (UIPanGestureRecognizer *)gestureRecognizer; - - if (panGesture.minimumNumberOfTouches == 2) - { - CGPoint velocity = [panGesture velocityInView:panGesture.view]; - double gestureAngle = MGLDegreesFromRadians(atan(velocity.y / velocity.x)); - double horizontalToleranceDegrees = 20.0; - - // cancel if gesture angle is not 90º±20º (more or less vertical) - if ( ! (fabs((fabs(gestureAngle) - 90.0)) < horizontalToleranceDegrees)) - { - return NO; - } - } - } - else if (gestureRecognizer == _singleTapGestureRecognizer) - { - // Gesture will be recognized if it could deselect an annotation - if(!self.selectedAnnotation) - { - idannotation = [self annotationForGestureRecognizer:(UITapGestureRecognizer*)gestureRecognizer persistingResults:NO]; - if(!annotation) { - return NO; - } - } - } - return YES; -} - - (void)handleCalloutAccessoryTapGesture:(UITapGestureRecognizer *)tap { if ([self.delegate respondsToSelector:@selector(mapView:annotation:calloutAccessoryControlTapped:)]) @@ -1864,12 +1831,60 @@ public: UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, calloutView); } +- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer +{ + if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) + { + UIPanGestureRecognizer *panGesture = (UIPanGestureRecognizer *)gestureRecognizer; + + if (panGesture.minimumNumberOfTouches == 2) + { + CGPoint west = [panGesture locationOfTouch:0 inView:panGesture.view]; + CGPoint east = [panGesture locationOfTouch:1 inView:panGesture.view]; + + if (west.x > east.x) { + CGPoint swap = west; + west = east; + east = swap; + } + + float horizontalToleranceDegrees = 60.0; + if ([self angleBetweenPoints:west east:east] > horizontalToleranceDegrees) { + return NO; + } + + } + } + else if (gestureRecognizer == _singleTapGestureRecognizer) + { + // Gesture will be recognized if it could deselect an annotation + if(!self.selectedAnnotation) + { + idannotation = [self annotationForGestureRecognizer:(UITapGestureRecognizer*)gestureRecognizer persistingResults:NO]; + if(!annotation) { + return NO; + } + } + } + return YES; +} + - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { NSArray *validSimultaneousGestures = @[ self.pan, self.pinch, self.rotate ]; return ([validSimultaneousGestures containsObject:gestureRecognizer] && [validSimultaneousGestures containsObject:otherGestureRecognizer]); } + +- (float)angleBetweenPoints:(CGPoint)west east:(CGPoint)east +{ + float slope = (west.y - east.y) / (west.x - east.x); + + float angle = atan(fabs(slope)); + float degrees = MGLDegreesFromRadians(angle); + + return degrees; +} - (void)trackGestureEvent:(NSString *)gestureID forRecognizer:(UIGestureRecognizer *)recognizer { From 5e6ca3ada217c84fa2f198e5de6bc89bc2c5874d Mon Sep 17 00:00:00 2001 From: Pablo Guardiola Date: Mon, 24 Jul 2017 12:08:20 +0200 Subject: [PATCH 43/71] [android] fix geojson source proguard issue (#9569) (#9577) --- platform/android/MapboxGLAndroidSDK/proguard-rules.pro | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/platform/android/MapboxGLAndroidSDK/proguard-rules.pro b/platform/android/MapboxGLAndroidSDK/proguard-rules.pro index 8e4781545..68edc12f3 100644 --- a/platform/android/MapboxGLAndroidSDK/proguard-rules.pro +++ b/platform/android/MapboxGLAndroidSDK/proguard-rules.pro @@ -6,4 +6,5 @@ -keep class com.mapbox.mapboxsdk.** { *; } -keep interface com.mapbox.mapboxsdk.** { *; } -keep class com.mapbox.services.android.telemetry.** { *; } --keep class com.mapbox.services.commons.** { *;} \ No newline at end of file +-keep class com.mapbox.services.commons.** { *;} +-keep class com.google.gson.** { *; } \ No newline at end of file From 0eee31e212a9936bcfe0c61a3bf3635a8778a434 Mon Sep 17 00:00:00 2001 From: Tobrun Van Nuland Date: Mon, 24 Jul 2017 10:45:35 +0200 Subject: [PATCH 44/71] [android] - harden click handling of shape annotations --- .../mapboxsdk/maps/AnnotationManager.java | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java index f35eb9b4f..ef794c848 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java @@ -43,6 +43,7 @@ import timber.log.Timber; class AnnotationManager { private static final String LAYER_ID_SHAPE_ANNOTATIONS = "com.mapbox.annotations.shape."; + private static final long NO_ANNOTATION_ID = -1; private final NativeMapView nativeMapView; private final MapView mapView; @@ -649,13 +650,13 @@ class AnnotationManager { boolean onTap(PointF tapPoint) { ShapeAnnotationHit shapeAnnotationHit = getShapeAnnotationHitFromTap(tapPoint); long shapeAnnotationId = new ShapeAnnotationHitResolver(mapboxMap).execute(shapeAnnotationHit); - if (shapeAnnotationId >= 0) { + if (shapeAnnotationId != NO_ANNOTATION_ID) { handleClickForShapeAnnotation(shapeAnnotationId); } MarkerHit markerHit = getMarkerHitFromTouchArea(tapPoint); long markerId = new MarkerHitResolver(mapboxMap).execute(markerHit); - return markerId >= 0 && isClickHandledForMarker(markerId); + return markerId != NO_ANNOTATION_ID && isClickHandledForMarker(markerId); } private ShapeAnnotationHit getShapeAnnotationHitFromTap(PointF tapPoint) { @@ -725,8 +726,21 @@ class AnnotationManager { } public long execute(ShapeAnnotationHit shapeHit) { + long foundAnnotationId = NO_ANNOTATION_ID; List features = mapboxMap.queryRenderedFeatures(shapeHit.tapPoint, shapeHit.layerIds); - return features.isEmpty() ? -1 : Long.valueOf(features.get(0).getId()); + if (!features.isEmpty()) { + foundAnnotationId = getIdFromFeature(features.get(0)); + } + return foundAnnotationId; + } + + private long getIdFromFeature(Feature feature) { + try { + return Long.valueOf(feature.getId()); + } catch (NumberFormatException exception) { + Timber.e(exception, "Couldn't parse feature id to a long, with id: %s", feature.getId()); + return NO_ANNOTATION_ID; + } } } @@ -743,7 +757,7 @@ class AnnotationManager { private RectF hitRectMarker = new RectF(); private RectF highestSurfaceIntersection = new RectF(); - private long closestMarkerId = -1; + private long closestMarkerId = NO_ANNOTATION_ID; MarkerHitResolver(@NonNull MapboxMap mapboxMap) { this.markerViewManager = mapboxMap.getMarkerViewManager(); From beaee9349f0608246547d9092b6690a80d633e6e Mon Sep 17 00:00:00 2001 From: Tobrun Van Nuland Date: Mon, 24 Jul 2017 11:05:29 +0200 Subject: [PATCH 45/71] [android] - validate Marker before opening InfoWindow --- .../com/mapbox/mapboxsdk/maps/InfoWindowManager.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/InfoWindowManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/InfoWindowManager.java index 7599b6afa..11100d6f1 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/InfoWindowManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/InfoWindowManager.java @@ -1,6 +1,5 @@ package com.mapbox.mapboxsdk.maps; -import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.TextUtils; @@ -56,12 +55,8 @@ class InfoWindowManager { return allowConcurrentMultipleInfoWindows; } - List getInfoWindows() { - return infoWindows; - } - - boolean isInfoWindowValidForMarker(@NonNull Marker marker) { - return !TextUtils.isEmpty(marker.getTitle()) || !TextUtils.isEmpty(marker.getSnippet()); + boolean isInfoWindowValidForMarker(Marker marker) { + return marker != null && (!TextUtils.isEmpty(marker.getTitle()) || !TextUtils.isEmpty(marker.getSnippet())); } void setOnInfoWindowClickListener(@Nullable MapboxMap.OnInfoWindowClickListener listener) { From 635a3f21a0373335d4a334f5bb487251f027f47c Mon Sep 17 00:00:00 2001 From: Pablo Guardiola Date: Tue, 25 Jul 2017 13:01:31 +0200 Subject: [PATCH 46/71] [android] fix added to map checks because LongSparseArray#indexOfKey could return a negative number when the specified key is not mapped (#9602) --- .../main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java index ef794c848..251e5afab 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java @@ -301,7 +301,7 @@ class AnnotationManager { } private boolean isAddedToMap(Annotation annotation) { - return annotation != null && annotation.getId() != -1 && annotations.indexOfKey(annotation.getId()) != -1; + return annotation != null && annotation.getId() != -1 && annotations.indexOfKey(annotation.getId()) > -1; } private void ensureIconLoaded(Marker marker) { From 009da507fa780d111bb574154840a061ab284c8f Mon Sep 17 00:00:00 2001 From: Tobrun Van Nuland Date: Tue, 25 Jul 2017 12:50:36 +0200 Subject: [PATCH 47/71] [android] - don't query for shape annotations if none were added --- .../com/mapbox/mapboxsdk/maps/AnnotationManager.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java index 251e5afab..712ab67de 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AnnotationManager.java @@ -648,10 +648,12 @@ class AnnotationManager { // boolean onTap(PointF tapPoint) { - ShapeAnnotationHit shapeAnnotationHit = getShapeAnnotationHitFromTap(tapPoint); - long shapeAnnotationId = new ShapeAnnotationHitResolver(mapboxMap).execute(shapeAnnotationHit); - if (shapeAnnotationId != NO_ANNOTATION_ID) { - handleClickForShapeAnnotation(shapeAnnotationId); + if (!shapeAnnotationIds.isEmpty()) { + ShapeAnnotationHit shapeAnnotationHit = getShapeAnnotationHitFromTap(tapPoint); + long shapeAnnotationId = new ShapeAnnotationHitResolver(mapboxMap).execute(shapeAnnotationHit); + if (shapeAnnotationId != NO_ANNOTATION_ID) { + handleClickForShapeAnnotation(shapeAnnotationId); + } } MarkerHit markerHit = getMarkerHitFromTouchArea(tapPoint); From 9b770f382ef7f1251f0f770175514345899101a8 Mon Sep 17 00:00:00 2001 From: Tobrun Van Nuland Date: Wed, 26 Jul 2017 16:45:49 +0200 Subject: [PATCH 48/71] [android] - build snapshots from release branch for v5.1.2 --- circle.yml | 2 +- platform/android/MapboxGLAndroidSDK/gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/circle.yml b/circle.yml index 7d75427bc..6bced990d 100644 --- a/circle.yml +++ b/circle.yml @@ -133,4 +133,4 @@ jobs: - deploy: name: Publish to Maven command: | - if [ "${CIRCLE_BRANCH}" == "master" ]; then make run-android-upload-archives ; fi + if [ "${CIRCLE_BRANCH}" == "release-ios-v3.6.0-android-v5.1.0" ]; then make run-android-upload-archives ; fi diff --git a/platform/android/MapboxGLAndroidSDK/gradle.properties b/platform/android/MapboxGLAndroidSDK/gradle.properties index 078fdbc80..a8646e327 100644 --- a/platform/android/MapboxGLAndroidSDK/gradle.properties +++ b/platform/android/MapboxGLAndroidSDK/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.mapbox.mapboxsdk -VERSION_NAME=5.2.0-SNAPSHOT +VERSION_NAME=5.1.2-SNAPSHOT POM_DESCRIPTION=Mapbox GL Android SDK POM_URL=https://github.com/mapbox/mapbox-gl-native From caba393c0ec3042a3e8e7f1a782e0283d131a996 Mon Sep 17 00:00:00 2001 From: Pablo Guardiola Date: Thu, 27 Jul 2017 15:38:53 +0200 Subject: [PATCH 49/71] [android] fix compass direction (Transform::getCameraOptions returns counterclockwise angle after transform.invalidateCameraPosition() which needs to be transformed to a clockwise bearing) (#9632) --- .../src/main/java/com/mapbox/mapboxsdk/maps/UiSettings.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/UiSettings.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/UiSettings.java index 7f8ba21e3..3f43c23a7 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/UiSettings.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/UiSettings.java @@ -421,7 +421,8 @@ public final class UiSettings { return; } - compassView.update(cameraPosition.bearing); + double clockwiseBearing = -cameraPosition.bearing; + compassView.update(clockwiseBearing); } /** From b0bae6d1dde4e8c3b0b0b50d4255e1073f36022a Mon Sep 17 00:00:00 2001 From: Fabian Guerra Soto Date: Fri, 28 Jul 2017 10:22:16 -0700 Subject: [PATCH 50/71] [ios] tilt gesture type fix (#9642) * [ios] Change function:angleBetweenPoints return type * [ios] Update changelog --- platform/ios/CHANGELOG.md | 1 + platform/ios/src/MGLMapView.mm | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index a8efb491d..43df49a9a 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -7,6 +7,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT * Reduced the size of the dynamic framework by optimizing symbol visibility. ([#7604](https://github.com/mapbox/mapbox-gl-native/pull/7604)) * Fixed an issue where the attribution button would have its custom tint color reset when the map view received a tint color change notification, such as when an alert controller was presented. ([#9598](https://github.com/mapbox/mapbox-gl-native/pull/9598)) * Improved the behavior of zoom gestures when the map reaches the minimum zoom limit. ([#9626](https://github.com/mapbox/mapbox-gl-native/pull/9626)) +* Fixed an issue where tilt gesture was triggered with two fingers aligned vertically and panning down. ([#9571](https://github.com/mapbox/mapbox-gl-native/pull/9571)) * Bitcode symbol maps (.bcsymbolmap files) are now included with the dynamic framework. ([#9613](https://github.com/mapbox/mapbox-gl-native/pull/9613)) ## 3.6.0 - June 29, 2017 diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm index 8dbf26da5..9cc4c42e5 100644 --- a/platform/ios/src/MGLMapView.mm +++ b/platform/ios/src/MGLMapView.mm @@ -1848,7 +1848,7 @@ public: east = swap; } - float horizontalToleranceDegrees = 60.0; + CLLocationDegrees horizontalToleranceDegrees = 60.0; if ([self angleBetweenPoints:west east:east] > horizontalToleranceDegrees) { return NO; } @@ -1876,12 +1876,12 @@ public: return ([validSimultaneousGestures containsObject:gestureRecognizer] && [validSimultaneousGestures containsObject:otherGestureRecognizer]); } -- (float)angleBetweenPoints:(CGPoint)west east:(CGPoint)east +- (CLLocationDegrees)angleBetweenPoints:(CGPoint)west east:(CGPoint)east { - float slope = (west.y - east.y) / (west.x - east.x); + CGFloat slope = (west.y - east.y) / (west.x - east.x); - float angle = atan(fabs(slope)); - float degrees = MGLDegreesFromRadians(angle); + CGFloat angle = atan(fabs(slope)); + CLLocationDegrees degrees = MGLDegreesFromRadians(angle); return degrees; } From 9a9c43308526dcb043981b68cc5d6e3aae8d987b Mon Sep 17 00:00:00 2001 From: Fabian Guerra Date: Fri, 28 Jul 2017 10:35:24 -0700 Subject: [PATCH 51/71] [ios] Bump podspec to 3.6.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 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/platform/ios/Mapbox-iOS-SDK-nightly-dynamic.podspec b/platform/ios/Mapbox-iOS-SDK-nightly-dynamic.podspec index c7b14ead3..db5ca23c1 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.6.0-alpha.1' + version = '3.6.1' 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 d2a686f1f..be4d469f7 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.6.0' + version = '3.6.1' 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 55e8791b4..53779307a 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.6.0' + version = '3.6.1' m.name = 'Mapbox-iOS-SDK' m.version = version From 21bbf5a9e0e82e60250f5b05dd5430052a7b2469 Mon Sep 17 00:00:00 2001 From: Tobrun Date: Tue, 1 Aug 2017 16:33:52 +0200 Subject: [PATCH 52/71] [android] - fix OnCameraMoveStartedListener typo (#9664) --- .../src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java | 2 +- .../testapp/activity/camera/CameraPositionActivity.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java index c8725d8d8..e074d938f 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java @@ -1708,7 +1708,7 @@ public final class MapboxMap { * @param listener the listener to notify */ @UiThread - public void setOnCameraMoveStartedistener(@Nullable OnCameraMoveStartedListener listener) { + public void setOnCameraMoveStartedListener(@Nullable OnCameraMoveStartedListener listener) { cameraChangeDispatcher.setOnCameraMoveStartedListener(listener); } diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/camera/CameraPositionActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/camera/CameraPositionActivity.java index cb2f57d86..10464b087 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/camera/CameraPositionActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/camera/CameraPositionActivity.java @@ -67,7 +67,7 @@ public class CameraPositionActivity extends AppCompatActivity implements OnMapRe } }); - mapboxMap.setOnCameraMoveStartedistener(new MapboxMap.OnCameraMoveStartedListener() { + mapboxMap.setOnCameraMoveStartedListener(new MapboxMap.OnCameraMoveStartedListener() { private final String[] REASONS = {"REASON_API_GESTURE", "REASON_DEVELOPER_ANIMATION", "REASON_API_ANIMATION"}; From e4a61426bf1893637617159ebea5b61b37114d12 Mon Sep 17 00:00:00 2001 From: Tobrun Date: Tue, 1 Aug 2017 15:25:24 +0200 Subject: [PATCH 53/71] [android] - try/catch initalisation of Telemetry (#9658) --- .../src/main/java/com/mapbox/mapboxsdk/Mapbox.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/Mapbox.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/Mapbox.java index 6722000be..eadc3fdcf 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/Mapbox.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/Mapbox.java @@ -15,6 +15,8 @@ import com.mapbox.services.android.telemetry.MapboxTelemetry; import com.mapbox.services.android.telemetry.location.LocationEngine; import com.mapbox.services.android.telemetry.location.LocationEnginePriority; +import timber.log.Timber; + /** * The entry point to initialize the Mapbox Android SDK. *

@@ -48,8 +50,14 @@ public final class Mapbox { INSTANCE = new Mapbox(appContext, accessToken, new LocationSource(appContext)); LocationEngine locationEngine = new LocationSource(appContext); locationEngine.setPriority(LocationEnginePriority.NO_POWER); - MapboxTelemetry.getInstance().initialize( - appContext, accessToken, BuildConfig.MAPBOX_EVENTS_USER_AGENT, locationEngine); + + try { + MapboxTelemetry.getInstance().initialize( + appContext, accessToken, BuildConfig.MAPBOX_EVENTS_USER_AGENT, locationEngine); + } catch (Exception exception) { + Timber.e(exception, "Unable to instantiate Mapbox telemetry"); + } + ConnectivityReceiver.instance(appContext); } return INSTANCE; From f5b48a5d06be88bfb373c8dab4d9576d122c8937 Mon Sep 17 00:00:00 2001 From: Tobrun Date: Tue, 1 Aug 2017 15:25:07 +0200 Subject: [PATCH 54/71] [android] - remove preview image integration (#9657) --- .../com/mapbox/mapboxsdk/maps/MapView.java | 3 +- .../mapbox_default_bg_selector.xml | 9 --- .../mapbox_mapview_preview.jpg | Bin 166053 -> 0 bytes .../drawable/mapbox_default_bg_selector.xml | 6 -- .../res/layout/mapbox_mapview_preview.xml | 52 ------------------ .../src/main/res/values/colors.xml | 1 - 6 files changed, 1 insertion(+), 70 deletions(-) delete mode 100644 platform/android/MapboxGLAndroidSDK/src/main/res/drawable-v21/mapbox_default_bg_selector.xml delete mode 100644 platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/mapbox_mapview_preview.jpg delete mode 100644 platform/android/MapboxGLAndroidSDK/src/main/res/drawable/mapbox_default_bg_selector.xml delete mode 100644 platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_mapview_preview.xml 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 c51d9327d..32c4952aa 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 @@ -95,8 +95,7 @@ public class MapView extends FrameLayout { private void initialise(@NonNull final Context context, @NonNull final MapboxMapOptions options) { if (isInEditMode()) { - // in IDE, show preview map - LayoutInflater.from(context).inflate(R.layout.mapbox_mapview_preview, this); + // in IDE layout editor, just return return; } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-v21/mapbox_default_bg_selector.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-v21/mapbox_default_bg_selector.xml deleted file mode 100644 index 2fd6815c2..000000000 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-v21/mapbox_default_bg_selector.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/mapbox_mapview_preview.jpg b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/mapbox_mapview_preview.jpg deleted file mode 100644 index 3a21fc08bcc3a69c4d5a519071c8733e10453e50..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 166053 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 diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable/mapbox_default_bg_selector.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/drawable/mapbox_default_bg_selector.xml deleted file mode 100644 index 3efe48615..000000000 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/drawable/mapbox_default_bg_selector.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_mapview_preview.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_mapview_preview.xml deleted file mode 100644 index 1c1ab0e71..000000000 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/layout/mapbox_mapview_preview.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values/colors.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values/colors.xml index 63ef42c2c..69ab7568b 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/values/colors.xml +++ b/platform/android/MapboxGLAndroidSDK/src/main/res/values/colors.xml @@ -1,7 +1,6 @@ #7D7F80 - #EEEEEE #1E8CAB @color/mapbox_blue From f8c418e2b1cf7229908dd322e1442fda1a05605e Mon Sep 17 00:00:00 2001 From: Tobrun Date: Tue, 1 Aug 2017 21:31:35 +0200 Subject: [PATCH 55/71] [android] - bump MAS dependency (#9671) --- platform/android/dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/android/dependencies.gradle b/platform/android/dependencies.gradle index 3d8f48b46..5fd2ec371 100644 --- a/platform/android/dependencies.gradle +++ b/platform/android/dependencies.gradle @@ -7,7 +7,7 @@ ext { versionCode = 11 versionName = "5.0.0" - mapboxServicesVersion = "2.1.3" + mapboxServicesVersion = "2.2.0" supportLibVersion = "25.3.1" wearableVersion = '2.0.0' espressoVersion = '2.2.2' From a523b710aceccc992c5aa193acbcd4bf4c93a62d Mon Sep 17 00:00:00 2001 From: Tobrun Van Nuland Date: Tue, 1 Aug 2017 21:33:31 +0200 Subject: [PATCH 56/71] [android] - update changelog for release v5.1.2 --- platform/android/CHANGELOG.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/platform/android/CHANGELOG.md b/platform/android/CHANGELOG.md index b2eaaa2a9..485ceaab8 100644 --- a/platform/android/CHANGELOG.md +++ b/platform/android/CHANGELOG.md @@ -2,7 +2,21 @@ 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.1.1 - TBA +## 5.1.2 - August 2, 20017 + +* Disable program caching on Ardreno GPU 3xx, 4xx and 5xx [#9625](https://github.com/mapbox/mapbox-gl-native/pull/9625) +* GeoJSON proguard issue [#9577](https://github.com/mapbox/mapbox-gl-native/pull/9577) +* Harden click events of shape annotations [#9585](https://github.com/mapbox/mapbox-gl-native/pull/9585) +* Validate Marker before opening InfoWindow [#9586](https://github.com/mapbox/mapbox-gl-native/pull/9586) +* Fix added to map check [#9602](https://github.com/mapbox/mapbox-gl-native/pull/9602) +* Don't query shape annotations if none were added [#9606](https://github.com/mapbox/mapbox-gl-native/pull/9606) +* Fix compass direction [#9632](https://github.com/mapbox/mapbox-gl-native/pull/9632) +* Remove preview image integration [#9657](https://github.com/mapbox/mapbox-gl-native/pull/9657) +* Try/catch initialization of telemetry [#9658](https://github.com/mapbox/mapbox-gl-native/pull/9658) +* Fix typo OnCameraMoveStartedListener [#9664](https://github.com/mapbox/mapbox-gl-native/pull/9664) +* Bump MAS dependency to 5.2.0 [#9671](https://github.com/mapbox/mapbox-gl-native/pull/9671) + +## 5.1.1 - July 21, 2017 * Rework attribution binding [#9433](https://github.com/mapbox/mapbox-gl-native/pull/9433) * BackendScope changes [#9538](https://github.com/mapbox/mapbox-gl-native/pull/9538) From afd4df9dbcdff08654a09e95fdae236061aaa4bd Mon Sep 17 00:00:00 2001 From: Fabian Guerra Soto Date: Tue, 1 Aug 2017 16:14:41 -0400 Subject: [PATCH 57/71] =?UTF-8?q?[ios]=20adapt=20Mapbox=20Streets=E2=80=93?= =?UTF-8?q?sourced=20layers=20for=20user=20preferred=20language=20(#9582)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [ios] Update label localization * [ios, macos] Move MGLVectorSource+MBXAdditions.h to darwin. * [ios, macos] Adapt Mapbox Streets to the user preferred language. * [ios, macos] Clarify style localization documentation. * [ios, macos] Update localization examples * [ios, macos] Add style language reset to original. * [ios, macos] Update changelogs. * [ios, macos] Rename Vector Source Additions to MGL standard. * [ios, macos] Add suport for stop localization. --- platform/darwin/src/MGLStyle.h | 15 ++ platform/darwin/src/MGLStyle.mm | 136 ++++++++++++++++++ .../src/MGLVectorSource+MGLAdditions.h} | 2 +- .../src/MGLVectorSource+MGLAdditions.m} | 4 +- platform/ios/CHANGELOG.md | 6 +- platform/ios/app/MBXViewController.m | 43 +----- platform/ios/ios.xcodeproj/project.pbxproj | 12 ++ platform/macos/CHANGELOG.md | 4 + platform/macos/app/MapDocument.m | 49 +------ .../macos/macos.xcodeproj/project.pbxproj | 14 +- 10 files changed, 188 insertions(+), 97 deletions(-) rename platform/{macos/app/MGLVectorSource+MBXAdditions.h => darwin/src/MGLVectorSource+MGLAdditions.h} (88%) rename platform/{macos/app/MGLVectorSource+MBXAdditions.m => darwin/src/MGLVectorSource+MGLAdditions.m} (96%) diff --git a/platform/darwin/src/MGLStyle.h b/platform/darwin/src/MGLStyle.h index 8ffd7db2b..8c009d22c 100644 --- a/platform/darwin/src/MGLStyle.h +++ b/platform/darwin/src/MGLStyle.h @@ -610,6 +610,21 @@ MGL_EXPORT */ @property (nonatomic, strong) MGLLight *light; +#pragma mark Localizing Map Content + +/** + A Boolean value that determines whether the style attempts to localize labels in + the style into the system’s preferred language. + + When this property is enabled, the style automatically modifies the text property + of any symbol style layer whose source is the + Mapbox + Streets source. On iOS, the user can set the system’s preferred language in + Settings, General Settings, Language & Region. On macOS, the user can set the + system’s preferred language in the Language & Region pane of System Preferences. + */ +@property (nonatomic) BOOL localizesLabels; + @end NS_ASSUME_NONNULL_END diff --git a/platform/darwin/src/MGLStyle.mm b/platform/darwin/src/MGLStyle.mm index eb838085d..1e0a2e02b 100644 --- a/platform/darwin/src/MGLStyle.mm +++ b/platform/darwin/src/MGLStyle.mm @@ -21,6 +21,7 @@ #import "MGLSource.h" #import "MGLTileSource_Private.h" #import "MGLVectorSource.h" +#import "MGLVectorSource+MGLAdditions.h" #import "MGLRasterSource.h" #import "MGLShapeSource.h" @@ -48,11 +49,34 @@ #import "NSImage+MGLAdditions.h" #endif +/** + Model class for localization changes. + */ +@interface MGLTextLanguage: NSObject +@property (strong, nonatomic) NSString *originalTextField; +@property (strong, nonatomic) NSString *updatedTextField; + +- (instancetype)initWithTextLanguage:(NSString *)originalTextField updatedTextField:(NSString *)updatedTextField; + +@end + +@implementation MGLTextLanguage +- (instancetype)initWithTextLanguage:(NSString *)originalTextField updatedTextField:(NSString *)updatedTextField +{ + if (self = [super init]) { + _originalTextField = originalTextField; + _updatedTextField = updatedTextField; + } + return self; +} +@end + @interface MGLStyle() @property (nonatomic, readwrite, weak) MGLMapView *mapView; @property (readonly, copy, nullable) NSURL *URL; @property (nonatomic, readwrite, strong) NS_MUTABLE_DICTIONARY_OF(NSString *, MGLOpenGLStyleLayer *) *openGLLayers; +@property (nonatomic) NS_MUTABLE_DICTIONARY_OF(NSString *, NS_DICTIONARY_OF(NSObject *, MGLTextLanguage *) *) *localizedLayersByIdentifier; @end @@ -116,6 +140,7 @@ static NSURL *MGLStyleURL_emerald; if (self = [super init]) { _mapView = mapView; _openGLLayers = [NSMutableDictionary dictionary]; + _localizedLayersByIdentifier = [NSMutableDictionary dictionary]; } return self; } @@ -609,4 +634,115 @@ static NSURL *MGLStyleURL_emerald; self.URL ? [NSString stringWithFormat:@"\"%@\"", self.URL] : self.URL]; } +#pragma mark Style language preferences + +- (void)setLocalizesLabels:(BOOL)localizesLabels +{ + if (_localizesLabels != localizesLabels) { + _localizesLabels = localizesLabels; + } else { + return; + } + + if (_localizesLabels) { + NSString *preferredLanguage = [MGLVectorSource preferredMapboxStreetsLanguage]; + NSMutableDictionary *localizedKeysByKeyBySourceIdentifier = [NSMutableDictionary dictionary]; + for (MGLSymbolStyleLayer *layer in self.layers) { + if (![layer isKindOfClass:[MGLSymbolStyleLayer class]]) { + continue; + } + + MGLVectorSource *source = (MGLVectorSource *)[self sourceWithIdentifier:layer.sourceIdentifier]; + if (![source isKindOfClass:[MGLVectorSource class]] || !source.mapboxStreets) { + continue; + } + + NSDictionary *localizedKeysByKey = localizedKeysByKeyBySourceIdentifier[layer.sourceIdentifier]; + if (!localizedKeysByKey) { + localizedKeysByKey = localizedKeysByKeyBySourceIdentifier[layer.sourceIdentifier] = [source localizedKeysByKeyForPreferredLanguage:preferredLanguage]; + } + + NSString *(^stringByLocalizingString)(NSString *) = ^ NSString * (NSString *string) { + NSMutableString *localizedString = string.mutableCopy; + [localizedKeysByKey enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSString * _Nonnull localizedKey, BOOL * _Nonnull stop) { + NSAssert([key isKindOfClass:[NSString class]], @"key is not a string"); + NSAssert([localizedKey isKindOfClass:[NSString class]], @"localizedKey is not a string"); + [localizedString replaceOccurrencesOfString:[NSString stringWithFormat:@"{%@}", key] + withString:[NSString stringWithFormat:@"{%@}", localizedKey] + options:0 + range:NSMakeRange(0, localizedString.length)]; + }]; + return localizedString; + }; + + if ([layer.text isKindOfClass:[MGLConstantStyleValue class]]) { + NSString *textField = [(MGLConstantStyleValue *)layer.text rawValue]; + NSString *localizingString = stringByLocalizingString(textField); + if (![textField isEqualToString:localizingString]) { + MGLTextLanguage *textLanguage = [[MGLTextLanguage alloc] initWithTextLanguage:textField + updatedTextField:localizingString]; + [self.localizedLayersByIdentifier setObject:@{ textField : textLanguage } forKey:layer.identifier]; + layer.text = [MGLStyleValue valueWithRawValue:localizingString]; + } + } + else if ([layer.text isKindOfClass:[MGLCameraStyleFunction class]]) { + MGLCameraStyleFunction *function = (MGLCameraStyleFunction *)layer.text; + NSMutableDictionary *stops = function.stops.mutableCopy; + NSMutableDictionary *cameraStops = [NSMutableDictionary dictionary]; + [stops enumerateKeysAndObjectsUsingBlock:^(NSNumber *zoomLevel, MGLConstantStyleValue *stop, BOOL *done) { + NSString *textField = stop.rawValue; + NSString *localizingString = stringByLocalizingString(textField); + if (![textField isEqualToString:localizingString]) { + MGLTextLanguage *textLanguage = [[MGLTextLanguage alloc] initWithTextLanguage:textField + updatedTextField:localizingString]; + [cameraStops setObject:textLanguage forKey:zoomLevel]; + stops[zoomLevel] = [MGLStyleValue valueWithRawValue:localizingString]; + } + + }]; + if (cameraStops.count > 0) { + [self.localizedLayersByIdentifier setObject:cameraStops forKey:layer.identifier]; + } + function.stops = stops; + layer.text = function; + } + } + } else { + + [self.localizedLayersByIdentifier enumerateKeysAndObjectsUsingBlock:^(NSString *identifier, NSDictionary *textFields, BOOL *done) { + MGLSymbolStyleLayer *layer = (MGLSymbolStyleLayer *)[self.mapView.style layerWithIdentifier:identifier]; + + if ([layer.text isKindOfClass:[MGLConstantStyleValue class]]) { + NSString *textField = [(MGLConstantStyleValue *)layer.text rawValue]; + [textFields enumerateKeysAndObjectsUsingBlock:^(NSObject *originalLanguage, MGLTextLanguage *textLanguage, BOOL *done) { + if ([textLanguage.updatedTextField isEqualToString:textField]) { + layer.text = [MGLStyleValue valueWithRawValue:textLanguage.originalTextField]; + } + }]; + + } + else if ([layer.text isKindOfClass:[MGLCameraStyleFunction class]]) { + MGLCameraStyleFunction *function = (MGLCameraStyleFunction *)layer.text; + NSMutableDictionary *stops = function.stops.mutableCopy; + [textFields enumerateKeysAndObjectsUsingBlock:^(NSObject *zoomKey, MGLTextLanguage *textLanguage, BOOL *done) { + if ([zoomKey isKindOfClass:[NSNumber class]]) { + NSNumber *zoomLevel = (NSNumber*)zoomKey; + MGLConstantStyleValue *stop = [stops objectForKey:zoomLevel]; + NSString *textField = stop.rawValue; + if ([textLanguage.updatedTextField isEqualToString:textField]) { + stops[zoomLevel] = [MGLStyleValue valueWithRawValue:textLanguage.originalTextField]; + } + } + }]; + + function.stops = stops; + layer.text = function; + } + + }]; + + self.localizedLayersByIdentifier = [NSMutableDictionary dictionary]; + } +} + @end diff --git a/platform/macos/app/MGLVectorSource+MBXAdditions.h b/platform/darwin/src/MGLVectorSource+MGLAdditions.h similarity index 88% rename from platform/macos/app/MGLVectorSource+MBXAdditions.h rename to platform/darwin/src/MGLVectorSource+MGLAdditions.h index 1e25ee5a6..43b0aba74 100644 --- a/platform/macos/app/MGLVectorSource+MBXAdditions.h +++ b/platform/darwin/src/MGLVectorSource+MGLAdditions.h @@ -2,7 +2,7 @@ NS_ASSUME_NONNULL_BEGIN -@interface MGLVectorSource (MBXAdditions) +@interface MGLVectorSource (MGLAdditions) + (NSString *)preferredMapboxStreetsLanguage; diff --git a/platform/macos/app/MGLVectorSource+MBXAdditions.m b/platform/darwin/src/MGLVectorSource+MGLAdditions.m similarity index 96% rename from platform/macos/app/MGLVectorSource+MBXAdditions.m rename to platform/darwin/src/MGLVectorSource+MGLAdditions.m index 323bc7436..a30538811 100644 --- a/platform/macos/app/MGLVectorSource+MBXAdditions.m +++ b/platform/darwin/src/MGLVectorSource+MGLAdditions.m @@ -1,6 +1,6 @@ -#import "MGLVectorSource+MBXAdditions.h" +#import "MGLVectorSource+MGLAdditions.h" -@implementation MGLVectorSource (MBXAdditions) +@implementation MGLVectorSource (MGLAdditions) + (NS_SET_OF(NSString *) *)mapboxStreetsLanguages { // https://www.mapbox.com/vector-tiles/mapbox-streets-v7/#overview diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 43df49a9a..55790577c 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.6.1 +## 3.6.2 + +* Added an MGLStyle.localizesLabels property, off by default, that localizes any Mapbox Streets–sourced symbol layer into the user’s preferred language. ([#9582](https://github.com/mapbox/mapbox-gl-native/pull/9582)) + +## 3.6.1 - July 28, 2017 * Reduced the size of the dynamic framework by optimizing symbol visibility. ([#7604](https://github.com/mapbox/mapbox-gl-native/pull/7604)) * Fixed an issue where the attribution button would have its custom tint color reset when the map view received a tint color change notification, such as when an alert controller was presented. ([#9598](https://github.com/mapbox/mapbox-gl-native/pull/9598)) diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m index d3927374a..ebbc21801 100644 --- a/platform/ios/app/MBXViewController.m +++ b/platform/ios/app/MBXViewController.m @@ -343,7 +343,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { @"Update Shape Source: Features", @"Style Vector Source", @"Style Raster Source", - [NSString stringWithFormat:@"Label Countries in %@", (_usingLocaleBasedCountryLabels ? @"Local Language" : [[NSLocale currentLocale] displayNameForKey:NSLocaleIdentifier value:[self bestLanguageForUser]])], + [NSString stringWithFormat:@"Show Labels in %@", (_usingLocaleBasedCountryLabels ? @"Local Language" : [[NSLocale currentLocale] displayNameForKey:NSLocaleIdentifier value:[self bestLanguageForUser]])], @"Add Route Line", @"Dynamically Style Polygon", ]]; @@ -1274,12 +1274,8 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { -(void)styleCountryLabelsLanguage { - NSArray *labelLayers = @[ - @"country-label-lg", - @"country-label-md", - @"country-label-sm", - ]; - [self styleLabelLanguageForLayersNamed:labelLayers]; + _usingLocaleBasedCountryLabels = !_usingLocaleBasedCountryLabels; + self.mapView.style.localizesLabels = _usingLocaleBasedCountryLabels; } - (void)styleRouteLine @@ -1362,39 +1358,6 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { [self.mapView.style addLayer:fillStyleLayer]; } -- (void)styleLabelLanguageForLayersNamed:(NSArray *)layers -{ - _usingLocaleBasedCountryLabels = !_usingLocaleBasedCountryLabels; - NSString *bestLanguageForUser = [NSString stringWithFormat:@"{name_%@}", [self bestLanguageForUser]]; - NSString *language = _usingLocaleBasedCountryLabels ? bestLanguageForUser : @"{name}"; - - for (NSString *layerName in layers) { - MGLSymbolStyleLayer *layer = (MGLSymbolStyleLayer *)[self.mapView.style layerWithIdentifier:layerName]; - - if ([layer isKindOfClass:[MGLSymbolStyleLayer class]]) { - if ([layer.text isKindOfClass:[MGLConstantStyleValue class]]) { - MGLConstantStyleValue *label = (MGLConstantStyleValue *)layer.text; - if ([label.rawValue hasPrefix:@"{name"]) { - layer.text = [MGLStyleValue valueWithRawValue:language]; - } - } - else if ([layer.text isKindOfClass:[MGLCameraStyleFunction class]]) { - MGLCameraStyleFunction *function = (MGLCameraStyleFunction *)layer.text; - NSMutableDictionary *stops = function.stops.mutableCopy; - [stops enumerateKeysAndObjectsUsingBlock:^(NSNumber *zoomLevel, MGLConstantStyleValue *stop, BOOL *done) { - if ([stop.rawValue hasPrefix:@"{name"]) { - stops[zoomLevel] = [MGLStyleValue valueWithRawValue:language]; - } - }]; - function.stops = stops; - layer.text = function; - } - } else { - NSLog(@"%@ is not a symbol style layer", layerName); - } - } -} - - (NSString *)bestLanguageForUser { // https://www.mapbox.com/vector-tiles/mapbox-streets-v7/#overview diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index 8ecbe0c6a..83b267df4 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -17,6 +17,10 @@ 1F7454971ECD450D00021D39 /* MGLLight_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F7454941ECD450D00021D39 /* MGLLight_Private.h */; }; 1F7454A91ED08AB400021D39 /* MGLLightTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1F7454A61ED08AB400021D39 /* MGLLightTest.mm */; }; 1F95931D1E6DE2E900D5B294 /* MGLNSDateAdditionsTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1F95931C1E6DE2E900D5B294 /* MGLNSDateAdditionsTests.mm */; }; + 1FB7DAAF1F2A4DBD00410606 /* MGLVectorSource+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FDD9D6D1F26936400252B09 /* MGLVectorSource+MGLAdditions.h */; }; + 1FB7DAB01F2A4DC200410606 /* MGLVectorSource+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FDD9D6D1F26936400252B09 /* MGLVectorSource+MGLAdditions.h */; }; + 1FB7DAB11F2A4DC800410606 /* MGLVectorSource+MGLAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 1FDD9D6E1F26936400252B09 /* MGLVectorSource+MGLAdditions.m */; }; + 1FB7DAB21F2A4DC900410606 /* MGLVectorSource+MGLAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 1FDD9D6E1F26936400252B09 /* MGLVectorSource+MGLAdditions.m */; }; 30E578171DAA85520050F07E /* UIImage+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 30E578111DAA7D690050F07E /* UIImage+MGLAdditions.h */; }; 30E578181DAA85520050F07E /* UIImage+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 30E578111DAA7D690050F07E /* UIImage+MGLAdditions.h */; }; 30E578191DAA855E0050F07E /* UIImage+MGLAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 30E578121DAA7D690050F07E /* UIImage+MGLAdditions.mm */; }; @@ -547,6 +551,8 @@ 1F7454941ECD450D00021D39 /* MGLLight_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLLight_Private.h; sourceTree = ""; }; 1F7454A61ED08AB400021D39 /* MGLLightTest.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLLightTest.mm; path = ../../darwin/test/MGLLightTest.mm; sourceTree = ""; }; 1F95931C1E6DE2E900D5B294 /* MGLNSDateAdditionsTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLNSDateAdditionsTests.mm; path = ../../darwin/test/MGLNSDateAdditionsTests.mm; sourceTree = ""; }; + 1FDD9D6D1F26936400252B09 /* MGLVectorSource+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MGLVectorSource+MGLAdditions.h"; sourceTree = ""; }; + 1FDD9D6E1F26936400252B09 /* MGLVectorSource+MGLAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MGLVectorSource+MGLAdditions.m"; sourceTree = ""; }; 20DABE861DF78148007AC5FF /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Foundation.strings"; sourceTree = ""; }; 20DABE881DF78148007AC5FF /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 20DABE8A1DF78149007AC5FF /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Root.strings"; sourceTree = ""; }; @@ -1537,6 +1543,8 @@ DAD165831CF4CFED001FF4B9 /* Categories */ = { isa = PBXGroup; children = ( + 1FDD9D6D1F26936400252B09 /* MGLVectorSource+MGLAdditions.h */, + 1FDD9D6E1F26936400252B09 /* MGLVectorSource+MGLAdditions.m */, 7E016D821D9E890300A29A21 /* MGLPolygon+MGLAdditions.h */, 7E016D831D9E890300A29A21 /* MGLPolygon+MGLAdditions.m */, 7E016D7C1D9E86BE00A29A21 /* MGLPolyline+MGLAdditions.h */, @@ -1642,6 +1650,7 @@ 350098DC1D484E60004B2AF0 /* NSValue+MGLStyleAttributeAdditions.h in Headers */, DA8848231CBAFA6200AB86E3 /* MGLOfflineStorage_Private.h in Headers */, 404326891D5B9B27007111BD /* MGLAnnotationContainerView_Private.h in Headers */, + 1FB7DAAF1F2A4DBD00410606 /* MGLVectorSource+MGLAdditions.h in Headers */, DA88483B1CBAFB8500AB86E3 /* MGLCalloutView.h in Headers */, 35E0CFE61D3E501500188327 /* MGLStyle_Private.h in Headers */, 3510FFF01D6D9D8C00F413B2 /* NSExpression+MGLAdditions.h in Headers */, @@ -1819,6 +1828,7 @@ 35136D4D1D4277FC00C20EFD /* MGLSource.h in Headers */, DA35A2BC1CCA9A6900E826B2 /* MGLClockDirectionFormatter.h in Headers */, 35D13AC41D3D19DD00AFB4E0 /* MGLFillStyleLayer.h in Headers */, + 1FB7DAB01F2A4DC200410606 /* MGLVectorSource+MGLAdditions.h in Headers */, DABFB86E1CBE9A0F00D62B32 /* MGLCalloutView.h in Headers */, 1F7454971ECD450D00021D39 /* MGLLight_Private.h in Headers */, DABFB8601CBE99E500D62B32 /* MGLMapCamera.h in Headers */, @@ -2197,6 +2207,7 @@ 9620BB3A1E69FE1700705A1D /* MGLSDKUpdateChecker.mm in Sources */, 354B83981D2E873E005D9406 /* MGLUserLocationAnnotationView.m in Sources */, DA88485D1CBAFB9800AB86E3 /* MGLFaux3DUserLocationAnnotationView.m in Sources */, + 1FB7DAB11F2A4DC800410606 /* MGLVectorSource+MGLAdditions.m in Sources */, DAD165701CF41981001FF4B9 /* MGLFeature.mm in Sources */, 30E578191DAA855E0050F07E /* UIImage+MGLAdditions.mm in Sources */, 40EDA1C11CFE0E0500D9EA68 /* MGLAnnotationContainerView.m in Sources */, @@ -2279,6 +2290,7 @@ 9620BB3B1E69FE1700705A1D /* MGLSDKUpdateChecker.mm in Sources */, DAA4E4221CBB730400178DFB /* MGLPointAnnotation.mm in Sources */, DAED38661D62D0FC00D7640F /* NSURL+MGLAdditions.m in Sources */, + 1FB7DAB21F2A4DC900410606 /* MGLVectorSource+MGLAdditions.m in Sources */, DAD165711CF41981001FF4B9 /* MGLFeature.mm in Sources */, 30E5781A1DAA855E0050F07E /* UIImage+MGLAdditions.mm in Sources */, 40EDA1C21CFE0E0500D9EA68 /* MGLAnnotationContainerView.m in Sources */, diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md index f69f7e1b5..1af3c03d6 100644 --- a/platform/macos/CHANGELOG.md +++ b/platform/macos/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog for Mapbox macOS SDK +## 0.5.1 + +* Added an MGLStyle.localizesLabels property, off by default, that localizes any Mapbox Streets–sourced symbol layer into the user’s preferred language. ([#9582](https://github.com/mapbox/mapbox-gl-native/pull/9582)) + ## 0.5.0 This version of the Mapbox macOS SDK corresponds to version 3.6.0 of the Mapbox iOS SDK. diff --git a/platform/macos/app/MapDocument.m b/platform/macos/app/MapDocument.m index 59844d363..1d22295f5 100644 --- a/platform/macos/app/MapDocument.m +++ b/platform/macos/app/MapDocument.m @@ -5,7 +5,7 @@ #import "DroppedPinAnnotation.h" #import "MGLStyle+MBXAdditions.h" -#import "MGLVectorSource+MBXAdditions.h" +#import "MGLVectorSource+MGLAdditions.h" #import @@ -344,52 +344,7 @@ NS_ARRAY_OF(id ) *MBXFlattenedShapes(NS_ARRAY_OF(id *)layer.text rawValue]; - layer.text = [MGLStyleValue valueWithRawValue:stringByLocalizingString(textField)]; - } - else if ([layer.text isKindOfClass:[MGLCameraStyleFunction class]]) { - MGLCameraStyleFunction *function = (MGLCameraStyleFunction *)layer.text; - NSMutableDictionary *stops = function.stops.mutableCopy; - [stops enumerateKeysAndObjectsUsingBlock:^(NSNumber *zoomLevel, MGLConstantStyleValue *stop, BOOL *done) { - NSString *textField = stop.rawValue; - stops[zoomLevel] = [MGLStyleValue valueWithRawValue:stringByLocalizingString(textField)]; - }]; - function.stops = stops; - layer.text = function; - } - } + self.mapView.style.localizesLabels = _isLocalizingLabels; } - (void)applyPendingState { diff --git a/platform/macos/macos.xcodeproj/project.pbxproj b/platform/macos/macos.xcodeproj/project.pbxproj index be33b822f..00cb6ce97 100644 --- a/platform/macos/macos.xcodeproj/project.pbxproj +++ b/platform/macos/macos.xcodeproj/project.pbxproj @@ -13,6 +13,8 @@ 1F7454A51ECFB00300021D39 /* MGLLight.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1F7454A21ECFB00300021D39 /* MGLLight.mm */; }; 1F7454AB1ED1DDBD00021D39 /* MGLLightTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1F7454AA1ED1DDBD00021D39 /* MGLLightTest.mm */; }; 1F95931B1E6DE2B600D5B294 /* MGLNSDateAdditionsTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1F95931A1E6DE2B600D5B294 /* MGLNSDateAdditionsTests.mm */; }; + 1FCDF1421F2A4F3600A46694 /* MGLVectorSource+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FCDF1401F2A4F3600A46694 /* MGLVectorSource+MGLAdditions.h */; }; + 1FCDF1431F2A4F3600A46694 /* MGLVectorSource+MGLAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 1FCDF1411F2A4F3600A46694 /* MGLVectorSource+MGLAdditions.m */; }; 30E5781B1DAA857E0050F07E /* NSImage+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 30E578141DAA7D920050F07E /* NSImage+MGLAdditions.h */; }; 3508EC641D749D39009B0EE4 /* NSExpression+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 3508EC621D749D39009B0EE4 /* NSExpression+MGLAdditions.h */; }; 3508EC651D749D39009B0EE4 /* NSExpression+MGLAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3508EC631D749D39009B0EE4 /* NSExpression+MGLAdditions.mm */; }; @@ -233,7 +235,6 @@ DAEDC4371D606291000224FF /* MGLAttributionButtonTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DAEDC4361D606291000224FF /* MGLAttributionButtonTests.m */; }; DAF0D80E1DFE0E5D00B28378 /* MGLPointCollection_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DAF0D80D1DFE0E5D00B28378 /* MGLPointCollection_Private.h */; }; DAF0D8161DFE6B1800B28378 /* MGLAttributionInfo_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DAF0D8151DFE6B1800B28378 /* MGLAttributionInfo_Private.h */; }; - DAF0D81C1DFF567C00B28378 /* MGLVectorSource+MBXAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = DAF0D81B1DFF567C00B28378 /* MGLVectorSource+MBXAdditions.m */; }; DD0902B21DB1AC6400C5BDCE /* MGLNetworkConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = DD0902AF1DB1AC6400C5BDCE /* MGLNetworkConfiguration.m */; }; DD0902B31DB1AC6400C5BDCE /* MGLNetworkConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = DD0902B01DB1AC6400C5BDCE /* MGLNetworkConfiguration.h */; }; DD58A4C91D822C6700E1F038 /* MGLExpressionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = DD58A4C71D822C6200E1F038 /* MGLExpressionTests.mm */; }; @@ -277,6 +278,8 @@ 1F7454A21ECFB00300021D39 /* MGLLight.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLLight.mm; sourceTree = ""; }; 1F7454AA1ED1DDBD00021D39 /* MGLLightTest.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLLightTest.mm; sourceTree = ""; }; 1F95931A1E6DE2B600D5B294 /* MGLNSDateAdditionsTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLNSDateAdditionsTests.mm; path = ../../darwin/test/MGLNSDateAdditionsTests.mm; sourceTree = ""; }; + 1FCDF1401F2A4F3600A46694 /* MGLVectorSource+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MGLVectorSource+MGLAdditions.h"; sourceTree = ""; }; + 1FCDF1411F2A4F3600A46694 /* MGLVectorSource+MGLAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MGLVectorSource+MGLAdditions.m"; sourceTree = ""; }; 30E578141DAA7D920050F07E /* NSImage+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSImage+MGLAdditions.h"; path = "src/NSImage+MGLAdditions.h"; sourceTree = SOURCE_ROOT; }; 3508EC621D749D39009B0EE4 /* NSExpression+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSExpression+MGLAdditions.h"; sourceTree = ""; }; 3508EC631D749D39009B0EE4 /* NSExpression+MGLAdditions.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSExpression+MGLAdditions.mm"; sourceTree = ""; }; @@ -568,8 +571,6 @@ DAEDC4361D606291000224FF /* MGLAttributionButtonTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLAttributionButtonTests.m; sourceTree = ""; }; DAF0D80D1DFE0E5D00B28378 /* MGLPointCollection_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLPointCollection_Private.h; sourceTree = ""; }; DAF0D8151DFE6B1800B28378 /* MGLAttributionInfo_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAttributionInfo_Private.h; sourceTree = ""; }; - DAF0D81A1DFF567C00B28378 /* MGLVectorSource+MBXAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MGLVectorSource+MBXAdditions.h"; sourceTree = ""; }; - DAF0D81B1DFF567C00B28378 /* MGLVectorSource+MBXAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MGLVectorSource+MBXAdditions.m"; sourceTree = ""; }; DAFBD0D51E3FA969000CD6BF /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = ""; }; DAFBD0D61E3FA983000CD6BF /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Foundation.strings"; sourceTree = ""; }; DD0902AF1DB1AC6400C5BDCE /* MGLNetworkConfiguration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLNetworkConfiguration.m; sourceTree = ""; }; @@ -740,8 +741,6 @@ DA839E9E1CC2E3400062CAFB /* MapDocument.xib */, DACB0C371E18DFFD005DDBEA /* MGLStyle+MBXAdditions.h */, DACB0C381E18DFFD005DDBEA /* MGLStyle+MBXAdditions.m */, - DAF0D81A1DFF567C00B28378 /* MGLVectorSource+MBXAdditions.h */, - DAF0D81B1DFF567C00B28378 /* MGLVectorSource+MBXAdditions.m */, DAE6C2E91CC3050F00DB3429 /* OfflinePackNameValueTransformer.h */, DAE6C2EA1CC3050F00DB3429 /* OfflinePackNameValueTransformer.m */, DAA48EFB1D6A4731006A7E36 /* StyleLayerIconTransformer.h */, @@ -920,6 +919,8 @@ DAD1657F1CF4CF50001FF4B9 /* Categories */ = { isa = PBXGroup; children = ( + 1FCDF1401F2A4F3600A46694 /* MGLVectorSource+MGLAdditions.h */, + 1FCDF1411F2A4F3600A46694 /* MGLVectorSource+MGLAdditions.m */, 408AA8601DAEED3300022900 /* MGLPolygon+MGLAdditions.h */, 408AA85C1DAEED3300022900 /* MGLPolygon+MGLAdditions.m */, 408AA8611DAEED3300022900 /* MGLPolyline+MGLAdditions.h */, @@ -1154,6 +1155,7 @@ DAE6C3601CC31E0400DB3429 /* MGLOfflineRegion.h in Headers */, DAE6C3681CC31E0400DB3429 /* MGLTilePyramidOfflineRegion.h in Headers */, DA35A2CF1CCAAED300E826B2 /* NSValue+MGLAdditions.h in Headers */, + 1FCDF1421F2A4F3600A46694 /* MGLVectorSource+MGLAdditions.h in Headers */, DAE6C3A61CC31E9400DB3429 /* MGLMapViewDelegate.h in Headers */, DAE6C38B1CC31E2A00DB3429 /* MGLOfflinePack_Private.h in Headers */, 558DE7A61E56161C00C7916D /* MGLFoundation_Private.h in Headers */, @@ -1378,7 +1380,6 @@ DACB0C391E18DFFD005DDBEA /* MGLStyle+MBXAdditions.m in Sources */, DA839E9A1CC2E3400062CAFB /* main.m in Sources */, DA839E971CC2E3400062CAFB /* AppDelegate.m in Sources */, - DAF0D81C1DFF567C00B28378 /* MGLVectorSource+MBXAdditions.m in Sources */, DAE6C2F01CC3050F00DB3429 /* OfflinePackNameValueTransformer.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1394,6 +1395,7 @@ 40B77E461DB11BCD003DA2FE /* NSArray+MGLAdditions.mm in Sources */, DAE6C38C1CC31E2A00DB3429 /* MGLOfflinePack.mm in Sources */, 35D65C5B1D65AD5500722C23 /* NSDate+MGLAdditions.mm in Sources */, + 1FCDF1431F2A4F3600A46694 /* MGLVectorSource+MGLAdditions.m in Sources */, DD0902B21DB1AC6400C5BDCE /* MGLNetworkConfiguration.m in Sources */, 1F7454A51ECFB00300021D39 /* MGLLight.mm in Sources */, DAE6C3B11CC31EF300DB3429 /* MGLAnnotationImage.m in Sources */, From ebab0c1a1bb6e8e76cefff72b262e33f9ac0555c Mon Sep 17 00:00:00 2001 From: Tobrun Van Nuland Date: Wed, 2 Aug 2017 12:29:10 +0200 Subject: [PATCH 58/71] [android] - release android v5.1.2, update config --- platform/android/MapboxGLAndroidSDK/gradle.properties | 2 +- .../fabric/com.mapbox.mapboxsdk.mapbox-android-sdk.properties | 2 +- platform/android/dependencies.gradle | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/platform/android/MapboxGLAndroidSDK/gradle.properties b/platform/android/MapboxGLAndroidSDK/gradle.properties index a8646e327..af1d04ab9 100644 --- a/platform/android/MapboxGLAndroidSDK/gradle.properties +++ b/platform/android/MapboxGLAndroidSDK/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.mapbox.mapboxsdk -VERSION_NAME=5.1.2-SNAPSHOT +VERSION_NAME=5.1.3-SNAPSHOT POM_DESCRIPTION=Mapbox GL Android SDK POM_URL=https://github.com/mapbox/mapbox-gl-native diff --git a/platform/android/MapboxGLAndroidSDK/src/main/resources/fabric/com.mapbox.mapboxsdk.mapbox-android-sdk.properties b/platform/android/MapboxGLAndroidSDK/src/main/resources/fabric/com.mapbox.mapboxsdk.mapbox-android-sdk.properties index a02b5d63a..3cfc7d5fd 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/resources/fabric/com.mapbox.mapboxsdk.mapbox-android-sdk.properties +++ b/platform/android/MapboxGLAndroidSDK/src/main/resources/fabric/com.mapbox.mapboxsdk.mapbox-android-sdk.properties @@ -1,3 +1,3 @@ fabric-identifier=com.mapbox.mapboxsdk.mapbox-android-sdk -fabric-version=5.1.1 +fabric-version=5.1.2 fabric-build-type=binary diff --git a/platform/android/dependencies.gradle b/platform/android/dependencies.gradle index 5fd2ec371..a4b202086 100644 --- a/platform/android/dependencies.gradle +++ b/platform/android/dependencies.gradle @@ -4,8 +4,8 @@ ext { compileSdkVersion = 25 buildToolsVersion = "25.0.2" - versionCode = 11 - versionName = "5.0.0" + versionCode = 12 + versionName = "5.1.3" mapboxServicesVersion = "2.2.0" supportLibVersion = "25.3.1" From 141cd328043fb58c137c77bcb4f7c949c31831ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Mon, 8 May 2017 16:27:56 +0200 Subject: [PATCH 59/71] [ios] remove Secure Transport exception from demo app --- platform/ios/app/Info.plist | 13 ------------- platform/ios/app/MBXViewController.m | 3 +-- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/platform/ios/app/Info.plist b/platform/ios/app/Info.plist index d05d81e49..d5b682542 100644 --- a/platform/ios/app/Info.plist +++ b/platform/ios/app/Info.plist @@ -51,18 +51,5 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - NSAppTransportSecurity - - NSExceptionDomains - - stamen.com - - NSIncludesSubdomains - - NSTemporaryExceptionAllowsInsecureHTTPLoads - - - - diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m index ebbc21801..b6b9eea7d 100644 --- a/platform/ios/app/MBXViewController.m +++ b/platform/ios/app/MBXViewController.m @@ -1261,8 +1261,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { - (void)styleRasterSource { - // 3rd party raster source requires NSAppTransportSecurity exception for stamen.com - NSArray *tileURLTemplates = @[@"http://a.tile.stamen.com/terrain-background/{z}/{x}/{y}.jpg"]; + NSArray *tileURLTemplates = @[@"https://stamen-tiles.a.ssl.fastly.net/terrain-background/{z}/{x}/{y}.jpg"]; MGLRasterSource *rasterSource = [[MGLRasterSource alloc] initWithIdentifier:@"style-raster-source-id" tileURLTemplates:tileURLTemplates options:@{ MGLTileSourceOptionTileSize: @256, }]; From 55d31922fcd47947b17261177e6d91201fe7c02a Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Tue, 1 Aug 2017 18:37:46 -0400 Subject: [PATCH 60/71] [ios] Add iosapp Settings.app force touch quick action shortcut --- .../settings.imageset/Contents.json | 15 +-------------- .../settings.imageset/settings.pdf | Bin 0 -> 9177 bytes .../settings.imageset/settings.png | Bin 528 -> 0 bytes .../settings.imageset/settings@2x.png | Bin 1130 -> 0 bytes platform/ios/app/Info.plist | 11 +++++++++++ platform/ios/app/MBXAppDelegate.m | 18 ++++++++++++++++++ 6 files changed, 30 insertions(+), 14 deletions(-) create mode 100644 platform/ios/app/Assets.xcassets/settings.imageset/settings.pdf delete mode 100644 platform/ios/app/Assets.xcassets/settings.imageset/settings.png delete mode 100644 platform/ios/app/Assets.xcassets/settings.imageset/settings@2x.png diff --git a/platform/ios/app/Assets.xcassets/settings.imageset/Contents.json b/platform/ios/app/Assets.xcassets/settings.imageset/Contents.json index 1eeddba9b..228b81a81 100644 --- a/platform/ios/app/Assets.xcassets/settings.imageset/Contents.json +++ b/platform/ios/app/Assets.xcassets/settings.imageset/Contents.json @@ -2,24 +2,11 @@ "images" : [ { "idiom" : "universal", - "filename" : "settings.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "settings@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" + "filename" : "settings.pdf" } ], "info" : { "version" : 1, "author" : "xcode" - }, - "properties" : { - "template-rendering-intent" : "template" } } \ No newline at end of file diff --git a/platform/ios/app/Assets.xcassets/settings.imageset/settings.pdf b/platform/ios/app/Assets.xcassets/settings.imageset/settings.pdf new file mode 100644 index 0000000000000000000000000000000000000000..46aa7443f05eb99b83b8a7d842aa75e32ef80e68 GIT binary patch literal 9177 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 S^=_#E0000 zzmH8p6vrP4LdZsIf~W%_71-A=#*GMTBHE zd_U`r%+B6>?#zAj=H3-2IXiRbe9y0Y=FZH$ZwG^X^j4;R1-z*1P5E4OXX#Xsn4jqB z?!p=jHfM5v$*?nJ$TozCDYzu|tLFES^>&(`P;j@zd&G6b6!a6S>RZzk+xWm&QR2D! zA|~K(;``z-(H$C_6IUhWkIf4M)+cOX6Wd_%h0h}7C+7$15Kiw?Se zV!lq1B$+C%a=!CYeSBw|u2rk(c{!L6cSZOmY3cmnGb>fCqRzn;*NJGCg}Emtz->dO&ls{;nvMmI zisRG~yNN#)c9-E0*%Ps=lGWl9-(w>V#he@G#8x0qMshI+X)s`&p4H9?M+`A`1&bq& zW;5a?vE|&CeHPZLoWyGgEYsDuVfBfHn5=`k#LZOoCT=-)vb)Wu*CBW&oqkHYu+CD# zK#C(Ddo;m^gQDZxNa;HpNiFjnCvAw;@@JyrR^e9?bsW6aGihxT&T!6_1dGxr2_9+@ z>A@B{BdL%ei%c83*h1+-3msJ6wbWZxV{Wq)m)IuQT4EO#f17Ca{HI9pzXpxEm(0)DSBh;#O;@;}hS|r1+|sJ$1i0#JX!_V6KbNi6%cumst2oofFR^v2#|}a{L186Sn+B z{p5J-_$0>ONAZJUzsmmX5Q7Yzq&Jq6Qn(`y6VuRN;@nu}*dGH^N7%kC#uwx#=i7+w zCnx#GZT(*k(o5pH0FZ%+p-F*UmlF~`vjCB@=fYvDSHumn zs8j7 zV6OEoIpX0>Vp$xMfQ7+E(rc6O#psAUjglm~*yuI+Npf(W5!Y3W1;riEH$-yo&NIb$ wj#zkD5$h5}1_o?zr=?(tg#Z8m07*qoM6N<$f@F^{f&c&j diff --git a/platform/ios/app/Info.plist b/platform/ios/app/Info.plist index d5b682542..167e66fa0 100644 --- a/platform/ios/app/Info.plist +++ b/platform/ios/app/Info.plist @@ -51,5 +51,16 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIApplicationShortcutItems + + + UIApplicationShortcutItemTitle + Settings + UIApplicationShortcutItemType + $(PRODUCT_BUNDLE_IDENTIFIER).settings + UIApplicationShortcutItemIconFile + settings + + diff --git a/platform/ios/app/MBXAppDelegate.m b/platform/ios/app/MBXAppDelegate.m index c2834bfa7..1934f4912 100644 --- a/platform/ios/app/MBXAppDelegate.m +++ b/platform/ios/app/MBXAppDelegate.m @@ -26,4 +26,22 @@ NSString * const MBXMapboxAccessTokenDefaultsKey = @"MBXMapboxAccessToken"; return YES; } +#pragma mark - Quick actions + +- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler { + completionHandler([self handleShortcut:shortcutItem]); +} + +- (BOOL)handleShortcut:(UIApplicationShortcutItem *)shortcut { + if ([[shortcut.type componentsSeparatedByString:@"."].lastObject isEqual:@"settings"]) { + dispatch_async(dispatch_get_main_queue(), ^{ + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]; + }); + + return YES; + } + + return NO; +} + @end From a0402475052b1fd29b107aca5bbe9074fadc2cb9 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Tue, 1 Aug 2017 18:55:04 -0400 Subject: [PATCH 61/71] [ios] Clean-up iosapp project --- platform/ios/app/Default-568h@2x.png | Bin 18594 -> 0 bytes platform/ios/app/LaunchScreen.storyboard | 14 +- platform/ios/app/MBXViewController.m | 333 ++++++++++----------- platform/ios/ios.xcodeproj/project.pbxproj | 26 +- 4 files changed, 190 insertions(+), 183 deletions(-) delete mode 100644 platform/ios/app/Default-568h@2x.png diff --git a/platform/ios/app/Default-568h@2x.png b/platform/ios/app/Default-568h@2x.png deleted file mode 100644 index 0891b7aabfcf3422423b109c8beed2bab838c607..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18594 zcmeI4X;f257Jx&9fS`ixvS;&$x8J@slQFSel)6zJN=?13FB7H(lQjRkSy8x_-S~tvu2gzn1oS+dLcF#eqtq$ z%tf9TTvX?`)R@}3uBI;jzS-=ZR-Td&MHaS&;!0?Ni*#$#`n*~CcQK)Q9vAQ~TUpnI!j)a2biYK^R)M~A5wUDZhx?ULMX z3x1P&qt=trOY6P2U67L=m=U?F|5#Uj(eCueNTZaHs_ceWiHeET+j+tp3Jt9g(ekqP z2WOvfR{qV+9r+o4J5?qK>7;;^+I7tGv-i)es$X_D=EoKF+S?zsyj^oRFElP}c}JT< zd8SUs-?O?}2YD#ngKbnHgzHBcboxK_2r9l(?eNCl-pEzkJm}fY?WC*jnS?VBE4EpY zO$fEejz6fU;W2Kl>JeQBZBl-%Irg`obSlg*@4QB;Dd1H7^Oi5wvt4d{RZ!8Og?^aE z)k0$1g+V3fd(gdQ3d&q2q-FL*uy#}|bc^=VhFsl0jBgUGJ+-s3U8MK9A!YJJMxpci z5hJ%|{DwV48fZn0{n5l$N_KcSb#NKE4plB`9I6Zt=Z!~-zw0{9tg$L&Ju1F0X)Cy8 zKF;(&lJ>x)Jw(=;p~sF(Sd9VWGwFE2rnyS9!f^DZ8+aCLq zQ};>lcJ1GDLqjm6Hd>|Eabno@P`~Bn(~6^aD_#yoEH(a?Nm1S<;S+hSxI5d16^<1lEM3NPFi zkqPrpL)+ zgnseFikg`gJVBha1&7C4;O6>h=dt~`ND+;Zd?W(4v2JIb7Pt>Td42%M-Ju-XAH#Pns762L}K3 zDhvsRqN0Ni(1UrishD2YvV?4*h2iFj$+&N||Fn$4n|^NSU+o?~jq`0jVQt8T9l{7b zXiwwODFh2V!Q6sqP9S>WH$oOf$N~=d0-bqTlD61!=`&0eAP-F>XN?*|gtOXX{ zQVTWyYo4ZK0GAw!GHf|pz9`D;-bbb*5LBX*{bnz|+)$@&P9|ORM2o?95{;ejvo&r- zq8cBhTN6nn)7~W>54U)%-F_-b?YKdfk5I8MHcuzBD5)!;yv#Z&R&^y=@=>VTIMy#r zX&U<=BsPkdqcMe<_}2+>H%XKyrr5ZR8_KVe>ZqYN z^=^~TFD};;rHJ$U;{~w^hYojl4hRI@SH$^K{YEo=sg)WY87r!*7blQK&qnpDo0`Vn zkl)9u9g=mCh&ZCJS(L4yN3k0kQ zuvg$h2KEEk51T+O0JQ+r0`R>g{jvqM0Mr6d3qUOZwE!?PI7HY@CE|dr sfw?Q;rAv?G4&^^8-z_>&sWXMxvD*gPOU4CBe-*@OtE+wfmVJNyHv)PfH~;_u diff --git a/platform/ios/app/LaunchScreen.storyboard b/platform/ios/app/LaunchScreen.storyboard index 323bd4317..299e18688 100644 --- a/platform/ios/app/LaunchScreen.storyboard +++ b/platform/ios/app/LaunchScreen.storyboard @@ -1,8 +1,12 @@ - - + + + + + - + + @@ -14,9 +18,9 @@ - + - + diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m index b6b9eea7d..992b7d587 100644 --- a/platform/ios/app/MBXViewController.m +++ b/platform/ios/app/MBXViewController.m @@ -34,7 +34,6 @@ typedef NS_ENUM(NSInteger, MBXSettingsCoreRenderingRows) { MBXSettingsCoreRenderingTimestamps, MBXSettingsCoreRenderingCollisionBoxes, MBXSettingsCoreRenderingOverdrawVisualization, - MBXSettingsCoreRenderingToggleTwoMaps, }; typedef NS_ENUM(NSInteger, MBXSettingsAnnotationsRows) { @@ -48,6 +47,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsAnnotationsRows) { MBXSettingsAnnotationsTestShapes, MBXSettingsAnnotationsCustomCallout, MBXSettingsAnnotationsQueryAnnotations, + MBXSettingsAnnotationsCustomUserDot, MBXSettingsAnnotationsRemoveAnnotations, }; @@ -71,7 +71,6 @@ typedef NS_ENUM(NSInteger, MBXSettingsRuntimeStylingRows) { MBXSettingsRuntimeStylingUpdateShapeSourceFeatures, MBXSettingsRuntimeStylingVectorSource, MBXSettingsRuntimeStylingRasterSource, - MBXSettingsRuntimeStylingCountryLabels, MBXSettingsRuntimeStylingRouteLine, MBXSettingsRuntimeStylingDDSPolygon, }; @@ -79,9 +78,10 @@ typedef NS_ENUM(NSInteger, MBXSettingsRuntimeStylingRows) { typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { MBXSettingsMiscellaneousShowReuseQueueStats = 0, MBXSettingsMiscellaneousWorldTour, - MBXSettingsMiscellaneousCustomUserDot, MBXSettingsMiscellaneousShowZoomLevel, MBXSettingsMiscellaneousScrollView, + MBXSettingsMiscellaneousToggleTwoMaps, + MBXSettingsMiscellaneousCountryLabels, MBXSettingsMiscellaneousPrintLogFile, MBXSettingsMiscellaneousDeleteLogFile, }; @@ -303,8 +303,6 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { (debugMask & MGLMapDebugCollisionBoxesMask ? @"Hide" :@"Show")], [NSString stringWithFormat:@"%@ Overdraw Visualization", (debugMask & MGLMapDebugOverdrawVisualizationMask ? @"Hide" :@"Show")], - [NSString stringWithFormat:@"%@ Second Map", - ([self.view viewWithTag:2] == nil ? @"Show" : @"Hide")], ]]; break; case MBXSettingsAnnotations: @@ -319,6 +317,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { @"Add Test Shapes", @"Add Point With Custom Callout", @"Query Annotations", + [NSString stringWithFormat:@"%@ Custom User Dot", (_customUserLocationAnnnotationEnabled ? @"Disable" : @"Enable")], @"Remove Annotations", ]]; break; @@ -343,7 +342,6 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { @"Update Shape Source: Features", @"Style Vector Source", @"Style Raster Source", - [NSString stringWithFormat:@"Show Labels in %@", (_usingLocaleBasedCountryLabels ? @"Local Language" : [[NSLocale currentLocale] displayNameForKey:NSLocaleIdentifier value:[self bestLanguageForUser]])], @"Add Route Line", @"Dynamically Style Polygon", ]]; @@ -352,9 +350,10 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { [settingsTitles addObjectsFromArray:@[ [NSString stringWithFormat:@"%@ Reuse Queue Stats", (_reuseQueueStatsEnabled ? @"Hide" :@"Show")], @"Start World Tour", - [NSString stringWithFormat:@"%@ Custom User Dot", (_customUserLocationAnnnotationEnabled ? @"Disable" : @"Enable")], [NSString stringWithFormat:@"%@ Zoom Level", (_showZoomLevelEnabled ? @"Hide" :@"Show")], @"Embedded Map View", + [NSString stringWithFormat:@"%@ Second Map", ([self.view viewWithTag:2] == nil ? @"Show" : @"Hide")], + [NSString stringWithFormat:@"Show Labels in %@", (_usingLocaleBasedCountryLabels ? @"Default Language" : [[NSLocale currentLocale] displayNameForKey:NSLocaleIdentifier value:[self bestLanguageForUser]])], ]]; if (self.debugLoggingEnabled) @@ -399,7 +398,163 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { case MBXSettingsCoreRenderingOverdrawVisualization: self.mapView.debugMask ^= MGLMapDebugOverdrawVisualizationMask; break; - case MBXSettingsCoreRenderingToggleTwoMaps: + default: + NSAssert(NO, @"All core rendering setting rows should be implemented"); + break; + } + break; + case MBXSettingsAnnotations: + switch (indexPath.row) + { + case MBXSettingsAnnotations100Views: + [self parseFeaturesAddingCount:100 usingViews:YES]; + break; + case MBXSettingsAnnotations1000Views: + [self parseFeaturesAddingCount:1000 usingViews:YES]; + break; + case MBXSettingsAnnotations10000Views: + [self parseFeaturesAddingCount:10000 usingViews:YES]; + break; + case MBXSettingsAnnotations100Sprites: + [self parseFeaturesAddingCount:100 usingViews:NO]; + break; + case MBXSettingsAnnotations1000Sprites: + [self parseFeaturesAddingCount:1000 usingViews:NO]; + break; + case MBXSettingsAnnotations10000Sprites: + [self parseFeaturesAddingCount:10000 usingViews:NO]; + break; + case MBXSettingsAnnotationAnimation: + [self animateAnnotationView]; + break; + case MBXSettingsAnnotationsTestShapes: + [self addTestShapes]; + break; + case MBXSettingsAnnotationsCustomCallout: + [self addAnnotationWithCustomCallout]; + break; + case MBXSettingsAnnotationsQueryAnnotations: + [self testQueryPointAnnotations]; + break; + case MBXSettingsAnnotationsCustomUserDot: + [self toggleCustomUserDot]; + break; + case MBXSettingsAnnotationsRemoveAnnotations: + [self.mapView removeAnnotations:self.mapView.annotations]; + break; + default: + NSAssert(NO, @"All annotations setting rows should be implemented"); + break; + } + break; + case MBXSettingsRuntimeStyling: + switch (indexPath.row) + { + case MBXSettingsRuntimeStylingWater: + [self styleWaterLayer]; + break; + case MBXSettingsRuntimeStylingRoads: + [self styleRoadLayer]; + break; + case MBXSettingsRuntimeStylingRaster: + [self styleRasterLayer]; + break; + case MBXSettingsRuntimeStylingShape: + [self styleShapeSource]; + break; + case MBXSettingsRuntimeStylingSymbols: + [self styleSymbolLayer]; + break; + case MBXSettingsRuntimeStylingBuildings: + [self styleBuildingLayer]; + break; + case MBXSettingsRuntimeStylingFerry: + [self styleFerryLayer]; + break; + case MBXSettingsRuntimeStylingParks: + [self removeParkLayer]; + break; + case MBXSettingsRuntimeStylingFilteredFill: + [self styleFilteredFill]; + break; + case MBXSettingsRuntimeStylingFilteredLines: + [self styleFilteredLines]; + break; + case MBXSettingsRuntimeStylingNumericFilteredFill: + [self styleNumericFilteredFills]; + break; + case MBXSettingsRuntimeStylingStyleQuery: + [self styleQuery]; + break; + case MBXSettingsRuntimeStylingFeatureSource: + [self styleFeature]; + break; + case MBXSettingsRuntimeStylingPointCollection: + [self styleDynamicPointCollection]; + break; + case MBXSettingsRuntimeStylingUpdateShapeSourceURL: + [self updateShapeSourceURL]; + break; + case MBXSettingsRuntimeStylingUpdateShapeSourceData: + [self updateShapeSourceData]; + break; + case MBXSettingsRuntimeStylingUpdateShapeSourceFeatures: + [self updateShapeSourceFeatures]; + break; + case MBXSettingsRuntimeStylingVectorSource: + [self styleVectorSource]; + break; + case MBXSettingsRuntimeStylingRasterSource: + [self styleRasterSource]; + break; + case MBXSettingsRuntimeStylingRouteLine: + [self styleRouteLine]; + break; + case MBXSettingsRuntimeStylingDDSPolygon: + [self stylePolygonWithDDS]; + break; + default: + NSAssert(NO, @"All runtime styling setting rows should be implemented"); + break; + } + break; + case MBXSettingsMiscellaneous: + switch (indexPath.row) + { + case MBXSettingsMiscellaneousCountryLabels: + [self styleCountryLabelsLanguage]; + break; + case MBXSettingsMiscellaneousWorldTour: + [self startWorldTour]; + break; + case MBXSettingsMiscellaneousPrintLogFile: + [self printTelemetryLogFile]; + break; + case MBXSettingsMiscellaneousDeleteLogFile: + [self deleteTelemetryLogFile]; + break; + case MBXSettingsMiscellaneousShowReuseQueueStats: + { + self.reuseQueueStatsEnabled = !self.reuseQueueStatsEnabled; + self.hudLabel.hidden = !self.reuseQueueStatsEnabled; + self.showZoomLevelEnabled = NO; + break; + } + case MBXSettingsMiscellaneousShowZoomLevel: + { + self.showZoomLevelEnabled = !self.showZoomLevelEnabled; + self.hudLabel.hidden = !self.showZoomLevelEnabled; + self.reuseQueueStatsEnabled = NO; + break; + } + case MBXSettingsMiscellaneousScrollView: + { + UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; + MBXEmbeddedMapViewController *embeddedMapViewController = (MBXEmbeddedMapViewController *)[storyboard instantiateViewControllerWithIdentifier:@"MBXEmbeddedMapViewController"]; + [self.navigationController pushViewController:embeddedMapViewController animated:YES]; + break; + } + case MBXSettingsMiscellaneousToggleTwoMaps: if ([self.view viewWithTag:2] == nil) { MGLMapView *secondMapView = [[MGLMapView alloc] initWithFrame: CGRectMake(0, self.view.bounds.size.height / 2, @@ -474,162 +629,6 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { constant:0]]; } break; - default: - NSAssert(NO, @"All core rendering setting rows should be implemented"); - break; - } - break; - case MBXSettingsAnnotations: - switch (indexPath.row) - { - case MBXSettingsAnnotations100Views: - [self parseFeaturesAddingCount:100 usingViews:YES]; - break; - case MBXSettingsAnnotations1000Views: - [self parseFeaturesAddingCount:1000 usingViews:YES]; - break; - case MBXSettingsAnnotations10000Views: - [self parseFeaturesAddingCount:10000 usingViews:YES]; - break; - case MBXSettingsAnnotations100Sprites: - [self parseFeaturesAddingCount:100 usingViews:NO]; - break; - case MBXSettingsAnnotations1000Sprites: - [self parseFeaturesAddingCount:1000 usingViews:NO]; - break; - case MBXSettingsAnnotations10000Sprites: - [self parseFeaturesAddingCount:10000 usingViews:NO]; - break; - case MBXSettingsAnnotationAnimation: - [self animateAnnotationView]; - break; - case MBXSettingsAnnotationsTestShapes: - [self addTestShapes]; - break; - case MBXSettingsAnnotationsCustomCallout: - [self addAnnotationWithCustomCallout]; - break; - case MBXSettingsAnnotationsQueryAnnotations: - [self testQueryPointAnnotations]; - break; - case MBXSettingsAnnotationsRemoveAnnotations: - [self.mapView removeAnnotations:self.mapView.annotations]; - break; - default: - NSAssert(NO, @"All annotations setting rows should be implemented"); - break; - } - break; - case MBXSettingsRuntimeStyling: - switch (indexPath.row) - { - case MBXSettingsRuntimeStylingWater: - [self styleWaterLayer]; - break; - case MBXSettingsRuntimeStylingRoads: - [self styleRoadLayer]; - break; - case MBXSettingsRuntimeStylingRaster: - [self styleRasterLayer]; - break; - case MBXSettingsRuntimeStylingShape: - [self styleShapeSource]; - break; - case MBXSettingsRuntimeStylingSymbols: - [self styleSymbolLayer]; - break; - case MBXSettingsRuntimeStylingBuildings: - [self styleBuildingLayer]; - break; - case MBXSettingsRuntimeStylingFerry: - [self styleFerryLayer]; - break; - case MBXSettingsRuntimeStylingParks: - [self removeParkLayer]; - break; - case MBXSettingsRuntimeStylingFilteredFill: - [self styleFilteredFill]; - break; - case MBXSettingsRuntimeStylingFilteredLines: - [self styleFilteredLines]; - break; - case MBXSettingsRuntimeStylingNumericFilteredFill: - [self styleNumericFilteredFills]; - break; - case MBXSettingsRuntimeStylingStyleQuery: - [self styleQuery]; - break; - case MBXSettingsRuntimeStylingFeatureSource: - [self styleFeature]; - break; - case MBXSettingsRuntimeStylingPointCollection: - [self styleDynamicPointCollection]; - break; - case MBXSettingsRuntimeStylingUpdateShapeSourceURL: - [self updateShapeSourceURL]; - break; - case MBXSettingsRuntimeStylingUpdateShapeSourceData: - [self updateShapeSourceData]; - break; - case MBXSettingsRuntimeStylingUpdateShapeSourceFeatures: - [self updateShapeSourceFeatures]; - break; - case MBXSettingsRuntimeStylingVectorSource: - [self styleVectorSource]; - break; - case MBXSettingsRuntimeStylingRasterSource: - [self styleRasterSource]; - break; - case MBXSettingsRuntimeStylingCountryLabels: - [self styleCountryLabelsLanguage]; - break; - case MBXSettingsRuntimeStylingRouteLine: - [self styleRouteLine]; - break; - case MBXSettingsRuntimeStylingDDSPolygon: - [self stylePolygonWithDDS]; - break; - default: - NSAssert(NO, @"All runtime styling setting rows should be implemented"); - break; - } - break; - case MBXSettingsMiscellaneous: - switch (indexPath.row) - { - case MBXSettingsMiscellaneousWorldTour: - [self startWorldTour]; - break; - case MBXSettingsMiscellaneousCustomUserDot: - [self toggleCustomUserDot]; - break; - case MBXSettingsMiscellaneousPrintLogFile: - [self printTelemetryLogFile]; - break; - case MBXSettingsMiscellaneousDeleteLogFile: - [self deleteTelemetryLogFile]; - break; - case MBXSettingsMiscellaneousShowReuseQueueStats: - { - self.reuseQueueStatsEnabled = !self.reuseQueueStatsEnabled; - self.hudLabel.hidden = !self.reuseQueueStatsEnabled; - self.showZoomLevelEnabled = NO; - break; - } - case MBXSettingsMiscellaneousShowZoomLevel: - { - self.showZoomLevelEnabled = !self.showZoomLevelEnabled; - self.hudLabel.hidden = !self.showZoomLevelEnabled; - self.reuseQueueStatsEnabled = NO; - break; - } - case MBXSettingsMiscellaneousScrollView: - { - UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; - MBXEmbeddedMapViewController *embeddedMapViewController = (MBXEmbeddedMapViewController *)[storyboard instantiateViewControllerWithIdentifier:@"MBXEmbeddedMapViewController"]; - [self.navigationController pushViewController:embeddedMapViewController animated:YES]; - break; - } default: NSAssert(NO, @"All miscellaneous setting rows should be implemented"); break; @@ -1261,8 +1260,8 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { - (void)styleRasterSource { - NSArray *tileURLTemplates = @[@"https://stamen-tiles.a.ssl.fastly.net/terrain-background/{z}/{x}/{y}.jpg"]; - MGLRasterSource *rasterSource = [[MGLRasterSource alloc] initWithIdentifier:@"style-raster-source-id" tileURLTemplates:tileURLTemplates options:@{ + NSString *tileURL = [NSString stringWithFormat:@"https://stamen-tiles.a.ssl.fastly.net/terrain-background/{z}/{x}/{y}%@.jpg", UIScreen.mainScreen.nativeScale > 1 ? @"@2x" : @""]; + MGLRasterSource *rasterSource = [[MGLRasterSource alloc] initWithIdentifier:@"style-raster-source-id" tileURLTemplates:@[tileURL] options:@{ MGLTileSourceOptionTileSize: @256, }]; [self.mapView.style addSource:rasterSource]; @@ -1574,7 +1573,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { [sender setAccessibilityValue:nextAccessibilityValue]; } -#pragma mark - Map Delegate +#pragma mark - MGLMapViewDelegate - (MGLAnnotationView *)mapView:(MGLMapView *)mapView viewForAnnotation:(id)annotation { diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index 83b267df4..76b35be2d 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -233,7 +233,6 @@ DA1DC9971CB6E046006E619F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DA1DC9961CB6E046006E619F /* main.m */; }; DA1DC9991CB6E054006E619F /* MBXAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DA1DC9981CB6E054006E619F /* MBXAppDelegate.m */; }; DA1DC99B1CB6E064006E619F /* MBXViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DA1DC99A1CB6E064006E619F /* MBXViewController.m */; }; - DA1DC99D1CB6E076006E619F /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA1DC99C1CB6E076006E619F /* Default-568h@2x.png */; }; DA1DC99F1CB6E088006E619F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DA1DC99E1CB6E088006E619F /* Assets.xcassets */; }; DA1F8F3D1EBD287B00367E42 /* MGLDocumentationGuideTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA1F8F3C1EBD287B00367E42 /* MGLDocumentationGuideTests.swift */; }; DA2207BF1DC0805F0002F84D /* MGLStyleValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA2207BE1DC0805F0002F84D /* MGLStyleValueTests.swift */; }; @@ -718,7 +717,6 @@ DA1DC9961CB6E046006E619F /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; DA1DC9981CB6E054006E619F /* MBXAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MBXAppDelegate.m; sourceTree = ""; }; DA1DC99A1CB6E064006E619F /* MBXViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MBXViewController.m; sourceTree = ""; }; - DA1DC99C1CB6E076006E619F /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; DA1DC99E1CB6E088006E619F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; DA1F8F3C1EBD287B00367E42 /* MGLDocumentationGuideTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MGLDocumentationGuideTests.swift; path = ../../darwin/test/MGLDocumentationGuideTests.swift; sourceTree = ""; }; DA2207BE1DC0805F0002F84D /* MGLStyleValueTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MGLStyleValueTests.swift; path = ../../darwin/test/MGLStyleValueTests.swift; sourceTree = ""; }; @@ -1154,6 +1152,20 @@ name = Sources; sourceTree = ""; }; + 9604FC341F313A5E003EEA02 /* Fixtures */ = { + isa = PBXGroup; + children = ( + 353BAEF51D646370009A8DA9 /* amsterdam.geojson */, + DA1DC96C1CB6C6CE006E619F /* points.geojson */, + DA1DC96D1CB6C6CE006E619F /* polyline.geojson */, + DA1DC96F1CB6C6CE006E619F /* threestates.geojson */, + DD4823721D94AE6C00EB71B7 /* fill_filter_style.json */, + DD4823731D94AE6C00EB71B7 /* line_filter_style.json */, + DD4823741D94AE6C00EB71B7 /* numeric_filter_style.json */, + ); + name = Fixtures; + sourceTree = ""; + }; DA1DC9411CB6C1C2006E619F = { isa = PBXGroup; children = ( @@ -1202,16 +1214,9 @@ DA821D051CCC6D59007508D4 /* Main.storyboard */, DA821D041CCC6D59007508D4 /* LaunchScreen.storyboard */, DA1DC99E1CB6E088006E619F /* Assets.xcassets */, - DA1DC96C1CB6C6CE006E619F /* points.geojson */, - DA1DC96D1CB6C6CE006E619F /* polyline.geojson */, - DA1DC96F1CB6C6CE006E619F /* threestates.geojson */, - 353BAEF51D646370009A8DA9 /* amsterdam.geojson */, - DD4823721D94AE6C00EB71B7 /* fill_filter_style.json */, - DD4823731D94AE6C00EB71B7 /* line_filter_style.json */, - DD4823741D94AE6C00EB71B7 /* numeric_filter_style.json */, DA1DC95E1CB6C1C2006E619F /* Info.plist */, - DA1DC99C1CB6E076006E619F /* Default-568h@2x.png */, 96E027251E57C76E004B8E66 /* Localizable.strings */, + 9604FC341F313A5E003EEA02 /* Fixtures */, DA1DC94D1CB6C1C2006E619F /* Supporting Files */, ); name = "Demo App"; @@ -2056,7 +2061,6 @@ 353BAEF61D646370009A8DA9 /* amsterdam.geojson in Resources */, DA1DC9711CB6C6CE006E619F /* polyline.geojson in Resources */, DD4823761D94AE6C00EB71B7 /* line_filter_style.json in Resources */, - DA1DC99D1CB6E076006E619F /* Default-568h@2x.png in Resources */, DA821D071CCC6D59007508D4 /* Main.storyboard in Resources */, DA1DC9731CB6C6CE006E619F /* threestates.geojson in Resources */, DA821D061CCC6D59007508D4 /* LaunchScreen.storyboard in Resources */, From 6a8ed6f83ae6ae9494cfc075214ba4558abd7e85 Mon Sep 17 00:00:00 2001 From: Fredrik Karlsson Date: Mon, 31 Jul 2017 13:57:41 +0200 Subject: [PATCH 62/71] [ios] expose setCamera with edge padding --- platform/ios/CHANGELOG.md | 1 + platform/ios/src/MGLMapView.h | 17 +++++++++++++++++ platform/ios/src/MGLMapView.mm | 6 +++++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 55790577c..dad6d0c4b 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.6.2 * Added an MGLStyle.localizesLabels property, off by default, that localizes any Mapbox Streets–sourced symbol layer into the user’s preferred language. ([#9582](https://github.com/mapbox/mapbox-gl-native/pull/9582)) +* Added an additional camera method to MGLMapView that accepts an edge padding parameter. ([#9651](https://github.com/mapbox/mapbox-gl-native/pull/9651)) ## 3.6.1 - July 28, 2017 diff --git a/platform/ios/src/MGLMapView.h b/platform/ios/src/MGLMapView.h index a93b8e851..a0aada9a9 100644 --- a/platform/ios/src/MGLMapView.h +++ b/platform/ios/src/MGLMapView.h @@ -764,6 +764,23 @@ MGL_EXPORT IB_DESIGNABLE */ - (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion; +/** + Moves the viewpoint to a different location with respect to the map with an + optional transition duration and timing function. + + @param camera The new viewpoint. + @param duration The amount of time, measured in seconds, that the transition + animation should take. Specify `0` to jump to the new viewpoint + instantaneously. + @param function A timing function used for the animation. Set this parameter to + `nil` for a transition that matches most system animations. If the duration + is `0`, this parameter is ignored. + @param edgePadding The minimum padding (in screen points) that would be visible + around the returned camera object if it were set as the receiver’s camera. + @param completion The block to execute after the animation finishes. + */ +- (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function edgePadding:(UIEdgeInsets)edgePadding completionHandler:(nullable void (^)(void))completion; + /** Moves the viewpoint to a different location using a transition animation that evokes powered flight and a default duration based on the length of the flight diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm index 9cc4c42e5..715c32186 100644 --- a/platform/ios/src/MGLMapView.mm +++ b/platform/ios/src/MGLMapView.mm @@ -2788,6 +2788,10 @@ public: - (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion { + [self setCamera:camera withDuration:duration animationTimingFunction:function edgePadding:self.contentInset completionHandler:completion]; +} + +- (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function edgePadding:(UIEdgeInsets)edgePadding completionHandler:(nullable void (^)(void))completion { mbgl::AnimationOptions animationOptions; if (duration > 0) { @@ -2816,7 +2820,7 @@ public: [self willChangeValueForKey:@"camera"]; _mbglMap->cancelTransitions(); - mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera edgePadding:self.contentInset]; + mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera edgePadding:edgePadding]; _mbglMap->easeTo(cameraOptions, animationOptions); [self didChangeValueForKey:@"camera"]; } From 01d801c8c73b77c75a28f4fa8148780d58fe464d Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Fri, 4 Aug 2017 15:47:42 -0400 Subject: [PATCH 63/71] [ios] Update READMEs to link to help page --- platform/ios/docs/doc-README.md | 2 +- platform/ios/docs/pod-README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/platform/ios/docs/doc-README.md b/platform/ios/docs/doc-README.md index 6c2693cbb..ebad32e04 100644 --- a/platform/ios/docs/doc-README.md +++ b/platform/ios/docs/doc-README.md @@ -6,4 +6,4 @@ The Mapbox iOS SDK is an open-source framework for embedding interactive map vie For setup information, check out the [Mapbox iOS SDK homepage](https://www.mapbox.com/ios-sdk/). For detailed usage instructions, read “[First steps with the Mapbox iOS SDK](https://www.mapbox.com/help/first-steps-ios-sdk/)” and consult the [online examples](https://www.mapbox.com/ios-sdk/examples/). A [full changelog](https://github.com/mapbox/mapbox-gl-native/blob/master/platform/ios/CHANGELOG.md) is also available. -If you have any questions, please [contact our support team](https://www.mapbox.com/contact/). We welcome your [bug reports and feature requests](https://github.com/mapbox/mapbox-gl-native/issues/). +If you have any questions, please see [our help page](https://www.mapbox.com/help/). We welcome your [bug reports, feature requests, and contributions](https://github.com/mapbox/mapbox-gl-native/issues/). diff --git a/platform/ios/docs/pod-README.md b/platform/ios/docs/pod-README.md index 0a7edc5a4..2e5a78841 100644 --- a/platform/ios/docs/pod-README.md +++ b/platform/ios/docs/pod-README.md @@ -96,4 +96,4 @@ class ViewController: UIViewController { Full API documentation is included in this package, within the `documentation` folder. For more details, read “[First steps with the Mapbox iOS SDK](https://www.mapbox.com/help/first-steps-ios-sdk/)” and consult the [online examples](https://www.mapbox.com/ios-sdk/examples/). -If you have any questions, please [contact our support team](https://www.mapbox.com/contact/). We welcome your [bug reports and feature requests](https://github.com/mapbox/mapbox-gl-native/issues/). +If you have any questions, please see [our help page](https://www.mapbox.com/help/). We welcome your [bug reports, feature requests, and contributions](https://github.com/mapbox/mapbox-gl-native/issues/). From 1394bfb554c5daa5d2384d960c9118e880a30480 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Mon, 7 Aug 2017 14:33:39 -0400 Subject: [PATCH 64/71] [ios] Fix user location horizontal accuracy ring inaccuracy Accuracy was off by +25%, depending on latitude. --- platform/ios/CHANGELOG.md | 1 + platform/ios/src/MGLFaux3DUserLocationAnnotationView.m | 7 ++----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index dad6d0c4b..8535073cd 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -6,6 +6,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT * Added an MGLStyle.localizesLabels property, off by default, that localizes any Mapbox Streets–sourced symbol layer into the user’s preferred language. ([#9582](https://github.com/mapbox/mapbox-gl-native/pull/9582)) * Added an additional camera method to MGLMapView that accepts an edge padding parameter. ([#9651](https://github.com/mapbox/mapbox-gl-native/pull/9651)) +* Fixed an issue with the scaling of the user location annotation’s horizontal accuracy indicator. ([#9721](https://github.com/mapbox/mapbox-gl-native/pull/9721)) ## 3.6.1 - July 28, 2017 diff --git a/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m b/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m index 6db9c0db1..5f67f24f4 100644 --- a/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m +++ b/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m @@ -460,11 +460,8 @@ const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuck - (CGFloat)calculateAccuracyRingSize { - CGFloat latitudeRadians = MGLRadiansFromDegrees(self.userLocation.coordinate.latitude); - CGFloat metersPerPoint = [self.mapView metersPerPointAtLatitude:self.userLocation.coordinate.latitude]; - CGFloat pixelRadius = self.userLocation.location.horizontalAccuracy / cos(latitudeRadians) / metersPerPoint; - - return pixelRadius * 2.0; + // diameter in screen points + return self.userLocation.location.horizontalAccuracy / [self.mapView metersPerPointAtLatitude:self.userLocation.coordinate.latitude] * 2.0; } - (UIImage *)headingIndicatorTintedGradientImage From 2e24562ee8d2c0ca43fa5eab4170222e544e1d26 Mon Sep 17 00:00:00 2001 From: Nadia Barbosa Date: Mon, 7 Aug 2017 15:06:13 -0400 Subject: [PATCH 65/71] [ios] Update MGLPolyline docs about antimeridian crossings --- platform/darwin/src/MGLPolyline.h | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/platform/darwin/src/MGLPolyline.h b/platform/darwin/src/MGLPolyline.h index b3db0fd39..079a1c617 100644 --- a/platform/darwin/src/MGLPolyline.h +++ b/platform/darwin/src/MGLPolyline.h @@ -33,8 +33,19 @@ NS_ASSUME_NONNULL_BEGIN `MGLPolygon` object. To group multiple polylines together in one shape, use an `MGLMultiPolyline` or `MGLShapeCollection` object. - To make the polyline straddle the antimeridian, specify some longitudes less - than −180 degrees or greater than 180 degrees. + To make the polyline go across the antimeridian or international date line, + specify some longitudes less than −180 degrees or greater than 180 degrees. + For example, a polyline that stretches from Tokyo to San Francisco would have + coordinates of (35.68476, -220.24257) and (37.78428, -122.41310). + + ```swift + let coordinates = [ + CLLocationCoordinate2D(latitude: 35.68476, longitude: -220.24257), + CLLocationCoordinate2D(latitude: 37.78428, longitude: -122.41310) + ] + + let polyline = MGLPolyline(coordinates: &coordinates, count: UInt(coordinates.count)) +``` A polyline is known as a LineString From 5d641f4d6892ae22c9b96f77d92a5295f3ca28b3 Mon Sep 17 00:00:00 2001 From: Nadia Barbosa Date: Tue, 8 Aug 2017 17:16:25 -0400 Subject: [PATCH 66/71] [ios] Add test for code snippet added to MGLPolyline doc change --- platform/darwin/src/MGLPolyline.h | 5 ++--- .../darwin/test/MGLDocumentationExampleTests.swift | 12 ++++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/platform/darwin/src/MGLPolyline.h b/platform/darwin/src/MGLPolyline.h index 079a1c617..e46baa91c 100644 --- a/platform/darwin/src/MGLPolyline.h +++ b/platform/darwin/src/MGLPolyline.h @@ -43,9 +43,8 @@ NS_ASSUME_NONNULL_BEGIN CLLocationCoordinate2D(latitude: 35.68476, longitude: -220.24257), CLLocationCoordinate2D(latitude: 37.78428, longitude: -122.41310) ] - - let polyline = MGLPolyline(coordinates: &coordinates, count: UInt(coordinates.count)) -``` + let polyline = MGLPolyline(coordinates: coordinates, count: UInt(coordinates.count)) + ``` A polyline is known as a LineString diff --git a/platform/darwin/test/MGLDocumentationExampleTests.swift b/platform/darwin/test/MGLDocumentationExampleTests.swift index 48e6b17f4..3e949b096 100644 --- a/platform/darwin/test/MGLDocumentationExampleTests.swift +++ b/platform/darwin/test/MGLDocumentationExampleTests.swift @@ -103,6 +103,18 @@ class MGLDocumentationExampleTests: XCTestCase, MGLMapViewDelegate { XCTAssertNotNil(mapView.style?.source(withIdentifier: "pois")) } + + func testMGLPolyline() { + //#-example-code + let coordinates = [ + CLLocationCoordinate2D(latitude: 35.68476, longitude: -220.24257), + CLLocationCoordinate2D(latitude: 37.78428, longitude: -122.41310) + ] + let polyline = MGLPolyline(coordinates: coordinates, count: UInt(coordinates.count)) + //#-end-example-code + + XCTAssertNotNil(polyline) + } func testMGLCircleStyleLayer() { let population = MGLVectorSource(identifier: "population", configurationURL: URL(string: "https://example.com/style.json")!) From c7d7319c77dd138cac487eda2232f9bc3bc7d0b6 Mon Sep 17 00:00:00 2001 From: Fabian Guerra Soto Date: Thu, 17 Aug 2017 19:19:06 -0400 Subject: [PATCH 67/71] [ios, macos] Fix querying features returning nil when features available. (#9784) * [ios, macos] Fix querying features returning nil when features available. * [ios, macos] Copyedited changelogs Also, macOS SDK v0.5.1 has yet to be released, because none of the changes that went into iOS SDK v3.6.1 were relevant to macOS. --- platform/darwin/src/MGLSource.mm | 5 +++-- platform/darwin/src/MGLSource_Private.h | 2 +- platform/darwin/src/MGLStyle.mm | 9 +++++---- platform/ios/CHANGELOG.md | 3 ++- platform/macos/CHANGELOG.md | 5 ++++- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/platform/darwin/src/MGLSource.mm b/platform/darwin/src/MGLSource.mm index eb859ba2c..7bab90e9d 100644 --- a/platform/darwin/src/MGLSource.mm +++ b/platform/darwin/src/MGLSource.mm @@ -27,17 +27,18 @@ return self; } -- (instancetype)initWithRawSource:(mbgl::style::Source *)rawSource { +- (instancetype)initWithRawSource:(mbgl::style::Source *)rawSource mapView:(MGLMapView *)mapView { NSString *identifier = @(rawSource->getID().c_str()); if (self = [self initWithIdentifier:identifier]) { _rawSource = rawSource; _rawSource->peer = SourceWrapper { self }; + _mapView = mapView; } return self; } - (instancetype)initWithPendingSource:(std::unique_ptr)pendingSource { - if (self = [self initWithRawSource:pendingSource.get()]) { + if (self = [self initWithRawSource:pendingSource.get() mapView:nil]) { _pendingSource = std::move(pendingSource); } return self; diff --git a/platform/darwin/src/MGLSource_Private.h b/platform/darwin/src/MGLSource_Private.h index 91bfac639..13a3dd5a3 100644 --- a/platform/darwin/src/MGLSource_Private.h +++ b/platform/darwin/src/MGLSource_Private.h @@ -26,7 +26,7 @@ struct SourceWrapper { Initializes and returns a source with a raw pointer to the backing store, associated with a style. */ -- (instancetype)initWithRawSource:(mbgl::style::Source *)rawSource; +- (instancetype)initWithRawSource:(mbgl::style::Source *)rawSource mapView:(nullable MGLMapView *)mapView; /** Initializes and returns a source with an owning pointer to the backing store, diff --git a/platform/darwin/src/MGLStyle.mm b/platform/darwin/src/MGLStyle.mm index 1e0a2e02b..d8aa01434 100644 --- a/platform/darwin/src/MGLStyle.mm +++ b/platform/darwin/src/MGLStyle.mm @@ -187,6 +187,7 @@ static NSURL *MGLStyleURL_emerald; - (MGLSource *)sourceWithIdentifier:(NSString *)identifier { auto rawSource = self.mapView.mbglMap->getSource(identifier.UTF8String); + return rawSource ? [self sourceFromMBGLSource:rawSource] : nil; } @@ -198,13 +199,13 @@ static NSURL *MGLStyleURL_emerald; // TODO: Fill in options specific to the respective source classes // https://github.com/mapbox/mapbox-gl-native/issues/6584 if (auto vectorSource = rawSource->as()) { - return [[MGLVectorSource alloc] initWithRawSource:vectorSource]; + return [[MGLVectorSource alloc] initWithRawSource:vectorSource mapView:self.mapView]; } else if (auto geoJSONSource = rawSource->as()) { - return [[MGLShapeSource alloc] initWithRawSource:geoJSONSource]; + return [[MGLShapeSource alloc] initWithRawSource:geoJSONSource mapView:self.mapView]; } else if (auto rasterSource = rawSource->as()) { - return [[MGLRasterSource alloc] initWithRawSource:rasterSource]; + return [[MGLRasterSource alloc] initWithRawSource:rasterSource mapView:self.mapView]; } else { - return [[MGLSource alloc] initWithRawSource:rawSource]; + return [[MGLSource alloc] initWithRawSource:rawSource mapView:self.mapView]; } } diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 8535073cd..e657472aa 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -4,9 +4,10 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT ## 3.6.2 -* Added an MGLStyle.localizesLabels property, off by default, that localizes any Mapbox Streets–sourced symbol layer into the user’s preferred language. ([#9582](https://github.com/mapbox/mapbox-gl-native/pull/9582)) +* Added an `MGLStyle.localizesLabels` property, off by default, that localizes any Mapbox Streets–sourced symbol layer into the user’s preferred language. ([#9582](https://github.com/mapbox/mapbox-gl-native/pull/9582)) * Added an additional camera method to MGLMapView that accepts an edge padding parameter. ([#9651](https://github.com/mapbox/mapbox-gl-native/pull/9651)) * Fixed an issue with the scaling of the user location annotation’s horizontal accuracy indicator. ([#9721](https://github.com/mapbox/mapbox-gl-native/pull/9721)) +* Fixed an issue that caused `-[MGLShapeSource featuresMatchingPredicate:]` and `-[MGLVectorSource featuresInSourceLayersWithIdentifiers:predicate:]` to always return an empty array. ([#9784](https://github.com/mapbox/mapbox-gl-native/pull/9784)) ## 3.6.1 - July 28, 2017 diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md index 1af3c03d6..b8ed40cde 100644 --- a/platform/macos/CHANGELOG.md +++ b/platform/macos/CHANGELOG.md @@ -2,7 +2,10 @@ ## 0.5.1 -* Added an MGLStyle.localizesLabels property, off by default, that localizes any Mapbox Streets–sourced symbol layer into the user’s preferred language. ([#9582](https://github.com/mapbox/mapbox-gl-native/pull/9582)) +This version of the Mapbox macOS SDK corresponds to version 3.6.2 of the Mapbox iOS SDK. + +* Added an `MGLStyle.localizesLabels` property, off by default, that localizes any Mapbox Streets–sourced symbol layer into the user’s preferred language. ([#9582](https://github.com/mapbox/mapbox-gl-native/pull/9582)) +* Fixed an issue that caused `-[MGLShapeSource featuresMatchingPredicate:]` and `-[MGLVectorSource featuresInSourceLayersWithIdentifiers:predicate:]` to always return an empty array. ([#9784](https://github.com/mapbox/mapbox-gl-native/pull/9784)) ## 0.5.0 From 1188707dc3ca6d88349d4bf323d03e3e93c0e548 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 10 Aug 2017 11:47:31 -0700 Subject: [PATCH 68/71] [core] Use separate attribute component for line normals Selective cherry pick of 822df3dd514ec3d05f876e6746cb8ba6348ac89d --- include/mbgl/util/constants.hpp | 3 ++- src/mbgl/programs/attributes.hpp | 1 + src/mbgl/programs/line_program.cpp | 2 +- src/mbgl/programs/line_program.hpp | 12 +++++++----- src/mbgl/renderer/line_bucket.cpp | 6 +++--- src/mbgl/shaders/line.cpp | 20 +++++++++----------- src/mbgl/shaders/line_pattern.cpp | 19 +++++++++---------- src/mbgl/shaders/line_sdf.cpp | 19 +++++++++---------- 8 files changed, 41 insertions(+), 41 deletions(-) diff --git a/include/mbgl/util/constants.hpp b/include/mbgl/util/constants.hpp index 0f2779e33..dbc4bddde 100644 --- a/include/mbgl/util/constants.hpp +++ b/include/mbgl/util/constants.hpp @@ -19,7 +19,8 @@ constexpr float tileSize = 512; * * Positions are stored as signed 16bit integers. * One bit is lost for signedness to support features extending past the left edge of the tile. - * One bit is lost because the line vertex buffer packs 1 bit of other data into the int. + * One bit is lost because the line vertex buffer used to pack 1 bit of other data into the int. + * This is no longer the case but we're reserving this bit anyway. * One bit is lost to support features extending past the extent on the right edge of the tile. * This leaves us with 2^13 = 8192 */ diff --git a/src/mbgl/programs/attributes.hpp b/src/mbgl/programs/attributes.hpp index a0b2b93e1..53eae49b7 100644 --- a/src/mbgl/programs/attributes.hpp +++ b/src/mbgl/programs/attributes.hpp @@ -23,6 +23,7 @@ inline uint16_t packUint8Pair(T a, T b) { MBGL_DEFINE_ATTRIBUTE(int16_t, 2, a_pos); MBGL_DEFINE_ATTRIBUTE(int16_t, 2, a_extrude); MBGL_DEFINE_ATTRIBUTE(int16_t, 4, a_pos_offset); +MBGL_DEFINE_ATTRIBUTE(int16_t, 3, a_pos_normal); MBGL_DEFINE_ATTRIBUTE(uint16_t, 2, a_texture_pos); MBGL_DEFINE_ATTRIBUTE(int16_t, 3, a_normal); MBGL_DEFINE_ATTRIBUTE(uint16_t, 1, a_edgedistance); diff --git a/src/mbgl/programs/line_program.cpp b/src/mbgl/programs/line_program.cpp index d9778ba7c..d18dc1e7e 100644 --- a/src/mbgl/programs/line_program.cpp +++ b/src/mbgl/programs/line_program.cpp @@ -10,7 +10,7 @@ namespace mbgl { using namespace style; -static_assert(sizeof(LineLayoutVertex) == 8, "expected LineLayoutVertex size"); +static_assert(sizeof(LineLayoutVertex) == 10, "expected LineLayoutVertex size"); template Values makeValues(const LinePaintProperties::Evaluated& properties, diff --git a/src/mbgl/programs/line_program.hpp b/src/mbgl/programs/line_program.hpp index b2e55a4f3..0a56ae5da 100644 --- a/src/mbgl/programs/line_program.hpp +++ b/src/mbgl/programs/line_program.hpp @@ -31,7 +31,7 @@ MBGL_DEFINE_UNIFORM_VECTOR(float, 2, u_gl_units_to_pixels); } // namespace uniforms struct LineLayoutAttributes : gl::Attributes< - attributes::a_pos, + attributes::a_pos_normal, attributes::a_data> {}; @@ -52,14 +52,16 @@ public: /* * @param p vertex position * @param e extrude normal - * @param t texture normal + * @param round whether the vertex uses a round line cap + * @param up whether the line normal points up or down * @param dir direction of the line cap (-1/0/1) */ - static LayoutVertex layoutVertex(Point p, Point e, Point t, int8_t dir, int32_t linesofar = 0) { + static LayoutVertex layoutVertex(Point p, Point e, bool round, bool up, int8_t dir, int32_t linesofar = 0) { return LayoutVertex { {{ - static_cast((p.x * 2) | t.x), - static_cast((p.y * 2) | t.y) + p.x, + p.y, + static_cast(attributes::packUint8Pair(round ? 1 : 0, up ? 1 : 0)) }}, {{ // add 128 to store a byte in an unsigned byte diff --git a/src/mbgl/renderer/line_bucket.cpp b/src/mbgl/renderer/line_bucket.cpp index 7bb4e2f20..c80b8900e 100644 --- a/src/mbgl/renderer/line_bucket.cpp +++ b/src/mbgl/renderer/line_bucket.cpp @@ -398,7 +398,7 @@ void LineBucket::addCurrentVertex(const GeometryCoordinate& currentCoordinate, Point extrude = normal; if (endLeft) extrude = extrude - (util::perp(normal) * endLeft); - vertices.emplace_back(LineProgram::layoutVertex(currentCoordinate, extrude, { round, false }, endLeft, distance * LINE_DISTANCE_SCALE)); + vertices.emplace_back(LineProgram::layoutVertex(currentCoordinate, extrude, round, false, endLeft, distance * LINE_DISTANCE_SCALE)); e3 = vertices.vertexSize() - 1 - startVertex; if (e1 >= 0 && e2 >= 0) { triangleStore.emplace_back(e1, e2, e3); @@ -409,7 +409,7 @@ void LineBucket::addCurrentVertex(const GeometryCoordinate& currentCoordinate, extrude = normal * -1.0; if (endRight) extrude = extrude - (util::perp(normal) * endRight); - vertices.emplace_back(LineProgram::layoutVertex(currentCoordinate, extrude, { round, true }, -endRight, distance * LINE_DISTANCE_SCALE)); + vertices.emplace_back(LineProgram::layoutVertex(currentCoordinate, extrude, round, true, -endRight, distance * LINE_DISTANCE_SCALE)); e3 = vertices.vertexSize() - 1 - startVertex; if (e1 >= 0 && e2 >= 0) { triangleStore.emplace_back(e1, e2, e3); @@ -434,7 +434,7 @@ void LineBucket::addPieSliceVertex(const GeometryCoordinate& currentVertex, std::size_t startVertex, std::vector& triangleStore) { Point flippedExtrude = extrude * (lineTurnsLeft ? -1.0 : 1.0); - vertices.emplace_back(LineProgram::layoutVertex(currentVertex, flippedExtrude, { false, lineTurnsLeft }, 0, distance * LINE_DISTANCE_SCALE)); + vertices.emplace_back(LineProgram::layoutVertex(currentVertex, flippedExtrude, false, lineTurnsLeft, 0, distance * LINE_DISTANCE_SCALE)); e3 = vertices.vertexSize() - 1 - startVertex; if (e1 >= 0 && e2 >= 0) { triangleStore.emplace_back(e1, e2, e3); diff --git a/src/mbgl/shaders/line.cpp b/src/mbgl/shaders/line.cpp index dce604625..ce62ecb3b 100644 --- a/src/mbgl/shaders/line.cpp +++ b/src/mbgl/shaders/line.cpp @@ -21,7 +21,7 @@ const char* line::vertexSource = R"MBGL_SHADER( // #define scale 63.0 #define scale 0.015873016 -attribute vec2 a_pos; +attribute vec3 a_pos_normal; attribute vec4 a_data; uniform mat4 u_matrix; @@ -107,20 +107,21 @@ void main() { vec2 a_extrude = a_data.xy - 128.0; float a_direction = mod(a_data.z, 4.0) - 1.0; - // We store the texture normals in the most insignificant bit - // transform y so that 0 => -1 and 1 => 1 + vec2 pos = a_pos_normal.xy; + + // transform y normal so that 0 => -1 and 1 => 1 // In the texture normal, x is 0 if the normal points straight up/down and 1 if it's a round cap // y is 1 if the normal points up, and -1 if it points down - mediump vec2 normal = mod(a_pos, 2.0); + mediump vec2 normal = unpack_float(a_pos_normal.z); normal.y = sign(normal.y - 0.5); + v_normal = normal; - - // these transformations used to be applied in the JS and native code bases. - // moved them into the shader for clarity and simplicity. + // these transformations used to be applied in the JS and native code bases. + // moved them into the shader for clarity and simplicity. gapwidth = gapwidth / 2.0; float width = u_width / 2.0; - offset = -1.0 * offset; + offset = -1.0 * offset; float inset = gapwidth + (gapwidth > 0.0 ? ANTIALIASING : 0.0); float outset = gapwidth + width * (gapwidth > 0.0 ? 2.0 : 1.0) + ANTIALIASING; @@ -137,9 +138,6 @@ void main() { mediump float t = 1.0 - abs(u); mediump vec2 offset2 = offset * a_extrude * scale * normal.y * mat2(t, -u, u, t); - // Remove the texture normal bit to get the position - vec2 pos = floor(a_pos * 0.5); - vec4 projected_extrude = u_matrix * vec4(dist / u_ratio, 0.0, 0.0); gl_Position = u_matrix * vec4(pos + offset2 / u_ratio, 0.0, 1.0) + projected_extrude; diff --git a/src/mbgl/shaders/line_pattern.cpp b/src/mbgl/shaders/line_pattern.cpp index 1c07b4f51..f43b3a3cf 100644 --- a/src/mbgl/shaders/line_pattern.cpp +++ b/src/mbgl/shaders/line_pattern.cpp @@ -23,7 +23,7 @@ const char* line_pattern::vertexSource = R"MBGL_SHADER( // Retina devices need a smaller distance to avoid aliasing. #define ANTIALIASING 1.0 / DEVICE_PIXEL_RATIO / 2.0 -attribute vec2 a_pos; +attribute vec3 a_pos_normal; attribute vec4 a_data; uniform mat4 u_matrix; @@ -97,19 +97,21 @@ void main() { float a_direction = mod(a_data.z, 4.0) - 1.0; float a_linesofar = (floor(a_data.z / 4.0) + a_data.w * 64.0) * LINE_DISTANCE_SCALE; - // We store the texture normals in the most insignificant bit - // transform y so that 0 => -1 and 1 => 1 + vec2 pos = a_pos_normal.xy; + + // transform y normal so that 0 => -1 and 1 => 1 // In the texture normal, x is 0 if the normal points straight up/down and 1 if it's a round cap // y is 1 if the normal points up, and -1 if it points down - mediump vec2 normal = mod(a_pos, 2.0); + mediump vec2 normal = unpack_float(a_pos_normal.z); normal.y = sign(normal.y - 0.5); + v_normal = normal; - // these transformations used to be applied in the JS and native code bases. - // moved them into the shader for clarity and simplicity. + // these transformations used to be applied in the JS and native code bases. + // moved them into the shader for clarity and simplicity. gapwidth = gapwidth / 2.0; float width = u_width / 2.0; - offset = -1.0 * offset; + offset = -1.0 * offset; float inset = gapwidth + (gapwidth > 0.0 ? ANTIALIASING : 0.0); float outset = gapwidth + width * (gapwidth > 0.0 ? 2.0 : 1.0) + ANTIALIASING; @@ -126,9 +128,6 @@ void main() { mediump float t = 1.0 - abs(u); mediump vec2 offset2 = offset * a_extrude * scale * normal.y * mat2(t, -u, u, t); - // Remove the texture normal bit to get the position - vec2 pos = floor(a_pos * 0.5); - vec4 projected_extrude = u_matrix * vec4(dist / u_ratio, 0.0, 0.0); gl_Position = u_matrix * vec4(pos + offset2 / u_ratio, 0.0, 1.0) + projected_extrude; diff --git a/src/mbgl/shaders/line_sdf.cpp b/src/mbgl/shaders/line_sdf.cpp index b37bf688d..24b7153c7 100644 --- a/src/mbgl/shaders/line_sdf.cpp +++ b/src/mbgl/shaders/line_sdf.cpp @@ -23,7 +23,7 @@ const char* line_sdf::vertexSource = R"MBGL_SHADER( // Retina devices need a smaller distance to avoid aliasing. #define ANTIALIASING 1.0 / DEVICE_PIXEL_RATIO / 2.0 -attribute vec2 a_pos; +attribute vec3 a_pos_normal; attribute vec4 a_data; uniform mat4 u_matrix; @@ -116,20 +116,22 @@ void main() { float a_direction = mod(a_data.z, 4.0) - 1.0; float a_linesofar = (floor(a_data.z / 4.0) + a_data.w * 64.0) * LINE_DISTANCE_SCALE; - // We store the texture normals in the most insignificant bit - // transform y so that 0 => -1 and 1 => 1 + vec2 pos = a_pos_normal.xy; + + // transform y normal so that 0 => -1 and 1 => 1 // In the texture normal, x is 0 if the normal points straight up/down and 1 if it's a round cap // y is 1 if the normal points up, and -1 if it points down - mediump vec2 normal = mod(a_pos, 2.0); + mediump vec2 normal = unpack_float(a_pos_normal.z); normal.y = sign(normal.y - 0.5); + v_normal = normal; - // these transformations used to be applied in the JS and native code bases. - // moved them into the shader for clarity and simplicity. + // these transformations used to be applied in the JS and native code bases. + // moved them into the shader for clarity and simplicity. gapwidth = gapwidth / 2.0; float width = u_width / 2.0; offset = -1.0 * offset; - + float inset = gapwidth + (gapwidth > 0.0 ? ANTIALIASING : 0.0); float outset = gapwidth + width * (gapwidth > 0.0 ? 2.0 : 1.0) + ANTIALIASING; @@ -145,9 +147,6 @@ void main() { mediump float t = 1.0 - abs(u); mediump vec2 offset2 = offset * a_extrude * scale * normal.y * mat2(t, -u, u, t); - // Remove the texture normal bit to get the position - vec2 pos = floor(a_pos * 0.5); - vec4 projected_extrude = u_matrix * vec4(dist / u_ratio, 0.0, 0.0); gl_Position = u_matrix * vec4(pos + offset2 / u_ratio, 0.0, 1.0) + projected_extrude; From cd36ec00bc44990df1bde02668e104307b30e7d2 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Tue, 18 Jul 2017 19:17:11 -0400 Subject: [PATCH 69/71] [ios] Add debug descriptions to shapes and features --- platform/darwin/src/MGLFeature.mm | 49 ++++++++++++++++++++++++++++++ platform/darwin/src/MGLPolygon.mm | 10 ++++++ platform/darwin/src/MGLPolyline.mm | 10 ++++++ 3 files changed, 69 insertions(+) diff --git a/platform/darwin/src/MGLFeature.mm b/platform/darwin/src/MGLFeature.mm index e169ee19b..84f1a1ff2 100644 --- a/platform/darwin/src/MGLFeature.mm +++ b/platform/darwin/src/MGLFeature.mm @@ -42,6 +42,15 @@ MGL_DEFINE_FEATURE_IS_EQUAL(); return mbglFeature({[self geometryObject]}, identifier, self.attributes); } +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@: %p; identifier = %@, coordinate = %f, %f, attributes = %@>", + NSStringFromClass([self class]), (void *)self, + self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier, + self.coordinate.latitude, self.coordinate.longitude, + self.attributes.count ? self.attributes : @"none"]; +} + @end @interface MGLPolylineFeature () @@ -68,6 +77,16 @@ MGL_DEFINE_FEATURE_IS_EQUAL(); return mbglFeature({[self geometryObject]}, identifier, self.attributes); } +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>", + NSStringFromClass([self class]), (void *)self, + self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier, + (unsigned long)[self pointCount], + MGLStringFromCoordinateBounds(self.overlayBounds), + self.attributes.count ? self.attributes : @"none"]; +} + @end @interface MGLPolygonFeature () @@ -94,6 +113,16 @@ MGL_DEFINE_FEATURE_IS_EQUAL(); return mbglFeature({[self geometryObject]}, identifier, self.attributes); } +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>", + NSStringFromClass([self class]), (void *)self, + self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier, + (unsigned long)[self pointCount], + MGLStringFromCoordinateBounds(self.overlayBounds), + self.attributes.count ? self.attributes : @"none"]; +} + @end @interface MGLPointCollectionFeature () @@ -146,6 +175,16 @@ MGL_DEFINE_FEATURE_IS_EQUAL(); return mbglFeature({[self geometryObject]}, identifier, self.attributes); } +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>", + NSStringFromClass([self class]), (void *)self, + self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier, + (unsigned long)self.polylines.count, + MGLStringFromCoordinateBounds(self.overlayBounds), + self.attributes.count ? self.attributes : @"none"]; +} + @end @interface MGLMultiPolygonFeature () @@ -172,6 +211,16 @@ MGL_DEFINE_FEATURE_IS_EQUAL(); return mbglFeature({[self geometryObject]}, identifier, self.attributes); } +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>", + NSStringFromClass([self class]), (void *)self, + self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier, + (unsigned long)self.polygons.count, + MGLStringFromCoordinateBounds(self.overlayBounds), + self.attributes.count ? self.attributes : @"none"]; +} + @end @interface MGLShapeCollectionFeature () diff --git a/platform/darwin/src/MGLPolygon.mm b/platform/darwin/src/MGLPolygon.mm index d966ff13c..e7843224e 100644 --- a/platform/darwin/src/MGLPolygon.mm +++ b/platform/darwin/src/MGLPolygon.mm @@ -200,4 +200,14 @@ @"coordinates": coordinates}; } +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@: %p; title = %@, subtitle: = %@, count = %lu; bounds = %@>", + NSStringFromClass([self class]), (void *)self, + self.title ? [NSString stringWithFormat:@"\"%@\"", self.title] : self.title, + self.subtitle ? [NSString stringWithFormat:@"\"%@\"", self.subtitle] : self.subtitle, + (unsigned long)self.polygons.count, + MGLStringFromCoordinateBounds(self.overlayBounds)]; +} + @end diff --git a/platform/darwin/src/MGLPolyline.mm b/platform/darwin/src/MGLPolyline.mm index fd75dc279..0e371a4dd 100644 --- a/platform/darwin/src/MGLPolyline.mm +++ b/platform/darwin/src/MGLPolyline.mm @@ -201,4 +201,14 @@ @"coordinates": coordinates}; } +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@: %p; title = %@, subtitle: = %@, count = %lu; bounds = %@>", + NSStringFromClass([self class]), (void *)self, + self.title ? [NSString stringWithFormat:@"\"%@\"", self.title] : self.title, + self.subtitle ? [NSString stringWithFormat:@"\"%@\"", self.subtitle] : self.subtitle, + (unsigned long)self.polylines.count, + MGLStringFromCoordinateBounds(self.overlayBounds)]; +} + @end From b5b84ef52db663f07fb5ecfdd0a9a0ea80b87b01 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Tue, 18 Jul 2017 19:11:55 -0400 Subject: [PATCH 70/71] [ios] Warn about using MGLFeature-conforming annotations w/querying --- .../docs/guides/Working with GeoJSON Data.md | 6 ++++-- platform/darwin/src/MGLFeature.h | 14 +++++++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/platform/darwin/docs/guides/Working with GeoJSON Data.md b/platform/darwin/docs/guides/Working with GeoJSON Data.md index 57aaa3855..f3b3dc091 100644 --- a/platform/darwin/docs/guides/Working with GeoJSON Data.md +++ b/platform/darwin/docs/guides/Working with GeoJSON Data.md @@ -81,8 +81,10 @@ Linear ring | `MGLPolygon.coordinates`, `MGLPolygon.interiorPolygons` A `Feature` object in GeoJSON corresponds to an instance of an `MGLShape` subclass conforming to the `MGLFeature` protocol. There is a distinct `MGLFeature`-conforming class for each type of geometry that a GeoJSON feature -can contain. This allows features to be used as shapes where convenient. For -example, some features can be added to a map view as annotations. +can contain. This allows features to be used as raw shapes where convenient. For +example, some features can be added to a map view as annotations. Note that +identifiers and attributes will not be available for feature querying when a +feature is used as an annotation. In contrast to the GeoJSON standard, it is possible for `MGLShape` subclasses other than `MGLPointAnnotation` to straddle the antimeridian. diff --git a/platform/darwin/src/MGLFeature.h b/platform/darwin/src/MGLFeature.h index 491c89b60..a13821cf9 100644 --- a/platform/darwin/src/MGLFeature.h +++ b/platform/darwin/src/MGLFeature.h @@ -18,15 +18,23 @@ NS_ASSUME_NONNULL_BEGIN You can add custom data to display on the map by creating feature objects and adding them to an `MGLShapeSource` using the `-[MGLShapeSource initWithIdentifier:shape:options:]` method or - `MGLShapeSource.shape` property. Similarly, you can add `MGLPointFeature`, - `MGLPolylineFeature`, and `MGLPolygonFeature` objects to the map as annotations - using `-[MGLMapView addAnnotations:]` and related methods. + `MGLShapeSource.shape` property. In addition to adding data to the map, you can also extract data from the map: `-[MGLMapView visibleFeaturesAtPoint:]` and related methods return feature objects that correspond to features in the source. This enables you to inspect the properties of features in vector tiles loaded by `MGLVectorSource` objects. You also reuse these feature objects as overlay annotations. + + While it is possible to add `MGLFeature`-conforming objects to the map as + annotations using `-[MGLMapView addAnnotations:]` and related methods, doing so + has trade-offs: + + - Features added as annotations will not have `identifier` or `attributes` + properties when used with feature querying. + + - Features added as annotations become interactive. Taps and selection can be + handled in `-[MGLMapViewDelegate mapView:didSelectAnnotation:]`. */ @protocol MGLFeature From 001988fba1e6eb2f6db2acdb68f26d583c48f53a Mon Sep 17 00:00:00 2001 From: Fabian Guerra Soto Date: Fri, 18 Aug 2017 15:38:16 -0400 Subject: [PATCH 71/71] [ios] Update pods spec for iOS v3.6.2 (#9806) --- platform/ios/Mapbox-iOS-SDK-nightly-dynamic.podspec | 2 +- platform/ios/Mapbox-iOS-SDK-symbols.podspec | 2 +- platform/ios/Mapbox-iOS-SDK.podspec | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/platform/ios/Mapbox-iOS-SDK-nightly-dynamic.podspec b/platform/ios/Mapbox-iOS-SDK-nightly-dynamic.podspec index db5ca23c1..b5f9af2e6 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.6.1' + version = '3.6.2' 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 be4d469f7..0fcd33507 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.6.1' + version = '3.6.2' 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 53779307a..0224048ff 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.6.1' + version = '3.6.2' m.name = 'Mapbox-iOS-SDK' m.version = version