diff options
author | Lloyd Sheng <i@lloydsheng.com> | 2018-03-27 13:47:53 +0800 |
---|---|---|
committer | Lloyd Sheng <i@lloydsheng.com> | 2018-03-27 13:47:53 +0800 |
commit | f8039e524e964ccd61721722d4f37460d29d7e5f (patch) | |
tree | 25c62c7153199330b6c0a3cc97767ee1463e02a5 | |
parent | ef9bac737fdb6a07ca3b0260459367c776fbffbb (diff) | |
parent | 4f196d48ddfc3bb05dee1cdd032af170a6c53d67 (diff) | |
download | qtlocation-mapboxgl-upstream/llooyd.tar.gz |
Merge branch 'release-boba' into llooydupstream/llooyd
117 files changed, 1837 insertions, 892 deletions
@@ -45,7 +45,7 @@ ifeq ($(V), 1) export XCPRETTY NINJA_ARGS ?= -v else - export XCPRETTY ?= | xcpretty + export XCPRETTY ?= | tee '$(shell pwd)/build/xcodebuild-$(shell date +"%Y-%m-%d_%H%M%S").log' | xcpretty NINJA_ARGS ?= endif @@ -235,13 +235,17 @@ ios-test: $(IOS_PROJ_PATH) ios-integration-test: $(IOS_PROJ_PATH) set -o pipefail && $(IOS_XCODEBUILD_SIM) -scheme 'Integration Test Harness' test $(XCPRETTY) +.PHONY: ios-sanitize +ios-sanitize: $(IOS_PROJ_PATH) + set -o pipefail && $(IOS_XCODEBUILD_SIM) -scheme 'CI' -enableThreadSanitizer YES -enableUndefinedBehaviorSanitizer YES test $(XCPRETTY) + .PHONY: ios-sanitize-address ios-sanitize-address: $(IOS_PROJ_PATH) set -o pipefail && $(IOS_XCODEBUILD_SIM) -scheme 'CI' -enableAddressSanitizer YES test $(XCPRETTY) -.PHONY: ios-sanitize-thread -ios-sanitize-thread: $(IOS_PROJ_PATH) - set -o pipefail && $(IOS_XCODEBUILD_SIM) -scheme 'CI' -enableThreadSanitizer YES test $(XCPRETTY) +.PHONY: ios-static-analyzer +ios-static-analyzer: $(IOS_PROJ_PATH) + set -o pipefail && $(IOS_XCODEBUILD_SIM) analyze -scheme 'CI' test $(XCPRETTY) .PHONY: ipackage ipackage: $(IOS_PROJ_PATH) diff --git a/circle.yml b/circle.yml index 3f67f8f6e6..789f6b0406 100644 --- a/circle.yml +++ b/circle.yml @@ -32,8 +32,9 @@ workflows: - linux-gcc5-release-qt4 - linux-gcc5-release-qt5 - ios-debug + - ios-sanitize #- ios-sanitize-address - - ios-sanitize-thread + - ios-static-analyzer - ios-release: filters: tags: @@ -223,6 +224,18 @@ step-library: path: mapbox-gl-js/test/integration/render-tests/index-recycle-map.html destination: render-tests + - &collect-xcode-build-logs + run: + name: Collect Xcode build logs + when: always + command: | + export XCODE_LOG_DIR=build/logs + mkdir -p $XCODE_LOG_DIR + cp build/*.log $XCODE_LOG_DIR + - &upload-xcode-build-logs + store_artifacts: + path: build/logs + jobs: nitpick: docker: @@ -730,6 +743,29 @@ jobs: command: scripts/nitpick/generated-code.js darwin - *show-ccache-stats - *save-cache + - *collect-xcode-build-logs + - *upload-xcode-build-logs + +# ------------------------------------------------------------------------------ + ios-sanitize: + macos: + xcode: "9.2.0" + environment: + BUILDTYPE: Debug + HOMEBREW_NO_AUTO_UPDATE: 1 + steps: + - checkout + - *install-macos-dependencies + - *generate-cache-key + - *restore-cache + - *reset-ccache-stats + - run: + name: Build and run SDK unit tests with thread and undefined behavior sanitizers + command: make ios-sanitize + - *show-ccache-stats + - *save-cache + - *collect-xcode-build-logs + - *upload-xcode-build-logs # ------------------------------------------------------------------------------ ios-sanitize-address: @@ -749,9 +785,11 @@ jobs: command: make ios-sanitize-address - *show-ccache-stats - *save-cache + - *collect-xcode-build-logs + - *upload-xcode-build-logs # ------------------------------------------------------------------------------ - ios-sanitize-thread: + ios-static-analyzer: macos: xcode: "9.2.0" environment: @@ -764,10 +802,12 @@ jobs: - *restore-cache - *reset-ccache-stats - run: - name: Build and run SDK unit tests with thread sanitizer - command: make ios-sanitize-thread + name: Build and run SDK unit tests with the static analyzer + command: make ios-static-analyzer - *show-ccache-stats - *save-cache + - *collect-xcode-build-logs + - *upload-xcode-build-logs # ------------------------------------------------------------------------------ ios-release: @@ -823,6 +863,8 @@ jobs: - store_artifacts: path: test/fixtures destination: test/fixtures + - *collect-xcode-build-logs + - *upload-xcode-build-logs # ------------------------------------------------------------------------------ macos-debug-qt5: @@ -869,6 +911,8 @@ jobs: - *run-node-macos-tests - *publish-node-package - *upload-render-tests + - *collect-xcode-build-logs + - *upload-xcode-build-logs # ------------------------------------------------------------------------------ macos-release-node6: @@ -890,3 +934,5 @@ jobs: - *run-node-macos-tests - *publish-node-package - *upload-render-tests + - *collect-xcode-build-logs + - *upload-xcode-build-logs diff --git a/include/mbgl/style/data_driven_property_value.hpp b/include/mbgl/style/data_driven_property_value.hpp index 5d7c596363..0a1fce29c7 100644 --- a/include/mbgl/style/data_driven_property_value.hpp +++ b/include/mbgl/style/data_driven_property_value.hpp @@ -50,6 +50,15 @@ public: return !value.template is<CameraFunction<T>>() && !value.template is<CompositeFunction<T>>(); } + bool isExpression() const { + return value.match( + [] (const Undefined&) { return false; }, + [] (const T&) { return false; }, + [] (const CameraFunction<T>& fn) { return fn.isExpression; }, + [] (const SourceFunction<T>& fn) { return fn.isExpression; }, + [] (const CompositeFunction<T>& fn) { return fn.isExpression; }); + } + template <class... Ts> auto match(Ts&&... ts) const { return value.match(std::forward<Ts>(ts)...); diff --git a/include/mbgl/style/function/camera_function.hpp b/include/mbgl/style/function/camera_function.hpp index 1da5d2c601..97ba633e44 100644 --- a/include/mbgl/style/function/camera_function.hpp +++ b/include/mbgl/style/function/camera_function.hpp @@ -27,15 +27,16 @@ public: IntervalStops<T>>>; CameraFunction(std::unique_ptr<expression::Expression> expression_) - : expression(std::move(expression_)), + : isExpression(true), + expression(std::move(expression_)), zoomCurve(expression::findZoomCurveChecked(expression.get())) { assert(!expression::isZoomConstant(*expression)); assert(expression::isFeatureConstant(*expression)); } - CameraFunction(Stops stops_) - : stops(std::move(stops_)), + CameraFunction(const Stops& stops) + : isExpression(false), expression(stops.match([&] (const auto& s) { return expression::Convert::toExpression(s); })), @@ -76,12 +77,10 @@ public: } bool useIntegerZoom = false; + bool isExpression; const expression::Expression& getExpression() const { return *expression; } - // retained for compatibility with pre-expression function API - Stops stops; - private: std::shared_ptr<expression::Expression> expression; const variant<const expression::InterpolateBase*, const expression::Step*> zoomCurve; diff --git a/include/mbgl/style/function/composite_function.hpp b/include/mbgl/style/function/composite_function.hpp index f391b101ae..614c345c25 100644 --- a/include/mbgl/style/function/composite_function.hpp +++ b/include/mbgl/style/function/composite_function.hpp @@ -51,16 +51,16 @@ public: CompositeCategoricalStops<T>>>; CompositeFunction(std::unique_ptr<expression::Expression> expression_) - : expression(std::move(expression_)), + : isExpression(true), + expression(std::move(expression_)), zoomCurve(expression::findZoomCurveChecked(expression.get())) { assert(!expression::isZoomConstant(*expression)); assert(!expression::isFeatureConstant(*expression)); } - CompositeFunction(std::string property_, Stops stops_, optional<T> defaultValue_ = {}) - : property(std::move(property_)), - stops(std::move(stops_)), + CompositeFunction(const std::string& property, const Stops& stops, optional<T> defaultValue_ = {}) + : isExpression(false), defaultValue(std::move(defaultValue_)), expression(stops.match([&] (const auto& s) { return expression::Convert::toExpression(property, s); @@ -113,12 +113,11 @@ public: const expression::Expression& getExpression() const { return *expression; } - std::string property; - Stops stops; - optional<T> defaultValue; bool useIntegerZoom = false; + bool isExpression; private: + optional<T> defaultValue; std::shared_ptr<expression::Expression> expression; const variant<const expression::InterpolateBase*, const expression::Step*> zoomCurve; }; diff --git a/include/mbgl/style/function/source_function.hpp b/include/mbgl/style/function/source_function.hpp index d3caa90ee5..5b51d0bf81 100644 --- a/include/mbgl/style/function/source_function.hpp +++ b/include/mbgl/style/function/source_function.hpp @@ -30,15 +30,15 @@ public: IdentityStops<T>>>; SourceFunction(std::unique_ptr<expression::Expression> expression_) - : expression(std::move(expression_)) + : isExpression(true), + expression(std::move(expression_)) { assert(expression::isZoomConstant(*expression)); assert(!expression::isFeatureConstant(*expression)); } - SourceFunction(std::string property_, Stops stops_, optional<T> defaultValue_ = {}) - : property(std::move(property_)), - stops(std::move(stops_)), + SourceFunction(const std::string& property, const Stops& stops, optional<T> defaultValue_ = {}) + : isExpression(false), defaultValue(std::move(defaultValue_)), expression(stops.match([&] (const IdentityStops<T>&) { return expression::Convert::fromIdentityFunction(expression::valueTypeToExpressionType<T>(), property); @@ -67,15 +67,12 @@ public: } bool useIntegerZoom = false; + bool isExpression; const expression::Expression& getExpression() const { return *expression; } - // retained for compatibility with pre-expression function API - std::string property; - Stops stops; - optional<T> defaultValue; - private: + optional<T> defaultValue; std::shared_ptr<expression::Expression> expression; }; diff --git a/mapbox-gl-js b/mapbox-gl-js -Subproject 4cd5570ad3ed3e0ad71e2f795e795a78f5ccf60 +Subproject 3c07b72a59d78bc6240a8e3d77a54bec7619af5 diff --git a/platform/android/CHANGELOG.md b/platform/android/CHANGELOG.md index 60d3dc6b70..63736c1ca4 100644 --- a/platform/android/CHANGELOG.md +++ b/platform/android/CHANGELOG.md @@ -2,6 +2,37 @@ 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.5.1 - March 26, 2018 + - Verify optional access of FileSource deactivation [#11480](https://github.com/mapbox/mapbox-gl-native/pull/11480) + - Prevent default style loading when style json was set [#11519](https://github.com/mapbox/mapbox-gl-native/pull/11519) + - Delete local reference when convering Image.java [#11350](https://github.com/mapbox/mapbox-gl-native/pull/11350) + - Use float for pixel ratio when creating a snapshotter [#11367](https://github.com/mapbox/mapbox-gl-native/pull/11367) + - Validate if width and height aren't 0 when creating a snapshot [#11364](https://github.com/mapbox/mapbox-gl-native/pull/11364) + - Null check body of http request [#11413](https://github.com/mapbox/mapbox-gl-native/pull/11413) + - Clamp TileJSON bounds [#11425](https://github.com/mapbox/mapbox-gl-native/pull/11425) + +## 6.0.0-beta.4 - March 20, 2018 + - Gesture library 0.1.0 stable [#11483](https://github.com/mapbox/mapbox-gl-native/pull/11483) + - Filters with expressions [#11429](https://github.com/mapbox/mapbox-gl-native/pull/11429) + - Telemetry 3.0.0-beta.2 [#11474](https://github.com/mapbox/mapbox-gl-native/pull/11474) + - High level JNI conversion for geojson [#11471](https://github.com/mapbox/mapbox-gl-native/pull/11471) + - Update to MAS 3.0.0-beta.4 [#11468](https://github.com/mapbox/mapbox-gl-native/pull/11468) + - Support for expression literal on arrays [#11457](https://github.com/mapbox/mapbox-gl-native/pull/11457) + - Fix telemetry integration for two finger tap gesture [#11460](https://github.com/mapbox/mapbox-gl-native/pull/11460) + - Revisit proguard configuration [#11434](https://github.com/mapbox/mapbox-gl-native/pull/11434) + - Expressions accessor support [#11352](https://github.com/mapbox/mapbox-gl-native/pull/11352) + - Calculate camera's LatLng for bounds without map padding [#11410](https://github.com/mapbox/mapbox-gl-native/pull/11410) + - Check for null on http body request [#11413](https://github.com/mapbox/mapbox-gl-native/pull/11413) + - Expose more gesture settings [#11407](https://github.com/mapbox/mapbox-gl-native/pull/11407) + - Revert java 8 language support [#11398](https://github.com/mapbox/mapbox-gl-native/pull/11398) + - Update to MAS 3.0.0-beta.3 [#11373](https://github.com/mapbox/mapbox-gl-native/pull/11373) + - Rework match expression to style specification syntax [#11388](https://github.com/mapbox/mapbox-gl-native/pull/11388) + - Update javadoc configuration for Gradle 4.4 [#11384](https://github.com/mapbox/mapbox-gl-native/pull/11384) + - Rework zoomIn and zoomOut to use ValueAnimators [#11382](https://github.com/mapbox/mapbox-gl-native/pull/11382) + - Delete LocalRef when converting Image.java [#11350](https://github.com/mapbox/mapbox-gl-native/pull/11350) + - Use float for pixelratio when creating a snapshotter [#11367](https://github.com/mapbox/mapbox-gl-native/pull/11367) + - Validate width/height when creating a snapshot [#11364](https://github.com/mapbox/mapbox-gl-native/pull/11364) + ## 6.0.0-beta.3 - March 2, 2018 - Added missing local reference deletes [#11243](https://github.com/mapbox/mapbox-gl-native/pull/11243), [#11272](https://github.com/mapbox/mapbox-gl-native/pull/11272) - Remove obsolete camera api [#11201](https://github.com/mapbox/mapbox-gl-native/pull/11201) diff --git a/platform/android/MapboxGLAndroidSDK/gradle.properties b/platform/android/MapboxGLAndroidSDK/gradle.properties index f00eb1d77f..2673f889e2 100644 --- a/platform/android/MapboxGLAndroidSDK/gradle.properties +++ b/platform/android/MapboxGLAndroidSDK/gradle.properties @@ -18,4 +18,3 @@ POM_PACKAGING=aar # Only build native dependencies for the current ABI # See https://code.google.com/p/android/issues/detail?id=221098#c20 android.buildOnlyTargetAbi=true - diff --git a/platform/android/MapboxGLAndroidSDK/proguard-rules.pro b/platform/android/MapboxGLAndroidSDK/proguard-rules.pro index 529c3f4948..ce6e1709a0 100644 --- a/platform/android/MapboxGLAndroidSDK/proguard-rules.pro +++ b/platform/android/MapboxGLAndroidSDK/proguard-rules.pro @@ -17,6 +17,8 @@ -dontwarn com.mapbox.android.core.location.MockLocationEngine$LocationUpdateRunnable -dontwarn java.awt.Color -dontwarn com.mapzen.android.lost.api** +-dontwarn org.conscrypt.OpenSSLProvider +-dontwarn org.conscrypt.Conscrypt # config for mapbox-sdk-geojson:3.0.0-beta.3 -keep class com.mapbox.geojson.** { *; } 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 b3dcf87c7e..858c1eed67 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 @@ -10,7 +10,7 @@ import android.text.TextUtils; import com.mapbox.mapboxsdk.constants.MapboxConstants; import com.mapbox.mapboxsdk.exceptions.MapboxConfigurationException; -import com.mapbox.mapboxsdk.maps.Events; +import com.mapbox.mapboxsdk.maps.Telemetry; import com.mapbox.mapboxsdk.net.ConnectivityReceiver; /** @@ -46,7 +46,7 @@ public final class Mapbox { Context appContext = context.getApplicationContext(); INSTANCE = new Mapbox(appContext, accessToken); - Events.initialize(); + Telemetry.initialize(); ConnectivityReceiver.instance(appContext); } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HTTPRequest.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HTTPRequest.java index ffc55fec46..ab1191c0cc 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HTTPRequest.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/http/HTTPRequest.java @@ -8,6 +8,7 @@ import android.support.annotation.NonNull; import android.text.TextUtils; import android.util.Log; +import com.mapbox.android.telemetry.TelemetryUtils; import com.mapbox.mapboxsdk.BuildConfig; import com.mapbox.mapboxsdk.Mapbox; import com.mapbox.mapboxsdk.constants.MapboxConstants; @@ -30,7 +31,6 @@ import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import okhttp3.ResponseBody; -import okhttp3.internal.Util; import timber.log.Timber; import static android.util.Log.DEBUG; @@ -224,7 +224,7 @@ class HTTPRequest implements Callback { private String getUserAgent() { if (userAgentString == null) { - userAgentString = Util.toHumanReadableAscii( + userAgentString = TelemetryUtils.toHumanReadableAscii( String.format("%s %s (%s) Android/%s (%s)", getApplicationIdentifier(), BuildConfig.MAPBOX_VERSION_STRING, diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AttributionDialogManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AttributionDialogManager.java index 5ccd6bd795..39cd25631e 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AttributionDialogManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/AttributionDialogManager.java @@ -12,6 +12,7 @@ import android.view.View; import android.widget.ArrayAdapter; import android.widget.Toast; +import com.mapbox.android.telemetry.TelemetryEnabler; import com.mapbox.mapboxsdk.R; import com.mapbox.mapboxsdk.attribution.Attribution; import com.mapbox.mapboxsdk.attribution.AttributionParser; @@ -98,7 +99,8 @@ public class AttributionDialogManager implements View.OnClickListener, DialogInt builder.setPositiveButton(R.string.mapbox_attributionTelemetryPositive, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - Events.obtainTelemetry().enable(); + TelemetryEnabler.updateTelemetryState(TelemetryEnabler.State.ENABLED); + Telemetry.obtainTelemetry().enable(); dialog.cancel(); } }); @@ -112,7 +114,8 @@ public class AttributionDialogManager implements View.OnClickListener, DialogInt builder.setNegativeButton(R.string.mapbox_attributionTelemetryNegative, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - Events.obtainTelemetry().disable(); + Telemetry.obtainTelemetry().disable(); + TelemetryEnabler.updateTelemetryState(TelemetryEnabler.State.DISABLED); dialog.cancel(); } }); 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 849719a1a2..de9b4fdbc2 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 @@ -12,6 +12,7 @@ import android.view.MotionEvent; import android.view.animation.DecelerateInterpolator; import com.mapbox.android.gestures.AndroidGesturesManager; +import com.mapbox.android.gestures.Constants; import com.mapbox.android.gestures.MoveGestureDetector; import com.mapbox.android.gestures.MultiFingerTapGestureDetector; import com.mapbox.android.gestures.RotateGestureDetector; @@ -77,13 +78,6 @@ final class MapGestureDetector { private final CopyOnWriteArrayList<MapboxMap.OnShoveListener> onShoveListenerList = new CopyOnWriteArrayList<>(); - private StandardGestureListener standardGestureListener; - private MoveGestureListener moveGestureListener; - private ScaleGestureListener scaleGestureListener; - private RotateGestureListener rotateGestureListener; - private ShoveGestureListener shoveGestureListener; - private TapGestureListener tapGestureListener; - /** * User-set focal point. */ @@ -112,29 +106,39 @@ final class MapGestureDetector { // Checking for context != null for testing purposes if (context != null) { - // Initialize gesture listeners - initializeGestureListeners(context); - // Initialize gestures manager AndroidGesturesManager androidGesturesManager = new AndroidGesturesManager(context); - initializeGesturesManager(androidGesturesManager, true, true); + initializeGesturesManager(androidGesturesManager, true); + + // Initialize gesture listeners + initializeGestureListeners(context, true); } } - private void initializeGestureListeners(Context context) { - standardGestureListener = new StandardGestureListener(); - moveGestureListener = new MoveGestureListener(); - scaleGestureListener = new ScaleGestureListener( - context.getResources().getDimension(R.dimen.mapbox_minimum_scale_velocity)); - rotateGestureListener = new RotateGestureListener( - context.getResources().getDimension(R.dimen.mapbox_minimum_scale_span_when_rotating), - context.getResources().getDimension(R.dimen.mapbox_minimum_angular_velocity)); - shoveGestureListener = new ShoveGestureListener(); - tapGestureListener = new TapGestureListener(); + private void initializeGestureListeners(Context context, boolean attachDefaultListeners) { + if (attachDefaultListeners) { + StandardGestureListener standardGestureListener = new StandardGestureListener(); + MoveGestureListener moveGestureListener = new MoveGestureListener(); + ScaleGestureListener scaleGestureListener = new ScaleGestureListener( + context.getResources().getDimension(R.dimen.mapbox_minimum_scale_velocity)); + RotateGestureListener rotateGestureListener = new RotateGestureListener( + context.getResources().getDimension(R.dimen.mapbox_minimum_scale_span_when_rotating), + context.getResources().getDimension(R.dimen.mapbox_minimum_angular_velocity), + context.getResources().getDimension(R.dimen.mapbox_defaultScaleSpanSinceStartThreshold)); + ShoveGestureListener shoveGestureListener = new ShoveGestureListener(); + TapGestureListener tapGestureListener = new TapGestureListener(); + + gesturesManager.setStandardGestureListener(standardGestureListener); + gesturesManager.setMoveGestureListener(moveGestureListener); + gesturesManager.setStandardScaleGestureListener(scaleGestureListener); + gesturesManager.setRotateGestureListener(rotateGestureListener); + gesturesManager.setShoveGestureListener(shoveGestureListener); + gesturesManager.setMultiFingerTapGestureListener(tapGestureListener); + } } private void initializeGesturesManager(AndroidGesturesManager androidGesturesManager, - boolean attachDefaultListeners, boolean setDefaultMutuallyExclusives) { + boolean setDefaultMutuallyExclusives) { if (setDefaultMutuallyExclusives) { Set<Integer> shoveScaleSet = new HashSet<>(); shoveScaleSet.add(AndroidGesturesManager.GESTURE_TYPE_SHOVE); @@ -151,15 +155,6 @@ final class MapGestureDetector { androidGesturesManager.setMutuallyExclusiveGestures(shoveScaleSet, shoveRotateSet, ScaleLongPressSet); } - if (attachDefaultListeners) { - androidGesturesManager.setStandardGestureListener(standardGestureListener); - androidGesturesManager.setMoveGestureListener(moveGestureListener); - androidGesturesManager.setStandardScaleGestureListener(scaleGestureListener); - androidGesturesManager.setRotateGestureListener(rotateGestureListener); - androidGesturesManager.setShoveGestureListener(shoveGestureListener); - androidGesturesManager.setMultiFingerTapGestureListener(tapGestureListener); - } - gesturesManager = androidGesturesManager; } @@ -349,7 +344,7 @@ final class MapGestureDetector { notifyOnMapClickListeners(tapPoint); } - sendTelemetryEvent(Events.SINGLE_TAP, new PointF(motionEvent.getX(), motionEvent.getY())); + sendTelemetryEvent(Telemetry.SINGLE_TAP, new PointF(motionEvent.getX(), motionEvent.getY())); return true; } @@ -380,7 +375,7 @@ final class MapGestureDetector { zoomInAnimated(zoomFocalPoint, false); - sendTelemetryEvent(Events.DOUBLE_TAP, new PointF(motionEvent.getX(), motionEvent.getY())); + sendTelemetryEvent(Telemetry.DOUBLE_TAP, new PointF(motionEvent.getX(), motionEvent.getY())); return true; } @@ -444,7 +439,7 @@ final class MapGestureDetector { transform.cancelTransitions(); cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE); - sendTelemetryEvent(Events.PAN, detector.getFocalPoint()); + sendTelemetryEvent(Telemetry.PAN, detector.getFocalPoint()); notifyOnMoveBeginListeners(detector); @@ -501,7 +496,7 @@ final class MapGestureDetector { if (uiSettings.isIncreaseRotateThresholdWhenScaling()) { // increase rotate angle threshold when scale is detected first gesturesManager.getRotateGestureDetector().setAngleThreshold( - gesturesManager.getRotateGestureDetector().getDefaultAngleThreshold() + Constants.DEFAULT_ROTATE_ANGLE_THRESHOLD + MapboxConstants.ROTATION_THRESHOLD_INCREASE_WHEN_SCALING ); } @@ -509,7 +504,7 @@ final class MapGestureDetector { // setting focalPoint in #onScaleBegin() as well, because #onScale() might not get called before #onScaleEnd() setScaleFocalPoint(detector); - sendTelemetryEvent(Events.PINCH, scaleFocalPoint); + sendTelemetryEvent(Telemetry.PINCH, scaleFocalPoint); notifyOnScaleBeginListeners(detector); @@ -544,7 +539,7 @@ final class MapGestureDetector { if (uiSettings.isIncreaseRotateThresholdWhenScaling()) { // resetting default angle threshold gesturesManager.getRotateGestureDetector().setAngleThreshold( - gesturesManager.getRotateGestureDetector().getDefaultAngleThreshold() + Constants.DEFAULT_ROTATE_ANGLE_THRESHOLD ); } @@ -603,10 +598,13 @@ final class MapGestureDetector { private PointF rotateFocalPoint; private final float minimumScaleSpanWhenRotating; private final float minimumAngularVelocity; + private final float defaultSpanSinceStartThreshold; - RotateGestureListener(float minimumScaleSpanWhenRotating, float minimumAngularVelocity) { + public RotateGestureListener(float minimumScaleSpanWhenRotating, float minimumAngularVelocity, + float defaultSpanSinceStartThreshold) { this.minimumScaleSpanWhenRotating = minimumScaleSpanWhenRotating; this.minimumAngularVelocity = minimumAngularVelocity; + this.defaultSpanSinceStartThreshold = defaultSpanSinceStartThreshold; } @Override @@ -628,7 +626,7 @@ final class MapGestureDetector { // setting in #onRotateBegin() as well, because #onRotate() might not get called before #onRotateEnd() setRotateFocalPoint(detector); - sendTelemetryEvent(Events.ROTATION, rotateFocalPoint); + sendTelemetryEvent(Telemetry.ROTATION, rotateFocalPoint); notifyOnRotateBeginListeners(detector); @@ -660,8 +658,7 @@ final class MapGestureDetector { if (uiSettings.isIncreaseScaleThresholdWhenRotating()) { // resetting default scale threshold values - gesturesManager.getStandardScaleGestureDetector().setSpanSinceStartThreshold( - gesturesManager.getStandardScaleGestureDetector().getDefaultSpanSinceStartThreshold()); + gesturesManager.getStandardScaleGestureDetector().setSpanSinceStartThreshold(defaultSpanSinceStartThreshold); } notifyOnRotateEndListeners(detector); @@ -747,7 +744,7 @@ final class MapGestureDetector { transform.cancelTransitions(); cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE); - sendTelemetryEvent(Events.PITCH, detector.getFocalPoint()); + sendTelemetryEvent(Telemetry.PITCH, detector.getFocalPoint()); // disabling move gesture during shove gesturesManager.getMoveGestureDetector().setEnabled(false); @@ -796,7 +793,7 @@ final class MapGestureDetector { transform.cancelTransitions(); cameraChangeDispatcher.onCameraMoveStarted(REASON_API_GESTURE); - sendTelemetryEvent(Events.TWO_FINGER_TAP, detector.getFocalPoint()); + sendTelemetryEvent(Telemetry.TWO_FINGER_TAP, detector.getFocalPoint()); PointF zoomFocalPoint; // Single finger double tap @@ -893,7 +890,7 @@ final class MapGestureDetector { LatLng latLng = projection.fromScreenLocation(focalPoint); MapState state = new MapState(latLng.getLatitude(), latLng.getLongitude(), transform.getZoom()); state.setGesture(eventType); - Events.obtainTelemetry().push(mapEventFactory.createMapGestureEvent(Event.Type.MAP_CLICK, state)); + Telemetry.obtainTelemetry().push(mapEventFactory.createMapGestureEvent(Event.Type.MAP_CLICK, state)); } } @@ -1109,8 +1106,9 @@ final class MapGestureDetector { return gesturesManager; } - void setGesturesManager(AndroidGesturesManager gesturesManager, boolean attachDefaultListeners, + void setGesturesManager(Context context, AndroidGesturesManager gesturesManager, boolean attachDefaultListeners, boolean setDefaultMutuallyExclusives) { - initializeGesturesManager(gesturesManager, attachDefaultListeners, setDefaultMutuallyExclusives); + initializeGesturesManager(gesturesManager, setDefaultMutuallyExclusives); + initializeGestureListeners(context, attachDefaultListeners); } }
\ No newline at end of file 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 9c8ed7de2b..22d5dd8f19 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 @@ -268,7 +268,7 @@ public class MapView extends FrameLayout { @UiThread public void onCreate(@Nullable Bundle savedInstanceState) { if (savedInstanceState == null) { - MapboxTelemetry telemetry = Events.obtainTelemetry(); + MapboxTelemetry telemetry = Telemetry.obtainTelemetry(); AppUserTurnstile turnstileEvent = new AppUserTurnstile(BuildConfig.MAPBOX_SDK_IDENTIFIER, BuildConfig.MAPBOX_SDK_VERSION); telemetry.push(turnstileEvent); @@ -1026,7 +1026,8 @@ public class MapView extends FrameLayout { @Override public void setGesturesManager(AndroidGesturesManager gesturesManager, boolean attachDefaultListeners, boolean setDefaultMutuallyExclusives) { - mapGestureDetector.setGesturesManager(gesturesManager, attachDefaultListeners, setDefaultMutuallyExclusives); + mapGestureDetector.setGesturesManager( + getContext(), gesturesManager, attachDefaultListeners, setDefaultMutuallyExclusives); } @Override 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 45cb75b2da..f23d3bd7f9 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 @@ -40,7 +40,7 @@ import com.mapbox.mapboxsdk.constants.MapboxConstants; import com.mapbox.mapboxsdk.constants.Style; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.geometry.LatLngBounds; -import com.mapbox.mapboxsdk.style.layers.Filter; +import com.mapbox.mapboxsdk.style.expressions.Expression; import com.mapbox.mapboxsdk.style.layers.Layer; import com.mapbox.mapboxsdk.style.light.Light; import com.mapbox.mapboxsdk.style.sources.Source; @@ -103,7 +103,7 @@ public final class MapboxMap { */ void onStart() { nativeMapView.update(); - if (TextUtils.isEmpty(nativeMapView.getStyleUrl())) { + if (TextUtils.isEmpty(nativeMapView.getStyleUrl()) && TextUtils.isEmpty(nativeMapView.getStyleJson())) { // if user hasn't loaded a Style yet nativeMapView.setStyleUrl(Style.MAPBOX_STREETS); } @@ -2079,13 +2079,13 @@ public final class MapboxMap { * Queries the map for rendered features * * @param coordinates the point to query - * @param filter filters the returned features + * @param filter filters the returned features with an expression * @param layerIds optionally - only query these layers * @return the list of feature */ @NonNull public List<Feature> queryRenderedFeatures(@NonNull PointF coordinates, - @Nullable Filter.Statement filter, + @Nullable Expression filter, @Nullable String... layerIds) { return nativeMapView.queryRenderedFeatures(coordinates, layerIds, filter); } @@ -2107,13 +2107,13 @@ public final class MapboxMap { * Queries the map for rendered features * * @param coordinates the box to query - * @param filter filters the returned features + * @param filter filters the returned features with an expression * @param layerIds optionally - only query these layers * @return the list of feature */ @NonNull public List<Feature> queryRenderedFeatures(@NonNull RectF coordinates, - @Nullable Filter.Statement filter, + @Nullable Expression filter, @Nullable String... layerIds) { return nativeMapView.queryRenderedFeatures(coordinates, layerIds, filter); } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java index 1b6522d348..976277dcac 100755 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java @@ -24,8 +24,8 @@ import com.mapbox.mapboxsdk.geometry.LatLngBounds; import com.mapbox.mapboxsdk.geometry.ProjectedMeters; import com.mapbox.mapboxsdk.maps.renderer.MapRenderer; import com.mapbox.mapboxsdk.storage.FileSource; +import com.mapbox.mapboxsdk.style.expressions.Expression; import com.mapbox.mapboxsdk.style.layers.CannotAddLayerException; -import com.mapbox.mapboxsdk.style.layers.Filter; import com.mapbox.mapboxsdk.style.layers.Layer; import com.mapbox.mapboxsdk.style.light.Light; import com.mapbox.mapboxsdk.style.sources.CannotAddSourceException; @@ -799,7 +799,7 @@ final class NativeMapView { @NonNull public List<Feature> queryRenderedFeatures(@NonNull PointF coordinates, @Nullable String[] layerIds, - @Nullable Filter.Statement filter) { + @Nullable Expression filter) { if (isDestroyedOn("queryRenderedFeatures")) { return new ArrayList<>(); } @@ -811,7 +811,7 @@ final class NativeMapView { @NonNull public List<Feature> queryRenderedFeatures(@NonNull RectF coordinates, @Nullable String[] layerIds, - @Nullable Filter.Statement filter) { + @Nullable Expression filter) { if (isDestroyedOn("queryRenderedFeatures")) { return new ArrayList<>(); } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Events.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Telemetry.java index 7ba592f121..e6b93e4b91 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Events.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Telemetry.java @@ -2,11 +2,12 @@ package com.mapbox.mapboxsdk.maps; import com.mapbox.android.telemetry.MapboxTelemetry; +import com.mapbox.android.telemetry.SessionInterval; import com.mapbox.android.telemetry.TelemetryEnabler; import com.mapbox.mapboxsdk.BuildConfig; import com.mapbox.mapboxsdk.Mapbox; -public class Events { +public class Telemetry { static final String TWO_FINGER_TAP = "TwoFingerTap"; static final String DOUBLE_TAP = "DoubleTap"; static final String SINGLE_TAP = "SingleTap"; @@ -16,12 +17,11 @@ public class Events { static final String PITCH = "Pitch"; private MapboxTelemetry telemetry; - private Events() { + private Telemetry() { telemetry = new MapboxTelemetry(Mapbox.getApplicationContext(), Mapbox.getAccessToken(), BuildConfig.MAPBOX_EVENTS_USER_AGENT); TelemetryEnabler.State telemetryState = TelemetryEnabler.retrieveTelemetryStateFromPreferences(); - if (TelemetryEnabler.State.NOT_INITIALIZED.equals(telemetryState) - || TelemetryEnabler.State.ENABLED.equals(telemetryState)) { + if (TelemetryEnabler.State.ENABLED.equals(telemetryState)) { telemetry.enable(); } } @@ -30,11 +30,19 @@ public class Events { obtainTelemetry(); } - private static class EventsHolder { - private static final Events INSTANCE = new Events(); + public static void updateDebugLoggingEnabled(boolean debugLoggingEnabled) { + TelemetryHolder.INSTANCE.telemetry.updateDebugLoggingEnabled(debugLoggingEnabled); + } + + public static boolean updateSessionIdRotationInterval(SessionInterval interval) { + return TelemetryHolder.INSTANCE.telemetry.updateSessionIdRotationInterval(interval); + } + + private static class TelemetryHolder { + private static final Telemetry INSTANCE = new Telemetry(); } static MapboxTelemetry obtainTelemetry() { - return EventsHolder.INSTANCE.telemetry; + return TelemetryHolder.INSTANCE.telemetry; } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/expressions/Expression.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/expressions/Expression.java index 8af140019f..efb2660da0 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/expressions/Expression.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/expressions/Expression.java @@ -112,6 +112,9 @@ public class Expression { * @return the expression */ public static Expression literal(@NonNull Object object) { + if (object.getClass().isArray()) { + return literal(ExpressionArray.toObjectArray(object)); + } return new ExpressionLiteral(object); } @@ -132,7 +135,7 @@ public class Expression { * @return the color expression */ public static Expression color(@ColorInt int color) { - return new ExpressionLiteral(new Color(color)); + return toColor(literal(PropertyFactory.colorToRgbaString(color))); } /** @@ -1354,7 +1357,7 @@ public class Expression { * @return expression * @see <a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-types-string">Style specification</a> */ - public static Expression string(@NonNull Expression input) { + public static Expression string(@NonNull Expression... input) { return new Expression("string", input); } @@ -1367,7 +1370,7 @@ public class Expression { * @return expression * @see <a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-types-number">Style specification</a> */ - public static Expression number(@NonNull Expression input) { + public static Expression number(@NonNull Expression... input) { return new Expression("number", input); } @@ -1380,7 +1383,7 @@ public class Expression { * @return expression * @see <a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-types-boolean">Style specification</a> */ - public static Expression bool(@NonNull Expression input) { + public static Expression bool(@NonNull Expression... input) { return new Expression("boolean", input); } @@ -1520,13 +1523,14 @@ public class Expression { * Returns the output value of the stop just less than the input, * or the first input if the input is less than the first stop. * - * @param input the input value - * @param stops pair of input and output values + * @param input the input value + * @param defaultOutput the default output expression + * @param stops pair of input and output values * @return expression * @see <a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-step">Style specification</a> */ - public static Expression step(@NonNull Number input, @NonNull Expression expression, Expression... stops) { - return step(literal(input), expression, stops); + public static Expression step(@NonNull Number input, @NonNull Expression defaultOutput, Expression... stops) { + return step(literal(input), defaultOutput, stops); } /** @@ -1536,13 +1540,14 @@ public class Expression { * Returns the output value of the stop just less than the input, * or the first input if the input is less than the first stop. * - * @param expression the input expression - * @param stops pair of input and output values + * @param input the input expression + * @param defaultOutput the default output expression + * @param stops pair of input and output values * @return expression * @see <a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-step">Style specification</a> */ - public static Expression step(@NonNull Expression input, @NonNull Expression expression, Expression... stops) { - return new Expression("step", join(new Expression[] {input, expression}, stops)); + public static Expression step(@NonNull Expression input, @NonNull Expression defaultOutput, Expression... stops) { + return new Expression("step", join(new Expression[] {input, defaultOutput}, stops)); } /** @@ -1552,13 +1557,14 @@ public class Expression { * Returns the output value of the stop just less than the input, * or the first input if the input is less than the first stop. * - * @param input the input value - * @param stops pair of input and output values + * @param input the input value + * @param defaultOutput the default output expression + * @param stops pair of input and output values * @return expression * @see <a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-step">Style specification</a> */ - public static Expression step(@NonNull Number input, @NonNull Expression expression, Stop... stops) { - return step(literal(input), expression, Stop.toExpressionArray(stops)); + public static Expression step(@NonNull Number input, @NonNull Expression defaultOutput, Stop... stops) { + return step(literal(input), defaultOutput, Stop.toExpressionArray(stops)); } /** @@ -1568,13 +1574,14 @@ public class Expression { * Returns the output value of the stop just less than the input, * or the first input if the input is less than the first stop. * - * @param input the input value - * @param stops pair of input and output values + * @param input the input value + * @param defaultOutput the default output expression + * @param stops pair of input and output values * @return expression * @see <a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-step">Style specification</a> */ - public static Expression step(@NonNull Expression input, @NonNull Expression expression, Stop... stops) { - return step(input, expression, Stop.toExpressionArray(stops)); + public static Expression step(@NonNull Expression input, @NonNull Expression defaultOutput, Stop... stops) { + return step(input, defaultOutput, Stop.toExpressionArray(stops)); } /** @@ -1728,9 +1735,7 @@ public class Expression { */ private Object toValue(ExpressionLiteral expressionValue) { Object value = expressionValue.toValue(); - if (value instanceof Expression.Color) { - return ((Expression.Color) value).convertColor(); - } else if (value instanceof Expression.ExpressionLiteral) { + if (value instanceof Expression.ExpressionLiteral) { return toValue((ExpressionLiteral) value); } else if (value instanceof Expression) { return ((Expression) value).toArray(); @@ -1887,32 +1892,6 @@ public class Expression { } /** - * Expression color type. - */ - public static class Color { - - private int color; - - /** - * Creates a color color type from a color int. - * - * @param color the int color - */ - public Color(@ColorInt int color) { - this.color = color; - } - - /** - * Converts the int color to rgba(d, d, d, d) string representation - * - * @return the string representation of a color - */ - String convertColor() { - return PropertyFactory.colorToRgbaString(color); - } - } - - /** * Expression array type. */ public static class Array { @@ -2033,6 +2012,11 @@ public class Expression { }; } + /** + * Convert the expression array to a string representation. + * + * @return the string representation of the expression array + */ @Override public String toString() { StringBuilder builder = new StringBuilder("[\"literal\"], ["); @@ -2053,4 +2037,20 @@ public class Expression { return builder.toString(); } } + + /** + * Converts an object that is a primitive array to an Object[] + * + * @param object the object to convert to an object array + * @return the converted object array + */ + static Object[] toObjectArray(Object object) { + // object is a primitive array + int len = java.lang.reflect.Array.getLength(object); + Object[] objects = new Object[len]; + for (int i = 0; i < len; i++) { + objects[i] = java.lang.reflect.Array.get(object, i); + } + return objects; + } }
\ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/BackgroundLayer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/BackgroundLayer.java index 978fa29ef8..60c2aa907b 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/BackgroundLayer.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/BackgroundLayer.java @@ -4,10 +4,13 @@ package com.mapbox.mapboxsdk.style.layers; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.annotation.UiThread; import static com.mapbox.mapboxsdk.utils.ColorUtils.rgbaToColor; +import com.google.gson.JsonArray; +import com.mapbox.mapboxsdk.style.expressions.Expression; import com.mapbox.mapboxsdk.style.layers.TransitionOptions; /** diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/CircleLayer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/CircleLayer.java index 10663142b5..8f1c00f1b5 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/CircleLayer.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/CircleLayer.java @@ -4,10 +4,13 @@ package com.mapbox.mapboxsdk.style.layers; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.annotation.UiThread; import static com.mapbox.mapboxsdk.utils.ColorUtils.rgbaToColor; +import com.google.gson.JsonArray; +import com.mapbox.mapboxsdk.style.expressions.Expression; import com.mapbox.mapboxsdk.style.layers.TransitionOptions; /** @@ -69,26 +72,41 @@ public class CircleLayer extends Layer { } /** - * Set a single filter. + * Set a single expression filter. * - * @param filter the filter to set + * @param filter the expression filter to set */ - public void setFilter(Filter.Statement filter) { + public void setFilter(Expression filter) { nativeSetFilter(filter.toArray()); } /** - * Set a single filter. + * Set a single expression filter. * - * @param filter the filter to set + * @param filter the expression filter to set * @return This */ - public CircleLayer withFilter(Filter.Statement filter) { + public CircleLayer withFilter(Expression filter) { setFilter(filter); return this; } /** + * Get a single expression filter. + * + * @return the expression filter to get + */ + @Nullable + public Expression getFilter() { + Expression expression = null; + JsonArray array = nativeGetFilter(); + if (array != null) { + expression = Expression.Converter.convert(array); + } + return expression; + } + + /** * Set a property or properties. * * @param properties the var-args properties diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/FillExtrusionLayer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/FillExtrusionLayer.java index 6772da73b1..34f062b4d6 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/FillExtrusionLayer.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/FillExtrusionLayer.java @@ -4,10 +4,13 @@ package com.mapbox.mapboxsdk.style.layers; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.annotation.UiThread; import static com.mapbox.mapboxsdk.utils.ColorUtils.rgbaToColor; +import com.google.gson.JsonArray; +import com.mapbox.mapboxsdk.style.expressions.Expression; import com.mapbox.mapboxsdk.style.layers.TransitionOptions; /** @@ -69,26 +72,41 @@ public class FillExtrusionLayer extends Layer { } /** - * Set a single filter. + * Set a single expression filter. * - * @param filter the filter to set + * @param filter the expression filter to set */ - public void setFilter(Filter.Statement filter) { + public void setFilter(Expression filter) { nativeSetFilter(filter.toArray()); } /** - * Set a single filter. + * Set a single expression filter. * - * @param filter the filter to set + * @param filter the expression filter to set * @return This */ - public FillExtrusionLayer withFilter(Filter.Statement filter) { + public FillExtrusionLayer withFilter(Expression filter) { setFilter(filter); return this; } /** + * Get a single expression filter. + * + * @return the expression filter to get + */ + @Nullable + public Expression getFilter() { + Expression expression = null; + JsonArray array = nativeGetFilter(); + if (array != null) { + expression = Expression.Converter.convert(array); + } + return expression; + } + + /** * Set a property or properties. * * @param properties the var-args properties diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/FillLayer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/FillLayer.java index 3719ae9e1b..1865349c42 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/FillLayer.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/FillLayer.java @@ -4,10 +4,13 @@ package com.mapbox.mapboxsdk.style.layers; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.annotation.UiThread; import static com.mapbox.mapboxsdk.utils.ColorUtils.rgbaToColor; +import com.google.gson.JsonArray; +import com.mapbox.mapboxsdk.style.expressions.Expression; import com.mapbox.mapboxsdk.style.layers.TransitionOptions; /** @@ -69,26 +72,41 @@ public class FillLayer extends Layer { } /** - * Set a single filter. + * Set a single expression filter. * - * @param filter the filter to set + * @param filter the expression filter to set */ - public void setFilter(Filter.Statement filter) { + public void setFilter(Expression filter) { nativeSetFilter(filter.toArray()); } /** - * Set a single filter. + * Set a single expression filter. * - * @param filter the filter to set + * @param filter the expression filter to set * @return This */ - public FillLayer withFilter(Filter.Statement filter) { + public FillLayer withFilter(Expression filter) { setFilter(filter); return this; } /** + * Get a single expression filter. + * + * @return the expression filter to get + */ + @Nullable + public Expression getFilter() { + Expression expression = null; + JsonArray array = nativeGetFilter(); + if (array != null) { + expression = Expression.Converter.convert(array); + } + return expression; + } + + /** * Set a property or properties. * * @param properties the var-args properties diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/Filter.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/Filter.java deleted file mode 100644 index 4dbb461e4c..0000000000 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/Filter.java +++ /dev/null @@ -1,230 +0,0 @@ -package com.mapbox.mapboxsdk.style.layers; - -import java.util.ArrayList; -import java.util.Collections; - -/** - * Utility to build filter expressions more easily. - * - * @see <a href="https://www.mapbox.com/mapbox-gl-style-spec/#types-filter">The online documentation</a> - */ -public class Filter { - - /** - * Base Filter statement. Subclassed to provide concrete statements. - */ - public abstract static class Statement { - protected final String operator; - - public Statement(String operator) { - this.operator = operator; - } - - /** - * Generate a raw array representation of the filter - * - * @return the filter represented as an array - */ - public abstract Object[] toArray(); - } - - /** - * Represents a {@link Filter} statement. Can be unary (eg `has()`, etc) or take any number of values. - */ - private static class SimpleStatement extends Statement { - private final String key; - private final Object[] values; - - /** - * @param operator the operator (eg `=`, etc) - * @param key the property key - * @param values the values to operate on, if any - */ - SimpleStatement(String operator, String key, Object... values) { - super(operator); - this.key = key; - this.values = values; - } - - - /** - * {@inheritDoc} - */ - @Override - public Object[] toArray() { - ArrayList<Object> array = new ArrayList<>(2 + values.length); - array.add(operator); - array.add(key); - Collections.addAll(array, values); - return array.toArray(); - } - } - - /** - * Represents a collection of {@link Statement}s with an operator that describes their relationship - */ - private static class CompoundStatement extends Statement { - private final Statement[] statements; - - /** - * @param operator the relationship operator - * @param statements the statements to compound - */ - CompoundStatement(String operator, Statement... statements) { - super(operator); - this.statements = statements; - } - - /** - * {@inheritDoc} - */ - @Override - public Object[] toArray() { - ArrayList<Object> array = new ArrayList<>(1 + statements.length); - array.add(operator); - for (Statement statement : statements) { - array.add(statement.toArray()); - } - return array.toArray(); - } - } - - /** - * Groups a collection of statements in an 'all' relationship - * - * @param statements the collection of statements - * @return the statements compounded - */ - public static Statement all(Statement... statements) { - return new CompoundStatement("all", statements); - } - - /** - * Groups a collection of statements in an 'any' relationship - * - * @param statements the collection of statements - * @return the statements compounded - */ - public static Statement any(Statement... statements) { - return new CompoundStatement("any", statements); - } - - /** - * Groups a collection of statements in an 'none' relationship - * - * @param statements the collection of statements - * @return the statements compounded - */ - public static Statement none(Statement... statements) { - return new CompoundStatement("none", statements); - } - - /** - * Check the property's existence - * - * @param key the property key - * @return the statement - */ - public static Statement has(String key) { - return new SimpleStatement("has", key); - } - - /** - * Check the property's existence, negated - * - * @param key the property key - * @return the statement - */ - public static Statement notHas(String key) { - return new SimpleStatement("!has", key); - } - - /** - * Check the property equals the given value - * - * @param key the property key - * @param value the value to check against - * @return the statement - */ - public static Statement eq(String key, Object value) { - return new SimpleStatement("==", key, value); - } - - /** - * Check the property does not equals the given value - * - * @param key the property key - * @param value the value to check against - * @return the statement - */ - public static Statement neq(String key, Object value) { - return new SimpleStatement("!=", key, value); - } - - /** - * Check the property exceeds the given value - * - * @param key the property key - * @param value the value to check against - * @return the statement - */ - public static Statement gt(String key, Object value) { - return new SimpleStatement(">", key, value); - } - - /** - * Check the property exceeds or equals the given value - * - * @param key the property key - * @param value the value to check against - * @return the statement - */ - public static Statement gte(String key, Object value) { - return new SimpleStatement(">=", key, value); - } - - /** - * Check the property does not exceeds the given value - * - * @param key the property key - * @param value the value to check against - * @return the statement - */ - public static Statement lt(String key, Object value) { - return new SimpleStatement("<", key, value); - } - - /** - * Check the property equals or does not exceeds the given value - * - * @param key the property key - * @param value the value to check against - * @return the statement - */ - public static Statement lte(String key, Object value) { - return new SimpleStatement("<=", key, value); - } - - /** - * Check the property is within the given set - * - * @param key the property key - * @param values the set of values to check against - * @return the statement - */ - public static Statement in(String key, Object... values) { - return new SimpleStatement("in", key, values); - } - - /** - * Check the property is not within the given set - * - * @param key the property key - * @param values the set of values to check against - * @return the statement - */ - public static Statement notIn(String key, Object... values) { - return new SimpleStatement("!in", key, values); - } - -} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HeatmapLayer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HeatmapLayer.java index bfc663449f..a8d07bdbb4 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HeatmapLayer.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HeatmapLayer.java @@ -4,10 +4,13 @@ package com.mapbox.mapboxsdk.style.layers; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.annotation.UiThread; import static com.mapbox.mapboxsdk.utils.ColorUtils.rgbaToColor; +import com.google.gson.JsonArray; +import com.mapbox.mapboxsdk.style.expressions.Expression; import com.mapbox.mapboxsdk.style.layers.TransitionOptions; /** @@ -69,26 +72,41 @@ public class HeatmapLayer extends Layer { } /** - * Set a single filter. + * Set a single expression filter. * - * @param filter the filter to set + * @param filter the expression filter to set */ - public void setFilter(Filter.Statement filter) { + public void setFilter(Expression filter) { nativeSetFilter(filter.toArray()); } /** - * Set a single filter. + * Set a single expression filter. * - * @param filter the filter to set + * @param filter the expression filter to set * @return This */ - public HeatmapLayer withFilter(Filter.Statement filter) { + public HeatmapLayer withFilter(Expression filter) { setFilter(filter); return this; } /** + * Get a single expression filter. + * + * @return the expression filter to get + */ + @Nullable + public Expression getFilter() { + Expression expression = null; + JsonArray array = nativeGetFilter(); + if (array != null) { + expression = Expression.Converter.convert(array); + } + return expression; + } + + /** * Set a property or properties. * * @param properties the var-args properties diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HillshadeLayer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HillshadeLayer.java index 7e3d3a7779..fb086f424b 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HillshadeLayer.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HillshadeLayer.java @@ -4,10 +4,13 @@ package com.mapbox.mapboxsdk.style.layers; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.annotation.UiThread; import static com.mapbox.mapboxsdk.utils.ColorUtils.rgbaToColor; +import com.google.gson.JsonArray; +import com.mapbox.mapboxsdk.style.expressions.Expression; import com.mapbox.mapboxsdk.style.layers.TransitionOptions; /** diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/Layer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/Layer.java index 66db88bafb..26fc9d32e1 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/Layer.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/Layer.java @@ -2,6 +2,7 @@ package com.mapbox.mapboxsdk.style.layers; import android.support.annotation.NonNull; +import com.google.gson.JsonArray; import com.mapbox.mapboxsdk.style.expressions.Expression; /** @@ -71,6 +72,8 @@ public abstract class Layer { protected native void nativeSetFilter(Object[] filter); + protected native JsonArray nativeGetFilter(); + protected native void nativeSetSourceLayer(String sourceLayer); protected native String nativeGetSourceLayer(); diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/LineLayer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/LineLayer.java index ca106cc106..9dbd3aeb19 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/LineLayer.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/LineLayer.java @@ -4,10 +4,13 @@ package com.mapbox.mapboxsdk.style.layers; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.annotation.UiThread; import static com.mapbox.mapboxsdk.utils.ColorUtils.rgbaToColor; +import com.google.gson.JsonArray; +import com.mapbox.mapboxsdk.style.expressions.Expression; import com.mapbox.mapboxsdk.style.layers.TransitionOptions; /** @@ -69,26 +72,41 @@ public class LineLayer extends Layer { } /** - * Set a single filter. + * Set a single expression filter. * - * @param filter the filter to set + * @param filter the expression filter to set */ - public void setFilter(Filter.Statement filter) { + public void setFilter(Expression filter) { nativeSetFilter(filter.toArray()); } /** - * Set a single filter. + * Set a single expression filter. * - * @param filter the filter to set + * @param filter the expression filter to set * @return This */ - public LineLayer withFilter(Filter.Statement filter) { + public LineLayer withFilter(Expression filter) { setFilter(filter); return this; } /** + * Get a single expression filter. + * + * @return the expression filter to get + */ + @Nullable + public Expression getFilter() { + Expression expression = null; + JsonArray array = nativeGetFilter(); + if (array != null) { + expression = Expression.Converter.convert(array); + } + return expression; + } + + /** * Set a property or properties. * * @param properties the var-args properties diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/RasterLayer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/RasterLayer.java index a0f45535f6..0c7948f62a 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/RasterLayer.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/RasterLayer.java @@ -4,10 +4,13 @@ package com.mapbox.mapboxsdk.style.layers; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.annotation.UiThread; import static com.mapbox.mapboxsdk.utils.ColorUtils.rgbaToColor; +import com.google.gson.JsonArray; +import com.mapbox.mapboxsdk.style.expressions.Expression; import com.mapbox.mapboxsdk.style.layers.TransitionOptions; /** diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/SymbolLayer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/SymbolLayer.java index d0fb82dce5..bc1e02a89f 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/SymbolLayer.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/SymbolLayer.java @@ -4,10 +4,13 @@ package com.mapbox.mapboxsdk.style.layers; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.annotation.UiThread; import static com.mapbox.mapboxsdk.utils.ColorUtils.rgbaToColor; +import com.google.gson.JsonArray; +import com.mapbox.mapboxsdk.style.expressions.Expression; import com.mapbox.mapboxsdk.style.layers.TransitionOptions; /** @@ -69,26 +72,41 @@ public class SymbolLayer extends Layer { } /** - * Set a single filter. + * Set a single expression filter. * - * @param filter the filter to set + * @param filter the expression filter to set */ - public void setFilter(Filter.Statement filter) { + public void setFilter(Expression filter) { nativeSetFilter(filter.toArray()); } /** - * Set a single filter. + * Set a single expression filter. * - * @param filter the filter to set + * @param filter the expression filter to set * @return This */ - public SymbolLayer withFilter(Filter.Statement filter) { + public SymbolLayer withFilter(Expression filter) { setFilter(filter); return this; } /** + * Get a single expression filter. + * + * @return the expression filter to get + */ + @Nullable + public Expression getFilter() { + Expression expression = null; + JsonArray array = nativeGetFilter(); + if (array != null) { + expression = Expression.Converter.convert(array); + } + return expression; + } + + /** * Set a property or properties. * * @param properties the var-args properties diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/layer.java.ejs b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/layer.java.ejs index 77fa11808e..f3941aacf6 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/layer.java.ejs +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/layer.java.ejs @@ -9,10 +9,13 @@ package com.mapbox.mapboxsdk.style.layers; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.annotation.UiThread; import static com.mapbox.mapboxsdk.utils.ColorUtils.rgbaToColor; +import com.google.gson.JsonArray; +import com.mapbox.mapboxsdk.style.expressions.Expression; import com.mapbox.mapboxsdk.style.layers.TransitionOptions; /** @@ -89,25 +92,40 @@ public class <%- camelize(type) %>Layer extends Layer { } /** - * Set a single filter. + * Set a single expression filter. * - * @param filter the filter to set + * @param filter the expression filter to set */ - public void setFilter(Filter.Statement filter) { + public void setFilter(Expression filter) { nativeSetFilter(filter.toArray()); } /** - * Set a single filter. + * Set a single expression filter. * - * @param filter the filter to set + * @param filter the expression filter to set * @return This */ - public <%- camelize(type) %>Layer withFilter(Filter.Statement filter) { + public <%- camelize(type) %>Layer withFilter(Expression filter) { setFilter(filter); return this; } + /** + * Get a single expression filter. + * + * @return the expression filter to get + */ + @Nullable + public Expression getFilter() { + Expression expression = null; + JsonArray array = nativeGetFilter(); + if (array != null) { + expression = Expression.Converter.convert(array); + } + return expression; + } + <% } -%> /** * Set a property or properties. diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/CustomGeometrySource.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/CustomGeometrySource.java index 1b0999ae2e..21a34f6064 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/CustomGeometrySource.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/CustomGeometrySource.java @@ -8,7 +8,7 @@ import android.support.annotation.WorkerThread; import com.mapbox.geojson.Feature; import com.mapbox.geojson.FeatureCollection; import com.mapbox.mapboxsdk.geometry.LatLngBounds; -import com.mapbox.mapboxsdk.style.layers.Filter; +import com.mapbox.mapboxsdk.style.expressions.Expression; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -94,11 +94,11 @@ public class CustomGeometrySource extends Source { /** * Queries the source for features. * - * @param filter an optional filter statement to filter the returned Features + * @param filter an optional filter expression to filter the returned Features * @return the features */ @NonNull - public List<Feature> querySourceFeatures(@Nullable Filter.Statement filter) { + public List<Feature> querySourceFeatures(@Nullable Expression filter) { Feature[] features = querySourceFeatures(filter != null ? filter.toArray() : null); return features != null ? Arrays.asList(features) : new ArrayList<Feature>(); } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/GeoJsonSource.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/GeoJsonSource.java index 4a2f29b503..efacc18741 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/GeoJsonSource.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/GeoJsonSource.java @@ -7,7 +7,7 @@ import android.support.annotation.UiThread; import com.mapbox.geojson.Feature; import com.mapbox.geojson.FeatureCollection; import com.mapbox.geojson.Geometry; -import com.mapbox.mapboxsdk.style.layers.Filter; +import com.mapbox.mapboxsdk.style.expressions.Expression; import java.net.URL; import java.util.ArrayList; @@ -238,11 +238,11 @@ public class GeoJsonSource extends Source { /** * Queries the source for features. * - * @param filter an optional filter statement to filter the returned Features + * @param filter an optional filter expression to filter the returned Features * @return the features */ @NonNull - public List<Feature> querySourceFeatures(@Nullable Filter.Statement filter) { + public List<Feature> querySourceFeatures(@Nullable Expression filter) { Feature[] features = querySourceFeatures(filter != null ? filter.toArray() : null); return features != null ? Arrays.asList(features) : new ArrayList<Feature>(); } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/VectorSource.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/VectorSource.java index 62b08a90ed..d82eaaa1c7 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/VectorSource.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/VectorSource.java @@ -6,7 +6,7 @@ import android.support.annotation.Size; import android.support.annotation.UiThread; import com.mapbox.geojson.Feature; -import com.mapbox.mapboxsdk.style.layers.Filter; +import com.mapbox.mapboxsdk.style.expressions.Expression; import java.net.URL; import java.util.ArrayList; @@ -64,12 +64,12 @@ public class VectorSource extends Source { * Queries the source for features. * * @param sourceLayerIds the source layer identifiers. At least one must be specified. - * @param filter an optional filter statement to filter the returned Features + * @param filter an optional filter expression to filter the returned Features * @return the features */ @NonNull public List<Feature> querySourceFeatures(@Size(min = 1) String[] sourceLayerIds, - @Nullable Filter.Statement filter) { + @Nullable Expression filter) { Feature[] features = querySourceFeatures( sourceLayerIds, filter != null ? filter.toArray() : null); diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/style/expressions/ExpressionTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/style/expressions/ExpressionTest.java index a2cf34738d..22c25fd0df 100644 --- a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/style/expressions/ExpressionTest.java +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/style/expressions/ExpressionTest.java @@ -2,8 +2,6 @@ package com.mapbox.mapboxsdk.style.expressions; import android.graphics.Color; -import com.mapbox.mapboxsdk.style.layers.PropertyFactory; - import org.junit.Test; import java.util.Arrays; @@ -113,7 +111,7 @@ public class ExpressionTest { @Test public void testToRgba() throws Exception { - Object[] expected = new Object[] {"to-rgba", PropertyFactory.colorToRgbaString(Color.RED)}; + Object[] expected = new Object[] {"to-rgba", new Object[] {"to-color", "rgba(255, 0, 0, 255)"}}; Object[] actual = toRgba(color(Color.RED)).toArray(); assertTrue("expression should match", Arrays.deepEquals(expected, actual)); } @@ -1081,4 +1079,20 @@ public class ExpressionTest { String actual = literal(array).toString(); assertEquals("literal array should match", expected, actual); } + + @Test + public void testLiteralPrimitiveArrayConversion() throws Exception { + float[] array = new float[] {0.2f, 0.5f}; + Object[] expected = new Object[] {"literal", new Object[] {0.2f, 0.5f}}; + Object[] actual = literal(array).toArray(); + assertEquals("primitive array should be converted", expected, actual); + } + + @Test + public void testColorConversion() { + Expression greenColor = color(0xFF00FF00); + Object[] expected = new Object[] {"to-color", "rgba(0, 255, 0, 255)"}; + assertTrue("expression should match", Arrays.deepEquals(expected, greenColor.toArray())); + } + }
\ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/style/layers/FilterTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/style/layers/FilterTest.java deleted file mode 100644 index 933bf05b39..0000000000 --- a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/style/layers/FilterTest.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.mapbox.mapboxsdk.style.layers; - -import org.junit.Test; - -import static com.mapbox.mapboxsdk.style.layers.Filter.all; -import static com.mapbox.mapboxsdk.style.layers.Filter.any; -import static com.mapbox.mapboxsdk.style.layers.Filter.eq; -import static com.mapbox.mapboxsdk.style.layers.Filter.gt; -import static com.mapbox.mapboxsdk.style.layers.Filter.gte; -import static com.mapbox.mapboxsdk.style.layers.Filter.has; -import static com.mapbox.mapboxsdk.style.layers.Filter.in; -import static com.mapbox.mapboxsdk.style.layers.Filter.lt; -import static com.mapbox.mapboxsdk.style.layers.Filter.lte; -import static com.mapbox.mapboxsdk.style.layers.Filter.neq; -import static com.mapbox.mapboxsdk.style.layers.Filter.none; -import static com.mapbox.mapboxsdk.style.layers.Filter.notHas; -import static com.mapbox.mapboxsdk.style.layers.Filter.notIn; -import static org.junit.Assert.assertArrayEquals; - -/** - * Tests for Filter - */ -public class FilterTest { - - @Test - public void testAll() { - assertArrayEquals(all().toArray(), new Object[] {"all"}); - assertArrayEquals( - all(eq("key", 2), neq("key", 3)).toArray(), - new Object[] {"all", new Object[] {"==", "key", 2}, new Object[] {"!=", "key", 3}} - ); - } - - @Test - public void testAny() { - assertArrayEquals(any().toArray(), new Object[] {"any"}); - assertArrayEquals( - any(eq("key", 2), neq("key", 3)).toArray(), - new Object[] {"any", new Object[] {"==", "key", 2}, new Object[] {"!=", "key", 3}} - ); - } - - @Test - public void testNone() { - assertArrayEquals(none().toArray(), new Object[] {"none"}); - assertArrayEquals( - none(eq("key", 2), neq("key", 3)).toArray(), - new Object[] {"none", new Object[] {"==", "key", 2}, new Object[] {"!=", "key", 3}} - ); - } - - @Test - public void testHas() { - assertArrayEquals(has("key").toArray(), new Object[] {"has", "key"}); - } - - @Test - public void testHasNot() { - assertArrayEquals(notHas("key").toArray(), new Object[] {"!has", "key"}); - } - - @Test - public void testEq() { - assertArrayEquals(eq("key", 1).toArray(), new Object[] {"==", "key", 1}); - - } - - @Test - public void testNeq() { - assertArrayEquals(neq("key", 1).toArray(), new Object[] {"!=", "key", 1}); - } - - @Test - public void testGt() { - assertArrayEquals(gt("key", 1).toArray(), new Object[] {">", "key", 1}); - } - - @Test - public void testGte() { - assertArrayEquals(gte("key", 1).toArray(), new Object[] {">=", "key", 1}); - } - - @Test - public void testLt() { - assertArrayEquals(lt("key", 1).toArray(), new Object[] {"<", "key", 1}); - } - - @Test - public void testLte() { - assertArrayEquals(lte("key", 1).toArray(), new Object[] {"<=", "key", 1}); - } - - @Test - public void testIn() { - assertArrayEquals(in("key", 1, 2, "Aap").toArray(), new Object[] {"in", "key", 1, 2, "Aap"}); - } - - @Test - public void testNotIn() { - assertArrayEquals(notIn("key", 1, 2, "Noot").toArray(), new Object[] {"!in", "key", 1, 2, "Noot"}); - } -} diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/telemetry/HttpTransportTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/telemetry/HttpTransportTest.java index 94a6dc2194..519124e1eb 100644 --- a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/telemetry/HttpTransportTest.java +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/telemetry/HttpTransportTest.java @@ -1,8 +1,8 @@ package com.mapbox.mapboxsdk.telemetry; -import org.junit.Test; +import com.mapbox.android.telemetry.TelemetryUtils; -import okhttp3.internal.Util; +import org.junit.Test; import static junit.framework.Assert.assertEquals; @@ -15,6 +15,6 @@ public class HttpTransportTest { final String asciiVersion = "Sveriges Fj?ll/1.0/1 MapboxEventsAndroid/4.0.0-SNAPSHOT"; assertEquals("asciiVersion and swedishUserAgent should match", asciiVersion, - Util.toHumanReadableAscii(swedishUserAgent)); + TelemetryUtils.toHumanReadableAscii(swedishUserAgent)); } } diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/CircleLayerTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/CircleLayerTest.java index 0460a59534..a851fde6dd 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/CircleLayerTest.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/CircleLayerTest.java @@ -86,6 +86,26 @@ public class CircleLayerTest extends BaseActivityTest { } @Test + public void testFilter() { + validateTestSetup(); + setupLayer(); + Timber.i("Filter"); + invoke(mapboxMap, (uiController, mapboxMap1) -> { + assertNotNull(layer); + + // Get initial + assertEquals(layer.getFilter(), null); + + // Set + Expression filter = eq(get("undefined"), literal(1.0)); + layer.setFilter(filter); + assertEquals(layer.getFilter().toString(), filter.toString()); + }); + } + + + + @Test public void testCircleRadiusTransition() { validateTestSetup(); setupLayer(); diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/FillExtrusionLayerTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/FillExtrusionLayerTest.java index dd8929e2f4..f22956072d 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/FillExtrusionLayerTest.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/FillExtrusionLayerTest.java @@ -86,6 +86,26 @@ public class FillExtrusionLayerTest extends BaseActivityTest { } @Test + public void testFilter() { + validateTestSetup(); + setupLayer(); + Timber.i("Filter"); + invoke(mapboxMap, (uiController, mapboxMap1) -> { + assertNotNull(layer); + + // Get initial + assertEquals(layer.getFilter(), null); + + // Set + Expression filter = eq(get("undefined"), literal(1.0)); + layer.setFilter(filter); + assertEquals(layer.getFilter().toString(), filter.toString()); + }); + } + + + + @Test public void testFillExtrusionOpacityTransition() { validateTestSetup(); setupLayer(); diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/FillLayerTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/FillLayerTest.java index c70ea39ffa..bdbd8958d2 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/FillLayerTest.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/FillLayerTest.java @@ -86,6 +86,26 @@ public class FillLayerTest extends BaseActivityTest { } @Test + public void testFilter() { + validateTestSetup(); + setupLayer(); + Timber.i("Filter"); + invoke(mapboxMap, (uiController, mapboxMap1) -> { + assertNotNull(layer); + + // Get initial + assertEquals(layer.getFilter(), null); + + // Set + Expression filter = eq(get("undefined"), literal(1.0)); + layer.setFilter(filter); + assertEquals(layer.getFilter().toString(), filter.toString()); + }); + } + + + + @Test public void testFillAntialiasAsConstant() { validateTestSetup(); setupLayer(); diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/HeatmapLayerTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/HeatmapLayerTest.java index 9a0374a763..549f309e4f 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/HeatmapLayerTest.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/HeatmapLayerTest.java @@ -86,6 +86,26 @@ public class HeatmapLayerTest extends BaseActivityTest { } @Test + public void testFilter() { + validateTestSetup(); + setupLayer(); + Timber.i("Filter"); + invoke(mapboxMap, (uiController, mapboxMap1) -> { + assertNotNull(layer); + + // Get initial + assertEquals(layer.getFilter(), null); + + // Set + Expression filter = eq(get("undefined"), literal(1.0)); + layer.setFilter(filter); + assertEquals(layer.getFilter().toString(), filter.toString()); + }); + } + + + + @Test public void testHeatmapRadiusTransition() { validateTestSetup(); setupLayer(); diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/LightTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/LightTest.java index 560aa91420..fce73bdead 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/LightTest.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/LightTest.java @@ -24,7 +24,7 @@ import org.junit.runner.RunWith; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; import static android.support.test.espresso.matcher.ViewMatchers.withId; -import static com.mapbox.mapboxsdk.style.layers.Filter.eq; +import static com.mapbox.mapboxsdk.style.expressions.Expression.eq; import static com.mapbox.mapboxsdk.style.layers.Property.ANCHOR_MAP; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.fillExtrusionBase; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.fillExtrusionColor; diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/LineLayerTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/LineLayerTest.java index a8e306d7a6..40cf0f2927 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/LineLayerTest.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/LineLayerTest.java @@ -86,6 +86,26 @@ public class LineLayerTest extends BaseActivityTest { } @Test + public void testFilter() { + validateTestSetup(); + setupLayer(); + Timber.i("Filter"); + invoke(mapboxMap, (uiController, mapboxMap1) -> { + assertNotNull(layer); + + // Get initial + assertEquals(layer.getFilter(), null); + + // Set + Expression filter = eq(get("undefined"), literal(1.0)); + layer.setFilter(filter); + assertEquals(layer.getFilter().toString(), filter.toString()); + }); + } + + + + @Test public void testLineCapAsConstant() { validateTestSetup(); setupLayer(); diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/StyleLoaderTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/StyleLoaderTest.java new file mode 100644 index 0000000000..1a5201193c --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/StyleLoaderTest.java @@ -0,0 +1,77 @@ +package com.mapbox.mapboxsdk.testapp.style; + + +import android.support.test.espresso.UiController; + +import com.mapbox.mapboxsdk.maps.MapView; +import com.mapbox.mapboxsdk.maps.MapboxMap; +import com.mapbox.mapboxsdk.testapp.R; +import com.mapbox.mapboxsdk.testapp.action.MapboxMapAction; +import com.mapbox.mapboxsdk.testapp.activity.BaseActivityTest; +import com.mapbox.mapboxsdk.testapp.activity.espresso.EspressoTestActivity; +import com.mapbox.mapboxsdk.testapp.utils.ResourceUtils; + +import org.junit.Test; + +import java.io.IOException; + +import static com.mapbox.mapboxsdk.testapp.action.MapboxMapAction.invoke; +import static org.junit.Assert.assertEquals; + +/** + * Tests around style loading + */ +public class StyleLoaderTest extends BaseActivityTest { + + + @Override + protected Class getActivityClass() { + return EspressoTestActivity.class; + } + + @Test + public void testSetGetStyleJsonString() throws Exception { + validateTestSetup(); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + try { + String expected = ResourceUtils.readRawResource(rule.getActivity(), R.raw.local_style); + mapboxMap.setStyleJson(expected); + String actual = mapboxMap.getStyleJson(); + assertEquals("Style json should match", expected, actual); + } catch (IOException exception) { + exception.printStackTrace(); + } + } + }); + } + + @Test + public void testDefaultStyleLoadWithActivityLifecycleChange() throws Exception { + validateTestSetup(); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + try { + String expected = ResourceUtils.readRawResource(rule.getActivity(), R.raw.local_style); + mapboxMap.setStyleJson(expected); + + // fake activity stop/start + MapView mapView = (MapView) rule.getActivity().findViewById(R.id.mapView); + mapView.onPause(); + mapView.onStop(); + + mapView.onStart(); + mapView.onResume(); + + String actual = mapboxMap.getStyleJson(); + assertEquals("Style URL should be empty", "", mapboxMap.getStyleUrl()); + assertEquals("Style json should match", expected, actual); + } catch (IOException exception) { + exception.printStackTrace(); + } + } + }); + } +}
\ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/SymbolLayerTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/SymbolLayerTest.java index 443c3d682a..62f73b1507 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/SymbolLayerTest.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/SymbolLayerTest.java @@ -86,6 +86,26 @@ public class SymbolLayerTest extends BaseActivityTest { } @Test + public void testFilter() { + validateTestSetup(); + setupLayer(); + Timber.i("Filter"); + invoke(mapboxMap, (uiController, mapboxMap1) -> { + assertNotNull(layer); + + // Get initial + assertEquals(layer.getFilter(), null); + + // Set + Expression filter = eq(get("undefined"), literal(1.0)); + layer.setFilter(filter); + assertEquals(layer.getFilter().toString(), filter.toString()); + }); + } + + + + @Test public void testSymbolPlacementAsConstant() { validateTestSetup(); setupLayer(); diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/layer.junit.ejs b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/layer.junit.ejs index 09d4328b70..935813899f 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/layer.junit.ejs +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/layer.junit.ejs @@ -96,6 +96,26 @@ public class <%- camelize(type) %>LayerTest extends BaseActivityTest { assertEquals(layer.getSourceLayer(), sourceLayer); }); } + + @Test + public void testFilter() { + validateTestSetup(); + setupLayer(); + Timber.i("Filter"); + invoke(mapboxMap, (uiController, mapboxMap1) -> { + assertNotNull(layer); + + // Get initial + assertEquals(layer.getFilter(), null); + + // Set + Expression filter = eq(get("undefined"), literal(1.0)); + layer.setFilter(filter); + assertEquals(layer.getFilter().toString(), filter.toString()); + }); + } + + <% } -%> <% for (const property of properties) { -%> <% if (property.name != 'heatmap-color') { -%> diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/light.junit.ejs b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/light.junit.ejs index 96f23871c5..a8207a903b 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/light.junit.ejs +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/light.junit.ejs @@ -27,7 +27,7 @@ import org.junit.runner.RunWith; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; import static android.support.test.espresso.matcher.ViewMatchers.withId; -import static com.mapbox.mapboxsdk.style.layers.Filter.eq; +import static com.mapbox.mapboxsdk.style.expressions.Expression.eq; import static com.mapbox.mapboxsdk.style.layers.Property.ANCHOR_MAP; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.fillExtrusionBase; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.fillExtrusionColor; diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/MapboxApplication.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/MapboxApplication.java index fba33bb380..fa13959112 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/MapboxApplication.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/MapboxApplication.java @@ -5,6 +5,7 @@ import android.os.StrictMode; import android.text.TextUtils; import com.mapbox.mapboxsdk.Mapbox; +import com.mapbox.mapboxsdk.maps.Telemetry; import com.mapbox.mapboxsdk.testapp.utils.TokenUtils; import com.squareup.leakcanary.LeakCanary; @@ -57,6 +58,8 @@ public class MapboxApplication extends Application { } Mapbox.getInstance(getApplicationContext(), mapboxAccessToken); + + Telemetry.updateDebugLoggingEnabled(true); } private void initializeLogger() { diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/camera/GestureDetectorActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/camera/GestureDetectorActivity.java index 2b79e2691d..c1698e20ab 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/camera/GestureDetectorActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/camera/GestureDetectorActivity.java @@ -58,7 +58,6 @@ public class GestureDetectorActivity extends AppCompatActivity { private Marker marker; @Nullable private LatLng focalPointLatLng; - private boolean animationsEnabled = true; @Override protected void onCreate(Bundle savedInstanceState) { @@ -198,9 +197,7 @@ public class GestureDetectorActivity extends AppCompatActivity { if (focalPointLatLng != null) { gestureAlertsAdapter.addAlert(new GestureAlert(GestureAlert.TYPE_OTHER, "REVERTING MOVE THRESHOLD")); - gesturesManager.getMoveGestureDetector().setMoveThreshold( - gesturesManager.getMoveGestureDetector().getDefaultMoveThreshold() - ); + gesturesManager.getMoveGestureDetector().setMoveThreshold(0f); } } }); @@ -242,8 +239,7 @@ public class GestureDetectorActivity extends AppCompatActivity { mapboxMap.getUiSettings().setFocalPoint(mapboxMap.getProjection().toScreenLocation(focalPointLatLng)); return true; case R.id.menu_gesture_animation: - animationsEnabled = !animationsEnabled; - mapboxMap.getUiSettings().setAllVelocityAnimationsEnabled(animationsEnabled); + mapboxMap.getUiSettings().setAllVelocityAnimationsEnabled(false); } return super.onOptionsItemSelected(item); } @@ -251,10 +247,8 @@ public class GestureDetectorActivity extends AppCompatActivity { private void resetModes() { focalPointLatLng = null; mapboxMap.getUiSettings().setFocalPoint(null); - - gesturesManager.getMoveGestureDetector().setMoveThreshold( - gesturesManager.getMoveGestureDetector().getDefaultMoveThreshold() - ); + gesturesManager.getMoveGestureDetector().setMoveThreshold(0f); + mapboxMap.getUiSettings().setAllVelocityAnimationsEnabled(true); if (marker != null) { mapboxMap.removeMarker(marker); diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesBoxHighlightActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesBoxHighlightActivity.java index df608360ad..7953824c36 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesBoxHighlightActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesBoxHighlightActivity.java @@ -11,16 +11,19 @@ import com.mapbox.geojson.Feature; import com.mapbox.geojson.FeatureCollection; import com.mapbox.mapboxsdk.maps.MapView; import com.mapbox.mapboxsdk.maps.MapboxMap; +import com.mapbox.mapboxsdk.style.expressions.Expression; import com.mapbox.mapboxsdk.style.layers.FillLayer; -import com.mapbox.mapboxsdk.style.layers.Filter; import com.mapbox.mapboxsdk.style.sources.GeoJsonSource; import com.mapbox.mapboxsdk.testapp.R; - import java.util.List; import timber.log.Timber; +import static com.mapbox.mapboxsdk.style.expressions.Expression.get; +import static com.mapbox.mapboxsdk.style.expressions.Expression.literal; +import static com.mapbox.mapboxsdk.style.expressions.Expression.lt; +import static com.mapbox.mapboxsdk.style.expressions.Expression.toNumber; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.fillColor; /** @@ -58,7 +61,9 @@ public class QueryRenderedFeaturesBoxHighlightActivity extends AppCompatActivity int left = selectionBox.getLeft() - mapView.getLeft(); RectF box = new RectF(left, top, left + selectionBox.getWidth(), top + selectionBox.getHeight()); Timber.i("Querying box %s for buildings", box); - List<Feature> features = mapboxMap.queryRenderedFeatures(box, Filter.lt("height", 10), "building"); + + Expression filter = lt(toNumber(get("height")), literal(10)); + List<Feature> features = mapboxMap.queryRenderedFeatures(box, filter, "building"); // Show count Toast.makeText( diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QuerySourceFeaturesActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QuerySourceFeaturesActivity.java index 14de81ab30..79069a26f7 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QuerySourceFeaturesActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QuerySourceFeaturesActivity.java @@ -11,13 +11,16 @@ import com.mapbox.geojson.Point; import com.mapbox.mapboxsdk.maps.MapView; import com.mapbox.mapboxsdk.maps.MapboxMap; import com.mapbox.mapboxsdk.style.layers.CircleLayer; -import com.mapbox.mapboxsdk.style.layers.Filter; import com.mapbox.mapboxsdk.style.sources.GeoJsonSource; import com.mapbox.mapboxsdk.testapp.R; - import java.util.List; +import static com.mapbox.mapboxsdk.style.expressions.Expression.eq; +import static com.mapbox.mapboxsdk.style.expressions.Expression.get; +import static com.mapbox.mapboxsdk.style.expressions.Expression.literal; +import static com.mapbox.mapboxsdk.style.expressions.Expression.neq; + /** * Test activity showcasing using the query source features API to query feature counts */ @@ -31,8 +34,6 @@ public class QuerySourceFeaturesActivity extends AppCompatActivity { super.onCreate(savedInstanceState); setContentView(R.layout.activity_query_source_features); - final float density = getResources().getDisplayMetrics().density; - // Initialize map as normal mapView = (MapView) findViewById(R.id.mapView); mapView.onCreate(savedInstanceState); @@ -47,12 +48,12 @@ public class QuerySourceFeaturesActivity extends AppCompatActivity { })); mapboxMap.addSource(source); - mapboxMap.addLayer(new CircleLayer("test-layer", source.getId()).withFilter(Filter.neq("key1", "value1"))); + mapboxMap.addLayer(new CircleLayer("test-layer", source.getId()).withFilter(neq(get("key1"), literal("value1")))); // Add a click listener mapboxMap.setOnMapClickListener(point -> { // Query - List<Feature> features = source.querySourceFeatures(Filter.eq("key1", "value1")); + List<Feature> features = source.querySourceFeatures(eq(get("key1"), literal("value1"))); Toast.makeText(QuerySourceFeaturesActivity.this, String.format("Found %s features", features.size()), Toast.LENGTH_SHORT).show(); }); diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/BuildingFillExtrusionActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/BuildingFillExtrusionActivity.java index c86e995956..97b4fbf6af 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/BuildingFillExtrusionActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/BuildingFillExtrusionActivity.java @@ -16,7 +16,9 @@ import com.mapbox.mapboxsdk.style.light.Light; import com.mapbox.mapboxsdk.style.light.Position; import com.mapbox.mapboxsdk.testapp.R; -import static com.mapbox.mapboxsdk.style.layers.Filter.eq; +import static com.mapbox.mapboxsdk.style.expressions.Expression.eq; +import static com.mapbox.mapboxsdk.style.expressions.Expression.get; +import static com.mapbox.mapboxsdk.style.expressions.Expression.literal; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.fillExtrusionBase; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.fillExtrusionColor; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.fillExtrusionHeight; @@ -52,7 +54,7 @@ public class BuildingFillExtrusionActivity extends AppCompatActivity { private void setupBuildings() { FillExtrusionLayer fillExtrusionLayer = new FillExtrusionLayer("3d-buildings", "composite"); fillExtrusionLayer.setSourceLayer("building"); - fillExtrusionLayer.setFilter(eq("extrude", "true")); + fillExtrusionLayer.setFilter(eq(get("extrude"), literal("true"))); fillExtrusionLayer.setMinZoom(15); fillExtrusionLayer.setProperties( fillExtrusionColor(Color.LTGRAY), diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/CircleLayerActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/CircleLayerActivity.java index 6aa8777777..9437422d84 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/CircleLayerActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/CircleLayerActivity.java @@ -13,6 +13,7 @@ import com.mapbox.mapboxsdk.constants.Style; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.maps.MapView; import com.mapbox.mapboxsdk.maps.MapboxMap; +import com.mapbox.mapboxsdk.style.expressions.Expression; import com.mapbox.mapboxsdk.style.layers.CircleLayer; import com.mapbox.mapboxsdk.style.layers.LineLayer; import com.mapbox.mapboxsdk.style.sources.GeoJsonSource; @@ -23,7 +24,10 @@ import java.net.URL; import timber.log.Timber; -import static com.mapbox.mapboxsdk.style.layers.Filter.in; +import static com.mapbox.mapboxsdk.style.expressions.Expression.array; +import static com.mapbox.mapboxsdk.style.expressions.Expression.get; +import static com.mapbox.mapboxsdk.style.expressions.Expression.has; +import static com.mapbox.mapboxsdk.style.expressions.Expression.literal; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleColor; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleRadius; @@ -117,7 +121,7 @@ public class CircleLayerActivity extends AppCompatActivity implements View.OnCli } private void applyBusRouteFilterToBusStopSource() { - layer.setFilter(in("number", (Object[]) Data.STOPS_FOR_ROUTE)); + layer.setFilter(has(Expression.toString(get("number")), array(literal(Data.STOPS_FOR_ROUTE)))); } private void addBusRouteSource() { diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/DataDrivenStyleActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/DataDrivenStyleActivity.java index eebe95a411..dae0714d42 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/DataDrivenStyleActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/DataDrivenStyleActivity.java @@ -1,5 +1,6 @@ package com.mapbox.mapboxsdk.testapp.activity.style; +import android.graphics.Color; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.Menu; @@ -33,6 +34,7 @@ import static com.mapbox.mapboxsdk.style.expressions.Expression.rgba; import static com.mapbox.mapboxsdk.style.expressions.Expression.step; import static com.mapbox.mapboxsdk.style.expressions.Expression.stop; import static com.mapbox.mapboxsdk.style.expressions.Expression.zoom; +import static com.mapbox.mapboxsdk.style.expressions.Expression.color; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.fillAntialias; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.fillColor; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.fillOpacity; @@ -173,9 +175,9 @@ public class DataDrivenStyleActivity extends AppCompatActivity { fillColor( interpolate( exponential(0.5f), zoom(), - stop(1, rgb(255, 0, 0)), - stop(5, rgb(0, 0, 255)), - stop(10, rgb(0, 255, 0)) + stop(1, color(Color.RED)), + stop(5, color(Color.BLUE)), + stop(10, color(Color.GREEN)) ) ) ); @@ -460,7 +462,7 @@ public class DataDrivenStyleActivity extends AppCompatActivity { // Add a fill layer mapboxMap.addLayer(new FillLayer(AMSTERDAM_PARKS_LAYER, source.getId()) .withProperties( - fillColor(rgba(0.0f, 0.0f, 0.0f, 0.5f)), + fillColor(color(Color.GREEN)), fillOutlineColor(rgb(0, 0, 255)), fillAntialias(true) ) diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/GeoJsonClusteringActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/GeoJsonClusteringActivity.java index 8664979292..655d4a8936 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/GeoJsonClusteringActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/GeoJsonClusteringActivity.java @@ -10,6 +10,7 @@ import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.maps.MapView; import com.mapbox.mapboxsdk.maps.MapboxMap; +import com.mapbox.mapboxsdk.style.expressions.Expression; import com.mapbox.mapboxsdk.style.layers.CircleLayer; import com.mapbox.mapboxsdk.style.layers.SymbolLayer; import com.mapbox.mapboxsdk.style.sources.GeoJsonOptions; @@ -21,9 +22,12 @@ import java.net.URL; import timber.log.Timber; -import static com.mapbox.mapboxsdk.style.layers.Filter.all; -import static com.mapbox.mapboxsdk.style.layers.Filter.gte; -import static com.mapbox.mapboxsdk.style.layers.Filter.lt; +import static com.mapbox.mapboxsdk.style.expressions.Expression.all; +import static com.mapbox.mapboxsdk.style.expressions.Expression.get; +import static com.mapbox.mapboxsdk.style.expressions.Expression.gte; +import static com.mapbox.mapboxsdk.style.expressions.Expression.literal; +import static com.mapbox.mapboxsdk.style.expressions.Expression.lt; +import static com.mapbox.mapboxsdk.style.expressions.Expression.toNumber; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleColor; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleRadius; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconImage; @@ -124,7 +128,7 @@ public class GeoJsonClusteringActivity extends AppCompatActivity { ) ); } catch (MalformedURLException malformedUrlException) { - Timber.e(malformedUrlException,"That's not an url... "); + Timber.e(malformedUrlException, "That's not an url... "); } // Add unclustered layer @@ -145,10 +149,15 @@ public class GeoJsonClusteringActivity extends AppCompatActivity { circleColor(layers[i][1]), circleRadius(18f) ); + + Expression pointCount = toNumber(get("point_count")); circles.setFilter( i == 0 - ? gte("point_count", layers[i][0]) : - all(gte("point_count", layers[i][0]), lt("point_count", layers[i - 1][0])) + ? gte(pointCount, literal(layers[i][0])) : + all( + gte(pointCount, literal(layers[i][0])), + lt(pointCount, literal(layers[i - 1][0])) + ) ); mapboxMap.addLayer(circles); } @@ -156,7 +165,7 @@ public class GeoJsonClusteringActivity extends AppCompatActivity { // Add the count labels SymbolLayer count = new SymbolLayer("count", "earthquakes"); count.setProperties( - textField("{point_count}"), + textField(get("point_count")), textSize(12f), textColor(Color.WHITE) ); diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/RuntimeStyleActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/RuntimeStyleActivity.java index 6eb4772b15..f686308a63 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/RuntimeStyleActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/RuntimeStyleActivity.java @@ -31,7 +31,6 @@ import com.mapbox.mapboxsdk.style.sources.VectorSource; import com.mapbox.mapboxsdk.testapp.R; import com.mapbox.mapboxsdk.testapp.utils.ResourceUtils; - import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -39,15 +38,18 @@ import java.util.List; import timber.log.Timber; +import static com.mapbox.mapboxsdk.style.expressions.Expression.all; import static com.mapbox.mapboxsdk.style.expressions.Expression.color; +import static com.mapbox.mapboxsdk.style.expressions.Expression.eq; import static com.mapbox.mapboxsdk.style.expressions.Expression.exponential; +import static com.mapbox.mapboxsdk.style.expressions.Expression.get; +import static com.mapbox.mapboxsdk.style.expressions.Expression.gte; import static com.mapbox.mapboxsdk.style.expressions.Expression.interpolate; +import static com.mapbox.mapboxsdk.style.expressions.Expression.literal; +import static com.mapbox.mapboxsdk.style.expressions.Expression.lt; import static com.mapbox.mapboxsdk.style.expressions.Expression.stop; +import static com.mapbox.mapboxsdk.style.expressions.Expression.toNumber; import static com.mapbox.mapboxsdk.style.expressions.Expression.zoom; -import static com.mapbox.mapboxsdk.style.layers.Filter.all; -import static com.mapbox.mapboxsdk.style.layers.Filter.eq; -import static com.mapbox.mapboxsdk.style.layers.Filter.gte; -import static com.mapbox.mapboxsdk.style.layers.Filter.lt; import static com.mapbox.mapboxsdk.style.layers.Property.FILL_TRANSLATE_ANCHOR_MAP; import static com.mapbox.mapboxsdk.style.layers.Property.NONE; import static com.mapbox.mapboxsdk.style.layers.Property.SYMBOL_PLACEMENT_POINT; @@ -295,7 +297,7 @@ public class RuntimeStyleActivity extends AppCompatActivity { ); // Only show me parks (except westerpark with stroke-width == 3) - layer.setFilter(all(eq("type", "park"), eq("stroke-width", 2))); + layer.setFilter(all(eq(get("type"), literal("park")), eq(get("stroke-width"), literal(3)))); mapboxMap.addLayerBelow(layer, "building"); // layer.setPaintProperty(fillColor(Color.RED)); // XXX But not after the object is attached @@ -345,7 +347,7 @@ public class RuntimeStyleActivity extends AppCompatActivity { ); // Only show me parks - layer.setFilter(all(eq("type", "park"))); + layer.setFilter(all(eq(get("type"), literal("park")))); mapboxMap.addLayer(layer); @@ -499,7 +501,7 @@ public class RuntimeStyleActivity extends AppCompatActivity { FillLayer states = (FillLayer) mapboxMap.getLayer("states"); if (states != null) { - states.setFilter(eq("name", "Texas")); + states.setFilter(eq(get("name"), literal("Texas"))); states.setFillOpacityTransition(new TransitionOptions(2500, 0)); states.setFillColorTransition(new TransitionOptions(2500, 0)); states.setProperties( @@ -555,7 +557,10 @@ public class RuntimeStyleActivity extends AppCompatActivity { FillLayer regions = (FillLayer) mapboxMap.getLayer("regions"); if (regions != null) { - regions.setFilter(all(gte("HRRNUM", 200), lt("HRRNUM", 300))); + regions.setFilter(all( + gte(toNumber(get("HRRNUM")), literal(200)), + lt(toNumber(get("HRRNUM")), literal(300))) + ); regions.setProperties( fillColor(Color.BLUE), diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java index 5a30d9ce30..c42f264478 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java @@ -21,7 +21,6 @@ import com.mapbox.mapboxsdk.maps.MapView; import com.mapbox.mapboxsdk.maps.MapboxMap; import com.mapbox.mapboxsdk.maps.OnMapReadyCallback; import com.mapbox.mapboxsdk.style.expressions.Expression; -import com.mapbox.mapboxsdk.style.layers.Filter; import com.mapbox.mapboxsdk.style.layers.SymbolLayer; import com.mapbox.mapboxsdk.style.sources.GeoJsonSource; import com.mapbox.mapboxsdk.style.sources.Source; @@ -38,6 +37,7 @@ import timber.log.Timber; import static com.mapbox.mapboxsdk.style.expressions.Expression.concat; import static com.mapbox.mapboxsdk.style.expressions.Expression.division; import static com.mapbox.mapboxsdk.style.expressions.Expression.downcase; +import static com.mapbox.mapboxsdk.style.expressions.Expression.eq; import static com.mapbox.mapboxsdk.style.expressions.Expression.get; import static com.mapbox.mapboxsdk.style.expressions.Expression.literal; import static com.mapbox.mapboxsdk.style.expressions.Expression.match; @@ -45,8 +45,11 @@ import static com.mapbox.mapboxsdk.style.expressions.Expression.number; import static com.mapbox.mapboxsdk.style.expressions.Expression.pi; import static com.mapbox.mapboxsdk.style.expressions.Expression.product; import static com.mapbox.mapboxsdk.style.expressions.Expression.rgba; +import static com.mapbox.mapboxsdk.style.expressions.Expression.step; +import static com.mapbox.mapboxsdk.style.expressions.Expression.stop; import static com.mapbox.mapboxsdk.style.expressions.Expression.string; import static com.mapbox.mapboxsdk.style.expressions.Expression.upcase; +import static com.mapbox.mapboxsdk.style.expressions.Expression.zoom; import static com.mapbox.mapboxsdk.style.layers.Property.ICON_ANCHOR_BOTTOM; import static com.mapbox.mapboxsdk.style.layers.Property.TEXT_ANCHOR_TOP; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconAllowOverlap; @@ -121,8 +124,8 @@ public class SymbolGeneratorActivity extends AppCompatActivity implements OnMapR return true; } else if (item.getItemId() == R.id.menu_action_filter) { SymbolLayer layer = mapboxMap.getLayerAs(LAYER_ID); - layer.setFilter(Filter.eq(FEATURE_RANK, 1)); - //layer.setFilter(eq(get(FEATURE_RANK), 1)); + layer.setFilter(eq(get(FEATURE_RANK), literal(1))); + Timber.e("Filter that was set: %s", layer.getFilter()); return true; } return super.onOptionsItemSelected(item); @@ -265,7 +268,10 @@ public class SymbolGeneratorActivity extends AppCompatActivity implements OnMapR iconAllowOverlap(false), iconSize(iconSizeExpression), iconAnchor(ICON_ANCHOR_BOTTOM), - iconOffset(new Float[] {0.0f, -5.0f}), + iconOffset(step(zoom(), literal(new float[] {0f, 0f}), + stop(1, new Float[] {0f, 0f}), + stop(10, new Float[] {0f, -35f}) + )), // text field configuration textField(textFieldExpression), diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_gestures.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_gestures.xml index 3f96188da4..8b3a540ffa 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_gestures.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_gestures.xml @@ -8,5 +8,5 @@ android:title="Focus on a point" /> <item android:id="@+id/menu_gesture_animation" - android:title="Toggle velocity animations" /> + android:title="Turn off velocity animations" /> </menu>
\ No newline at end of file diff --git a/platform/android/config.cmake b/platform/android/config.cmake index 2694068dac..80d0810564 100644 --- a/platform/android/config.cmake +++ b/platform/android/config.cmake @@ -229,6 +229,8 @@ add_library(mbgl-android STATIC platform/android/src/geojson/feature_collection.hpp platform/android/src/geojson/geometry.cpp platform/android/src/geojson/geometry.hpp + platform/android/src/geojson/geometry_collection.cpp + platform/android/src/geojson/geometry_collection.hpp platform/android/src/geojson/line_string.cpp platform/android/src/geojson/line_string.hpp platform/android/src/geojson/multi_line_string.cpp diff --git a/platform/android/gradle/dependencies.gradle b/platform/android/gradle/dependencies.gradle index bf0c919986..c875569830 100644 --- a/platform/android/gradle/dependencies.gradle +++ b/platform/android/gradle/dependencies.gradle @@ -9,8 +9,8 @@ ext { versions = [ mapboxServices : '3.0.0-beta.4', - mapboxTelemetry: '3.0.0-beta.1', - mapboxGestures : '0.1.0-20180228.152340-13', + mapboxTelemetry: '3.0.0-beta.2', + mapboxGestures : '0.1.0', supportLib : '25.4.0', espresso : '3.0.1', testRunner : '1.0.1', @@ -20,14 +20,14 @@ ext { mockito : '2.10.0', robolectric : '3.5.1', timber : '4.5.1', - okhttp : '3.9.1' + okhttp : '3.10.0' ] dependenciesList = [ mapboxJavaServices : "com.mapbox.mapboxsdk:mapbox-sdk-services:${versions.mapboxServices}", mapboxJavaGeoJSON : "com.mapbox.mapboxsdk:mapbox-sdk-geojson:${versions.mapboxServices}", mapboxAndroidTelemetry: "com.mapbox.mapboxsdk:mapbox-android-telemetry:${versions.mapboxTelemetry}", - mapboxAndroidGestures : "com.mapbox.mapboxsdk:mapbox-android-gestures:${versions.mapboxGestures}@aar", + mapboxAndroidGestures : "com.mapbox.mapboxsdk:mapbox-android-gestures:${versions.mapboxGestures}", // for testApp mapboxJavaTurf : "com.mapbox.mapboxsdk:mapbox-sdk-turf:${versions.mapboxServices}", diff --git a/platform/android/src/file_source.cpp b/platform/android/src/file_source.cpp index 612619a30b..d8d715dbd3 100644 --- a/platform/android/src/file_source.cpp +++ b/platform/android/src/file_source.cpp @@ -70,14 +70,16 @@ void FileSource::resume(jni::JNIEnv&) { activationCounter.value()++; if (activationCounter == 1) { - fileSource->resume(); + fileSource->resume(); } } void FileSource::pause(jni::JNIEnv&) { - activationCounter.value()--; - if (activationCounter == 0) { - fileSource->pause(); + if (activationCounter) { + activationCounter.value()--; + if (activationCounter == 0) { + fileSource->pause(); + } } } diff --git a/platform/android/src/geojson/conversion/feature.hpp b/platform/android/src/geojson/conversion/feature.hpp index 86aa5fc03c..8fc62a2789 100644 --- a/platform/android/src/geojson/conversion/feature.hpp +++ b/platform/android/src/geojson/conversion/feature.hpp @@ -182,7 +182,7 @@ struct Converter<jni::Object<android::geojson::Feature>, mbgl::Feature> { auto properties = jni::Object<gson::JsonObject>(*convert<jni::jobject*>(env, value.properties)); // Convert geometry - auto geometry = jni::Object<android::geojson::Geometry>(*convert<jni::jobject*>(env, value.geometry)); + auto geometry = *convert<jni::Object<android::geojson::Geometry>>(env, value.geometry); // Create feature auto feature = android::geojson::Feature::fromGeometry(env, geometry, properties, jid); diff --git a/platform/android/src/geojson/conversion/geometry.hpp b/platform/android/src/geojson/conversion/geometry.hpp index 48b9089692..242a68df02 100644 --- a/platform/android/src/geojson/conversion/geometry.hpp +++ b/platform/android/src/geojson/conversion/geometry.hpp @@ -1,192 +1,24 @@ #pragma once -#include "../../conversion/constant.hpp" -#include "../../conversion/collection.hpp" - #include <mapbox/geometry.hpp> +#include "../geometry.hpp" #include <jni/jni.hpp> -#include "../../jni/local_object.hpp" namespace mbgl { namespace android { namespace conversion { /** - * Turn mapbox::geometry type into Java GeoJson Geometries - */ -template <typename T> -class GeometryEvaluator { -public: - - jni::JNIEnv& env; - - /** - * static Point fromLngLat(double longitude,double latitude) - */ - jni::jobject* operator()(const mapbox::geometry::point<T> &geometry) const { - static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/geojson/Point")).release(); - static jni::jmethodID* fromLngLat = &jni::GetStaticMethodID(env, *javaClass, "fromLngLat", "(DD)Lcom/mapbox/geojson/Point;"); - - return reinterpret_cast<jni::jobject*>(jni::CallStaticMethod<jni::jobject*>(env, *javaClass, *fromLngLat, geometry.x, geometry.y)); - } - - /** - * static LineString fromLngLats(double [][]) - */ - jni::jobject* operator()(const mapbox::geometry::line_string<T> &geometry) const { - static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/geojson/LineString")).release(); - static jni::jmethodID* fromLngLats = &jni::GetStaticMethodID(env, *javaClass, "fromLngLats", "([[D)Lcom/mapbox/geojson/LineString;"); - - // Create - jni::LocalObject<jni::jarray<jni::jobject>> lngLatsArray = - jni::NewLocalObject(env, toLngLatArray(env, geometry)); - return reinterpret_cast<jni::jobject*>(jni::CallStaticMethod<jni::jobject*>(env, *javaClass, *fromLngLats, lngLatsArray.get())); - } - - /** - * static MultiPoint fromLngLats(double [][]) - */ - jni::jobject* operator()(const mapbox::geometry::multi_point<T> &geometry) const { - static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/geojson/MultiPoint")).release(); - static jni::jmethodID* fromLngLats = &jni::GetStaticMethodID(env, *javaClass, "fromLngLats", "([[D)Lcom/mapbox/geojson/MultiPoint;"); - - // Create - jni::LocalObject<jni::jarray<jni::jobject>> lngLatsArray = - jni::NewLocalObject(env, toLngLatArray(env, geometry)); - return reinterpret_cast<jni::jobject*>(jni::CallStaticMethod<jni::jobject*>(env, *javaClass, *fromLngLats, lngLatsArray.get())); - } - - /** - * static Polygon fromLngLats(double [][][]) - */ - jni::jobject* operator()(const mapbox::geometry::polygon<T> &geometry) const { - static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/geojson/Polygon")).release(); - static jni::jmethodID* fromLngLats = &jni::GetStaticMethodID(env, *javaClass, "fromLngLats", "([[[D)Lcom/mapbox/geojson/Polygon;"); - - // Create - jni::LocalObject<jni::jarray<jni::jobject>> shape = - jni::NewLocalObject(env, shapeToLngLatArray<>(env, geometry)); - return reinterpret_cast<jni::jobject*>(jni::CallStaticMethod<jni::jobject*>(env, *javaClass, *fromLngLats, shape.get())); - } - - /** - * static MultiLineString fromLngLats(double [][][]) - */ - jni::jobject* operator()(const mapbox::geometry::multi_line_string<T> &geometry) const { - static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/geojson/MultiLineString")).release(); - static jni::jmethodID* fromLngLats = &jni::GetStaticMethodID(env, *javaClass, "fromLngLats", "([[[D)Lcom/mapbox/geojson/MultiLineString;"); - - // Create - jni::LocalObject<jni::jarray<jni::jobject>> shape = - jni::NewLocalObject(env,shapeToLngLatArray<>(env, geometry)); - return reinterpret_cast<jni::jobject*>(jni::CallStaticMethod<jni::jobject*>(env, *javaClass, *fromLngLats, shape.get())); - } - - /** - * MultiPolygon (double[][][][]) -> [[[D + Object array == [[[[D - */ - jni::jobject* operator()(const mapbox::geometry::multi_polygon<T> &geometry) const { - - static jni::jclass* arrayClass = jni::NewGlobalRef(env, &jni::FindClass(env, "[[[D")).release(); - jni::LocalObject<jni::jarray<jni::jobject>> jarray = - jni::NewLocalObject(env, &jni::NewObjectArray(env, geometry.size(), *arrayClass)); - - for(size_t i = 0; i < geometry.size(); i = i + 1) { - jni::LocalObject<jni::jarray<jni::jobject>> shape = - jni::NewLocalObject(env, shapeToLngLatArray<>(env, geometry.at(i))); - jni::SetObjectArrayElement(env, *jarray, i, shape.get()); - } - - // Create the MultiPolygon - static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/geojson/MultiPolygon")).release(); - static jni::jmethodID* fromGeometries = &jni::GetStaticMethodID(env, *javaClass, "fromLngLats", "([[[[D)Lcom/mapbox/geojson/MultiPolygon;"); - return reinterpret_cast<jni::jobject*>(jni::CallStaticMethod<jni::jobject*>(env, *javaClass, *fromGeometries, jarray.get())); - } - - /** - * GeometryCollection - */ - jni::jobject* operator()(const mapbox::geometry::geometry_collection<T> &collection) const { - static jni::jclass* geometryClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/geojson/Geometry")).release(); - jni::LocalObject<jni::jarray<jni::jobject>> jarray = jni::NewLocalObject(env, &jni::NewObjectArray(env, collection.size(), *geometryClass)); - - for(size_t i = 0; i < collection.size(); i = i + 1) { - auto& geometry = collection.at(i); - jni::LocalObject<jni::jobject> converted = jni::NewLocalObject(env, mapbox::geometry::geometry<T>::visit(geometry, *this)); - jni::SetObjectArrayElement(env, *jarray, i, converted.get()); - } - - // Turn into array list and create the GeometryCollection - static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/geojson/GeometryCollection")).release(); - static jni::jmethodID* fromGeometries = &jni::GetStaticMethodID(env, *javaClass, "fromGeometries", "(Ljava/util/List;)Lcom/mapbox/geojson/GeometryCollection;"); - - jni::LocalObject<jni::jobject> list = jni::NewLocalObject(env, toArrayList<>(env, *jarray)); - return reinterpret_cast<jni::jobject*>(jni::CallStaticMethod<jni::jobject*>(env, *javaClass, *fromGeometries, list.get())); - } - -private: - - /** - * x, y -> jarray<jdouble> ([x,y]) - * - */ - static jni::jarray<jni::jdouble> *toLngLatArray(JNIEnv &env, double x, double y) { - jni::jarray<jni::jdouble> &jarray = jni::NewArray<jni::jdouble>(env, 2); - jni::jdouble array[] = {x, y}; - jni::SetArrayRegion(env, jarray, 0, 2, array); - return &jarray; - } - - /** - * vector<point<T>> -> jarray<jobject> -> [D + Object array == [[D (double[][]) - */ - static jni::jarray<jni::jobject>* toLngLatArray(JNIEnv &env, - std::vector<mapbox::geometry::point<T>> points) { - - static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "[D")).release(); - jni::jarray<jni::jobject >& jarray = jni::NewObjectArray(env, points.size(), *javaClass); - - for(size_t i = 0; i < points.size(); i = i + 1) { - mapbox::geometry::point<T> point = points.at(i); - jni::jarray<jni::jdouble> *lngLatArray = toLngLatArray(env, point.x, point.y); - jni::SetObjectArrayElement(env, jarray, i, lngLatArray); - } - - return &jarray; - } - - /** - * polygon<T> - * multi_line_string<T> -> jarray<jobject> -> [[D + Object array == [[[D (double[][][]) - */ - template <class SHAPE> - static jni::jarray<jni::jobject>* shapeToLngLatArray(JNIEnv &env, SHAPE value) { - static jni::jclass *javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "[[D")).release(); - jni::jarray<jni::jobject> &jarray = jni::NewObjectArray(env, value.size(), *javaClass); - - for (size_t i = 0; i < value.size(); i = i + 1) { - jni::LocalObject<jni::jarray<jni::jobject>> lngLatsArray = - jni::NewLocalObject(env, toLngLatArray(env, value.at(i))); - jni::SetObjectArrayElement(env, jarray, i, lngLatsArray.get()); - } - - return &jarray; - } -}; - -/** - * mapbox::geometry::geometry<T> -> Java GeoJson Geometry<> + * mapbox::geometry::geometry<T> -> Java GeoJson Geometry */ template <class T> -struct Converter<jni::jobject*, mapbox::geometry::geometry<T>> { - Result<jni::jobject*> operator()(jni::JNIEnv& env, const mapbox::geometry::geometry<T>& value) const { - GeometryEvaluator<double> evaluator { env } ; - jni::jobject* converted = mapbox::geometry::geometry<double>::visit(value, evaluator); - return {converted}; +struct Converter<jni::Object<android::geojson::Geometry>, mapbox::geometry::geometry<T>> { + Result<jni::Object<android::geojson::Geometry>> operator()(jni::JNIEnv& env, const mapbox::geometry::geometry<T>& value) const { + return { android::geojson::Geometry::New(env, value) }; } }; -} -} -} +} // conversion +} // android +} // mbgl diff --git a/platform/android/src/geojson/geometry.cpp b/platform/android/src/geojson/geometry.cpp index ca19d8fb03..5635b5a0f5 100644 --- a/platform/android/src/geojson/geometry.cpp +++ b/platform/android/src/geojson/geometry.cpp @@ -6,6 +6,7 @@ #include "multi_line_string.hpp" #include "polygon.hpp" #include "multi_polygon.hpp" +#include "geometry_collection.hpp" #include <string> @@ -13,7 +14,49 @@ namespace mbgl { namespace android { namespace geojson { -mapbox::geojson::geometry Geometry::convert(jni::JNIEnv &env, jni::Object<Geometry> jGeometry) { +/** + * Turn mapbox::geometry type into Java GeoJson Geometries + */ +class GeometryEvaluator { +public: + + jni::JNIEnv& env; + + jni::Object<Geometry> operator()(const mbgl::Point<double> &geometry) const { + return jni::Cast(env, Point::New(env, geometry), Geometry::javaClass); + } + + jni::Object<Geometry> operator()(const mbgl::LineString<double> &geometry) const { + return jni::Cast(env, LineString::New(env, geometry), Geometry::javaClass); + } + + jni::Object<Geometry> operator()(const mbgl::MultiLineString<double> &geometry) const { + return jni::Cast(env, MultiLineString::New(env, geometry), Geometry::javaClass); + } + + jni::Object<Geometry> operator()(const mbgl::MultiPoint<double> &geometry) const { + return jni::Cast(env, MultiPoint::New(env, geometry), Geometry::javaClass); + } + + jni::Object<Geometry> operator()(const mbgl::Polygon<double> &geometry) const { + return jni::Cast(env, Polygon::New(env, geometry), Geometry::javaClass); + } + + jni::Object<Geometry> operator()(const mbgl::MultiPolygon<double> &geometry) const { + return jni::Cast(env, MultiPolygon::New(env, geometry), Geometry::javaClass); + } + + jni::Object<Geometry> operator()(const mapbox::geometry::geometry_collection<double> &geometry) const { + return jni::Cast(env, GeometryCollection::New(env, geometry), Geometry::javaClass); + } +}; + +jni::Object<Geometry> Geometry::New(jni::JNIEnv& env, mbgl::Geometry<double> geometry) { + GeometryEvaluator evaluator { env } ; + return mbgl::Geometry<double>::visit(geometry, evaluator); +} + +mbgl::Geometry<double> Geometry::convert(jni::JNIEnv &env, jni::Object<Geometry> jGeometry) { auto type = Geometry::getType(env, jGeometry); if (type == Point::Type()) { return { Point::convert(env, jni::Object<Point>(jGeometry.Get())) }; @@ -27,6 +70,8 @@ mapbox::geojson::geometry Geometry::convert(jni::JNIEnv &env, jni::Object<Geomet return { Polygon::convert(env, jni::Object<Polygon>(jGeometry.Get())) }; } else if (type == MultiPolygon::Type()) { return { MultiPolygon::convert(env, jni::Object<MultiPolygon>(jGeometry.Get())) }; + } else if (type == GeometryCollection::Type()) { + return { GeometryCollection::convert(env, jni::Object<GeometryCollection>(jGeometry.Get())) }; } throw std::runtime_error(std::string {"Unsupported GeoJSON type: " } + type); diff --git a/platform/android/src/geojson/geometry.hpp b/platform/android/src/geojson/geometry.hpp index b7f8909f6f..a1bb886683 100644 --- a/platform/android/src/geojson/geometry.hpp +++ b/platform/android/src/geojson/geometry.hpp @@ -1,8 +1,10 @@ #pragma once -#include <mbgl/util/geojson.hpp> +#include <mbgl/util/geometry.hpp> #include <mbgl/util/noncopyable.hpp> +#include "../java/util.hpp" + #include <jni/jni.hpp> namespace mbgl { @@ -13,7 +15,9 @@ class Geometry : private mbgl::util::noncopyable { public: static constexpr auto Name() { return "com/mapbox/geojson/Geometry"; }; - static mapbox::geojson::geometry convert(jni::JNIEnv&, jni::Object<Geometry>); + static jni::Object<Geometry> New(jni::JNIEnv&, mbgl::Geometry<double>); + + static mbgl::Geometry<double> convert(jni::JNIEnv&, jni::Object<Geometry>); static std::string getType(jni::JNIEnv&, jni::Object<Geometry>); diff --git a/platform/android/src/geojson/geometry_collection.cpp b/platform/android/src/geojson/geometry_collection.cpp new file mode 100644 index 0000000000..eb3a790404 --- /dev/null +++ b/platform/android/src/geojson/geometry_collection.cpp @@ -0,0 +1,63 @@ +#include "geometry_collection.hpp" +#include "../java/util.hpp" + +namespace mbgl { +namespace android { +namespace geojson { + +jni::Object<GeometryCollection> GeometryCollection::New(jni::JNIEnv& env, const mapbox::geometry::geometry_collection<double>& collection) { + // Create an array of geometries + auto jarray = jni::Array<jni::Object<Geometry>>::New(env, collection.size(), Geometry::javaClass); + + for (size_t i = 0; i < collection.size(); i++) { + auto& geometry = collection.at(i); + auto jGeometry = Geometry::New(env, geometry); + jarray.Set(env, i, jGeometry); + jni::DeleteLocalRef(env, jGeometry); + } + + // Turn into array list + auto jList = java::util::Arrays::asList(env, jarray); + jni::DeleteLocalRef(env, jarray); + + // create the GeometryCollection + static auto method = javaClass.GetStaticMethod<jni::Object<GeometryCollection> (jni::Object<java::util::List>)>(env, "fromGeometries"); + auto jCollection = javaClass.Call(env, method, jList); + + jni::DeleteLocalRef(env, jList); + return jCollection; +} + +mapbox::geometry::geometry_collection<double> GeometryCollection::convert(jni::JNIEnv &env, jni::Object<GeometryCollection> jCollection) { + // Get geometries + static auto getGeometries = javaClass.GetMethod<jni::Object<java::util::List> ()>(env, "getGeometries"); + auto jList = jCollection.Call(env, getGeometries); + + // Turn into array + auto jarray = java::util::List::toArray<Geometry>(env, jList); + jni::DeleteLocalRef(env, jList); + + // Convert each geometry + mapbox::geometry::geometry_collection<double> collection{}; + + auto size = jarray.Length(env); + for (jni::jsize i = 0; i < size; i++) { + auto element = jarray.Get(env, i); + collection.push_back(Geometry::convert(env, element)); + jni::DeleteLocalRef(env, element); + } + + jni::DeleteLocalRef(env, jarray); + return collection; +} + +void GeometryCollection::registerNative(jni::JNIEnv &env) { + // Lookup the class + javaClass = *jni::Class<GeometryCollection>::Find(env).NewGlobalRef(env).release(); +} + +jni::Class<GeometryCollection> GeometryCollection::javaClass; + +} // namespace geojson +} // namespace android +} // namespace mbgl
\ No newline at end of file diff --git a/platform/android/src/geojson/geometry_collection.hpp b/platform/android/src/geojson/geometry_collection.hpp new file mode 100644 index 0000000000..9ed9953b0d --- /dev/null +++ b/platform/android/src/geojson/geometry_collection.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "geometry.hpp" + +#include <jni/jni.hpp> + +namespace mbgl { +namespace android { +namespace geojson { + +class GeometryCollection : public Geometry { +public: + static constexpr auto Name() { return "com/mapbox/geojson/GeometryCollection"; }; + + static constexpr auto Type() { return "GeometryCollection"; }; + + static jni::Object<GeometryCollection> New(jni::JNIEnv&, const mapbox::geometry::geometry_collection<double>&); + + static mapbox::geometry::geometry_collection<double> convert(jni::JNIEnv&, jni::Object<GeometryCollection>); + + static jni::Class<GeometryCollection> javaClass; + + static void registerNative(jni::JNIEnv&); +}; + +} // namespace geojson +} // namespace android +} // namespace mbgl
\ No newline at end of file diff --git a/platform/android/src/geojson/line_string.cpp b/platform/android/src/geojson/line_string.cpp index 8eebd53550..a5f1a468ce 100644 --- a/platform/android/src/geojson/line_string.cpp +++ b/platform/android/src/geojson/line_string.cpp @@ -1,11 +1,22 @@ #include "line_string.hpp" - #include "point.hpp" +#include "util.hpp" +#include "../java/util.hpp" namespace mbgl { namespace android { namespace geojson { +jni::Object<LineString> LineString::New(jni::JNIEnv& env, const mbgl::LineString<double>& lineString) { + auto jList = asPointsList(env, lineString); + + static auto method = javaClass.GetStaticMethod<jni::Object<LineString>(jni::Object<java::util::List>)>(env, "fromLngLats"); + auto jLineString = javaClass.Call(env, method, jList); + + jni::DeleteLocalRef(env, jList); + return jLineString; +} + mapbox::geojson::line_string LineString::convert(jni::JNIEnv &env, jni::Object<LineString> jLineString) { mapbox::geojson::line_string lineString; diff --git a/platform/android/src/geojson/line_string.hpp b/platform/android/src/geojson/line_string.hpp index 86033c2e6a..98dc414642 100644 --- a/platform/android/src/geojson/line_string.hpp +++ b/platform/android/src/geojson/line_string.hpp @@ -1,23 +1,27 @@ #pragma once #include <mbgl/util/geojson.hpp> +#include <mbgl/util/geometry.hpp> #include <mbgl/util/noncopyable.hpp> -#include <jni/jni.hpp> - +#include "geometry.hpp" #include "../java/util.hpp" +#include <jni/jni.hpp> + namespace mbgl { namespace android { namespace geojson { -class LineString : private mbgl::util::noncopyable { +class LineString : public Geometry { public: static constexpr auto Name() { return "com/mapbox/geojson/LineString"; }; static constexpr auto Type() { return "LineString"; }; + static jni::Object<LineString> New(jni::JNIEnv&, const mbgl::LineString<double>&); + static mapbox::geojson::line_string convert(jni::JNIEnv&, jni::Object<LineString>); static mapbox::geojson::line_string convert(jni::JNIEnv&, jni::Object<java::util::List/*<Point>*/>); diff --git a/platform/android/src/geojson/multi_line_string.cpp b/platform/android/src/geojson/multi_line_string.cpp index c748d4786f..4a6ea37dd1 100644 --- a/platform/android/src/geojson/multi_line_string.cpp +++ b/platform/android/src/geojson/multi_line_string.cpp @@ -1,11 +1,22 @@ #include "multi_line_string.hpp" #include "line_string.hpp" +#include "util.hpp" namespace mbgl { namespace android { namespace geojson { +jni::Object<MultiLineString> MultiLineString::New(jni::JNIEnv& env, const mbgl::MultiLineString<double>& multiLineString) { + auto jList = asPointsListsList(env, multiLineString); + + static auto method = javaClass.GetStaticMethod<jni::Object<MultiLineString> (jni::Object<java::util::List>)>(env, "fromLngLats"); + auto jMultiLineString = javaClass.Call(env, method, jList); + + jni::DeleteLocalRef(env, jList); + return jMultiLineString; +} + mapbox::geojson::multi_line_string MultiLineString::convert(jni::JNIEnv &env, jni::Object<MultiLineString> jMultiLineString) { mapbox::geojson::multi_line_string multiLineString; diff --git a/platform/android/src/geojson/multi_line_string.hpp b/platform/android/src/geojson/multi_line_string.hpp index 358a6b5dda..934a0cb6b5 100644 --- a/platform/android/src/geojson/multi_line_string.hpp +++ b/platform/android/src/geojson/multi_line_string.hpp @@ -3,20 +3,23 @@ #include <mbgl/util/geojson.hpp> #include <mbgl/util/noncopyable.hpp> -#include <jni/jni.hpp> - #include "../java/util.hpp" +#include "geometry.hpp" + +#include <jni/jni.hpp> namespace mbgl { namespace android { namespace geojson { -class MultiLineString : private mbgl::util::noncopyable { +class MultiLineString : public Geometry { public: static constexpr auto Name() { return "com/mapbox/geojson/MultiLineString"; }; static constexpr auto Type() { return "MultiLineString"; }; + static jni::Object<MultiLineString> New(jni::JNIEnv&, const mbgl::MultiLineString<double>&); + static mapbox::geojson::multi_line_string convert(jni::JNIEnv&, jni::Object<MultiLineString>); static mapbox::geojson::multi_line_string convert(jni::JNIEnv&, jni::Object<java::util::List/*<java::util::List<Point>>*/>); diff --git a/platform/android/src/geojson/multi_point.cpp b/platform/android/src/geojson/multi_point.cpp index 4f9ff596b2..6f62541209 100644 --- a/platform/android/src/geojson/multi_point.cpp +++ b/platform/android/src/geojson/multi_point.cpp @@ -8,6 +8,16 @@ namespace mbgl { namespace android { namespace geojson { +jni::Object<MultiPoint> MultiPoint::New(JNIEnv& env, const mbgl::MultiPoint<double>& multiPoint) { + auto jList = asPointsList(env, multiPoint); + + static auto method = javaClass.GetStaticMethod<jni::Object<MultiPoint>(jni::Object<java::util::List>)>(env, "fromLngLats"); + auto jMultiPoint = javaClass.Call(env, method, jList); + + jni::DeleteLocalRef(env, jList); + return jMultiPoint; +} + mapbox::geojson::multi_point MultiPoint::convert(jni::JNIEnv &env, jni::Object<MultiPoint> jMultiPoint) { mapbox::geojson::multi_point multiPoint; diff --git a/platform/android/src/geojson/multi_point.hpp b/platform/android/src/geojson/multi_point.hpp index e893e879af..cfe80cd34c 100644 --- a/platform/android/src/geojson/multi_point.hpp +++ b/platform/android/src/geojson/multi_point.hpp @@ -1,22 +1,26 @@ #pragma once #include <mbgl/util/geojson.hpp> +#include <mbgl/util/geometry.hpp> #include <mbgl/util/noncopyable.hpp> -#include <jni/jni.hpp> - +#include "geometry.hpp" #include "../java/util.hpp" +#include <jni/jni.hpp> + namespace mbgl { namespace android { namespace geojson { -class MultiPoint : private mbgl::util::noncopyable { +class MultiPoint : public Geometry { public: static constexpr auto Name() { return "com/mapbox/geojson/MultiPoint"; }; static constexpr auto Type() { return "MultiPoint"; }; + static jni::Object<MultiPoint> New(jni::JNIEnv&, const mbgl::MultiPoint<double>&); + static mapbox::geojson::multi_point convert(jni::JNIEnv&, jni::Object<MultiPoint>); static jni::Object<java::util::List> coordinates(jni::JNIEnv&, jni::Object<MultiPoint>); diff --git a/platform/android/src/geojson/multi_polygon.cpp b/platform/android/src/geojson/multi_polygon.cpp index aadba8c8a6..cc872d4955 100644 --- a/platform/android/src/geojson/multi_polygon.cpp +++ b/platform/android/src/geojson/multi_polygon.cpp @@ -1,11 +1,34 @@ #include "multi_polygon.hpp" #include "polygon.hpp" +#include "util.hpp" namespace mbgl { namespace android { namespace geojson { +jni::Object<MultiPolygon> MultiPolygon::New(JNIEnv& env, const mbgl::MultiPolygon<double>& multiPolygon) { + auto jarray = jni::Array<jni::Object<java::util::List>>::New(env, multiPolygon.size(), java::util::List::javaClass); + + for (size_t i = 0; i < multiPolygon.size(); i++) { + auto& geometry = multiPolygon.at(i); + auto jPolygon = asPointsListsList(env, geometry); + jarray.Set(env, i, jPolygon); + jni::DeleteLocalRef(env, jPolygon); + } + + // Turn into array list + auto jList = java::util::Arrays::asList(env, jarray); + jni::DeleteLocalRef(env, jarray); + + // create the MultiPolygon + static auto method = javaClass.GetStaticMethod<jni::Object<MultiPolygon> (jni::Object<java::util::List>)>(env, "fromLngLats"); + auto jMultiPolygon = javaClass.Call(env, method, jList); + + jni::DeleteLocalRef(env, jList); + return jMultiPolygon; +} + mapbox::geojson::multi_polygon MultiPolygon::convert(jni::JNIEnv &env, jni::Object<MultiPolygon> jMultiPolygon) { mapbox::geojson::multi_polygon multiPolygon; diff --git a/platform/android/src/geojson/multi_polygon.hpp b/platform/android/src/geojson/multi_polygon.hpp index 6e1dfacfc8..b4657af09d 100644 --- a/platform/android/src/geojson/multi_polygon.hpp +++ b/platform/android/src/geojson/multi_polygon.hpp @@ -3,20 +3,23 @@ #include <mbgl/util/geojson.hpp> #include <mbgl/util/noncopyable.hpp> -#include <jni/jni.hpp> - #include "../java/util.hpp" +#include "geometry.hpp" + +#include <jni/jni.hpp> namespace mbgl { namespace android { namespace geojson { -class MultiPolygon : private mbgl::util::noncopyable { +class MultiPolygon : public Geometry { public: static constexpr auto Name() { return "com/mapbox/geojson/MultiPolygon"; }; static constexpr auto Type() { return "MultiPolygon"; }; + static jni::Object<MultiPolygon> New(jni::JNIEnv&, const mbgl::MultiPolygon<double>&); + static mapbox::geojson::multi_polygon convert(jni::JNIEnv&, jni::Object<MultiPolygon>); static jni::Object<java::util::List> coordinates(jni::JNIEnv&, jni::Object<MultiPolygon>); diff --git a/platform/android/src/geojson/point.cpp b/platform/android/src/geojson/point.cpp index e95376cd2e..aa9dc1a7f6 100644 --- a/platform/android/src/geojson/point.cpp +++ b/platform/android/src/geojson/point.cpp @@ -7,6 +7,11 @@ namespace mbgl { namespace android { namespace geojson { +jni::Object<Point> Point::New(jni::JNIEnv& env, const mbgl::Point<double>& point) { + static auto method = javaClass.GetStaticMethod<jni::Object<Point> (jni::jdouble, jni::jdouble)>(env, "fromLngLat"); + return javaClass.Call(env, method, point.x, point.y); +} + mapbox::geojson::point Point::convert(jni::JNIEnv &env, jni::Object<Point> jPoint) { mapbox::geojson::point point; diff --git a/platform/android/src/geojson/point.hpp b/platform/android/src/geojson/point.hpp index c6412299bf..627bd1b649 100644 --- a/platform/android/src/geojson/point.hpp +++ b/platform/android/src/geojson/point.hpp @@ -3,20 +3,23 @@ #include <mbgl/util/geojson.hpp> #include <mbgl/util/noncopyable.hpp> -#include <jni/jni.hpp> - #include "../java/util.hpp" +#include "geometry.hpp" + +#include <jni/jni.hpp> namespace mbgl { namespace android { namespace geojson { -class Point : private mbgl::util::noncopyable { +class Point : public Geometry { public: static constexpr auto Name() { return "com/mapbox/geojson/Point"; }; static constexpr auto Type() { return "Point"; }; + static jni::Object<Point> New(jni::JNIEnv&, const mbgl::Point<double>&); + static mapbox::geojson::point convert(jni::JNIEnv&, jni::Object<Point>); static mapbox::geojson::point convert(jni::JNIEnv&, jni::Object<java::util::List/*<Double>*/>); diff --git a/platform/android/src/geojson/polygon.cpp b/platform/android/src/geojson/polygon.cpp index 30ba996640..96058b63b3 100644 --- a/platform/android/src/geojson/polygon.cpp +++ b/platform/android/src/geojson/polygon.cpp @@ -8,6 +8,16 @@ namespace mbgl { namespace android { namespace geojson { +jni::Object<Polygon> Polygon::New(jni::JNIEnv& env, const mbgl::Polygon<double>& polygon) { + auto jList = asPointsListsList(env, polygon); + + static auto method = javaClass.GetStaticMethod<jni::Object<Polygon> (jni::Object<java::util::List>)>(env, "fromLngLats"); + auto jPolygon = javaClass.Call(env, method, jList); + + jni::DeleteLocalRef(env, jList); + return jPolygon; +} + mapbox::geojson::polygon Polygon::convert(jni::JNIEnv &env, jni::Object<Polygon> jPolygon) { mapbox::geojson::polygon polygon; diff --git a/platform/android/src/geojson/polygon.hpp b/platform/android/src/geojson/polygon.hpp index a8b2b93827..f3c23b4d7b 100644 --- a/platform/android/src/geojson/polygon.hpp +++ b/platform/android/src/geojson/polygon.hpp @@ -3,20 +3,24 @@ #include <mbgl/util/geojson.hpp> #include <mbgl/util/noncopyable.hpp> +#include "geometry.hpp" +#include "../java/util.hpp" + #include <jni/jni.hpp> -#include "../java/util.hpp" namespace mbgl { namespace android { namespace geojson { -class Polygon : private mbgl::util::noncopyable { +class Polygon : public Geometry { public: static constexpr auto Name() { return "com/mapbox/geojson/Polygon"; }; static constexpr auto Type() { return "Polygon"; }; + static jni::Object<Polygon> New(jni::JNIEnv&, const mbgl::Polygon<double>&); + static mapbox::geojson::polygon convert(jni::JNIEnv &, jni::Object<Polygon>); static mapbox::geojson::polygon convert(jni::JNIEnv&, jni::Object<java::util::List/*<java::util::List<Point>>*/>); diff --git a/platform/android/src/geojson/util.hpp b/platform/android/src/geojson/util.hpp index ece8e52433..5e6d90a953 100644 --- a/platform/android/src/geojson/util.hpp +++ b/platform/android/src/geojson/util.hpp @@ -1,5 +1,7 @@ #pragma once +#include "point.hpp" + #include <type_traits> namespace mbgl { @@ -17,6 +19,42 @@ To convertExplicit(From&& src) { return *reinterpret_cast<std::add_pointer_t<To>>(&src); } +/** + * Geometry -> List<Point> + */ +template <class T> +static jni::Object<java::util::List> asPointsList(jni::JNIEnv& env, const T& pointsList) { + auto jarray = jni::Array<jni::Object<Point>>::New(env, pointsList.size(), Point::javaClass); + + for (jni::jsize i = 0; i < pointsList.size(); i++) { + auto jPoint = Point::New(env, pointsList.at(i)); + jarray.Set(env, i, jPoint); + jni::DeleteLocalRef(env, jPoint); + } + + auto jList = java::util::Arrays::asList(env, jarray); + jni::DeleteLocalRef(env, jarray); + return jList; +} + +/** + * Geometry -> List<List<Point>> + */ +template <class SHAPE> +static jni::Object<java::util::List> asPointsListsList(JNIEnv& env, SHAPE value) { + auto jarray = jni::Array<jni::Object<java::util::List>>::New(env, value.size(), java::util::List::javaClass); + + for (size_t i = 0; i < value.size(); i = i + 1) { + auto pointsList = asPointsList(env, value[i]); + jarray.Set(env, i, pointsList); + jni::DeleteLocalRef(env, pointsList); + } + + auto jList = java::util::Arrays::asList(env, jarray); + jni::DeleteLocalRef(env, jarray); + return jList; +} + } // namespace geojson } // namespace android } // namespace mbgl diff --git a/platform/android/src/java/util.cpp b/platform/android/src/java/util.cpp index effd2ae0d0..89c4c77794 100644 --- a/platform/android/src/java/util.cpp +++ b/platform/android/src/java/util.cpp @@ -5,12 +5,14 @@ namespace android { namespace java { namespace util { +jni::Class<Arrays> Arrays::javaClass; jni::Class<List> List::javaClass; jni::Class<Set> Set::javaClass; jni::Class<Map> Map::javaClass; jni::Class<Map::Entry> Map::Entry::javaClass; void registerNative(jni::JNIEnv& env) { + Arrays::javaClass = *jni::Class<Arrays>::Find(env).NewGlobalRef(env).release(); List::javaClass = *jni::Class<List>::Find(env).NewGlobalRef(env).release(); Set::javaClass = *jni::Class<Set>::Find(env).NewGlobalRef(env).release(); Map::javaClass = *jni::Class<Map>::Find(env).NewGlobalRef(env).release(); diff --git a/platform/android/src/java/util.hpp b/platform/android/src/java/util.hpp index dedb8ac348..c6b07acac5 100644 --- a/platform/android/src/java/util.hpp +++ b/platform/android/src/java/util.hpp @@ -24,6 +24,21 @@ public: }; +class Arrays : private mbgl::util::noncopyable { +public: + + static constexpr auto Name() { return "java/util/Arrays"; }; + + template <class T> + static jni::Object<List> asList(jni::JNIEnv& env, jni::Array<jni::Object<T>> array) { + static auto asList = Arrays::javaClass.GetStaticMethod<jni::Object<List>(jni::Array<jni::Object<>>)>(env, "asList"); + return javaClass.Call(env, asList, (jni::Array<jni::Object<>>) array); + } + + static jni::Class<Arrays> javaClass; + +}; + class Set : private mbgl::util::noncopyable { public: diff --git a/platform/android/src/jni.cpp b/platform/android/src/jni.cpp index 68db977407..c8c20e939a 100755 --- a/platform/android/src/jni.cpp +++ b/platform/android/src/jni.cpp @@ -14,6 +14,7 @@ #include "geojson/feature.hpp" #include "geojson/feature_collection.hpp" #include "geojson/geometry.hpp" +#include "geojson/geometry_collection.hpp" #include "geojson/line_string.hpp" #include "geojson/multi_line_string.hpp" #include "geojson/multi_point.hpp" @@ -116,6 +117,7 @@ void registerNatives(JavaVM *vm) { geojson::Feature::registerNative(env); geojson::FeatureCollection::registerNative(env); geojson::Geometry::registerNative(env); + geojson::GeometryCollection::registerNative(env); geojson::LineString::registerNative(env); geojson::MultiLineString::registerNative(env); geojson::MultiPoint::registerNative(env); diff --git a/platform/android/src/style/layers/layer.cpp b/platform/android/src/style/layers/layer.cpp index 29530879a5..a2f4087fce 100644 --- a/platform/android/src/style/layers/layer.cpp +++ b/platform/android/src/style/layers/layer.cpp @@ -25,6 +25,8 @@ // C++ -> Java conversion #include "../conversion/property_value.hpp" +#include <mbgl/style/filter.hpp> +#include "../conversion/gson.hpp" #include <string> @@ -134,6 +136,39 @@ namespace android { layer.accept(SetFilterEvaluator {std::move(*converted)}); } + struct GetFilterEvaluator { + mbgl::style::Filter noop(std::string layerType) { + Log::Warning(mbgl::Event::JNI, "%s doesn't support filter", layerType.c_str()); + return {}; + } + + mbgl::style::Filter operator()(style::BackgroundLayer&) { return noop("BackgroundLayer"); } + mbgl::style::Filter operator()(style::CustomLayer&) { return noop("CustomLayer"); } + mbgl::style::Filter operator()(style::RasterLayer&) { return noop("RasterLayer"); } + mbgl::style::Filter operator()(style::HillshadeLayer&) { return noop("HillshadeLayer"); } + + template <class LayerType> + mbgl::style::Filter operator()(LayerType& layer) { + return layer.getFilter(); + } + }; + + jni::Object<gson::JsonArray> Layer::getFilter(jni::JNIEnv& env) { + using namespace mbgl::style; + using namespace mbgl::style::conversion; + + Filter filter = layer.accept(GetFilterEvaluator()); + + jni::jobject* converted = nullptr; + if (filter.is<ExpressionFilter>()) { + ExpressionFilter filterExpression = filter.get<ExpressionFilter>(); + mbgl::Value expressionValue = filterExpression.expression.get()->serialize(); + conversion::JsonEvaluator jsonEvaluator{env}; + converted = apply_visitor(jsonEvaluator, expressionValue); + } + return jni::Object<gson::JsonArray>(converted); + } + struct SetSourceLayerEvaluator { std::string sourceLayer; @@ -208,6 +243,7 @@ namespace android { METHOD(&Layer::setLayoutProperty, "nativeSetLayoutProperty"), METHOD(&Layer::setPaintProperty, "nativeSetPaintProperty"), METHOD(&Layer::setFilter, "nativeSetFilter"), + METHOD(&Layer::getFilter, "nativeGetFilter"), METHOD(&Layer::setSourceLayer, "nativeSetSourceLayer"), METHOD(&Layer::getSourceLayer, "nativeGetSourceLayer"), METHOD(&Layer::getMinZoom, "nativeGetMinZoom"), diff --git a/platform/android/src/style/layers/layer.hpp b/platform/android/src/style/layers/layer.hpp index 78c3f80b48..19713a1baa 100644 --- a/platform/android/src/style/layers/layer.hpp +++ b/platform/android/src/style/layers/layer.hpp @@ -3,7 +3,7 @@ #include <mbgl/util/noncopyable.hpp> #include <mbgl/map/map.hpp> #include <mbgl/style/layer.hpp> - +#include "../../gson/json_array.hpp" #include "../value.hpp" #include <jni/jni.hpp> @@ -68,6 +68,8 @@ public: void setFilter(jni::JNIEnv&, jni::Array<jni::Object<>>); + jni::Object<gson::JsonArray> getFilter(jni::JNIEnv&); + void setSourceLayer(jni::JNIEnv&, jni::String); jni::String getSourceLayer(jni::JNIEnv&); diff --git a/platform/darwin/src/MGLPointAnnotation.h b/platform/darwin/src/MGLPointAnnotation.h index 1ef0962f99..78b92f45ac 100644 --- a/platform/darwin/src/MGLPointAnnotation.h +++ b/platform/darwin/src/MGLPointAnnotation.h @@ -28,7 +28,8 @@ NS_ASSUME_NONNULL_BEGIN default content of the annotation’s callout (on iOS) or popover (on macOS). To group multiple related points together in one shape, use an - `MGLPointCollection` or `MGLShapeCollection` object. + `MGLPointCollection` or `MGLShapeCollection` object. To access + a point’s attributes, use an `MGLPointFeature` object. A point shape is known as a <a href="https://tools.ietf.org/html/rfc7946#section-3.1.2">Point</a> geometry diff --git a/platform/darwin/src/MGLPointCollection.h b/platform/darwin/src/MGLPointCollection.h index 74b30385a0..5aab580b4d 100644 --- a/platform/darwin/src/MGLPointCollection.h +++ b/platform/darwin/src/MGLPointCollection.h @@ -15,7 +15,8 @@ You can add point collections to the map by adding them to an `MGLShapeSource` object. Configure the appearance of an `MGLShapeSource`’s or `MGLVectorSource`’s point collections collectively using an - `MGLCircleStyleLayer` or `MGLSymbolStyleLayer` object. + `MGLCircleStyleLayer` or `MGLSymbolStyleLayer` object. To access a point + collection’s attributes, use an `MGLPointCollectionFeature` object. You cannot add an `MGLPointCollection` object directly to a map view as an annotation. However, you can create individual `MGLPointAnnotation` objects diff --git a/platform/darwin/src/MGLPolygon.h b/platform/darwin/src/MGLPolygon.h index 3fcc1be76d..bdd2e1eeab 100644 --- a/platform/darwin/src/MGLPolygon.h +++ b/platform/darwin/src/MGLPolygon.h @@ -18,7 +18,8 @@ NS_ASSUME_NONNULL_BEGIN You can add polygon shapes to the map by adding them to an `MGLShapeSource` object. Configure the appearance of an `MGLShapeSource`’s or `MGLVectorSource`’s polygons collectively using an `MGLFillStyleLayer` or - `MGLSymbolStyleLayer` object. + `MGLSymbolStyleLayer` object. To access a polygon’s attributes, use an + `MGLPolygonFeature` object. Alternatively, you can add a polygon overlay directly to a map view using the `-[MGLMapView addAnnotation:]` or `-[MGLMapView addOverlay:]` method. Configure diff --git a/platform/darwin/src/MGLPolyline.h b/platform/darwin/src/MGLPolyline.h index e46baa91cc..1ed3dd0b9f 100644 --- a/platform/darwin/src/MGLPolyline.h +++ b/platform/darwin/src/MGLPolyline.h @@ -18,7 +18,8 @@ NS_ASSUME_NONNULL_BEGIN You can add polyline shapes to the map by adding them to an `MGLShapeSource` object. Configure the appearance of an `MGLShapeSource`’s or `MGLVectorSource`’s polylines collectively using an `MGLLineStyleLayer` or - `MGLSymbolStyleLayer` object. + `MGLSymbolStyleLayer` object. To access a polyline’s attributes, use an + `MGLPolylineFeature` object. Alternatively, you can add a polyline overlay directly to a map view using the `-[MGLMapView addAnnotation:]` or `-[MGLMapView addOverlay:]` method. Configure diff --git a/platform/darwin/src/MGLShape.h b/platform/darwin/src/MGLShape.h index e965710552..4bab2b81c1 100644 --- a/platform/darwin/src/MGLShape.h +++ b/platform/darwin/src/MGLShape.h @@ -20,7 +20,8 @@ NS_ASSUME_NONNULL_BEGIN Although you do not create instances of this class directly, you can use its `+[MGLShape shapeWithData:encoding:error:]` factory method to create one of the - concrete subclasses of `MGLShape` noted above from GeoJSON data. + concrete subclasses of `MGLShape` noted above from GeoJSON data. To access a + shape’s attributes, use the corresponding `MGLFeature` class instead. You can add shapes to the map by adding them to an `MGLShapeSource` object. Configure the appearance of an `MGLShapeSource`’s or `MGLVectorSource`’s shapes diff --git a/platform/darwin/src/MGLShapeCollection.h b/platform/darwin/src/MGLShapeCollection.h index bb107ee7f0..094c630167 100644 --- a/platform/darwin/src/MGLShapeCollection.h +++ b/platform/darwin/src/MGLShapeCollection.h @@ -27,7 +27,8 @@ NS_ASSUME_NONNULL_BEGIN To represent a collection of point, polyline, or polygon shapes, it may be more convenient to use an `MGLPointCollection`, `MGLMultiPolyline`, or - `MGLMultiPolygon` object, respectively. + `MGLMultiPolygon` object, respectively. To access a shape collection’s attributes, + use the corresponding `MGLFeature` object. A shape collection is known as a <a href="https://tools.ietf.org/html/rfc7946#section-3.1.8">GeometryCollection</a> diff --git a/platform/darwin/src/NSExpression+MGLAdditions.mm b/platform/darwin/src/NSExpression+MGLAdditions.mm index 5ad565c398..9797709b7b 100644 --- a/platform/darwin/src/NSExpression+MGLAdditions.mm +++ b/platform/darwin/src/NSExpression+MGLAdditions.mm @@ -258,6 +258,12 @@ return [self valueForKeyPath:@"mgl_jsonExpressionObject"]; } +- (id)mgl_coalesce { + [NSException raise:NSInvalidArgumentException + format:@"Coalesce expressions lack underlying Objective-C implementations."]; + return nil; +} + @end @implementation NSDictionary (MGLExpressionAdditions) @@ -464,18 +470,43 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) { } else if ([op isEqualToString:@"var"]) { return [NSExpression expressionForVariable:argumentObjects.firstObject]; } else if ([op isEqualToString:@"case"]) { - NSPredicate *conditional = [NSPredicate mgl_predicateWithJSONObject:argumentObjects.firstObject]; - NSExpression *trueExpression = [NSExpression mgl_expressionWithJSONObject:argumentObjects[1]]; - NSExpression *falseExpression; - if (argumentObjects.count > 3) { - NSArray *falseObjects = [@[@"case"] arrayByAddingObjectsFromArray: - [argumentObjects subarrayWithRange:NSMakeRange(2, argumentObjects.count - 2)]]; - falseExpression = [NSExpression mgl_expressionWithJSONObject:falseObjects]; - } else { - falseExpression = [NSExpression mgl_expressionWithJSONObject:argumentObjects[2]]; + NSArray *caseExpressions = argumentObjects; + NSExpression *firstConditional = [NSExpression expressionWithFormat:@"%@", [NSPredicate mgl_predicateWithJSONObject:caseExpressions[0]]]; + NSMutableArray *arguments = [NSMutableArray array]; + + for (NSUInteger index = 1; index < caseExpressions.count; index++) { + if ([caseExpressions[index] isKindOfClass:[NSArray class]]) { + NSPredicate *conditional = [NSPredicate mgl_predicateWithJSONObject:caseExpressions[index]]; + NSExpression *argument = [NSExpression expressionWithFormat:@"%@", conditional]; + [arguments addObject:argument]; + } else { + [arguments addObject:[NSExpression mgl_expressionWithJSONObject:caseExpressions[index]]]; + } + } + + return [NSExpression expressionForFunction:firstConditional selectorName:@"mgl_case:" arguments:arguments]; + } else if ([op isEqualToString:@"match"]) { + NSExpression *operand = [NSExpression mgl_expressionWithJSONObject:argumentObjects[0]]; + NSArray *matchOptions = [argumentObjects subarrayWithRange:NSMakeRange(1, argumentObjects.count - 1)]; + + NSMutableArray *optionsArray = [NSMutableArray array]; + NSEnumerator *optionsEnumerator = matchOptions.objectEnumerator; + while (id object = optionsEnumerator.nextObject) { + NSExpression *option = [NSExpression mgl_expressionWithJSONObject:object]; + [optionsArray addObject:option]; + } + + return [NSExpression expressionForFunction:operand + selectorName:@"mgl_match:" + arguments:optionsArray]; + } else if ([op isEqualToString:@"coalesce"]) { + NSMutableArray *expressions = [NSMutableArray array]; + for (id operand in argumentObjects) { + [expressions addObject:[NSExpression mgl_expressionWithJSONObject:operand]]; } - return [NSExpression expressionForConditional:conditional trueExpression:trueExpression falseExpression:falseExpression]; - } else { + + return [NSExpression expressionWithFormat:@"FUNCTION(%@, 'mgl_coalesce')", expressions]; + }else { [NSException raise:NSInvalidArgumentException format:@"Expression operator %@ not yet implemented.", op]; } @@ -666,6 +697,37 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) { }]; [expressionObject addObject:self.operand.mgl_jsonExpressionObject]; return expressionObject; + } else if ([function isEqualToString:@"mgl_case:"]) { + NSPredicate *firstConditional = (NSPredicate *)self.operand.constantValue; + NSMutableArray *expressionObject = [NSMutableArray arrayWithObjects:@"case", firstConditional.mgl_jsonExpressionObject, nil]; + + for (NSExpression *option in self.arguments) { + if ([option respondsToSelector:@selector(constantValue)] && [option.constantValue isKindOfClass:[NSComparisonPredicate class]]) { + NSPredicate *predicate = (NSPredicate *)option.constantValue; + [expressionObject addObject:predicate.mgl_jsonExpressionObject]; + } else { + [expressionObject addObject:option.mgl_jsonExpressionObject]; + } + } + + return expressionObject; + } else if ([function isEqualToString:@"mgl_match:"]) { + NSMutableArray *expressionObject = [NSMutableArray arrayWithObjects:@"match", self.operand.mgl_jsonExpressionObject, nil]; + + + for (NSExpression *option in self.arguments) { + [expressionObject addObject:option.mgl_jsonExpressionObject]; + } + + return expressionObject; + } else if ([function isEqualToString:@"mgl_coalesce"]) { + NSMutableArray *expressionObject = [NSMutableArray arrayWithObjects:@"coalesce", nil]; + + for (NSExpression *expression in self.operand.constantValue) { + [expressionObject addObject:[expression mgl_jsonExpressionObject]]; + } + + return expressionObject; } else if ([function isEqualToString:@"median:"] || [function isEqualToString:@"mode:"] || [function isEqualToString:@"stddev:"] || @@ -690,7 +752,17 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) { } case NSConditionalExpressionType: { - NSMutableArray *arguments = [NSMutableArray arrayWithObjects:self.predicate.mgl_jsonExpressionObject, self.trueExpression.mgl_jsonExpressionObject, nil]; + NSMutableArray *arguments = [NSMutableArray arrayWithObjects:self.predicate.mgl_jsonExpressionObject, nil]; + + if (self.trueExpression.expressionType == NSConditionalExpressionType) { + // Fold nested conditionals into a single case expression. + NSArray *trueArguments = self.trueExpression.mgl_jsonExpressionObject; + trueArguments = [trueArguments subarrayWithRange:NSMakeRange(1, trueArguments.count - 1)]; + [arguments addObjectsFromArray:trueArguments]; + } else { + [arguments addObject:self.trueExpression.mgl_jsonExpressionObject]; + } + if (self.falseExpression.expressionType == NSConditionalExpressionType) { // Fold nested conditionals into a single case expression. NSArray *falseArguments = self.falseExpression.mgl_jsonExpressionObject; diff --git a/platform/darwin/src/NSExpression+MGLPrivateAdditions.h b/platform/darwin/src/NSExpression+MGLPrivateAdditions.h index 8d1b4d6af5..b16e2625a0 100644 --- a/platform/darwin/src/NSExpression+MGLPrivateAdditions.h +++ b/platform/darwin/src/NSExpression+MGLPrivateAdditions.h @@ -54,6 +54,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly) id mgl_jsonExpressionObject; +@property (nonatomic, readonly) id mgl_coalesce; + @end @interface NSDictionary (MGLExpressionAdditions) diff --git a/platform/darwin/src/NSPredicate+MGLAdditions.h b/platform/darwin/src/NSPredicate+MGLAdditions.h index 89e9e65c64..a67c6ca005 100644 --- a/platform/darwin/src/NSPredicate+MGLAdditions.h +++ b/platform/darwin/src/NSPredicate+MGLAdditions.h @@ -16,4 +16,8 @@ @property (nonatomic, readonly) id mgl_jsonExpressionObject; +- (id)mgl_case:(id)firstValue, ...; + +- (id)mgl_match:(NSExpression *)firstCase, ...; + @end diff --git a/platform/darwin/src/NSPredicate+MGLAdditions.mm b/platform/darwin/src/NSPredicate+MGLAdditions.mm index 63c8307803..1a7bb30a92 100644 --- a/platform/darwin/src/NSPredicate+MGLAdditions.mm +++ b/platform/darwin/src/NSPredicate+MGLAdditions.mm @@ -324,4 +324,35 @@ NSArray *MGLSubpredicatesWithJSONObjects(NSArray *objects) { return nil; } +- (id)mgl_case:(id)firstValue, ... { + + if ([self evaluateWithObject:nil]) { + return firstValue; + } + + id eachExpression; + va_list argumentList; + va_start(argumentList, firstValue); + + while ((eachExpression = va_arg(argumentList, id))) { + if ([eachExpression isKindOfClass:[NSComparisonPredicate class]]) { + id valueExpression = va_arg(argumentList, id); + if ([eachExpression evaluateWithObject:nil]) { + return valueExpression; + } + } else { + return eachExpression; + } + } + va_end(argumentList); + + return nil; +} + +- (id)mgl_match:(NSExpression *)firstCase, ... { + [NSException raise:NSInvalidArgumentException + format:@"Match expressions lack underlying Objective-C implementations."]; + return nil; +} + @end diff --git a/platform/darwin/test/MGLExpressionTests.mm b/platform/darwin/test/MGLExpressionTests.mm index a5ed2f7bf5..ea5a1cd41c 100644 --- a/platform/darwin/test/MGLExpressionTests.mm +++ b/platform/darwin/test/MGLExpressionTests.mm @@ -572,29 +572,61 @@ using namespace std::string_literals; } } +- (void)testMatchExpressionObject { + { + NSExpression *expression = [NSExpression expressionWithFormat:@"FUNCTION(2 - 1, 'mgl_match:', %@, %@, %@, %@, 'default')", MGLConstantExpression(@1), + MGLConstantExpression(@"one"), + MGLConstantExpression(@0), + MGLConstantExpression(@"zero")]; + NSArray *jsonExpression = @[@"match", @[@"-", @2, @1], @1, @"one", @0, @"zero", @"default"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"FUNCTION(2 * 1, 'mgl_match:', %@, %@, 'default')", MGLConstantExpression(@1), MGLConstantExpression(@"one")]; + NSArray *jsonExpression = @[@"match", @[@"*", @2, @1], @1, @"one", @"default"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } +} + +- (void)testCoalesceExpressionObject { + { + NSExpression *expression = [NSExpression expressionWithFormat:@"FUNCTION(%@, 'mgl_coalesce')", @[[NSExpression expressionForKeyPath:@"x"], + [NSExpression expressionForKeyPath:@"y"], + [NSExpression expressionForKeyPath:@"z"], + [NSExpression expressionForConstantValue:@0]]]; + NSArray *jsonExpression = @[@"coalesce", @[@"get", @"x"], @[@"get", @"y"], @[@"get", @"z"], @0]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + } + +} + - (void)testConditionalExpressionObject { - // FIXME: This test crashes because iOS 8 doesn't have `+[NSExpression expressionForConditional:trueExpression:falseExpression:]`. - // https://github.com/mapbox/mapbox-gl-native/issues/11007 - if (@available(iOS 9.0, *)) { - { - NSPredicate *conditional = [NSPredicate predicateWithFormat:@"1 = 2"]; - NSExpression *trueExpression = [NSExpression expressionForConstantValue:@YES]; - NSExpression *falseExpression = [NSExpression expressionForConstantValue:@NO]; - NSExpression *expression = [NSExpression expressionForConditional:conditional trueExpression:trueExpression falseExpression:falseExpression]; - NSArray *jsonExpression = @[@"case", @[@"==", @1, @2], @YES, @NO]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithFormat:@"TERNARY(1 = 2, TRUE, FALSE)"].mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @NO); - XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionWithFormat:@"TERNARY(0 = 1, TRUE, TERNARY(1 = 2, TRUE, FALSE))"]; - NSArray *jsonExpression = @[@"case", @[@"==", @0, @1], @YES, @[@"==", @1, @2], @YES, @NO]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @NO); - XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); - } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"FUNCTION(%@, 'mgl_case:', %@, %@)", + [NSExpression expressionWithFormat:@"%@", [NSPredicate predicateWithFormat:@"1 = 2"]], + MGLConstantExpression(@YES), + MGLConstantExpression(@NO)]; + NSArray *jsonExpression = @[@"case", @[@"==", @1, @2], @YES, @NO]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @NO); } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"FUNCTION(%@, 'mgl_case:', %@, %@, %@, %@)", + [NSExpression expressionWithFormat:@"%@", [NSPredicate predicateWithFormat:@"1 = 2"]], + MGLConstantExpression(@YES), + [NSExpression expressionWithFormat:@"%@", [NSPredicate predicateWithFormat:@"1 = 1"]], + MGLConstantExpression(@YES), + MGLConstantExpression(@NO)]; + NSArray *jsonExpression = @[@"case", @[@"==", @1, @2], @YES, @[@"==", @1, @1], @YES, @NO]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression mgl_expressionWithJSONObject:jsonExpression], expression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @YES); + } + } @end diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 5ef1039d00..0279653451 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -37,6 +37,8 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT * Improved performance of `MGLAnnotationView`-backed annotations that have `scalesWithViewingDistance` enabled. ([#10951](https://github.com/mapbox/mapbox-gl-native/pull/10951)) * Fixed an issue may potentially caused `NSInvalidArgumentException`. ([#11366](https://github.com/mapbox/mapbox-gl-native/pull/11366)) * Fix an issue where a wrong annotation may selected if annotations were set close together. ([#11438](https://github.com/mapbox/mapbox-gl-native/pull/11438)) +* The `MGLMapView.selectedAnnotations` property (backed by `-[MGLMapView setSelectedAnnotations:]`) now selects annotations that are off-screen. ([#9790](https://github.com/mapbox/mapbox-gl-native/issues/9790)) +* The `animated` parameter to `-[MGLMapView selectAnnotation:animated:]` now controls whether the annotation and its callout are brought on-screen. If `animated` is `NO` then the annotation is selected if offscreen, but the map is not panned. Currently only point annotations are supported. Setting the `MGLMapView.selectedAnnotations` property now animates. ([#3249](https://github.com/mapbox/mapbox-gl-native/issues/3249)) ### Map snapshots diff --git a/platform/ios/app/MBXCustomCalloutView.m b/platform/ios/app/MBXCustomCalloutView.m index 13564c5cbf..0626b0997a 100644 --- a/platform/ios/app/MBXCustomCalloutView.m +++ b/platform/ios/app/MBXCustomCalloutView.m @@ -37,11 +37,15 @@ static CGFloat const tipWidth = 10.0; return self; } - #pragma mark - API - (void)presentCalloutFromRect:(CGRect)rect inView:(UIView *)view constrainedToView:(UIView *)constrainedView animated:(BOOL)animated { + [self presentCalloutFromRect:rect inView:view constrainedToRect:CGRectNull animated:animated]; +} + +- (void)presentCalloutFromRect:(CGRect)rect inView:(nonnull UIView *)view constrainedToRect:(__unused CGRect)constrainedRect animated:(BOOL)animated +{ if ([self.delegate respondsToSelector:@selector(calloutViewWillAppear:)]) { [self.delegate performSelector:@selector(calloutViewWillAppear:) withObject:self]; @@ -108,5 +112,4 @@ static CGFloat const tipWidth = 10.0; CGPathRelease(tipPath); } - @end diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m index a472e3a221..5282469223 100644 --- a/platform/ios/app/MBXViewController.m +++ b/platform/ios/app/MBXViewController.m @@ -54,6 +54,9 @@ typedef NS_ENUM(NSInteger, MBXSettingsAnnotationsRows) { MBXSettingsAnnotationsQueryAnnotations, MBXSettingsAnnotationsCustomUserDot, MBXSettingsAnnotationsRemoveAnnotations, + MBXSettingsAnnotationSelectRandomOffscreenPointAnnotation, + MBXSettingsAnnotationCenterSelectedAnnotation, + MBXSettingsAnnotationAddVisibleAreaPolyline }; typedef NS_ENUM(NSInteger, MBXSettingsRuntimeStylingRows) { @@ -340,6 +343,9 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { @"Query Annotations", [NSString stringWithFormat:@"%@ Custom User Dot", (_customUserLocationAnnnotationEnabled ? @"Disable" : @"Enable")], @"Remove Annotations", + @"Select an offscreen point annotation", + @"Center selected annotation", + @"Add visible area polyline" ]]; break; case MBXSettingsRuntimeStyling: @@ -468,6 +474,18 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { case MBXSettingsAnnotationsRemoveAnnotations: [self.mapView removeAnnotations:self.mapView.annotations]; break; + case MBXSettingsAnnotationSelectRandomOffscreenPointAnnotation: + [self selectAnOffscreenPointAnnotation]; + break; + + case MBXSettingsAnnotationCenterSelectedAnnotation: + [self centerSelectedAnnotation]; + break; + + case MBXSettingsAnnotationAddVisibleAreaPolyline: + [self addVisibleAreaPolyline]; + break; + default: NSAssert(NO, @"All annotations setting rows should be implemented"); break; @@ -1551,6 +1569,73 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { [self presentViewController:alertController animated:YES completion:nil]; } +- (id<MGLAnnotation>)randomOffscreenPointAnnotation { + + NSPredicate *pointAnnotationPredicate = [NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) { + return [evaluatedObject isKindOfClass:[MGLPointAnnotation class]]; + }]; + + NSArray *annotations = [self.mapView.annotations filteredArrayUsingPredicate:pointAnnotationPredicate]; + + if (annotations.count == 0) { + return nil; + } + + NSArray *visibleAnnotations = [self.mapView.visibleAnnotations filteredArrayUsingPredicate:pointAnnotationPredicate]; + + if (visibleAnnotations.count == annotations.count) { + return nil; + } + + NSMutableArray *invisibleAnnotations = [annotations mutableCopy]; + + if (visibleAnnotations.count > 0) { + [invisibleAnnotations removeObjectsInArray:visibleAnnotations]; + } + + // Now pick a random offscreen annotation. + uint32_t index = arc4random_uniform((uint32_t)invisibleAnnotations.count); + return invisibleAnnotations[index]; +} + +- (void)selectAnOffscreenPointAnnotation { + id<MGLAnnotation> annotation = [self randomOffscreenPointAnnotation]; + if (annotation) { + [self.mapView selectAnnotation:annotation animated:YES]; + + NSAssert(self.mapView.selectedAnnotations.firstObject, @"The annotation was not selected"); + } +} + +- (void)centerSelectedAnnotation { + id<MGLAnnotation> annotation = self.mapView.selectedAnnotations.firstObject; + + if (!annotation) + return; + + CGPoint point = [self.mapView convertCoordinate:annotation.coordinate toPointToView:self.mapView]; + + // Animate, so that point becomes the the center + CLLocationCoordinate2D center = [self.mapView convertPoint:point toCoordinateFromView:self.mapView]; + [self.mapView setCenterCoordinate:center animated:YES]; +} + +- (void)addVisibleAreaPolyline { + CGRect constrainedRect = UIEdgeInsetsInsetRect(self.mapView.bounds, self.mapView.contentInset); + + CLLocationCoordinate2D lineCoords[5]; + + lineCoords[0] = [self.mapView convertPoint: CGPointMake(CGRectGetMinX(constrainedRect), CGRectGetMinY(constrainedRect)) toCoordinateFromView:self.mapView]; + lineCoords[1] = [self.mapView convertPoint: CGPointMake(CGRectGetMaxX(constrainedRect), CGRectGetMinY(constrainedRect)) toCoordinateFromView:self.mapView]; + lineCoords[2] = [self.mapView convertPoint: CGPointMake(CGRectGetMaxX(constrainedRect), CGRectGetMaxY(constrainedRect)) toCoordinateFromView:self.mapView]; + lineCoords[3] = [self.mapView convertPoint: CGPointMake(CGRectGetMinX(constrainedRect), CGRectGetMaxY(constrainedRect)) toCoordinateFromView:self.mapView]; + lineCoords[4] = lineCoords[0]; + + MGLPolyline *line = [MGLPolyline polylineWithCoordinates:lineCoords + count:sizeof(lineCoords)/sizeof(lineCoords[0])]; + [self.mapView addAnnotation:line]; +} + - (void)printTelemetryLogFile { NSString *fileContents = [NSString stringWithContentsOfFile:[self telemetryDebugLogFilePath] encoding:NSUTF8StringEncoding error:nil]; @@ -1602,7 +1687,13 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { toCoordinateFromView:self.mapView]; pin.title = title ?: @"Dropped Pin"; pin.subtitle = [[[MGLCoordinateFormatter alloc] init] stringFromCoordinate:pin.coordinate]; - // Calling `addAnnotation:` on mapView is not required since `selectAnnotation:animated` has the side effect of adding the annotation if required + + + // Calling `addAnnotation:` on mapView is required here (since `selectAnnotation:animated` has + // the side effect of adding the annotation if required, but returning an incorrect callout + // positioning rect) + + [self.mapView addAnnotation:pin]; [self.mapView selectAnnotation:pin animated:YES]; } } diff --git a/platform/ios/src/MGLCalloutView.h b/platform/ios/src/MGLCalloutView.h index 0481a39680..7e7cf2d02e 100644 --- a/platform/ios/src/MGLCalloutView.h +++ b/platform/ios/src/MGLCalloutView.h @@ -41,6 +41,13 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)presentCalloutFromRect:(CGRect)rect inView:(UIView *)view constrainedToView:(UIView *)constrainedView animated:(BOOL)animated; + +/** + Presents a callout view by adding it to `view` and pointing at the given rect + of `view`’s bounds. Constrains the callout to the rect in the space of `view`. + */ +- (void)presentCalloutFromRect:(CGRect)rect inView:(UIView *)view constrainedToRect:(CGRect)constrainedRect animated:(BOOL)animated; + /** Dismisses the callout view. */ @@ -49,6 +56,24 @@ NS_ASSUME_NONNULL_BEGIN @optional /** + If implemented, should provide margins to expand the rect the callout is presented from. + + These are used to determine positioning. Currently only the top and bottom properties of the return + value are used. For example, `{ .top = -50.0, .left = -10.0, .bottom = 0.0, .right = -10.0 }` indicates + a 50 point margin above the presentation origin rect (and 10 point margins to the left and the right) + in which the callout is assumed to be displayed. + + There are no assumed defaults for these margins, as they should be calculated from the callout that + is to be presented. For example, `SMCalloutView` generates the top margin from the callout height, but + the left and right margins from a minimum width that the callout should have. + + @param rect Rect that the callout is presented from. This should be the same as the one passed in + `-[MGLCalloutView presentCalloutFromRect:inView:constrainedToRect:animated:]` + @return `UIEdgeInsets` representing the margins. Values should be negative. + */ +- (UIEdgeInsets)marginInsetsHintForPresentationFromRect:(CGRect)rect NS_SWIFT_NAME(marginInsetsHintForPresentation(from:)); + +/** A Boolean value indicating whether the callout view should be anchored to the corresponding annotation. You can adjust the callout view’s precise location by overriding -[UIView setCenter:]. The callout view will not be anchored to the diff --git a/platform/ios/src/MGLMapView.h b/platform/ios/src/MGLMapView.h index 52d28d871c..2d566f26a0 100644 --- a/platform/ios/src/MGLMapView.h +++ b/platform/ios/src/MGLMapView.h @@ -1200,17 +1200,32 @@ MGL_EXPORT IB_DESIGNABLE Assigning a new array to this property selects only the first annotation in the array. + + If the annotation is of type `MGLPointAnnotation` and is offscreen, the camera + will animate to bring the annotation and its callout just on screen. If you + need finer control, consider using `-selectAnnotation:animated:`. + + @note In versions prior to `4.0.0` if the annotation was offscreen it was not + selected. */ @property (nonatomic, copy) NS_ARRAY_OF(id <MGLAnnotation>) *selectedAnnotations; /** - Selects an annotation and displays a callout view for it. + Selects an annotation and displays its callout view. - If the given annotation is not visible within the current viewport, this - method has no effect. + The `animated` parameter determines whether the map is panned to bring the + annotation on-screen, specifically: + + | `animated` parameter | Effect | + |------------------|--------| + | `NO` | The annotation is selected, and the callout is presented. However the map is not panned to bring the annotation or callout onscreen. The presentation of the callout is animated. | + | `YES` | The annotation is selected, and the callout is presented. If the annotation is offscreen *and* is of type `MGLPointAnnotation`, the map is panned so that the annotation and its callout are brought just onscreen. The annotation is *not* centered within the viewport. | @param annotation The annotation object to select. - @param animated If `YES`, the callout view is animated into position. + @param animated If `YES`, the annotation and callout view are animated on-screen. + + @note In versions prior to `4.0.0` selecting an offscreen annotation did not + change the camera. */ - (void)selectAnnotation:(id <MGLAnnotation>)annotation animated:(BOOL)animated; diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm index 72198ba637..482fd07c00 100644 --- a/platform/ios/src/MGLMapView.mm +++ b/platform/ios/src/MGLMapView.mm @@ -144,6 +144,9 @@ const CGFloat MGLAnnotationImagePaddingForCallout = 1; const CGSize MGLAnnotationAccessibilityElementMinimumSize = CGSizeMake(10, 10); +/// Padding to edge of view that an offscreen annotation must have when being brought onscreen (by being selected) +const UIEdgeInsets MGLMapViewOffscreenAnnotationPadding = UIEdgeInsetsMake(-20.0f, -20.0f, -20.0f, -20.0f); + /// An indication that the requested annotation was not found or is nonexistent. enum { MGLAnnotationTagNotFound = UINT32_MAX }; @@ -272,7 +275,7 @@ public: CADisplayLink *_displayLink; BOOL _needsDisplayRefresh; - NSUInteger _changeDelimiterSuppressionDepth; + NSInteger _changeDelimiterSuppressionDepth; /// Center coordinate of the pinch gesture on the previous iteration of the gesture. CLLocationCoordinate2D _previousPinchCenterCoordinate; @@ -1625,7 +1628,7 @@ public: { CGPoint calloutPoint = [singleTap locationInView:self]; CGRect positionRect = [self positioningRectForAnnotation:annotation defaultCalloutPoint:calloutPoint]; - [self selectAnnotation:annotation animated:YES calloutPositioningRect:positionRect]; + [self selectAnnotation:annotation moveOnscreen:YES animateSelection:YES calloutPositioningRect:positionRect]; } else if (self.selectedAnnotation) { @@ -4234,6 +4237,12 @@ public: }); } + +- (BOOL)isBringingAnnotationOnscreenSupportedForAnnotation:(id<MGLAnnotation>)annotation animated:(BOOL)animated { + // Consider delegating + return animated && [annotation isKindOfClass:[MGLPointAnnotation class]]; +} + - (id <MGLAnnotation>)selectedAnnotation { if (_userLocationAnnotationIsSelected) @@ -4274,20 +4283,16 @@ public: if ([firstAnnotation isKindOfClass:[MGLMultiPoint class]]) return; - // Select the annotation if it’s visible. - if (MGLCoordinateInCoordinateBounds(firstAnnotation.coordinate, self.visibleCoordinateBounds)) - { - [self selectAnnotation:firstAnnotation animated:NO]; - } + [self selectAnnotation:firstAnnotation animated:YES]; } - (void)selectAnnotation:(id <MGLAnnotation>)annotation animated:(BOOL)animated { CGRect positioningRect = [self positioningRectForAnnotation:annotation defaultCalloutPoint:CGPointZero]; - [self selectAnnotation:annotation animated:animated calloutPositioningRect:positioningRect]; + [self selectAnnotation:annotation moveOnscreen:animated animateSelection:YES calloutPositioningRect:positioningRect]; } -- (void)selectAnnotation:(id <MGLAnnotation>)annotation animated:(BOOL)animated calloutPositioningRect:(CGRect)calloutPositioningRect +- (void)selectAnnotation:(id <MGLAnnotation>)annotation moveOnscreen:(BOOL)moveOnscreen animateSelection:(BOOL)animateSelection calloutPositioningRect:(CGRect)calloutPositioningRect { if ( ! annotation) return; @@ -4311,22 +4316,34 @@ public: MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(annotationTag); annotationView = annotationContext.annotationView; if (annotationView && annotationView.enabled) { - // Annotations represented by views use the view frame as the positioning rect. - calloutPositioningRect = annotationView.frame; - [annotationView.superview bringSubviewToFront:annotationView]; - [annotationView setSelected:YES animated:animated]; + // Annotations represented by views use the view frame as the positioning rect. + calloutPositioningRect = annotationView.frame; + [annotationView.superview bringSubviewToFront:annotationView]; + + [annotationView setSelected:YES animated:animateSelection]; } } self.selectedAnnotation = annotation; + // Determine if we're allowed to move this offscreen annotation on screen, even though we've asked it to + if (moveOnscreen) { + moveOnscreen = [self isBringingAnnotationOnscreenSupportedForAnnotation:annotation animated:animateSelection]; + } + + CGRect expandedPositioningRect = UIEdgeInsetsInsetRect(calloutPositioningRect, MGLMapViewOffscreenAnnotationPadding); + + // Used for callout positioning, and moving offscreen annotations onscreen. + CGRect constrainedRect = UIEdgeInsetsInsetRect(self.bounds, self.contentInset); + + UIView <MGLCalloutView> *calloutView = nil; + if ([annotation respondsToSelector:@selector(title)] && annotation.title && [self.delegate respondsToSelector:@selector(mapView:annotationCanShowCallout:)] && [self.delegate mapView:self annotationCanShowCallout:annotation]) { // build the callout - UIView <MGLCalloutView> *calloutView; if ([self.delegate respondsToSelector:@selector(mapView:calloutViewForAnnotation:)]) { id providedCalloutView = [self.delegate mapView:self calloutViewForAnnotation:annotation]; @@ -4384,13 +4401,51 @@ public: // set annotation delegate to handle taps on the callout view calloutView.delegate = self; - // present popup - [calloutView presentCalloutFromRect:calloutPositioningRect - inView:self.glView - constrainedToView:self.glView - animated:animated]; + // If the callout view provides inset (outset) information, we can use it to expand our positioning + // rect, which we then use to help move the annotation on-screen if want need to. + if (moveOnscreen && [calloutView respondsToSelector:@selector(marginInsetsHintForPresentationFromRect:)]) { + UIEdgeInsets margins = [calloutView marginInsetsHintForPresentationFromRect:calloutPositioningRect]; + expandedPositioningRect = UIEdgeInsetsInsetRect(expandedPositioningRect, margins); + } + } + + if (moveOnscreen) + { + moveOnscreen = NO; + + // Need to consider the content insets. + CGRect bounds = UIEdgeInsetsInsetRect(self.bounds, self.contentInset); + + // Any one of these cases should trigger a move onscreen + if (CGRectGetMinX(calloutPositioningRect) < CGRectGetMinX(bounds)) + { + constrainedRect.origin.x = expandedPositioningRect.origin.x; + moveOnscreen = YES; + } + else if (CGRectGetMaxX(calloutPositioningRect) > CGRectGetMaxX(bounds)) + { + constrainedRect.origin.x = CGRectGetMaxX(expandedPositioningRect) - constrainedRect.size.width; + moveOnscreen = YES; + } + + if (CGRectGetMinY(calloutPositioningRect) < CGRectGetMinY(bounds)) + { + constrainedRect.origin.y = expandedPositioningRect.origin.y; + moveOnscreen = YES; + } + else if (CGRectGetMaxY(calloutPositioningRect) > CGRectGetMaxY(bounds)) + { + constrainedRect.origin.y = CGRectGetMaxY(expandedPositioningRect) - constrainedRect.size.height; + moveOnscreen = YES; + } } + // Remember, calloutView can be nil here. + [calloutView presentCalloutFromRect:calloutPositioningRect + inView:self.glView + constrainedToRect:constrainedRect + animated:animateSelection]; + // notify delegate if ([self.delegate respondsToSelector:@selector(mapView:didSelectAnnotation:)]) { @@ -4401,6 +4456,13 @@ public: { [self.delegate mapView:self didSelectAnnotationView:annotationView]; } + + if (moveOnscreen) + { + CGPoint center = CGPointMake(CGRectGetMidX(constrainedRect), CGRectGetMidY(constrainedRect)); + CLLocationCoordinate2D centerCoord = [self convertPoint:center toCoordinateFromView:self]; + [self setCenterCoordinate:centerCoord animated:animateSelection]; + } } - (MGLCompactCalloutView *)calloutViewForAnnotation:(id <MGLAnnotation>)annotation @@ -4591,6 +4653,7 @@ public: animated:animated]; } + #pragma mark Annotation Image Delegate - (void)annotationImageNeedsRedisplay:(MGLAnnotationImage *)annotationImage diff --git a/platform/ios/test/MGLAnnotationViewTests.m b/platform/ios/test/MGLAnnotationViewTests.m index fc4f35a9e1..c8b140b274 100644 --- a/platform/ios/test/MGLAnnotationViewTests.m +++ b/platform/ios/test/MGLAnnotationViewTests.m @@ -51,12 +51,15 @@ static NSString * const MGLTestAnnotationReuseIdentifer = @"MGLTestAnnotationReu - (void)presentCalloutFromRect:(CGRect)rect inView:(UIView *)view constrainedToView:(UIView *)constrainedView animated:(BOOL)animated { } +- (void)presentCalloutFromRect:(CGRect)rect inView:(nonnull UIView *)view constrainedToRect:(CGRect)constrainedRect animated:(BOOL)animated {} + @end @interface MGLAnnotationViewTests : XCTestCase <MGLMapViewDelegate> @property (nonatomic) XCTestExpectation *expectation; @property (nonatomic) MGLMapView *mapView; @property (nonatomic, weak) MGLAnnotationView *annotationView; +@property (nonatomic) NSInteger annotationSelectedCount; @end @implementation MGLAnnotationViewTests @@ -98,6 +101,61 @@ static NSString * const MGLTestAnnotationReuseIdentifer = @"MGLTestAnnotationReu XCTAssertNotNil(customAnnotationView); } +- (void)testSelectingOffscreenAnnotation +{ + // Partial test for https://github.com/mapbox/mapbox-gl-native/issues/9790 + + // This isn't quite the same as in updateAnnotationViews, but should be sufficient for this test. + MGLCoordinateBounds coordinateBounds = [_mapView convertRect:_mapView.bounds toCoordinateBoundsFromView:_mapView]; + + // -90 latitude is invalid. TBD. + BOOL anyOffscreen = NO; + NSInteger selectionCount = 0; + + for (NSInteger latitude = -89; latitude <= 90; latitude += 10) + { + for (NSInteger longitude = -180; longitude <= 180; longitude += 10) + { + MGLTestAnnotation *annotation = [[MGLTestAnnotation alloc] init]; + + annotation.coordinate = CLLocationCoordinate2DMake(latitude, longitude); + [_mapView addAnnotation:annotation]; + + if (!(MGLCoordinateInCoordinateBounds(annotation.coordinate, coordinateBounds))) + anyOffscreen = YES; + + XCTAssertNil(_mapView.selectedAnnotations.firstObject, @"There should be no selected annotation"); + + // First selection + [_mapView selectAnnotation:annotation animated:NO]; + selectionCount++; + + XCTAssert(_mapView.selectedAnnotations.count == 1, @"There should only be 1 selected annotation"); + XCTAssertEqualObjects(_mapView.selectedAnnotations.firstObject, annotation, @"The annotation should be selected"); + + // Deselect + [_mapView deselectAnnotation:annotation animated:NO]; + XCTAssert(_mapView.selectedAnnotations.count == 0, @"There should be no selected annotations"); + + // Second selection + _mapView.selectedAnnotations = @[annotation]; + selectionCount++; + + XCTAssert(_mapView.selectedAnnotations.count == 1, @"There should be 1 selected annotation"); + XCTAssertEqualObjects(_mapView.selectedAnnotations.firstObject, annotation, @"The annotation should be selected"); + + // Deselect + [_mapView deselectAnnotation:annotation animated:NO]; + XCTAssert(_mapView.selectedAnnotations.count == 0, @"There should be no selected annotations"); + } + } + + XCTAssert(anyOffscreen, @"At least one of these annotations should be offscreen"); + XCTAssertEqual(selectionCount, self.annotationSelectedCount, @"-mapView:didSelectAnnotation: should be called for each selection"); +} + +#pragma mark - MGLMapViewDelegate - + - (MGLAnnotationView *)mapView:(MGLMapView *)mapView viewForAnnotation:(id<MGLAnnotation>)annotation { MGLAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:MGLTestAnnotationReuseIdentifer]; @@ -117,4 +175,9 @@ static NSString * const MGLTestAnnotationReuseIdentifer = @"MGLTestAnnotationReu [_expectation fulfill]; } +- (void)mapView:(MGLMapView *)mapView didSelectAnnotation:(id<MGLAnnotation>)annotation +{ + self.annotationSelectedCount++; +} + @end diff --git a/platform/ios/vendor/SMCalloutView/SMCalloutView.h b/platform/ios/vendor/SMCalloutView/SMCalloutView.h index 0b14913626..5bb73d4c84 100755 --- a/platform/ios/vendor/SMCalloutView/SMCalloutView.h +++ b/platform/ios/vendor/SMCalloutView/SMCalloutView.h @@ -114,6 +114,18 @@ extern NSTimeInterval const kMGLSMCalloutViewRepositionDelayForUIScrollView; - (void)presentCalloutFromRect:(CGRect)rect inView:(UIView *)view constrainedToView:(UIView *)constrainedView animated:(BOOL)animated; /** + @brief Presents a callout view by adding it to "inView" and pointing at the given rect of inView's bounds. + + @discussion Constrains the callout to the rect (in the space of the given view). + + @param rect @c CGRect to present the view from + @param view view to 'constrain' the @c constrainedView to + @param constrainedRect Rect to constrain the callout to + @param animated @c BOOL if presentation should be animated + */ +- (void)presentCalloutFromRect:(CGRect)rect inView:(UIView *)view constrainedToRect:(CGRect)constrainedRect animated:(BOOL)animated; + +/** @brief Present a callout layer in the `layer` and pointing at the given rect of the `layer` bounds @discussion Same as the view-based presentation, but inserts the callout into a CALayer hierarchy instead. diff --git a/platform/ios/vendor/SMCalloutView/SMCalloutView.m b/platform/ios/vendor/SMCalloutView/SMCalloutView.m index 9631ca0367..a0049a3e2d 100755 --- a/platform/ios/vendor/SMCalloutView/SMCalloutView.m +++ b/platform/ios/vendor/SMCalloutView/SMCalloutView.m @@ -247,6 +247,35 @@ NSTimeInterval const kMGLSMCalloutViewRepositionDelayForUIScrollView = 1.0/3.0; return CGSizeMake(nudgeLeft ? nudgeLeft : nudgeRight, nudgeTop ? nudgeTop : nudgeBottom); } +- (UIEdgeInsets)marginInsetsHintForPresentationFromRect:(CGRect)rect { + + // form our subviews based on our content set so far + [self rebuildSubviews]; + + // size the callout to fit the width constraint as best as possible + CGFloat height = self.calloutHeight; + CGSize size = [self sizeThatFits:CGSizeMake(0.0f, height)]; + + // Without re-jigging presentCalloutFromRect, let's just make a best-guess with what we have + // right now. + CGFloat horizontalMargin = fmaxf(0, ceilf((CALLOUT_MIN_WIDTH-rect.size.width)/2)); + + UIEdgeInsets insets = { + .top = 0.0f, + .right = -horizontalMargin, + .bottom = 0.0f, + .left = -horizontalMargin + }; + + if (self.permittedArrowDirection == MGLSMCalloutArrowDirectionUp) + insets.bottom -= size.height; + else + insets.top -= size.height; + + return insets; +} + + - (void)presentCalloutFromRect:(CGRect)rect inView:(UIView *)view constrainedToView:(UIView *)constrainedView animated:(BOOL)animated { [self presentCalloutFromRect:rect inLayer:view.layer ofView:view constrainedToLayer:constrainedView.layer animated:animated]; } @@ -255,8 +284,18 @@ NSTimeInterval const kMGLSMCalloutViewRepositionDelayForUIScrollView = 1.0/3.0; [self presentCalloutFromRect:rect inLayer:layer ofView:nil constrainedToLayer:constrainedLayer animated:animated]; } -// this private method handles both CALayer and UIView parents depending on what's passed. - (void)presentCalloutFromRect:(CGRect)rect inLayer:(CALayer *)layer ofView:(UIView *)view constrainedToLayer:(CALayer *)constrainedLayer animated:(BOOL)animated { + // figure out the constrained view's rect in our popup view's coordinate system + CGRect constrainedRect = [constrainedLayer convertRect:constrainedLayer.bounds toLayer:layer]; + [self presentCalloutFromRect:rect inLayer:layer ofView:view constrainedToRect:constrainedRect animated:animated]; +} + +- (void)presentCalloutFromRect:(CGRect)rect inView:(UIView *)view constrainedToRect:(CGRect)constrainedRect animated:(BOOL)animated { + [self presentCalloutFromRect:rect inLayer:view.layer ofView:view constrainedToRect:constrainedRect animated:animated]; +} + +// this private method handles both CALayer and UIView parents depending on what's passed. +- (void)presentCalloutFromRect:(CGRect)rect inLayer:(CALayer *)layer ofView:(UIView *)view constrainedToRect:(CGRect)constrainedRect animated:(BOOL)animated { // Sanity check: dismiss this callout immediately if it's displayed somewhere if (self.layer.superlayer) [self dismissCalloutAnimated:NO]; @@ -265,8 +304,6 @@ NSTimeInterval const kMGLSMCalloutViewRepositionDelayForUIScrollView = 1.0/3.0; [self.layer removeAnimationForKey:@"present"]; [self.layer removeAnimationForKey:@"dismiss"]; - // figure out the constrained view's rect in our popup view's coordinate system - CGRect constrainedRect = [constrainedLayer convertRect:constrainedLayer.bounds toLayer:layer]; // apply our edge constraints constrainedRect = UIEdgeInsetsInsetRect(constrainedRect, self.constrainedInsets); diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md index 6de2a4e87e..fd22589473 100644 --- a/platform/macos/CHANGELOG.md +++ b/platform/macos/CHANGELOG.md @@ -25,6 +25,8 @@ ### Annotations * Fix an issue where a wrong annotation may selected if annotations were set close together. ([#11438](https://github.com/mapbox/mapbox-gl-native/pull/11438)) +* The `MGLMapView.selectedAnnotations` property (backed by `-[MGLMapView setSelectedAnnotations:]`) now selects annotations that are off-screen. ([#9790](https://github.com/mapbox/mapbox-gl-native/issues/9790)) +* The `animated` parameter to `-[MGLMapView selectAnnotation:animated:]` now controls whether the annotation and its callout are brought on-screen. If `animated` is `NO` then the annotation is selected if offscreen, but the map is not panned. Currently only point annotations are supported.([#3249](https://github.com/mapbox/mapbox-gl-native/issues/3249)) ### Map snapshots diff --git a/platform/macos/app/Base.lproj/MainMenu.xib b/platform/macos/app/Base.lproj/MainMenu.xib index 4cf8d87653..8f0aeaf69c 100644 --- a/platform/macos/app/Base.lproj/MainMenu.xib +++ b/platform/macos/app/Base.lproj/MainMenu.xib @@ -545,7 +545,13 @@ <action selector="drawAnimatedAnnotation:" target="-1" id="CYM-WB-s97"/> </connections> </menuItem> - <menuItem title="Show All Annnotations" keyEquivalent="A" id="yMj-uM-8SN"> + <menuItem title="Select an Offscreen Point Annotation" id="Xy2-Cc-RUB"> + <modifierMask key="keyEquivalentModifierMask"/> + <connections> + <action selector="selectOffscreenPointAnnotation:" target="-1" id="Fhm-l3-G6h"/> + </connections> + </menuItem> + <menuItem title="Show All Annotations" keyEquivalent="A" id="yMj-uM-8SN"> <modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/> <connections> <action selector="showAllAnnotations:" target="-1" id="ahr-OR-Em2"/> @@ -664,7 +670,7 @@ CA <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/> <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> <rect key="contentRect" x="109" y="131" width="350" height="84"/> - <rect key="screenRect" x="0.0" y="0.0" width="1280" height="777"/> + <rect key="screenRect" x="0.0" y="0.0" width="1440" height="877"/> <view key="contentView" id="eA4-n3-qPe"> <rect key="frame" x="0.0" y="0.0" width="350" height="84"/> <autoresizingMask key="autoresizingMask"/> @@ -740,7 +746,7 @@ CA <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES" utility="YES"/> <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> <rect key="contentRect" x="830" y="430" width="400" height="300"/> - <rect key="screenRect" x="0.0" y="0.0" width="1280" height="777"/> + <rect key="screenRect" x="0.0" y="0.0" width="1440" height="877"/> <view key="contentView" id="8ha-hw-zOD"> <rect key="frame" x="0.0" y="0.0" width="400" height="300"/> <autoresizingMask key="autoresizingMask"/> @@ -748,11 +754,11 @@ CA <scrollView autohidesScrollers="YES" horizontalLineScroll="19" horizontalPageScroll="10" verticalLineScroll="19" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Q8b-0e-dLv"> <rect key="frame" x="-1" y="20" width="402" height="281"/> <clipView key="contentView" id="J9U-Yx-o2S"> - <rect key="frame" x="1" y="0.0" width="400" height="280"/> + <rect key="frame" x="1" y="0.0" width="400" height="265"/> <autoresizingMask key="autoresizingMask"/> <subviews> <tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" autosaveColumns="NO" headerView="MAZ-Iq-hBi" id="Ato-Vu-HYT"> - <rect key="frame" x="0.0" y="0.0" width="423" height="257"/> + <rect key="frame" x="0.0" y="0.0" width="423" height="242"/> <autoresizingMask key="autoresizingMask"/> <size key="intercellSpacing" width="3" height="2"/> <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/> @@ -891,7 +897,7 @@ CA </subviews> </clipView> <scroller key="horizontalScroller" verticalHuggingPriority="750" horizontal="YES" id="QLr-6P-Ogs"> - <rect key="frame" x="1" y="264" width="400" height="16"/> + <rect key="frame" x="1" y="265" width="400" height="15"/> <autoresizingMask key="autoresizingMask"/> </scroller> <scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="q0K-eE-mzL"> diff --git a/platform/macos/app/MapDocument.m b/platform/macos/app/MapDocument.m index 03557caca2..23bc652229 100644 --- a/platform/macos/app/MapDocument.m +++ b/platform/macos/app/MapDocument.m @@ -641,6 +641,51 @@ NS_ARRAY_OF(id <MGLAnnotation>) *MBXFlattenedShapes(NS_ARRAY_OF(id <MGLAnnotatio repeats:YES]; } + +- (id<MGLAnnotation>)randomOffscreenPointAnnotation { + + NSPredicate *pointAnnotationPredicate = [NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) { + return [evaluatedObject isKindOfClass:[MGLPointAnnotation class]]; + }]; + + NSArray *annotations = [self.mapView.annotations filteredArrayUsingPredicate:pointAnnotationPredicate]; + + if (annotations.count == 0) { + return nil; + } + + // NOTE: self.mapView.visibleAnnotations occasionally returns nil - see + // https://github.com/mapbox/mapbox-gl-native/issues/11296 + NSArray *visibleAnnotations = [self.mapView.visibleAnnotations filteredArrayUsingPredicate:pointAnnotationPredicate]; + + NSLog(@"Number of visible point annotations = %ld", visibleAnnotations.count); + + if (visibleAnnotations.count == annotations.count) { + return nil; + } + + NSMutableArray *invisibleAnnotations = [annotations mutableCopy]; + + if (visibleAnnotations.count > 0) { + [invisibleAnnotations removeObjectsInArray:visibleAnnotations]; + } + + // Now pick a random offscreen annotation. + uint32_t index = arc4random_uniform((uint32_t)invisibleAnnotations.count); + return invisibleAnnotations[index]; +} + +- (IBAction)selectOffscreenPointAnnotation:(id)sender { + id<MGLAnnotation> annotation = [self randomOffscreenPointAnnotation]; + if (annotation) { + [self.mapView selectAnnotation:annotation]; + + // Alternative method to select the annotation. These two should do the same thing. + // self.mapView.selectedAnnotations = @[annotation]; + NSAssert(self.mapView.selectedAnnotations.firstObject, @"The annotation was not selected"); + } +} + - (void)updateAnimatedAnnotation:(NSTimer *)timer { DroppedPinAnnotation *annotation = timer.userInfo; double angle = timer.fireDate.timeIntervalSinceReferenceDate; @@ -1111,6 +1156,9 @@ NS_ARRAY_OF(id <MGLAnnotation>) *MBXFlattenedShapes(NS_ARRAY_OF(id <MGLAnnotatio if (menuItem.action == @selector(insertGraticuleLayer:)) { return ![self.mapView.style sourceWithIdentifier:@"graticule"]; } + if (menuItem.action == @selector(selectOffscreenPointAnnotation:)) { + return YES; + } if (menuItem.action == @selector(showAllAnnotations:) || menuItem.action == @selector(removeAllAnnotations:)) { return self.mapView.annotations.count > 0; } diff --git a/platform/macos/src/MGLMapView.h b/platform/macos/src/MGLMapView.h index 050145b80b..74224622d4 100644 --- a/platform/macos/src/MGLMapView.h +++ b/platform/macos/src/MGLMapView.h @@ -721,16 +721,27 @@ MGL_EXPORT IB_DESIGNABLE Assigning a new array to this property selects only the first annotation in the array. + + If the annotation is of type `MGLPointAnnotation` and is offscreen, the map is + panned so that the annotation and its callout are brought just onscreen. The + annotation is *not* centered within the viewport. + + @note In versions prior to `4.0.0` if the annotation was offscreen it was not + selected. */ @property (nonatomic, copy) NS_ARRAY_OF(id <MGLAnnotation>) *selectedAnnotations; /** Selects an annotation and displays a callout popover for it. - If the given annotation is not visible within the current viewport, this method - has no effect. + If the annotation is of type `MGLPointAnnotation` and is offscreen, the map is + panned so that the annotation and its callout are brought just onscreen. The + annotation is *not* centered within the viewport. @param annotation The annotation object to select. + + @note In versions prior to `4.0.0` selecting an offscreen annotation did not + change the camera. */ - (void)selectAnnotation:(id <MGLAnnotation>)annotation; diff --git a/platform/macos/src/MGLMapView.mm b/platform/macos/src/MGLMapView.mm index 353b2bf2f1..9cab9a76da 100644 --- a/platform/macos/src/MGLMapView.mm +++ b/platform/macos/src/MGLMapView.mm @@ -97,6 +97,9 @@ const CGFloat MGLAnnotationImagePaddingForHitTest = 4; /// Distance from the callout’s anchor point to the annotation it points to. const CGFloat MGLAnnotationImagePaddingForCallout = 4; +/// Padding to edge of view that an offscreen annotation must have when being brought onscreen (by being selected) +const NSEdgeInsets MGLMapViewOffscreenAnnotationPadding = NSEdgeInsetsMake(-30.0f, -30.0f, -30.0f, -30.0f); + /// Unique identifier representing a single annotation in mbgl. typedef uint32_t MGLAnnotationTag; @@ -2205,10 +2208,12 @@ public: return; } - // Select the annotation if it’s visible. - if (MGLCoordinateInCoordinateBounds(firstAnnotation.coordinate, self.visibleCoordinateBounds)) { - [self selectAnnotation:firstAnnotation]; - } + [self selectAnnotation:firstAnnotation]; +} + +- (BOOL)isBringingAnnotationOnscreenSupportedForAnnotation:(id<MGLAnnotation>)annotation animated:(BOOL)animated { + // Consider delegating + return animated && [annotation isKindOfClass:[MGLPointAnnotation class]]; } - (void)selectAnnotation:(id <MGLAnnotation>)annotation @@ -2218,6 +2223,11 @@ public: - (void)selectAnnotation:(id <MGLAnnotation>)annotation atPoint:(NSPoint)gesturePoint { + [self selectAnnotation:annotation atPoint:gesturePoint moveOnscreen:YES animateSelection:YES]; +} + +- (void)selectAnnotation:(id <MGLAnnotation>)annotation atPoint:(NSPoint)gesturePoint moveOnscreen:(BOOL)moveOnscreen animateSelection:(BOOL)animateSelection +{ id <MGLAnnotation> selectedAnnotation = self.selectedAnnotation; if (annotation == selectedAnnotation) { return; @@ -2232,9 +2242,14 @@ public: [self addAnnotation:annotation]; } + if (moveOnscreen) { + moveOnscreen = [self isBringingAnnotationOnscreenSupportedForAnnotation:annotation animated:animateSelection]; + } + // The annotation's anchor will bounce to the current click. NSRect positioningRect = [self positioningRectForCalloutForAnnotationWithTag:annotationTag]; - if (NSIsEmptyRect(NSIntersectionRect(positioningRect, self.bounds))) { + + if (!moveOnscreen && NSIsEmptyRect(NSIntersectionRect(positioningRect, self.bounds))) { positioningRect = CGRectMake(gesturePoint.x, gesturePoint.y, positioningRect.size.width, positioningRect.size.height); } @@ -2254,11 +2269,65 @@ public: // alignment rect, or off the left edge in a right-to-left UI. callout.delegate = self; self.calloutForSelectedAnnotation = callout; + NSRectEdge edge = (self.userInterfaceLayoutDirection == NSUserInterfaceLayoutDirectionRightToLeft ? NSMinXEdge : NSMaxXEdge); + + // The following will do nothing if the positioning rect is not on-screen. See + // `-[MGLMapView updateAnnotationCallouts]` for presenting the callout when the selected + // annotation comes back on-screen. [callout showRelativeToRect:positioningRect ofView:self preferredEdge:edge]; } + + if (moveOnscreen) + { + moveOnscreen = NO; + + NSRect (^edgeInsetsInsetRect)(NSRect, NSEdgeInsets) = ^(NSRect rect, NSEdgeInsets insets) { + return NSMakeRect(rect.origin.x + insets.left, + rect.origin.y + insets.top, + rect.size.width - insets.left - insets.right, + rect.size.height - insets.top - insets.bottom); + }; + + // Add padding around the positioning rect (in essence an inset from the edge of the viewport + NSRect expandedPositioningRect = edgeInsetsInsetRect(positioningRect, MGLMapViewOffscreenAnnotationPadding); + + // Used for callout positioning, and moving offscreen annotations onscreen. + CGRect constrainedRect = edgeInsetsInsetRect(self.bounds, self.contentInsets); + CGRect bounds = constrainedRect; + + // Any one of these cases should trigger a move onscreen + if (CGRectGetMinX(positioningRect) < CGRectGetMinX(bounds)) + { + constrainedRect.origin.x = expandedPositioningRect.origin.x; + moveOnscreen = YES; + } + else if (CGRectGetMaxX(positioningRect) > CGRectGetMaxX(bounds)) + { + constrainedRect.origin.x = CGRectGetMaxX(expandedPositioningRect) - constrainedRect.size.width; + moveOnscreen = YES; + } + + if (CGRectGetMinY(positioningRect) < CGRectGetMinY(bounds)) + { + constrainedRect.origin.y = expandedPositioningRect.origin.y; + moveOnscreen = YES; + } + else if (CGRectGetMaxY(positioningRect) > CGRectGetMaxY(bounds)) + { + constrainedRect.origin.y = CGRectGetMaxY(expandedPositioningRect) - constrainedRect.size.height; + moveOnscreen = YES; + } + + if (moveOnscreen) + { + CGPoint center = CGPointMake(CGRectGetMidX(constrainedRect), CGRectGetMidY(constrainedRect)); + CLLocationCoordinate2D centerCoord = [self convertPoint:center toCoordinateFromView:self]; + [self setCenterCoordinate:centerCoord animated:animateSelection]; + } + } } - (void)showAnnotations:(NS_ARRAY_OF(id <MGLAnnotation>) *)annotations animated:(BOOL)animated { @@ -2393,7 +2462,25 @@ public: - (void)updateAnnotationCallouts { NSPopover *callout = self.calloutForSelectedAnnotation; if (callout) { - callout.positioningRect = [self positioningRectForCalloutForAnnotationWithTag:_selectedAnnotationTag]; + NSRect rect = [self positioningRectForCalloutForAnnotationWithTag:_selectedAnnotationTag]; + + if (!NSIsEmptyRect(NSIntersectionRect(rect, self.bounds))) { + + // It's possible that the current callout hasn't been presented (since the original + // positioningRect was offscreen). We can check that the callout has a valid window + // This results in the callout being presented just as the annotation comes on screen + // which matches MapKit, but (currently) not iOS. + if (!callout.contentViewController.view.window) { + NSRectEdge edge = (self.userInterfaceLayoutDirection == NSUserInterfaceLayoutDirectionRightToLeft + ? NSMinXEdge + : NSMaxXEdge); + // Re-present the callout + [callout showRelativeToRect:rect ofView:self preferredEdge:edge]; + } + else { + callout.positioningRect = rect; + } + } } } diff --git a/platform/node/test/ignores.json b/platform/node/test/ignores.json index 840daa2049..79debab587 100644 --- a/platform/node/test/ignores.json +++ b/platform/node/test/ignores.json @@ -36,10 +36,8 @@ "render-tests/regressions/mapbox-gl-js#2769": "https://github.com/mapbox/mapbox-gl-native/issues/10619", "render-tests/regressions/mapbox-gl-js#3682": "https://github.com/mapbox/mapbox-gl-js/issues/3682", "render-tests/regressions/mapbox-gl-js#5370": "skip - https://github.com/mapbox/mapbox-gl-native/pull/9439", - "render-tests/regressions/mapbox-gl-js#5599": "https://github.com/mapbox/mapbox-gl-native/issues/10399", "render-tests/regressions/mapbox-gl-js#5740": "https://github.com/mapbox/mapbox-gl-native/issues/10619", "render-tests/regressions/mapbox-gl-js#5982": "https://github.com/mapbox/mapbox-gl-native/issues/10619", - "render-tests/regressions/mapbox-gl-js#6160": "https://github.com/mapbox/mapbox-gl-native/pull/11206", "render-tests/regressions/mapbox-gl-native#7357": "https://github.com/mapbox/mapbox-gl-native/issues/7357", "render-tests/runtime-styling/image-add-sdf": "https://github.com/mapbox/mapbox-gl-native/issues/9847", "render-tests/runtime-styling/paint-property-fill-flat-to-extrude": "https://github.com/mapbox/mapbox-gl-native/issues/6745", diff --git a/src/mbgl/layout/symbol_layout.cpp b/src/mbgl/layout/symbol_layout.cpp index 3bf85407c6..82a9255824 100644 --- a/src/mbgl/layout/symbol_layout.cpp +++ b/src/mbgl/layout/symbol_layout.cpp @@ -126,7 +126,7 @@ SymbolLayout::SymbolLayout(const BucketParameters& parameters, if (hasText) { std::string u8string = layout.evaluate<TextField>(zoom, ft); - if (layout.get<TextField>().isConstant()) { + if (layout.get<TextField>().isConstant() && !leader.layout.get<TextField>().isExpression()) { u8string = util::replaceTokens(u8string, getValue); } @@ -159,7 +159,7 @@ SymbolLayout::SymbolLayout(const BucketParameters& parameters, if (hasIcon) { std::string icon = layout.evaluate<IconImage>(zoom, ft); - if (layout.get<IconImage>().isConstant()) { + if (layout.get<IconImage>().isConstant() && !leader.layout.get<IconImage>().isExpression()) { icon = util::replaceTokens(icon, getValue); } ft.icon = icon; diff --git a/src/mbgl/programs/symbol_program.hpp b/src/mbgl/programs/symbol_program.hpp index a14afac702..9b5037ed9f 100644 --- a/src/mbgl/programs/symbol_program.hpp +++ b/src/mbgl/programs/symbol_program.hpp @@ -61,8 +61,8 @@ struct SymbolLayoutAttributes : gl::Attributes< {{ static_cast<int16_t>(labelAnchor.x), static_cast<int16_t>(labelAnchor.y), - static_cast<int16_t>(::round(o.x * 64)), // use 1/64 pixels for placement - static_cast<int16_t>(::round((o.y + glyphOffsetY) * 64)) + static_cast<int16_t>(::round(o.x * 32)), // use 1/32 pixels for placement + static_cast<int16_t>(::round((o.y + glyphOffsetY) * 32)) }}, {{ tx, diff --git a/src/mbgl/shaders/collision_box.cpp b/src/mbgl/shaders/collision_box.cpp index 9d11640bf4..bc5d9bc6f9 100644 --- a/src/mbgl/shaders/collision_box.cpp +++ b/src/mbgl/shaders/collision_box.cpp @@ -22,7 +22,10 @@ varying float v_notUsed; void main() { vec4 projectedPoint = u_matrix * vec4(a_anchor_pos, 0, 1); highp float camera_to_anchor_distance = projectedPoint.w; - highp float collision_perspective_ratio = 0.5 + 0.5 * (u_camera_to_center_distance / camera_to_anchor_distance); + highp float collision_perspective_ratio = clamp( + 0.5 + 0.5 * (u_camera_to_center_distance / camera_to_anchor_distance), + 0.0, // Prevents oversized near-field boxes in pitched/overzoomed tiles + 4.0); gl_Position = u_matrix * vec4(a_pos, 0.0, 1.0); gl_Position.xy += a_extrude * u_extrude_scale * gl_Position.w * collision_perspective_ratio; diff --git a/src/mbgl/shaders/symbol_icon.cpp b/src/mbgl/shaders/symbol_icon.cpp index f5c2bbe22d..c037c81005 100644 --- a/src/mbgl/shaders/symbol_icon.cpp +++ b/src/mbgl/shaders/symbol_icon.cpp @@ -80,7 +80,10 @@ void main() { highp float distance_ratio = u_pitch_with_map ? camera_to_anchor_distance / u_camera_to_center_distance : u_camera_to_center_distance / camera_to_anchor_distance; - highp float perspective_ratio = 0.5 + 0.5 * distance_ratio; + highp float perspective_ratio = clamp( + 0.5 + 0.5 * distance_ratio, + 0.0, // Prevents oversized near-field symbols in pitched/overzoomed tiles + 4.0); size *= perspective_ratio; @@ -102,7 +105,7 @@ void main() { mat2 rotation_matrix = mat2(angle_cos, -1.0 * angle_sin, angle_sin, angle_cos); vec4 projected_pos = u_label_plane_matrix * vec4(a_projected_pos.xy, 0.0, 1.0); - gl_Position = u_gl_coord_matrix * vec4(projected_pos.xy / projected_pos.w + rotation_matrix * (a_offset / 64.0 * fontScale), 0.0, 1.0); + gl_Position = u_gl_coord_matrix * vec4(projected_pos.xy / projected_pos.w + rotation_matrix * (a_offset / 32.0 * fontScale), 0.0, 1.0); v_tex = a_tex / u_texsize; vec2 fade_opacity = unpack_opacity(a_fade_opacity); diff --git a/src/mbgl/shaders/symbol_sdf.cpp b/src/mbgl/shaders/symbol_sdf.cpp index 441eaf7aac..b584c00315 100644 --- a/src/mbgl/shaders/symbol_sdf.cpp +++ b/src/mbgl/shaders/symbol_sdf.cpp @@ -156,7 +156,10 @@ void main() { highp float distance_ratio = u_pitch_with_map ? camera_to_anchor_distance / u_camera_to_center_distance : u_camera_to_center_distance / camera_to_anchor_distance; - highp float perspective_ratio = 0.5 + 0.5 * distance_ratio; + highp float perspective_ratio = clamp( + 0.5 + 0.5 * distance_ratio, + 0.0, // Prevents oversized near-field symbols in pitched/overzoomed tiles + 4.0); size *= perspective_ratio; @@ -180,7 +183,7 @@ void main() { mat2 rotation_matrix = mat2(angle_cos, -1.0 * angle_sin, angle_sin, angle_cos); vec4 projected_pos = u_label_plane_matrix * vec4(a_projected_pos.xy, 0.0, 1.0); - gl_Position = u_gl_coord_matrix * vec4(projected_pos.xy / projected_pos.w + rotation_matrix * (a_offset / 64.0 * fontScale), 0.0, 1.0); + gl_Position = u_gl_coord_matrix * vec4(projected_pos.xy / projected_pos.w + rotation_matrix * (a_offset / 32.0 * fontScale), 0.0, 1.0); float gamma_scale = gl_Position.w; vec2 tex = a_tex / u_texsize; diff --git a/src/mbgl/text/placement.cpp b/src/mbgl/text/placement.cpp index fc24c78902..54b2b7539b 100644 --- a/src/mbgl/text/placement.cpp +++ b/src/mbgl/text/placement.cpp @@ -95,9 +95,6 @@ void Placement::placeLayerBucket( auto partiallyEvaluatedTextSize = bucket.textSizeBinder->evaluateForZoom(state.getZoom()); auto partiallyEvaluatedIconSize = bucket.iconSizeBinder->evaluateForZoom(state.getZoom()); - const bool iconWithoutText = !bucket.hasTextData() || bucket.layout.get<style::TextOptional>(); - const bool textWithoutIcon = !bucket.hasIconData() || bucket.layout.get<style::IconOptional>(); - for (auto& symbolInstance : bucket.symbolInstances) { if (seenCrossTileIDs.count(symbolInstance.crossTileID) == 0) { @@ -140,6 +137,9 @@ void Placement::placeLayerBucket( offscreen &= placed.second; } + const bool iconWithoutText = !symbolInstance.hasText || bucket.layout.get<style::TextOptional>(); + const bool textWithoutIcon = !symbolInstance.hasIcon || bucket.layout.get<style::IconOptional>(); + // combine placements for icon and text if (!iconWithoutText && !textWithoutIcon) { placeText = placeIcon = placeText && placeIcon; |