From 5e08ce062ee60e01c6b589fc8963b4f1babc2095 Mon Sep 17 00:00:00 2001 From: Fabian Guerra Soto Date: Wed, 4 Oct 2017 17:35:06 -0400 Subject: [ios] Update changelog and bump pod spec. (#10122) --- platform/ios/CHANGELOG.md | 2 +- platform/ios/Mapbox-iOS-SDK-nightly-dynamic.podspec | 2 +- platform/ios/Mapbox-iOS-SDK-symbols.podspec | 2 +- platform/ios/Mapbox-iOS-SDK.podspec | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 1a8190990b..c28ed7cd19 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -2,7 +2,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONTRIBUTING.md](../../CONTRIBUTING.md) to get started. -## master +## 3.7.0 ### Networking and storage diff --git a/platform/ios/Mapbox-iOS-SDK-nightly-dynamic.podspec b/platform/ios/Mapbox-iOS-SDK-nightly-dynamic.podspec index 1abcaf467c..72b378b512 100644 --- a/platform/ios/Mapbox-iOS-SDK-nightly-dynamic.podspec +++ b/platform/ios/Mapbox-iOS-SDK-nightly-dynamic.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |m| - version = '3.7.0-alpha.1' + version = '3.7.0-beta.1' m.name = 'Mapbox-iOS-SDK-nightly-dynamic' m.version = "#{version}-nightly" diff --git a/platform/ios/Mapbox-iOS-SDK-symbols.podspec b/platform/ios/Mapbox-iOS-SDK-symbols.podspec index ceb7f21c47..acda3f277f 100644 --- a/platform/ios/Mapbox-iOS-SDK-symbols.podspec +++ b/platform/ios/Mapbox-iOS-SDK-symbols.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |m| - version = '3.7.0-alpha.1' + version = '3.7.0-beta.1' m.name = 'Mapbox-iOS-SDK-symbols' m.version = "#{version}-symbols" diff --git a/platform/ios/Mapbox-iOS-SDK.podspec b/platform/ios/Mapbox-iOS-SDK.podspec index bcd37d0d06..b8cdbe0580 100644 --- a/platform/ios/Mapbox-iOS-SDK.podspec +++ b/platform/ios/Mapbox-iOS-SDK.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |m| - version = '3.7.0-alpha.1' + version = '3.7.0-beta.1' m.name = 'Mapbox-iOS-SDK' m.version = version -- cgit v1.2.1 From 2a9752d11ce15e8c2619da64d548f3d341bea8c9 Mon Sep 17 00:00:00 2001 From: Tobrun Van Nuland Date: Thu, 5 Oct 2017 15:23:38 +0200 Subject: [android] - build nightly builds from release-agua --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index 69f5403d9c..39b00e8ed0 100644 --- a/circle.yml +++ b/circle.yml @@ -294,7 +294,7 @@ jobs: - deploy: name: Publish to Maven command: | - if [ "${CIRCLE_BRANCH}" == "master" ]; then make run-android-upload-archives ; fi + if [ "${CIRCLE_BRANCH}" == "release-agua" ]; then make run-android-upload-archives ; fi # ------------------------------------------------------------------------------ -- cgit v1.2.1 From 94312acd8b00bd05009f21ed8e0bbfaec65d5a01 Mon Sep 17 00:00:00 2001 From: Tobrun Van Nuland Date: Fri, 6 Oct 2017 13:47:01 +0200 Subject: [android] - allow multiple listeners for camera events, deprecate old api --- .../mapboxsdk/maps/CameraChangeDispatcher.java | 74 +++++++ .../java/com/mapbox/mapboxsdk/maps/MapboxMap.java | 84 ++++++++ .../mapboxsdk/maps/CameraChangeDispatcherTest.java | 87 +++++++++ .../activity/camera/CameraPositionActivity.java | 212 ++++++++++++--------- 4 files changed, 369 insertions(+), 88 deletions(-) create mode 100644 platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/CameraChangeDispatcherTest.java diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CameraChangeDispatcher.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CameraChangeDispatcher.java index 6f7d7c0080..e3ebc74ae3 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CameraChangeDispatcher.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CameraChangeDispatcher.java @@ -1,5 +1,10 @@ package com.mapbox.mapboxsdk.maps; +import android.support.annotation.NonNull; + +import java.util.ArrayList; +import java.util.List; + import static com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraIdleListener; import static com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraMoveCanceledListener; import static com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraMoveListener; @@ -10,6 +15,11 @@ class CameraChangeDispatcher implements MapboxMap.OnCameraMoveStartedListener, M private boolean idle = true; + private final List onCameraMoveStartedListenerList = new ArrayList<>(); + private final List onCameraMoveCanceledListenerList = new ArrayList<>(); + private final List onCameraMoveListenerList = new ArrayList<>(); + private final List onCameraIdleListenerList = new ArrayList<>(); + private OnCameraMoveStartedListener onCameraMoveStartedListener; private OnCameraMoveCanceledListener onCameraMoveCanceledListener; private OnCameraMoveListener onCameraMoveListener; @@ -41,6 +51,12 @@ class CameraChangeDispatcher implements MapboxMap.OnCameraMoveStartedListener, M if (onCameraMoveStartedListener != null) { onCameraMoveStartedListener.onCameraMoveStarted(reason); } + + if (!onCameraMoveStartedListenerList.isEmpty()) { + for (OnCameraMoveStartedListener cameraMoveStartedListener : onCameraMoveStartedListenerList) { + cameraMoveStartedListener.onCameraMoveStarted(reason); + } + } } @Override @@ -48,6 +64,12 @@ class CameraChangeDispatcher implements MapboxMap.OnCameraMoveStartedListener, M if (onCameraMoveListener != null && !idle) { onCameraMoveListener.onCameraMove(); } + + if (!onCameraMoveListenerList.isEmpty()) { + for (OnCameraMoveListener cameraMoveListener : onCameraMoveListenerList) { + cameraMoveListener.onCameraMove(); + } + } } @Override @@ -55,6 +77,12 @@ class CameraChangeDispatcher implements MapboxMap.OnCameraMoveStartedListener, M if (onCameraMoveCanceledListener != null && !idle) { onCameraMoveCanceledListener.onCameraMoveCanceled(); } + + if (!onCameraMoveCanceledListenerList.isEmpty()) { + for (OnCameraMoveCanceledListener cameraMoveCanceledListener : onCameraMoveCanceledListenerList) { + cameraMoveCanceledListener.onCameraMoveCanceled(); + } + } } @Override @@ -64,6 +92,52 @@ class CameraChangeDispatcher implements MapboxMap.OnCameraMoveStartedListener, M if (onCameraIdleListener != null) { onCameraIdleListener.onCameraIdle(); } + + if (!onCameraIdleListenerList.isEmpty()) { + for (OnCameraIdleListener cameraIdleListener : onCameraIdleListenerList) { + cameraIdleListener.onCameraIdle(); + } + } + } + } + + void addOnCameraIdleListener(@NonNull OnCameraIdleListener listener) { + onCameraIdleListenerList.add(listener); + } + + void removeOnCameraIdleListener(@NonNull OnCameraIdleListener listener) { + if (onCameraIdleListenerList.contains(listener)) { + onCameraIdleListenerList.remove(listener); + } + } + + void addOnCameraMoveCancelListener(OnCameraMoveCanceledListener listener) { + onCameraMoveCanceledListenerList.add(listener); + } + + void removeOnCameraMoveCancelListener(OnCameraMoveCanceledListener listener) { + if (onCameraMoveCanceledListenerList.contains(listener)) { + onCameraMoveCanceledListenerList.remove(listener); + } + } + + void addOnCameraMoveStartedListener(OnCameraMoveStartedListener listener) { + onCameraMoveStartedListenerList.add(listener); + } + + void removeOnCameraMoveStartedListener(OnCameraMoveStartedListener listener) { + if (onCameraMoveStartedListenerList.contains(listener)) { + onCameraMoveStartedListenerList.remove(listener); + } + } + + void addOnCameraMoveListener(OnCameraMoveListener listener) { + onCameraMoveListenerList.add(listener); + } + + void removeOnCameraMoveListener(OnCameraMoveListener listener) { + if (onCameraMoveListenerList.contains(listener)) { + onCameraMoveListenerList.remove(listener); } } } 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 0c820d844c..a5c49dae7c 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 @@ -1725,38 +1725,122 @@ public final class MapboxMap { * Sets a callback that is invoked when camera movement has ended. * * @param listener the listener to notify + * @deprecated use {@link #addOnCameraIdleListener(OnCameraIdleListener)} + * and {@link #removeOnCameraIdleListener(OnCameraIdleListener)} instead */ + @Deprecated public void setOnCameraIdleListener(@Nullable OnCameraIdleListener listener) { cameraChangeDispatcher.setOnCameraIdleListener(listener); } + /** + * Adds a callback that is invoked when camera movement has ended. + * + * @param listener the listener to notify + */ + public void addOnCameraIdleListener(@Nullable OnCameraIdleListener listener) { + cameraChangeDispatcher.addOnCameraIdleListener(listener); + } + + /** + * Removes a callback that is invoked when camera movement has ended. + * + * @param listener the listener to remove + */ + public void removeOnCameraIdleListener(@Nullable OnCameraIdleListener listener) { + cameraChangeDispatcher.removeOnCameraIdleListener(listener); + } + /** * Sets a callback that is invoked when camera movement was cancelled. * * @param listener the listener to notify + * @deprecated use {@link #addOnCameraMoveCancelListener(OnCameraMoveCanceledListener)} and + * {@link #removeOnCameraMoveCancelListener(OnCameraMoveCanceledListener)} instead */ + @Deprecated public void setOnCameraMoveCancelListener(@Nullable OnCameraMoveCanceledListener listener) { cameraChangeDispatcher.setOnCameraMoveCanceledListener(listener); } + /** + * Adds a callback that is invoked when camera movement was cancelled. + * + * @param listener the listener to notify + */ + public void addOnCameraMoveCancelListener(@Nullable OnCameraMoveCanceledListener listener) { + cameraChangeDispatcher.addOnCameraMoveCancelListener(listener); + } + + /** + * Removes a callback that is invoked when camera movement was cancelled. + * + * @param listener the listener to remove + */ + public void removeOnCameraMoveCancelListener(@Nullable OnCameraMoveCanceledListener listener) { + cameraChangeDispatcher.removeOnCameraMoveCancelListener(listener); + } + /** * Sets a callback that is invoked when camera movement has started. * * @param listener the listener to notify + * @deprecated use {@link #addOnCameraMoveStartedListener(OnCameraMoveStartedListener)} and + * {@link #removeOnCameraMoveStartedListener(OnCameraMoveStartedListener)} instead */ + @Deprecated public void setOnCameraMoveStartedListener(@Nullable OnCameraMoveStartedListener listener) { cameraChangeDispatcher.setOnCameraMoveStartedListener(listener); } + /** + * Adds a callback that is invoked when camera movement has started. + * + * @param listener the listener to notify + */ + public void addOnCameraMoveStartedListener(@Nullable OnCameraMoveStartedListener listener) { + cameraChangeDispatcher.addOnCameraMoveStartedListener(listener); + } + + /** + * Removes a callback that is invoked when camera movement has started. + * + * @param listener the listener to remove + */ + public void removeOnCameraMoveStartedListener(@Nullable OnCameraMoveStartedListener listener) { + cameraChangeDispatcher.removeOnCameraMoveStartedListener(listener); + } + /** * Sets a callback that is invoked when camera position changes. * * @param listener the listener to notify + * @deprecated use {@link #addOnCameraMoveListener(OnCameraMoveListener)} and + * {@link #removeOnCameraMoveListener(OnCameraMoveListener)}instead */ + @Deprecated public void setOnCameraMoveListener(@Nullable OnCameraMoveListener listener) { cameraChangeDispatcher.setOnCameraMoveListener(listener); } + /** + * Adds a callback that is invoked when camera position changes. + * + * @param listener the listener to notify + */ + public void addOnCameraMoveListener(@Nullable OnCameraMoveListener listener) { + cameraChangeDispatcher.addOnCameraMoveListener(listener); + } + + /** + * Removes a callback that is invoked when camera position changes. + * + * @param listener the listener to remove + */ + public void removeOnCameraMoveListener(@Nullable OnCameraMoveListener listener) { + cameraChangeDispatcher.removeOnCameraMoveListener(listener); + } + /** * Sets a callback that's invoked on every frame rendered to the map view. * diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/CameraChangeDispatcherTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/CameraChangeDispatcherTest.java new file mode 100644 index 0000000000..090d274fe7 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/CameraChangeDispatcherTest.java @@ -0,0 +1,87 @@ +package com.mapbox.mapboxsdk.maps; + +import org.junit.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class CameraChangeDispatcherTest { + + @Test + public void testSetCameraIdleListener() { + CameraChangeDispatcher dispatcher = new CameraChangeDispatcher(); + MapboxMap.OnCameraIdleListener listener = mock(MapboxMap.OnCameraIdleListener.class); + dispatcher.setOnCameraIdleListener(listener); + dispatcher.onCameraMoveStarted(MapboxMap.OnCameraMoveStartedListener.REASON_API_GESTURE); + dispatcher.onCameraIdle(); + verify(listener).onCameraIdle(); + } + + @Test + public void testSetCameraMoveStartedListener() { + CameraChangeDispatcher dispatcher = new CameraChangeDispatcher(); + MapboxMap.OnCameraMoveStartedListener listener = mock(MapboxMap.OnCameraMoveStartedListener.class); + dispatcher.setOnCameraMoveStartedListener(listener); + dispatcher.onCameraMoveStarted(MapboxMap.OnCameraMoveStartedListener.REASON_API_GESTURE); + verify(listener).onCameraMoveStarted(MapboxMap.OnCameraMoveStartedListener.REASON_API_GESTURE); + } + + @Test + public void testSetCameraMoveCancelListener() { + CameraChangeDispatcher dispatcher = new CameraChangeDispatcher(); + MapboxMap.OnCameraMoveCanceledListener listener = mock(MapboxMap.OnCameraMoveCanceledListener.class); + dispatcher.setOnCameraMoveCanceledListener(listener); + dispatcher.onCameraMoveStarted(MapboxMap.OnCameraMoveStartedListener.REASON_API_GESTURE); + dispatcher.onCameraMoveCanceled(); + verify(listener).onCameraMoveCanceled(); + } + + @Test + public void testSetCameraMoveListener() { + CameraChangeDispatcher dispatcher = new CameraChangeDispatcher(); + MapboxMap.OnCameraMoveListener listener = mock(MapboxMap.OnCameraMoveListener.class); + dispatcher.setOnCameraMoveListener(listener); + dispatcher.onCameraMoveStarted(MapboxMap.OnCameraMoveStartedListener.REASON_API_GESTURE); + dispatcher.onCameraMove(); + verify(listener).onCameraMove(); + } + + @Test + public void testAddCameraIdleListener() { + CameraChangeDispatcher dispatcher = new CameraChangeDispatcher(); + MapboxMap.OnCameraIdleListener listener = mock(MapboxMap.OnCameraIdleListener.class); + dispatcher.addOnCameraIdleListener(listener); + dispatcher.onCameraMoveStarted(MapboxMap.OnCameraMoveStartedListener.REASON_API_GESTURE); + dispatcher.onCameraIdle(); + verify(listener).onCameraIdle(); + } + + @Test + public void testAddCameraMoveStartedListener() { + CameraChangeDispatcher dispatcher = new CameraChangeDispatcher(); + MapboxMap.OnCameraMoveStartedListener listener = mock(MapboxMap.OnCameraMoveStartedListener.class); + dispatcher.addOnCameraMoveStartedListener(listener); + dispatcher.onCameraMoveStarted(MapboxMap.OnCameraMoveStartedListener.REASON_API_GESTURE); + verify(listener).onCameraMoveStarted(MapboxMap.OnCameraMoveStartedListener.REASON_API_GESTURE); + } + + @Test + public void testAddCameraMoveCancelListener() { + CameraChangeDispatcher dispatcher = new CameraChangeDispatcher(); + MapboxMap.OnCameraMoveCanceledListener listener = mock(MapboxMap.OnCameraMoveCanceledListener.class); + dispatcher.addOnCameraMoveCancelListener(listener); + dispatcher.onCameraMoveStarted(MapboxMap.OnCameraMoveStartedListener.REASON_API_GESTURE); + dispatcher.onCameraMoveCanceled(); + verify(listener).onCameraMoveCanceled(); + } + + @Test + public void testAddCameraMoveListener() { + CameraChangeDispatcher dispatcher = new CameraChangeDispatcher(); + MapboxMap.OnCameraMoveListener listener = mock(MapboxMap.OnCameraMoveListener.class); + dispatcher.addOnCameraMoveListener(listener); + dispatcher.onCameraMoveStarted(MapboxMap.OnCameraMoveStartedListener.REASON_API_GESTURE); + dispatcher.onCameraMove(); + verify(listener).onCameraMove(); + } +} diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/camera/CameraPositionActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/camera/CameraPositionActivity.java index 2820bdbd53..d35eb83f7d 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/camera/CameraPositionActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/camera/CameraPositionActivity.java @@ -1,6 +1,5 @@ package com.mapbox.mapboxsdk.testapp.activity.camera; -import android.annotation.SuppressLint; import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; @@ -28,11 +27,13 @@ import timber.log.Timber; /** * Test activity showcasing how to listen to camera change events. */ -public class CameraPositionActivity extends AppCompatActivity implements OnMapReadyCallback { +public class CameraPositionActivity extends AppCompatActivity implements OnMapReadyCallback, View.OnClickListener, + MapboxMap.OnMapLongClickListener { private MapView mapView; private MapboxMap mapboxMap; private FloatingActionButton fab; + private boolean logCameraChanges; @Override protected void onCreate(Bundle savedInstanceState) { @@ -47,96 +48,49 @@ public class CameraPositionActivity extends AppCompatActivity implements OnMapRe @Override public void onMapReady(@NonNull final MapboxMap map) { mapboxMap = map; - - mapboxMap.setOnCameraIdleListener(new MapboxMap.OnCameraIdleListener() { - @Override - public void onCameraIdle() { - Timber.e("OnCameraIdle"); - fab.setColorFilter(ContextCompat.getColor(CameraPositionActivity.this, android.R.color.holo_green_dark)); - } - }); - - mapboxMap.setOnCameraMoveCancelListener(new MapboxMap.OnCameraMoveCanceledListener() { - @Override - public void onCameraMoveCanceled() { - Timber.e("OnCameraMoveCanceled"); - } - }); - - mapboxMap.setOnCameraMoveListener(new MapboxMap.OnCameraMoveListener() { - @Override - public void onCameraMove() { - Timber.e("OnCameraMove"); - fab.setColorFilter(ContextCompat.getColor(CameraPositionActivity.this, android.R.color.holo_orange_dark)); - } - }); - - mapboxMap.setOnCameraMoveStartedListener(new MapboxMap.OnCameraMoveStartedListener() { - - private final String[] REASONS = {"REASON_API_GESTURE", "REASON_DEVELOPER_ANIMATION", "REASON_API_ANIMATION"}; - - @Override - public void onCameraMoveStarted(int reason) { - // reason ranges from 1 <-> 3 - fab.setColorFilter(ContextCompat.getColor(CameraPositionActivity.this, android.R.color.holo_red_dark)); - Timber.e("OnCameraMoveStarted: %s", REASONS[reason - 1]); - } - }); + toggleLogCameraChanges(); // add a listener to FAB fab = (FloatingActionButton) findViewById(R.id.fab); fab.setColorFilter(ContextCompat.getColor(CameraPositionActivity.this, R.color.primary)); - fab.setOnClickListener(new View.OnClickListener() { - @SuppressLint("InflateParams") - @Override - public void onClick(View view) { - Context context = view.getContext(); - final View dialogContent = LayoutInflater.from(context).inflate(R.layout.dialog_camera_position, null); - AlertDialog.Builder builder = new AlertDialog.Builder( - context, com.mapbox.mapboxsdk.R.style.mapbox_AlertDialogStyle); - builder.setTitle(R.string.dialog_camera_position); - builder.setView(onInflateDialogContent(dialogContent)); - builder.setPositiveButton("Animate", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - double latitude = Double.parseDouble( - ((TextView) dialogContent.findViewById(R.id.value_lat)).getText().toString()); - double longitude = Double.parseDouble( - ((TextView) dialogContent.findViewById(R.id.value_lon)).getText().toString()); - double zoom = Double.parseDouble( - ((TextView) dialogContent.findViewById(R.id.value_zoom)).getText().toString()); - double bearing = Double.parseDouble( - ((TextView) dialogContent.findViewById(R.id.value_bearing)).getText().toString()); - double tilt = Double.parseDouble( - ((TextView) dialogContent.findViewById(R.id.value_tilt)).getText().toString()); - - CameraPosition cameraPosition = new CameraPosition.Builder() - .target(new LatLng(latitude, longitude)) - .zoom(zoom) - .bearing(bearing) - .tilt(tilt) - .build(); - - mapboxMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition), 5000, - new MapboxMap.CancelableCallback() { - @Override - public void onCancel() { - Timber.v("OnCancel called"); - } - - @Override - public void onFinish() { - Timber.v("OnFinish called"); - } - }); - Timber.v(cameraPosition.toString()); - } - }); - builder.setNegativeButton("Cancel", null); - builder.setCancelable(false); - builder.show(); - } - }); + fab.setOnClickListener(this); + + // listen to long click events to toggle logging camera changes + mapboxMap.setOnMapLongClickListener(this); + } + + @Override + public void onMapLongClick(@NonNull LatLng point) { + toggleLogCameraChanges(); + } + + @Override + public void onClick(View view) { + Context context = view.getContext(); + final View dialogContent = LayoutInflater.from(context).inflate(R.layout.dialog_camera_position, null); + AlertDialog.Builder builder = new AlertDialog.Builder( + context, com.mapbox.mapboxsdk.R.style.mapbox_AlertDialogStyle); + builder.setTitle(R.string.dialog_camera_position); + builder.setView(onInflateDialogContent(dialogContent)); + builder.setPositiveButton("Animate", new DialogClickListener(mapboxMap, dialogContent)); + builder.setNegativeButton("Cancel", null); + builder.setCancelable(false); + builder.show(); + } + + private void toggleLogCameraChanges() { + logCameraChanges = !logCameraChanges; + if (logCameraChanges) { + mapboxMap.addOnCameraIdleListener(idleListener); + mapboxMap.addOnCameraMoveCancelListener(moveCanceledListener); + mapboxMap.addOnCameraMoveListener(moveListener); + mapboxMap.addOnCameraMoveStartedListener(moveStartedListener); + } else { + mapboxMap.removeOnCameraIdleListener(idleListener); + mapboxMap.removeOnCameraMoveCancelListener(moveCanceledListener); + mapboxMap.removeOnCameraMoveListener(moveListener); + mapboxMap.removeOnCameraMoveStartedListener(moveStartedListener); + } } @Override @@ -193,6 +147,42 @@ public class CameraPositionActivity extends AppCompatActivity implements OnMapRe seekBar.setProgress(defaultValue); } + private MapboxMap.OnCameraIdleListener idleListener = new MapboxMap.OnCameraIdleListener() { + @Override + public void onCameraIdle() { + Timber.e("OnCameraIdle"); + fab.setColorFilter(ContextCompat.getColor(CameraPositionActivity.this, android.R.color.holo_green_dark)); + } + }; + + private MapboxMap.OnCameraMoveListener moveListener = new MapboxMap.OnCameraMoveListener() { + @Override + public void onCameraMove() { + Timber.e("OnCameraMove"); + fab.setColorFilter(ContextCompat.getColor(CameraPositionActivity.this, android.R.color.holo_orange_dark)); + } + }; + + private MapboxMap.OnCameraMoveCanceledListener moveCanceledListener = new MapboxMap.OnCameraMoveCanceledListener() { + @Override + public void onCameraMoveCanceled() { + Timber.e("OnCameraMoveCanceled"); + + } + }; + + private MapboxMap.OnCameraMoveStartedListener moveStartedListener = new MapboxMap.OnCameraMoveStartedListener() { + + private final String[] REASONS = {"REASON_API_GESTURE", "REASON_DEVELOPER_ANIMATION", "REASON_API_ANIMATION"}; + + @Override + public void onCameraMoveStarted(int reason) { + // reason ranges from 1 <-> 3 + fab.setColorFilter(ContextCompat.getColor(CameraPositionActivity.this, android.R.color.holo_red_dark)); + Timber.e("OnCameraMoveStarted: %s", REASONS[reason - 1]); + } + }; + private class ValueChangeListener implements SeekBar.OnSeekBarChangeListener { protected TextView textView; @@ -224,4 +214,50 @@ public class CameraPositionActivity extends AppCompatActivity implements OnMapRe super.onProgressChanged(seekBar, progress - 180, fromUser); } } + + private static class DialogClickListener implements DialogInterface.OnClickListener { + + private MapboxMap mapboxMap; + private View dialogContent; + + public DialogClickListener(MapboxMap mapboxMap, View view) { + this.mapboxMap = mapboxMap; + this.dialogContent = view; + } + + @Override + public void onClick(DialogInterface dialog, int which) { + double latitude = Double.parseDouble( + ((TextView) dialogContent.findViewById(R.id.value_lat)).getText().toString()); + double longitude = Double.parseDouble( + ((TextView) dialogContent.findViewById(R.id.value_lon)).getText().toString()); + double zoom = Double.parseDouble( + ((TextView) dialogContent.findViewById(R.id.value_zoom)).getText().toString()); + double bearing = Double.parseDouble( + ((TextView) dialogContent.findViewById(R.id.value_bearing)).getText().toString()); + double tilt = Double.parseDouble( + ((TextView) dialogContent.findViewById(R.id.value_tilt)).getText().toString()); + + CameraPosition cameraPosition = new CameraPosition.Builder() + .target(new LatLng(latitude, longitude)) + .zoom(zoom) + .bearing(bearing) + .tilt(tilt) + .build(); + + mapboxMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition), 5000, + new MapboxMap.CancelableCallback() { + @Override + public void onCancel() { + Timber.v("OnCancel called"); + } + + @Override + public void onFinish() { + Timber.v("OnFinish called"); + } + }); + Timber.v(cameraPosition.toString()); + } + } } -- cgit v1.2.1 From a5a495a9007963c36df541289a1ec504a759aca1 Mon Sep 17 00:00:00 2001 From: Jordan Kiley Date: Fri, 6 Oct 2017 11:40:22 -0700 Subject: [ios, macos] Added layer to the code snippet (#10149) --- platform/darwin/src/MGLImageSource.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/platform/darwin/src/MGLImageSource.h b/platform/darwin/src/MGLImageSource.h index 21487d9739..5088f6bac0 100644 --- a/platform/darwin/src/MGLImageSource.h +++ b/platform/darwin/src/MGLImageSource.h @@ -38,8 +38,11 @@ MGL_EXPORT bottomLeft: CLLocationCoordinate2D(latitude: 37.936, longitude: -80.425), bottomRight: CLLocationCoordinate2D(latitude: 37.936, longitude: -71.516), topRight: CLLocationCoordinate2D(latitude: 46.437, longitude: -71.516)) - let source = MGLImageSource(identifier: "radar", coordinateQuad: coordinates, url: URL(string: "https://www.mapbox.com/mapbox-gl-js/assets/radar.gif")!) + let source = MGLImageSource(identifier: "radar-source", coordinateQuad: coordinates, url: URL(string: "https://www.mapbox.com/mapbox-gl-js/assets/radar.gif")!) mapView.style?.addSource(source) + + let layer = MGLRasterStyleLayer(identifier: "radar-layer", source: source) + style.addLayer(layer) ``` */ MGL_EXPORT -- cgit v1.2.1 From 503a96c6b1d831236f6c937069bc9db81ec8ad5b Mon Sep 17 00:00:00 2001 From: Pablo Guardiola Date: Mon, 9 Oct 2017 13:57:50 +0200 Subject: [android] release v5.2.0-beta.1 (#10144) --- platform/android/CHANGELOG.md | 56 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/platform/android/CHANGELOG.md b/platform/android/CHANGELOG.md index 8ce8b3d3c1..56a33f7e6b 100644 --- a/platform/android/CHANGELOG.md +++ b/platform/android/CHANGELOG.md @@ -4,10 +4,62 @@ Mapbox welcomes participation and contributions from everyone. If you'd like to ## 5.2.0 - TBA -* Add support for ImageSource [#9110](https://github.com/mapbox/mapbox-gl-native/pull/9110) -* Increased the default maximum zoom level from 20 to 22. ([#9835](https://github.com/mapbox/mapbox-gl-native/pull/9835)) * TBA +## 5.2.0-beta.1 - October 6, 2017 + +* Allow multiple listeners for camera events, deprecate old API [#10141](https://github.com/mapbox/mapbox-gl-native/pull/10141) +* Update symbol layer example with location [#10092](https://github.com/mapbox/mapbox-gl-native/pull/10092) +* Make OfflineTilePyramidRegionDefinition parceable [#10080](https://github.com/mapbox/mapbox-gl-native/pull/10080) +- Fix 5.2.0-SNAPSHOT CI build failing [#10079](https://github.com/mapbox/mapbox-gl-native/pull/10079) +- Deprecate MarkerView [#9782](https://github.com/mapbox/mapbox-gl-native/pull/9782) +- Hide overlain views on initalisation [#10068](https://github.com/mapbox/mapbox-gl-native/pull/10068) +- API for platform side animations [#10001](https://github.com/mapbox/mapbox-gl-native/pull/10001) +- Android asynchronous rendering [#9576](https://github.com/mapbox/mapbox-gl-native/pull/9576) +- Set error handler when starting snapshotter [#10035](https://github.com/mapbox/mapbox-gl-native/pull/10035) +- Hook camera events into compass [#10019](https://github.com/mapbox/mapbox-gl-native/pull/10019) +- Testapp cleanup [#10006](https://github.com/mapbox/mapbox-gl-native/pull/10006) +- Update zoom function example with selected state [#9987](https://github.com/mapbox/mapbox-gl-native/pull/9987) +- Add style inspection to debug activity [#9773](https://github.com/mapbox/mapbox-gl-native/pull/9773) +- Bump external dependencies [#9972](https://github.com/mapbox/mapbox-gl-native/pull/9972) +- Don't recycle bitmap for icon reuse. [#9966](https://github.com/mapbox/mapbox-gl-native/pull/9966) +- Android snapshotter [#9748](https://github.com/mapbox/mapbox-gl-native/pull/9748) +- Revert #9764 [#9851](https://github.com/mapbox/mapbox-gl-native/pull/9851) +- Update docs replacing Bitrise mentions with CircleCI [#9515](https://github.com/mapbox/mapbox-gl-native/pull/9515) +- Style image accessor [#9763](https://github.com/mapbox/mapbox-gl-native/pull/9763) +- Update readme with checkstyle and ndk-stack [#9788](https://github.com/mapbox/mapbox-gl-native/pull/9788) +- make android-check [#9787](https://github.com/mapbox/mapbox-gl-native/pull/9787) +- Deprecate MyLocationView in favor of LocationLayer plugin [#9771](https://github.com/mapbox/mapbox-gl-native/pull/9771) +- Increase firebase timeout for CI testing [#9774](https://github.com/mapbox/mapbox-gl-native/pull/9774) +- Restore max zoom to 25.5 [#9765](https://github.com/mapbox/mapbox-gl-native/pull/9765) +- Update example of camera zoom function on a symbol layer. [#9743](https://github.com/mapbox/mapbox-gl-native/pull/9743) +- Optimise icon management [#9643](https://github.com/mapbox/mapbox-gl-native/pull/9643) +- Expose setStyleJson and getStyleJson [#9714](https://github.com/mapbox/mapbox-gl-native/pull/9714) +- update LatLngBounds activity with BottomSheet interaction [#9736](https://github.com/mapbox/mapbox-gl-native/pull/9736) +- post updating InfoWindow update for InfoWindowAdapter [#9716](https://github.com/mapbox/mapbox-gl-native/pull/9716) +- Annotate MapboxMap class with UiThread [#9712](https://github.com/mapbox/mapbox-gl-native/pull/9712) +- Move ZoomButtonController creation to view initalisation [#9587](https://github.com/mapbox/mapbox-gl-native/pull/9587) +- Solve lint issues, reduce baseline [#9627](https://github.com/mapbox/mapbox-gl-native/pull/9627) +- Remove wear module from project [#9618](https://github.com/mapbox/mapbox-gl-native/pull/9618) +- Add zMediaOverlay configuration + bottom sheet integration [#9592](https://github.com/mapbox/mapbox-gl-native/pull/9592) +- Forward getMapAsync to map for fragment [#9621](https://github.com/mapbox/mapbox-gl-native/pull/9621) +- Make target for dumping system gfx information [#9616](https://github.com/mapbox/mapbox-gl-native/pull/9616) +- Make target documentation [#9617](https://github.com/mapbox/mapbox-gl-native/pull/9617) +- onGlobalLayout hook for map creation [#9607](https://github.com/mapbox/mapbox-gl-native/pull/9607) +- Custom viewpager for horizontal swiping [#9601](https://github.com/mapbox/mapbox-gl-native/pull/9601) +- Disable program caching on Adreno 3xx, 4xx, and 5xx GPUs due to known bugs [#9574](https://github.com/mapbox/mapbox-gl-native/pull/9574) +- Avoid creating InfoWindow iterator if no InfoWindows are shown [#9477](https://github.com/mapbox/mapbox-gl-native/pull/9477) +- Rewire map initialisation [#9462](https://github.com/mapbox/mapbox-gl-native/pull/9462) +- Trying to update non-existent polyline fix [#9544](https://github.com/mapbox/mapbox-gl-native/pull/9544) +- Location accuracy threshold [#9472](https://github.com/mapbox/mapbox-gl-native/pull/9472) +- Rewire gesture handling and telemetry event push [#9494](https://github.com/mapbox/mapbox-gl-native/pull/9494) +- run style instrumentation tests on CI [#9353](https://github.com/mapbox/mapbox-gl-native/pull/9353) +- Fix javadoc comment for public setOfflineMapboxTileCountLimit method [#9454](https://github.com/mapbox/mapbox-gl-native/pull/9454) +- add Map change & visibility test activities [#9425](https://github.com/mapbox/mapbox-gl-native/pull/9425) +- build release package once during ci build [#9351](https://github.com/mapbox/mapbox-gl-native/pull/9351) +* Add support for ImageSource [#9110](https://github.com/mapbox/mapbox-gl-native/pull/9110) +* Increased the default maximum zoom level from 20 to 22. [#9835](https://github.com/mapbox/mapbox-gl-native/pull/9835) + ### 5.1.4 - September 25, 2017 * Update translations [#10033](https://github.com/mapbox/mapbox-gl-native/pull/10033) & [#9945](https://github.com/mapbox/mapbox-gl-native/pull/9945) -- cgit v1.2.1 From a1aed6f095c6326fa9c8a8ecff591c5a03144070 Mon Sep 17 00:00:00 2001 From: Antonio Zugaldia Date: Mon, 9 Oct 2017 10:07:09 -0400 Subject: Provide a fallback method to load the native library (#10154) * provide a fallback method to load the native library * move context call inside loader to avoid changing method signature --- .../java/com/mapbox/mapboxsdk/LibraryLoader.java | 28 +++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/LibraryLoader.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/LibraryLoader.java index 8a75176ccd..35b1e7cf6a 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/LibraryLoader.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/LibraryLoader.java @@ -1,15 +1,41 @@ package com.mapbox.mapboxsdk; +import android.content.Context; + +import java.io.File; + +import timber.log.Timber; + /** * Centralises the knowledge about "mapbox-gl" library loading. */ public class LibraryLoader { + private static final String LIBRARY_NAME = "libmapbox-gl.so"; + /** * Loads "libmapbox-gl.so" native shared library. */ public static void load() { - System.loadLibrary("mapbox-gl"); + try { + System.loadLibrary("mapbox-gl"); + } catch (UnsatisfiedLinkError error) { + Context context = Mapbox.getApplicationContext(); + if (context != null) { + Timber.d("Loading %s from internal storage.", LIBRARY_NAME); + System.load(getLibraryLocation(context).getAbsolutePath()); + } + } } + /** + * Returns a file in the app internal storage that may contain a locally cached copy + * of the Mapbox native library. + * + * @param context The application context + * @return a file object + */ + public static File getLibraryLocation(Context context) { + return new File(context.getFilesDir(), LIBRARY_NAME); + } } -- cgit v1.2.1 From 9048d6cfd0f794af3f3810a880adb0d2fc691552 Mon Sep 17 00:00:00 2001 From: Tobrun Van Nuland Date: Thu, 5 Oct 2017 15:04:50 +0200 Subject: [android] - fine tune gesture zoom & rotation --- .../gesturedetectors/TwoFingerGestureDetector.java | 4 +++- .../java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java | 14 ++++++-------- .../src/main/java/com/mapbox/mapboxsdk/maps/MapView.java | 12 ++++++++++-- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/TwoFingerGestureDetector.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/TwoFingerGestureDetector.java index db492b6556..3d28c2295d 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/TwoFingerGestureDetector.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/gesturedetectors/TwoFingerGestureDetector.java @@ -54,7 +54,9 @@ public abstract class TwoFingerGestureDetector extends BaseGestureDetector { ViewConfiguration config = ViewConfiguration.get(context); - edgeSlop = config.getScaledEdgeSlop(); + // lowering the edgeSlop allows to execute gesture faster + // https://github.com/mapbox/mapbox-gl-native/issues/10102 + edgeSlop = config.getScaledEdgeSlop() / 3.0f; } @Override 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 2394e52193..f309570a7b 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 @@ -526,10 +526,10 @@ final class MapGestureDetector { */ private class RotateGestureListener extends RotateGestureDetector.SimpleOnRotateGestureListener { - private static final long ROTATE_INVOKE_WAIT_TIME = 1500; + private static final long ROTATE_INVOKE_WAIT_TIME = 750; + private static final float ROTATE_INVOKE_ANGLE = 17.5f; private long beginTime = 0; - private float totalAngle = 0.0f; private boolean started = false; // Called when two fingers first touch the screen @@ -551,7 +551,6 @@ final class MapGestureDetector { public void onRotateEnd(RotateGestureDetector detector) { // notify camera change listener beginTime = 0; - totalAngle = 0.0f; started = false; } @@ -573,8 +572,8 @@ final class MapGestureDetector { // If rotate is large enough ignore a tap // Also is zoom already started, don't rotate - totalAngle += detector.getRotationDegreesDelta(); - if (totalAngle > 35.0f || totalAngle < -35.0f) { + float angle = detector.getRotationDegreesDelta(); + if (Math.abs(angle) >= ROTATE_INVOKE_ANGLE) { MapboxTelemetry.getInstance().pushEvent(MapboxEventWrapper.buildMapClickEvent( getLocationFromGesture(detector.getFocusX(), detector.getFocusY()), MapboxEvent.GESTURE_ROTATION_START, transform)); @@ -589,9 +588,8 @@ final class MapGestureDetector { // rotation, so cancel both location and bearing tracking if required trackingSettings.resetTrackingModesIfRequired(true, true, false); - // Get rotate value - double bearing = transform.getRawBearing(); - bearing += detector.getRotationDegreesDelta(); + // Calculate map bearing value + double bearing = transform.getRawBearing() + angle; // Rotate the map if (focalPoint != null) { 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 12e4c675ee..a93d05eaed 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 @@ -382,6 +382,10 @@ public class MapView extends FrameLayout { @Override public boolean onTouchEvent(MotionEvent event) { + if (!isMapInitialized()) { + return super.onTouchEvent(event); + } + if (event.getAction() == MotionEvent.ACTION_DOWN) { mapZoomButtonController.setVisible(true); } @@ -471,7 +475,7 @@ public class MapView extends FrameLayout { if (destroyed) { return; } - if (nativeMapView == null) { + if (!isMapInitialized()) { mapboxMapOptions.styleUrl(url); return; } @@ -488,7 +492,7 @@ public class MapView extends FrameLayout { return; } - if (!isInEditMode() && nativeMapView != null) { + if (!isInEditMode() && isMapInitialized()) { nativeMapView.resizeView(width, height); } } @@ -574,6 +578,10 @@ public class MapView extends FrameLayout { } } + private boolean isMapInitialized() { + return nativeMapView != null; + } + MapboxMap getMapboxMap() { return mapboxMap; } -- cgit v1.2.1 From df20b0cbecb25b8bc6812c4a558fc0f0e6cfb7ed Mon Sep 17 00:00:00 2001 From: Asheem Mamoowala Date: Mon, 2 Oct 2017 13:42:26 -0700 Subject: [core] Add `cameraForLatLngs()` method with modified bearing --- include/mbgl/map/map.hpp | 8 +++++--- include/mbgl/util/geometry.hpp | 6 ++++++ src/mbgl/map/map.cpp | 45 +++++++++++++++++++++++++++++++----------- test/map/map.test.cpp | 26 ++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 14 deletions(-) diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index 4108725776..c5f90d99e1 100644 --- a/include/mbgl/map/map.hpp +++ b/include/mbgl/map/map.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -66,6 +67,10 @@ public: void jumpTo(const CameraOptions&); void easeTo(const CameraOptions&, const AnimationOptions&); void flyTo(const CameraOptions&, const AnimationOptions&); + CameraOptions cameraForLatLngBounds(const LatLngBounds&, const EdgeInsets&, optional bearing = {}) const; + CameraOptions cameraForLatLngs(const std::vector&, const EdgeInsets&, optional bearing = {}) const; + CameraOptions cameraForGeometry(const Geometry&, const EdgeInsets&, optional bearing = {}) const; + LatLngBounds latLngBoundsForCamera(const CameraOptions&) const; // Position void moveBy(const ScreenCoordinate&, const AnimationOptions& = {}); @@ -82,9 +87,6 @@ public: double getZoom() const; void setLatLngZoom(const LatLng&, double zoom, const AnimationOptions& = {}); void setLatLngZoom(const LatLng&, double zoom, const EdgeInsets&, const AnimationOptions& = {}); - CameraOptions cameraForLatLngBounds(const LatLngBounds&, const EdgeInsets&) const; - CameraOptions cameraForLatLngs(const std::vector&, const EdgeInsets&) const; - LatLngBounds latLngBoundsForCamera(const CameraOptions&) const; void resetZoom(); // Bounds diff --git a/include/mbgl/util/geometry.hpp b/include/mbgl/util/geometry.hpp index 6dc16bc514..a28c59a47d 100644 --- a/include/mbgl/util/geometry.hpp +++ b/include/mbgl/util/geometry.hpp @@ -2,6 +2,7 @@ #include #include +#include namespace mbgl { @@ -58,4 +59,9 @@ struct ToFeatureType { FeatureType operator()(const mapbox::geometry::geometry_collection &) const { return FeatureType::Unknown; } }; +template +auto forEachPoint(const Geometry& g, F f) { + mapbox::geometry::for_each_point(g, f); +} + } // namespace mbgl diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index 7534fe67ad..6eb555ad1e 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -364,27 +364,27 @@ void Map::setLatLngZoom(const LatLng& latLng, double zoom, const EdgeInsets& pad impl->onUpdate(); } -CameraOptions Map::cameraForLatLngBounds(const LatLngBounds& bounds, const EdgeInsets& padding) const { +CameraOptions Map::cameraForLatLngBounds(const LatLngBounds& bounds, const EdgeInsets& padding, optional bearing) const { return cameraForLatLngs({ bounds.northwest(), bounds.southwest(), bounds.southeast(), bounds.northeast(), - }, padding); + }, padding, bearing); } -CameraOptions Map::cameraForLatLngs(const std::vector& latLngs, const EdgeInsets& padding) const { +CameraOptions cameraForLatLngs(const std::vector& latLngs, const Transform& transform, const EdgeInsets& padding) { CameraOptions options; if (latLngs.empty()) { return options; } - + Size size = transform.getState().getSize(); // Calculate the bounds of the possibly rotated shape with respect to the viewport. ScreenCoordinate nePixel = {-INFINITY, -INFINITY}; ScreenCoordinate swPixel = {INFINITY, INFINITY}; - double viewportHeight = getSize().height; + double viewportHeight = size.height; for (LatLng latLng : latLngs) { - ScreenCoordinate pixel = impl->transform.latLngToScreenCoordinate(latLng); + ScreenCoordinate pixel = transform.latLngToScreenCoordinate(latLng); swPixel.x = std::min(swPixel.x, pixel.x); nePixel.x = std::max(nePixel.x, pixel.x); swPixel.y = std::min(swPixel.y, viewportHeight - pixel.y); @@ -396,14 +396,14 @@ CameraOptions Map::cameraForLatLngs(const std::vector& latLngs, const Ed // Calculate the zoom level. double minScale = INFINITY; if (width > 0 || height > 0) { - double scaleX = double(getSize().width) / width; - double scaleY = double(getSize().height) / height; + double scaleX = double(size.width) / width; + double scaleY = double(size.height) / height; scaleX -= (padding.left() + padding.right()) / width; scaleY -= (padding.top() + padding.bottom()) / height; minScale = util::min(scaleX, scaleY); } - double zoom = getZoom() + util::log2(minScale); - zoom = util::clamp(zoom, getMinZoom(), getMaxZoom()); + double zoom = transform.getZoom() + util::log2(minScale); + zoom = util::clamp(zoom, transform.getState().getMinZoom(), transform.getState().getMaxZoom()); // Calculate the center point of a virtual bounds that is extended in all directions by padding. ScreenCoordinate centerPixel = nePixel + swPixel; @@ -421,11 +421,34 @@ CameraOptions Map::cameraForLatLngs(const std::vector& latLngs, const Ed // CameraOptions origin is at the top-left corner. centerPixel.y = viewportHeight - centerPixel.y; - options.center = latLngForPixel(centerPixel); + options.center = transform.screenCoordinateToLatLng(centerPixel); options.zoom = zoom; return options; } +CameraOptions Map::cameraForLatLngs(const std::vector& latLngs, const EdgeInsets& padding, optional bearing) const { + if(bearing) { + double angle = -*bearing * util::DEG2RAD; // Convert to radians + Transform transform(impl->transform.getState()); + transform.setAngle(angle); + CameraOptions options = mbgl::cameraForLatLngs(latLngs, transform, padding); + options.angle = angle; + return options; + } else { + return mbgl::cameraForLatLngs(latLngs, impl->transform, padding); + } +} + +CameraOptions Map::cameraForGeometry(const Geometry& geometry, const EdgeInsets& padding, optional bearing) const { + + std::vector latLngs; + forEachPoint(geometry, [&](const Point& pt) { + latLngs.push_back({ pt.y, pt.x }); + }); + return cameraForLatLngs(latLngs, padding, bearing); + +} + LatLngBounds Map::latLngBoundsForCamera(const CameraOptions& camera) const { Transform shallow { impl->transform.getState() }; Size size = shallow.getState().getSize(); diff --git a/test/map/map.test.cpp b/test/map/map.test.cpp index 50d5e50abb..9358175297 100644 --- a/test/map/map.test.cpp +++ b/test/map/map.test.cpp @@ -109,6 +109,32 @@ TEST(Map, LatLngBoundsToCamera) { CameraOptions virtualCamera = test.map.cameraForLatLngBounds(bounds, {}); ASSERT_TRUE(bounds.contains(*virtualCamera.center)); + EXPECT_NEAR(*virtualCamera.zoom, 1.55467, 1e-5); +} + +TEST(Map, LatLngBoundsToCameraWithAngle) { + MapTest<> test; + + test.map.setLatLngZoom({ 40.712730, -74.005953 }, 16.0); + + LatLngBounds bounds = LatLngBounds::hull({15.68169,73.499857}, {53.560711, 134.77281}); + + CameraOptions virtualCamera = test.map.cameraForLatLngBounds(bounds, {}, 35); + ASSERT_TRUE(bounds.contains(*virtualCamera.center)); + EXPECT_NEAR(*virtualCamera.zoom, 1.21385, 1e-5); + EXPECT_DOUBLE_EQ(virtualCamera.angle.value_or(0), -35 * util::DEG2RAD); +} + +TEST(Map, LatLngsToCamera) { + MapTest<> test; + + std::vector latLngs{{ 40.712730, 74.005953 }, {15.68169,73.499857}, {30.82678, 83.4082}}; + + CameraOptions virtualCamera = test.map.cameraForLatLngs(latLngs, {}, 23); + EXPECT_DOUBLE_EQ(virtualCamera.angle.value_or(0), -23 * util::DEG2RAD); + EXPECT_NEAR(virtualCamera.zoom.value_or(0), 2.75434, 1e-5); + EXPECT_NEAR(virtualCamera.center->latitude(), 28.49288, 1e-5); + EXPECT_NEAR(virtualCamera.center->longitude(), 74.97437, 1e-5); } TEST(Map, CameraToLatLngBounds) { -- cgit v1.2.1 From 24bd336b88b4f2dae25277efeeb36a96061139a3 Mon Sep 17 00:00:00 2001 From: Asheem Mamoowala Date: Mon, 9 Oct 2017 11:50:48 -0700 Subject: [android][ios][macOS] Implement bindings for `Map::cameraForGeometry`. On macOS, also added -[MGLMapView setCamera: withDuration: animationTimingFunction: edgePadding: completionHandler:] for parity with iOS --- platform/android/CHANGELOG.md | 1 + .../java/com/mapbox/mapboxsdk/maps/MapboxMap.java | 25 ++++++++++++++++ .../com/mapbox/mapboxsdk/maps/NativeMapView.java | 10 +++++++ platform/android/src/native_map_view.cpp | 6 ++++ platform/android/src/native_map_view.hpp | 3 ++ platform/ios/CHANGELOG.md | 1 + platform/ios/src/MGLMapView.h | 14 +++++++++ platform/ios/src/MGLMapView.mm | 11 ++++++++ platform/macos/CHANGELOG.md | 1 + platform/macos/src/MGLMapView.h | 33 ++++++++++++++++++++++ platform/macos/src/MGLMapView.mm | 22 ++++++++++++--- 11 files changed, 123 insertions(+), 4 deletions(-) diff --git a/platform/android/CHANGELOG.md b/platform/android/CHANGELOG.md index 56a33f7e6b..520a678b73 100644 --- a/platform/android/CHANGELOG.md +++ b/platform/android/CHANGELOG.md @@ -59,6 +59,7 @@ Mapbox welcomes participation and contributions from everyone. If you'd like to - build release package once during ci build [#9351](https://github.com/mapbox/mapbox-gl-native/pull/9351) * Add support for ImageSource [#9110](https://github.com/mapbox/mapbox-gl-native/pull/9110) * Increased the default maximum zoom level from 20 to 22. [#9835](https://github.com/mapbox/mapbox-gl-native/pull/9835) +* Added `MapboxMap.getCameraForGeometry()` to get a camera with zoom level and center coordinate computed to fit a shape. ([#10107](https://github.com/mapbox/mapbox-gl-native/pull/10107)) ### 5.1.4 - September 25, 2017 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 a5c49dae7c..6a88470ee7 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 @@ -44,6 +44,7 @@ import com.mapbox.mapboxsdk.style.light.Light; import com.mapbox.mapboxsdk.style.sources.Source; import com.mapbox.services.android.telemetry.location.LocationEngine; import com.mapbox.services.commons.geojson.Feature; +import com.mapbox.services.commons.geojson.Geometry; import java.lang.reflect.ParameterizedType; import java.util.List; @@ -1666,6 +1667,30 @@ public final class MapboxMap { return cameraPosition; } + /** + * Get a camera position that fits a provided shape with a given bearing and padding. + * + * @param geometry the geometry to constrain the map with + * @param bearing the bearing at which to compute the geometry's bounds + * @param padding the padding to apply to the bounds + * @return the camera position that fits the bounds and padding + */ + public CameraPosition getCameraForGeometry(Geometry geometry, double bearing, int[] padding) { + // calculate and set additional bounds padding + int[] mapPadding = getPadding(); + for (int i = 0; i < padding.length; i++) { + padding[i] = mapPadding[i] + padding[i]; + } + projection.setContentPadding(padding, myLocationViewSettings.getPadding()); + + // get padded camera position from LatLngBounds + CameraPosition cameraPosition = nativeMapView.getCameraForGeometry(geometry, bearing); + + // reset map padding + setPadding(mapPadding); + return cameraPosition; + } + // // Padding // 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 3ce6aab581..bd8a54783e 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 @@ -29,6 +29,7 @@ import com.mapbox.mapboxsdk.style.sources.CannotAddSourceException; import com.mapbox.mapboxsdk.style.sources.Source; import com.mapbox.mapboxsdk.utils.BitmapUtils; import com.mapbox.services.commons.geojson.Feature; +import com.mapbox.services.commons.geojson.Geometry; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -232,6 +233,13 @@ final class NativeMapView { return nativeGetCameraForLatLngBounds(latLngBounds); } + public CameraPosition getCameraForGeometry(Geometry geometry, double bearing) { + if (isDestroyedOn("getCameraForGeometry")) { + return null; + } + return nativeGetCameraForGeometry(geometry, bearing); + } + public void resetPosition() { if (isDestroyedOn("resetPosition")) { return; @@ -873,6 +881,8 @@ final class NativeMapView { private native CameraPosition nativeGetCameraForLatLngBounds(LatLngBounds latLngBounds); + private native CameraPosition nativeGetCameraForGeometry(Geometry geometry, double bearing); + private native void nativeResetPosition(); private native double nativeGetPitch(); diff --git a/platform/android/src/native_map_view.cpp b/platform/android/src/native_map_view.cpp index a9ed6d5ead..24a35a7068 100755 --- a/platform/android/src/native_map_view.cpp +++ b/platform/android/src/native_map_view.cpp @@ -291,6 +291,11 @@ jni::Object NativeMapView::getCameraForLatLngBounds(jni::JNIEnv& return CameraPosition::New(env, map->cameraForLatLngBounds(mbgl::android::LatLngBounds::getLatLngBounds(env, jBounds), insets)); } +jni::Object NativeMapView::getCameraForGeometry(jni::JNIEnv& env, jni::Object jGeometry, double bearing) { + auto geometry = geojson::Geometry::convert(env, jGeometry); + return CameraPosition::New(env, map->cameraForGeometry(geometry, insets, bearing)); +} + void NativeMapView::setReachability(jni::JNIEnv&, jni::jboolean reachable) { if (reachable) { mbgl::NetworkStatus::Reachable(); @@ -949,6 +954,7 @@ void NativeMapView::registerNative(jni::JNIEnv& env) { METHOD(&NativeMapView::getLatLng, "nativeGetLatLng"), METHOD(&NativeMapView::setLatLng, "nativeSetLatLng"), METHOD(&NativeMapView::getCameraForLatLngBounds, "nativeGetCameraForLatLngBounds"), + METHOD(&NativeMapView::getCameraForGeometry, "nativeGetCameraForGeometry"), METHOD(&NativeMapView::setReachability, "nativeSetReachability"), METHOD(&NativeMapView::resetPosition, "nativeResetPosition"), METHOD(&NativeMapView::getPitch, "nativeGetPitch"), diff --git a/platform/android/src/native_map_view.hpp b/platform/android/src/native_map_view.hpp index 72c7b1a9eb..4d226d0fa9 100755 --- a/platform/android/src/native_map_view.hpp +++ b/platform/android/src/native_map_view.hpp @@ -15,6 +15,7 @@ #include "graphics/pointf.hpp" #include "graphics/rectf.hpp" #include "geojson/feature.hpp" +#include "geojson/geometry.hpp" #include "geometry/lat_lng.hpp" #include "geometry/projected_meters.hpp" #include "style/layers/layers.hpp" @@ -104,6 +105,8 @@ public: jni::Object getCameraForLatLngBounds(jni::JNIEnv&, jni::Object); + jni::Object getCameraForGeometry(jni::JNIEnv&, jni::Object, double bearing); + void setReachability(jni::JNIEnv&, jni::jboolean); void resetPosition(jni::JNIEnv&); diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index c28ed7cd19..3035be70e1 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -29,6 +29,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT * Increased the default maximum zoom level from 20 to 22. ([#9835](https://github.com/mapbox/mapbox-gl-native/pull/9835)) * Added an `overlays` property to `MGLMapView`. ([#8617](https://github.com/mapbox/mapbox-gl-native/pull/8617)) * Selecting an annotation no longer sets the user tracking mode to `MGLUserTrackingModeNone`. ([#10094](https://github.com/mapbox/mapbox-gl-native/pull/10094)) +* Added `-[MGLMapView cameraThatFitsShape:direction:edgePadding:]` to get a camera with zoom level and center coordinate computed to fit a shape. ([#10107](https://github.com/mapbox/mapbox-gl-native/pull/10107)) ### Other changes diff --git a/platform/ios/src/MGLMapView.h b/platform/ios/src/MGLMapView.h index a1347cc09c..e2c070a54f 100644 --- a/platform/ios/src/MGLMapView.h +++ b/platform/ios/src/MGLMapView.h @@ -889,6 +889,20 @@ MGL_EXPORT IB_DESIGNABLE */ - (MGLMapCamera *)cameraThatFitsCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets; +/** + Returns the camera that best fits the given shape, with the specified direction, + optionally with some additional padding on each side. + + @param shape The shape to fit to the receiver’s viewport. + @param direction The direction of the viewport, measured in degrees clockwise from true north. + @param insets The minimum padding (in screen points) that would be visible + around the returned camera object if it were set as the receiver’s camera. + @return A camera object centered on the shape's center with zoom level as high + (close to the ground) as possible while still including the entire shape. The + camera object uses the current pitch. + */ +- (MGLMapCamera *)cameraThatFitsShape:(MGLShape *)shape direction:(CLLocationDirection)direction edgePadding:(UIEdgeInsets)insets; + /** Returns the point in this view’s coordinate system on which to "anchor" in response to a user-initiated gesture. diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm index 3288a93ab4..0e76c0c71c 100644 --- a/platform/ios/src/MGLMapView.mm +++ b/platform/ios/src/MGLMapView.mm @@ -35,6 +35,7 @@ #include #import "Mapbox.h" +#import "MGLShape_Private.h" #import "MGLFeature_Private.h" #import "MGLGeometry_Private.h" #import "MGLMultiPoint_Private.h" @@ -3011,6 +3012,16 @@ public: return [self cameraForCameraOptions:cameraOptions]; } +- (MGLMapCamera *)cameraThatFitsShape:(MGLShape *)shape direction:(CLLocationDirection)direction edgePadding:(UIEdgeInsets)insets { + mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(insets); + padding += MGLEdgeInsetsFromNSEdgeInsets(self.contentInset); + + mbgl::CameraOptions cameraOptions = _mbglMap->cameraForGeometry([shape geometryObject], padding, direction); + + return [self cameraForCameraOptions:cameraOptions]; + +} + - (MGLMapCamera *)cameraForCameraOptions:(const mbgl::CameraOptions &)cameraOptions { CLLocationCoordinate2D centerCoordinate = MGLLocationCoordinate2DFromLatLng(cameraOptions.center ? *cameraOptions.center : _mbglMap->getLatLng()); diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md index 3ddffbb7fd..31b3540a7c 100644 --- a/platform/macos/CHANGELOG.md +++ b/platform/macos/CHANGELOG.md @@ -26,6 +26,7 @@ * Fixed several bugs and performance issues related to the use of annotations backed by `MGLAnnotationImage`s. The limits on the number and size of images and glyphs has been effectively eliminated and should now depend on hardware constraints. These fixes also apply to images used to represent icons in `MGLSymbolStyleLayer`s. ([#9213](https://github.com/mapbox/mapbox-gl-native/pull/9213)) * Increased the default maximum zoom level from 20 to 22. ([#9835](https://github.com/mapbox/mapbox-gl-native/pull/9835)) * Added an `overlays` property to `MGLMapView`. ([#8617](https://github.com/mapbox/mapbox-gl-native/pull/8617)) +* Added `-[MGLMapView cameraThatFitsShape:direction:edgePadding:]` to get a camera with zoom level and center coordinate computed to fit a shape. ([#10107](https://github.com/mapbox/mapbox-gl-native/pull/10107)) ### Other changes diff --git a/platform/macos/src/MGLMapView.h b/platform/macos/src/MGLMapView.h index 04c2e77110..de099157c8 100644 --- a/platform/macos/src/MGLMapView.h +++ b/platform/macos/src/MGLMapView.h @@ -10,6 +10,7 @@ NS_ASSUME_NONNULL_BEGIN @class MGLAnnotationImage; @class MGLMapCamera; @class MGLStyle; +@class MGLShape; @protocol MGLAnnotation; @protocol MGLMapViewDelegate; @@ -322,6 +323,24 @@ MGL_EXPORT IB_DESIGNABLE */ - (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion; + /** + Moves the viewpoint to a different location with respect to the map with an + optional transition duration and timing function. + + @param camera The new viewpoint. + @param duration The amount of time, measured in seconds, that the transition + animation should take. Specify `0` to jump to the new viewpoint + instantaneously. + @param function A timing function used for the animation. Set this parameter to + `nil` for a transition that matches most system animations. If the duration + is `0`, this parameter is ignored. + @param edgePadding The minimum padding (in screen points) that would be visible + around the returned camera object if it were set as the receiver’s camera. + @param completion The block to execute after the animation finishes. + */ +- (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function edgePadding:(NSEdgeInsets)edgePadding completionHandler:(nullable void (^)(void))completion; + + /** Moves the viewpoint to a different location using a transition animation that evokes powered flight and a default duration based on the length of the flight @@ -456,6 +475,20 @@ MGL_EXPORT IB_DESIGNABLE */ - (MGLMapCamera *)cameraThatFitsCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(NSEdgeInsets)insets; +/** + Returns the camera that best fits the given shape, with the specified direction, + optionally with some additional padding on each side. + + @param shape The shape to fit to the receiver’s viewport. + @param direction The direction of the viewport, measured in degrees clockwise from true north. + @param insets The minimum padding (in screen points) that would be visible + around the returned camera object if it were set as the receiver’s camera. + @return A camera object centered on the shape's center with zoom level as high + (close to the ground) as possible while still including the entire shape. The + camera object uses the current pitch. + */ +- (MGLMapCamera *)cameraThatFitsShape:(MGLShape *)shape direction:(CLLocationDirection)direction edgePadding:(NSEdgeInsets)insets; + /** A Boolean value indicating whether the receiver automatically adjusts its content insets. diff --git a/platform/macos/src/MGLMapView.mm b/platform/macos/src/MGLMapView.mm index eb4099f330..0aa5bdc9db 100644 --- a/platform/macos/src/MGLMapView.mm +++ b/platform/macos/src/MGLMapView.mm @@ -14,6 +14,7 @@ #import "MGLMultiPoint_Private.h" #import "MGLOfflineStorage_Private.h" #import "MGLStyle_Private.h" +#import "MGLShape_Private.h" #import "MGLAccountManager.h" #import "MGLMapCamera.h" @@ -1121,6 +1122,10 @@ public: } - (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion { + [self setCamera:camera withDuration:duration animationTimingFunction:function edgePadding:self.contentInsets completionHandler:completion]; +} + +- (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function edgePadding:(NSEdgeInsets)edgePadding completionHandler:(nullable void (^)(void))completion { mbgl::AnimationOptions animationOptions; if (duration > 0) { animationOptions.duration.emplace(MGLDurationFromTimeInterval(duration)); @@ -1148,7 +1153,7 @@ public: [self willChangeValueForKey:@"camera"]; _mbglMap->cancelTransitions(); - mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera]; + mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera edgePadding:edgePadding]; _mbglMap->easeTo(cameraOptions, animationOptions); [self didChangeValueForKey:@"camera"]; } @@ -1194,17 +1199,17 @@ public: [self willChangeValueForKey:@"camera"]; _mbglMap->cancelTransitions(); - mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera]; + mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera edgePadding:self.contentInsets]; _mbglMap->flyTo(cameraOptions, animationOptions); [self didChangeValueForKey:@"camera"]; } /// Returns a CameraOptions object that specifies parameters for animating to /// the given camera. -- (mbgl::CameraOptions)cameraOptionsObjectForAnimatingToCamera:(MGLMapCamera *)camera { +- (mbgl::CameraOptions)cameraOptionsObjectForAnimatingToCamera:(MGLMapCamera *)camera edgePadding:(NSEdgeInsets) edgePadding { mbgl::CameraOptions options; options.center = MGLLatLngFromLocationCoordinate2D(camera.centerCoordinate); - options.padding = MGLEdgeInsetsFromNSEdgeInsets(self.contentInsets); + options.padding = MGLEdgeInsetsFromNSEdgeInsets(edgePadding); options.zoom = MGLZoomLevelForAltitude(camera.altitude, camera.pitch, camera.centerCoordinate.latitude, self.frame.size); @@ -1267,6 +1272,15 @@ public: return [self cameraForCameraOptions:cameraOptions]; } +- (MGLMapCamera *)cameraThatFitsShape:(MGLShape *)shape direction:(CLLocationDirection)direction edgePadding:(NSEdgeInsets)insets { + mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(insets); + padding += MGLEdgeInsetsFromNSEdgeInsets(self.contentInsets); + + mbgl::CameraOptions cameraOptions = _mbglMap->cameraForGeometry([shape geometryObject], padding, direction); + + return [self cameraForCameraOptions:cameraOptions]; +} + - (MGLMapCamera *)cameraForCameraOptions:(const mbgl::CameraOptions &)cameraOptions { CLLocationCoordinate2D centerCoordinate = MGLLocationCoordinate2DFromLatLng(cameraOptions.center ? *cameraOptions.center : _mbglMap->getLatLng()); double zoomLevel = cameraOptions.zoom ? *cameraOptions.zoom : self.zoomLevel; -- cgit v1.2.1 From ff03a75da41be5fd7063e2dc7ef8c281656a8c4c Mon Sep 17 00:00:00 2001 From: Tobrun Van Nuland Date: Fri, 6 Oct 2017 16:29:49 +0200 Subject: [android] - hold off handling hover events untill map has been created --- .../main/java/com/mapbox/mapboxsdk/maps/MapView.java | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java index a93d05eaed..260933ffa1 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 @@ -2,8 +2,8 @@ package com.mapbox.mapboxsdk.maps; import android.content.Context; import android.graphics.PointF; -import android.os.Build; import android.opengl.GLSurfaceView; +import android.os.Build; import android.os.Bundle; import android.support.annotation.CallSuper; import android.support.annotation.IntDef; @@ -47,9 +47,9 @@ import javax.microedition.khronos.opengles.GL10; import timber.log.Timber; +import static android.opengl.GLSurfaceView.RENDERMODE_WHEN_DIRTY; import static com.mapbox.mapboxsdk.maps.widgets.CompassView.TIME_MAP_NORTH_ANIMATION; import static com.mapbox.mapboxsdk.maps.widgets.CompassView.TIME_WAIT_IDLE; -import static android.opengl.GLSurfaceView.RENDERMODE_WHEN_DIRTY; /** *

@@ -382,7 +382,7 @@ public class MapView extends FrameLayout { @Override public boolean onTouchEvent(MotionEvent event) { - if (!isMapInitialized()) { + if (!isMapInitialized() || !isZoomButtonControllerInitialized()) { return super.onTouchEvent(event); } @@ -419,6 +419,10 @@ public class MapView extends FrameLayout { @Override public boolean onHoverEvent(MotionEvent event) { + if (!isZoomButtonControllerInitialized()) { + return super.onHoverEvent(event); + } + switch (event.getActionMasked()) { case MotionEvent.ACTION_HOVER_ENTER: case MotionEvent.ACTION_HOVER_MOVE: @@ -506,7 +510,9 @@ public class MapView extends FrameLayout { @CallSuper protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - mapZoomButtonController.setVisible(false); + if (isZoomButtonControllerInitialized()) { + mapZoomButtonController.setVisible(false); + } } // Called when view is hidden and shown @@ -516,7 +522,7 @@ public class MapView extends FrameLayout { return; } - if (mapZoomButtonController != null) { + if (isZoomButtonControllerInitialized()) { mapZoomButtonController.setVisible(visibility == View.VISIBLE); } } @@ -582,6 +588,10 @@ public class MapView extends FrameLayout { return nativeMapView != null; } + private boolean isZoomButtonControllerInitialized() { + return mapZoomButtonController != null; + } + MapboxMap getMapboxMap() { return mapboxMap; } -- cgit v1.2.1 From 25be86281d72bdc84d5454503324a01627ea93c5 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Mon, 9 Oct 2017 15:21:39 -0700 Subject: [ios, build] Add tag-based Bitrise iOS deployment workflow --- platform/ios/bitrise.yml | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/platform/ios/bitrise.yml b/platform/ios/bitrise.yml index 36adbf7a77..108a837462 100644 --- a/platform/ios/bitrise.yml +++ b/platform/ios/bitrise.yml @@ -4,6 +4,8 @@ default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git trigger_map: - pattern: nightly-release workflow: nightly-release +- pattern: release-from-tag + workflow: release-from-tag - pattern: "*" is_pull_request_allowed: true workflow: primary @@ -88,8 +90,8 @@ workflows: inputs: - webhook_url: "$SLACK_HOOK_URL" - channel: "#gl-bots" - - from_username: 'Bitrise iOS Nightly \U0001F31D' - - from_username_on_error: 'Bitrise iOS Nightly \U0001F31D' + - from_username: 'Bitrise iOS Nightly 💤' + - from_username_on_error: 'Bitrise iOS Nightly 💤' - message: '<${BITRISE_BUILD_URL}|Build #${BITRISE_BUILD_NUMBER}> for completed successfully.' @@ -98,3 +100,44 @@ workflows: failed.' - icon_url: https://bitrise-public-content-production.s3.amazonaws.com/slack/bitrise-slack-icon-128.png - icon_url_on_error: https://bitrise-public-content-production.s3.amazonaws.com/slack/bitrise-slack-error-icon-128.png + release-from-tag: + steps: + - script: + title: Install Dependencies + inputs: + - content: |- + #!/bin/bash + set -eu -o pipefail + brew install cmake + - is_debug: 'yes' + - script: + title: Configure AWS-CLI + inputs: + - content: |- + #!/bin/bash + apt-get install -y python-pip python-dev build-essential + pip install awscli + - script: + title: Build package + inputs: + - content: |- + #!/bin/bash + set -eu -o pipefail + export VERSION_TAG=${BITRISE_GIT_TAG} + platform/ios/scripts/deploy-packages.sh + - is_debug: 'yes' + - slack: + title: Post to Slack + inputs: + - webhook_url: "$SLACK_HOOK_URL" + - channel: "#gl-bots" + - from_username: 'Bitrise iOS Deploy' + - from_username_on_error: 'Bitrise iOS Deploy' + - message: '<${BITRISE_BUILD_URL}|Build #${BITRISE_BUILD_NUMBER}> + for + completed successfully.' + - message_on_error: '<${BITRISE_BUILD_URL}|Build #${BITRISE_BUILD_NUMBER}> + for + failed.' + - icon_url: https://bitrise-public-content-production.s3.amazonaws.com/slack/bitrise-slack-icon-128.png + - icon_url_on_error: https://bitrise-public-content-production.s3.amazonaws.com/slack/bitrise-slack-error-icon-128.png -- cgit v1.2.1 From 20df5f3844a0874a9978826d3e0e5f4903576fd2 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Mon, 9 Oct 2017 17:46:12 -0700 Subject: [ios, build] Use github-release to check if release has been published --- platform/ios/scripts/deploy-packages.sh | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/platform/ios/scripts/deploy-packages.sh b/platform/ios/scripts/deploy-packages.sh index bdc946497d..103e53768c 100755 --- a/platform/ios/scripts/deploy-packages.sh +++ b/platform/ios/scripts/deploy-packages.sh @@ -58,17 +58,17 @@ BINARY_DIRECTORY=${BINARY_DIRECTORY:-build/ios/deploy} GITHUB_RELEASE=${GITHUB_RELEASE:-true} PUBLISH_PRE_FLAG='' +if [[ -z `which github-release` ]]; then + step "Installing github-release…" + brew install github-release + if [ -z `which github-release` ]; then + echo "Unable to install github-release. See: https://github.com/aktau/github-release" + exit 1 + fi +fi + if [[ ${GITHUB_RELEASE} = "true" ]]; then GITHUB_RELEASE=true # Assign bool, not just a string - - if [[ -z `which github-release` ]]; then - step "Installing github-release…" - brew install github-release - if [ -z `which github-release` ]; then - echo "Unable to install github-release. See: https://github.com/aktau/github-release" - exit 1 - fi - fi fi if [[ -z ${VERSION_TAG} ]]; then @@ -83,7 +83,7 @@ if [[ $( echo ${VERSION_TAG} | grep --invert-match ios-v ) ]]; then exit 1 fi -if [[ $( curl --head https://api.github.com/repos/${GITHUB_USER}/${GITHUB_REPO}/releases/tags/${VERSION_TAG} | head -n 1 | grep -c "404 Not Found") == 0 ]]; then +if github-release info --tag ${VERSION_TAG} | grep --quiet "draft: ✗"; then echo "Error: ${VERSION_TAG} has already been published on GitHub" echo "See: https://github.com/${GITHUB_USER}/${GITHUB_REPO}/releases/tag/${VERSION_TAG}" exit 1 -- cgit v1.2.1 From 16aa29f47597422a83dbbce6c9626a06fa584cf3 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Mon, 9 Oct 2017 17:46:48 -0700 Subject: [ios, build] Remove unnecessary apt-get in Bitrise workflows --- platform/ios/bitrise.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/platform/ios/bitrise.yml b/platform/ios/bitrise.yml index 108a837462..8eb8b3962b 100644 --- a/platform/ios/bitrise.yml +++ b/platform/ios/bitrise.yml @@ -70,7 +70,6 @@ workflows: inputs: - content: |- #!/bin/bash - apt-get install -y python-pip python-dev build-essential pip install awscli - script: title: Build package @@ -115,7 +114,6 @@ workflows: inputs: - content: |- #!/bin/bash - apt-get install -y python-pip python-dev build-essential pip install awscli - script: title: Build package -- cgit v1.2.1 From 50ee8daca1cfc7288bcc39ba962a2af6118e174b Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Tue, 10 Oct 2017 11:45:12 -0700 Subject: [ios, build] Don't include docs for auto-installed jazzy --- platform/ios/scripts/document.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/ios/scripts/document.sh b/platform/ios/scripts/document.sh index 170debb625..57b596a4b9 100755 --- a/platform/ios/scripts/document.sh +++ b/platform/ios/scripts/document.sh @@ -6,7 +6,7 @@ set -u if [ -z `which jazzy` ]; then echo "Installing jazzy…" - gem install jazzy + gem install jazzy --no-rdoc --no-ri if [ -z `which jazzy` ]; then echo "Unable to install jazzy. See https://github.com/mapbox/mapbox-gl-native/blob/master/platform/ios/INSTALL.md" exit 1 -- cgit v1.2.1 From 6d673799f8e632e5566197433f67c39fd3663565 Mon Sep 17 00:00:00 2001 From: Antonio Zugaldia Date: Wed, 11 Oct 2017 09:06:58 -0400 Subject: [android] Revert native loading behavior (#10180) --- .../java/com/mapbox/mapboxsdk/LibraryLoader.java | 23 +--------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/LibraryLoader.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/LibraryLoader.java index 35b1e7cf6a..a024f0ab70 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/LibraryLoader.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/LibraryLoader.java @@ -1,9 +1,5 @@ package com.mapbox.mapboxsdk; -import android.content.Context; - -import java.io.File; - import timber.log.Timber; /** @@ -11,8 +7,6 @@ import timber.log.Timber; */ public class LibraryLoader { - private static final String LIBRARY_NAME = "libmapbox-gl.so"; - /** * Loads "libmapbox-gl.so" native shared library. */ @@ -20,22 +14,7 @@ public class LibraryLoader { try { System.loadLibrary("mapbox-gl"); } catch (UnsatisfiedLinkError error) { - Context context = Mapbox.getApplicationContext(); - if (context != null) { - Timber.d("Loading %s from internal storage.", LIBRARY_NAME); - System.load(getLibraryLocation(context).getAbsolutePath()); - } + Timber.e(error, "Failed to load native shared library."); } } - - /** - * Returns a file in the app internal storage that may contain a locally cached copy - * of the Mapbox native library. - * - * @param context The application context - * @return a file object - */ - public static File getLibraryLocation(Context context) { - return new File(context.getFilesDir(), LIBRARY_NAME); - } } -- cgit v1.2.1 From b7879d5a370f232b3030fb404a9eb8a51d3a7eb1 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Wed, 11 Oct 2017 10:06:30 -0700 Subject: [ios] Update MGLUserLocation.heading for showsUserHeadingIndicator --- platform/ios/src/MGLUserLocation.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/platform/ios/src/MGLUserLocation.h b/platform/ios/src/MGLUserLocation.h index 91abadbcb7..4e01cf00c9 100644 --- a/platform/ios/src/MGLUserLocation.h +++ b/platform/ios/src/MGLUserLocation.h @@ -34,7 +34,8 @@ MGL_EXPORT The heading of the user location. (read-only) This property is `nil` if the user location tracking mode is not - `MGLUserTrackingModeFollowWithHeading`. + `MGLUserTrackingModeFollowWithHeading` or if + `MGLMapView.showsUserHeadingIndicator` is disabled. */ @property (nonatomic, readonly, nullable) CLHeading *heading; -- cgit v1.2.1 From 5cefa515112c538fbb39aca1aa985c13a8299dc4 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Wed, 11 Oct 2017 11:47:05 -0700 Subject: [ios] Rename SMCalloutView and stop using submodule --- .gitmodules | 3 - platform/ios/CHANGELOG.md | 1 + platform/ios/src/MGLCompactCalloutView.h | 2 +- platform/ios/src/MGLMapView.mm | 4 +- platform/ios/vendor/SMCalloutView | 1 - platform/ios/vendor/SMCalloutView/SMCalloutView.h | 205 ++++++ platform/ios/vendor/SMCalloutView/SMCalloutView.m | 851 ++++++++++++++++++++++ 7 files changed, 1060 insertions(+), 7 deletions(-) delete mode 160000 platform/ios/vendor/SMCalloutView create mode 100755 platform/ios/vendor/SMCalloutView/SMCalloutView.h create mode 100755 platform/ios/vendor/SMCalloutView/SMCalloutView.m diff --git a/.gitmodules b/.gitmodules index 72cbe56da7..422fc3930e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "platform/ios/vendor/SMCalloutView"] - path = platform/ios/vendor/SMCalloutView - url = https://github.com/nfarina/calloutview.git [submodule "platform/ios/uitest/KIF"] path = platform/ios/uitest/KIF url = https://github.com/kif-framework/KIF.git diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 3035be70e1..82de619d7b 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -36,6 +36,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT * Fixed an issue that could cause line label rendering glitches when the line geometry is projected to a point behind the plane of the camera. ([#9865](https://github.com/mapbox/mapbox-gl-native/pull/9865)) * Fixed an issue that could cause a crash when using `-[MGLMapView flyToCamera:completionHandler:]` and related methods with zoom levels at or near the maximum value. ([#9381](https://github.com/mapbox/mapbox-gl-native/pull/9381)) * Added `-[MGLMapView showAttribution:]` to allow custom attribution buttons to show the default attribution interface. ([#10085](https://github.com/mapbox/mapbox-gl-native/pull/10085)) +* Fixed a conflict between multiple copies of SMCalloutView in a project. ([#10183](https://github.com/mapbox/mapbox-gl-native/pull/10183)) ## 3.6.4 - September 25, 2017 diff --git a/platform/ios/src/MGLCompactCalloutView.h b/platform/ios/src/MGLCompactCalloutView.h index 56c48a99e5..5cecf37ff6 100644 --- a/platform/ios/src/MGLCompactCalloutView.h +++ b/platform/ios/src/MGLCompactCalloutView.h @@ -7,7 +7,7 @@ callout view displays the represented annotation’s title, subtitle, and accessory views in a compact, two-line layout. */ -@interface MGLCompactCalloutView : SMCalloutView +@interface MGLCompactCalloutView : MGLSMCalloutView + (instancetype)platformCalloutView; diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm index 0e76c0c71c..ac608dd074 100644 --- a/platform/ios/src/MGLMapView.mm +++ b/platform/ios/src/MGLMapView.mm @@ -232,7 +232,7 @@ public: @interface MGLMapView () @@ -1929,7 +1929,7 @@ public: return [self.delegate respondsToSelector:@selector(mapView:tapOnCalloutForAnnotation:)]; } -- (void)calloutViewClicked:(__unused SMCalloutView *)calloutView +- (void)calloutViewClicked:(__unused MGLSMCalloutView *)calloutView { if ([self.delegate respondsToSelector:@selector(mapView:tapOnCalloutForAnnotation:)]) { diff --git a/platform/ios/vendor/SMCalloutView b/platform/ios/vendor/SMCalloutView deleted file mode 160000 index d6ecaba377..0000000000 --- a/platform/ios/vendor/SMCalloutView +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d6ecaba377c9f963aef630faf86e3b8f8cdb88d1 diff --git a/platform/ios/vendor/SMCalloutView/SMCalloutView.h b/platform/ios/vendor/SMCalloutView/SMCalloutView.h new file mode 100755 index 0000000000..0b14913626 --- /dev/null +++ b/platform/ios/vendor/SMCalloutView/SMCalloutView.h @@ -0,0 +1,205 @@ +#import +#import + +/* + +SMCalloutView +------------- +Created by Nick Farina (nfarina@gmail.com) +Version 2.1.5 + +*/ + +/// options for which directions the callout is allowed to "point" in. +typedef NS_OPTIONS(NSUInteger, MGLSMCalloutArrowDirection) { + MGLSMCalloutArrowDirectionUp = 1 << 0, + MGLSMCalloutArrowDirectionDown = 1 << 1, + MGLSMCalloutArrowDirectionAny = MGLSMCalloutArrowDirectionUp | MGLSMCalloutArrowDirectionDown +}; + +/// options for the callout present/dismiss animation +typedef NS_ENUM(NSInteger, MGLSMCalloutAnimation) { + /// the "bounce" animation we all know and love from @c UIAlertView + MGLSMCalloutAnimationBounce, + /// a simple fade in or out + MGLSMCalloutAnimationFade, + /// grow or shrink linearly, like in the iPad Calendar app + MGLSMCalloutAnimationStretch +}; + +NS_ASSUME_NONNULL_BEGIN + +/// when delaying our popup in order to scroll content into view, you can use this amount to match the +/// animation duration of UIScrollView when using @c -setContentOffset:animated. +extern NSTimeInterval const kMGLSMCalloutViewRepositionDelayForUIScrollView; + +@protocol MGLSMCalloutViewDelegate; +@class MGLSMCalloutBackgroundView; + +// +// Callout view. +// + +// iOS 10+ expects CAAnimationDelegate to be set explicitly. +#if __IPHONE_OS_VERSION_MAX_ALLOWED < 100000 +@interface MGLSMCalloutView : UIView +#else +@interface MGLSMCalloutView : UIView +#endif + +@property (nonatomic, weak, nullable) id delegate; +/// title/titleView relationship mimics UINavigationBar. +@property (nonatomic, copy, nullable) NSString *title; +@property (nonatomic, copy, nullable) NSString *subtitle; + +/// Left accessory view for the call out +@property (nonatomic, strong, nullable) UIView *leftAccessoryView; +/// Right accessoty view for the call out +@property (nonatomic, strong, nullable) UIView *rightAccessoryView; +/// Default @c SMCalloutArrowDirectionDown +@property (nonatomic, assign) MGLSMCalloutArrowDirection permittedArrowDirection; +/// The current arrow direction +@property (nonatomic, readonly) MGLSMCalloutArrowDirection currentArrowDirection; +/// if the @c UIView you're constraining to has portions that are overlapped by nav bar, tab bar, etc. you'll need to tell us those insets. +@property (nonatomic, assign) UIEdgeInsets constrainedInsets; +/// default is @c SMCalloutMaskedBackgroundView, or @c SMCalloutDrawnBackgroundView when using @c SMClassicCalloutView +@property (nonatomic, strong) MGLSMCalloutBackgroundView *backgroundView; + +/** + @brief Custom title view. + + @disucssion Keep in mind that @c SMCalloutView calls @c -sizeThatFits on titleView/subtitleView if defined, so your view + may be resized as a result of that (especially if you're using @c UILabel/UITextField). You may want to subclass and override @c -sizeThatFits, or just wrap your view in a "generic" @c UIView if you do not want it to be auto-sized. + + @warning If this is set, the respective @c title property will be ignored. + */ +@property (nonatomic, strong, nullable) UIView *titleView; + +/** + @brief Custom subtitle view. + + @discussion Keep in mind that @c SMCalloutView calls @c -sizeThatFits on subtitleView if defined, so your view + may be resized as a result of that (especially if you're using @c UILabel/UITextField). You may want to subclass and override @c -sizeThatFits, or just wrap your view in a "generic" @c UIView if you do not want it to be auto-sized. + + @warning If this is set, the respective @c subtitle property will be ignored. + */ +@property (nonatomic, strong, nullable) UIView *subtitleView; + +/// Custom "content" view that can be any width/height. If this is set, title/subtitle/titleView/subtitleView are all ignored. +@property (nonatomic, retain, nullable) UIView *contentView; + +/// Custom content view margin +@property (nonatomic, assign) UIEdgeInsets contentViewInset; + +/// calloutOffset is the offset in screen points from the top-middle of the target view, where the anchor of the callout should be shown. +@property (nonatomic, assign) CGPoint calloutOffset; + +/// default SMCalloutAnimationBounce, SMCalloutAnimationFade respectively +@property (nonatomic, assign) MGLSMCalloutAnimation presentAnimation, dismissAnimation; + +/// Returns a new instance of SMCalloutView if running on iOS 7 or better, otherwise a new instance of SMClassicCalloutView if available. ++ (MGLSMCalloutView *)platformCalloutView; + +/** + @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 bounds of the given view. Optionally scrolls the given rect into view (plus margins) + if @c -delegate is set and responds to @c -delayForRepositionWithSize. + + @param rect @c CGRect to present the view from + @param view view to 'constrain' the @c constrainedView to + @param constrainedView @c UIView to be constrainted in @c view + @param animated @c BOOL if presentation should be animated + */ +- (void)presentCalloutFromRect:(CGRect)rect inView:(UIView *)view constrainedToView:(UIView *)constrainedView 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. + @note Be aware that you'll have to direct your own touches to any accessory views, since CALayer doesn't relay touch events. + + @param rect @c CGRect to present the view from + @param layer layer to 'constrain' the @c constrainedLayer to + @param constrainedLayer @c CALayer to be constrained in @c layer + @param animated @c BOOL if presentation should be animated + */ +- (void)presentCalloutFromRect:(CGRect)rect inLayer:(CALayer *)layer constrainedToLayer:(CALayer *)constrainedLayer animated:(BOOL)animated; + +/** + Dismiss the callout view + + @param animated @c BOOL if dismissal should be animated + */ +- (void)dismissCalloutAnimated:(BOOL)animated; + +/// For subclassers. You can override this method to provide your own custom animation for presenting/dismissing the callout. +- (CAAnimation *)animationWithType:(MGLSMCalloutAnimation)type presenting:(BOOL)presenting; + +@end + +// +// Background view - default draws the iOS 7 system background style (translucent white with rounded arrow). +// + +/// Abstract base class +@interface MGLSMCalloutBackgroundView : UIView +/// indicates where the tip of the arrow should be drawn, as a pixel offset +@property (nonatomic, assign) CGPoint arrowPoint; +/// will be set by the callout when the callout is in a highlighted state +@property (nonatomic, assign) BOOL highlighted; +/// returns an optional layer whose contents should mask the callout view's contents (not honored by @c SMClassicCalloutView ) +@property (nonatomic, assign) CALayer *contentMask; +/// height of the callout "arrow" +@property (nonatomic, assign) CGFloat anchorHeight; +/// the smallest possible distance from the edge of our control to the "tip" of the anchor, from either left or right +@property (nonatomic, assign) CGFloat anchorMargin; +@end + +/// Default for iOS 7, this reproduces the "masked" behavior of the iOS 7-style callout view. +/// Accessories are masked by the shape of the callout (including the arrow itself). +@interface MGLSMCalloutMaskedBackgroundView : MGLSMCalloutBackgroundView +@end + +// +// Delegate methods +// + +@protocol MGLSMCalloutViewDelegate +@optional + +/// Controls whether the callout "highlights" when pressed. default YES. You must also respond to @c -calloutViewClicked below. +/// Not honored by @c SMClassicCalloutView. +- (BOOL)calloutViewShouldHighlight:(MGLSMCalloutView *)calloutView; + +/// Called when the callout view is clicked. Not honored by @c SMClassicCalloutView. +- (void)calloutViewClicked:(MGLSMCalloutView *)calloutView; + +/** + Called when the callout view detects that it will be outside the constrained view when it appears, + or if the target rect was already outside the constrained view. You can implement this selector + to respond to this situation by repositioning your content first in order to make everything visible. + The @c CGSize passed is the calculated offset necessary to make everything visible (plus a nice margin). + It expects you to return the amount of time you need to reposition things so the popup can be delayed. + Typically you would return @c kSMCalloutViewRepositionDelayForUIScrollView if you're repositioning by calling @c [UIScrollView @c setContentOffset:animated:]. + + @param calloutView the @c SMCalloutView to reposition + @param offset caluclated offset necessary to make everything visible + @returns @c NSTimeInterval to delay the repositioning + */ +- (NSTimeInterval)calloutView:(MGLSMCalloutView *)calloutView delayForRepositionWithSize:(CGSize)offset; + +/// Called before the callout view appears on screen, or before the appearance animation will start. +- (void)calloutViewWillAppear:(MGLSMCalloutView *)calloutView; + +/// Called after the callout view appears on screen, or after the appearance animation is complete. +- (void)calloutViewDidAppear:(MGLSMCalloutView *)calloutView; + +/// Called before the callout view is removed from the screen, or before the disappearance animation is complete. +- (void)calloutViewWillDisappear:(MGLSMCalloutView *)calloutView; + +/// Called after the callout view is removed from the screen, or after the disappearance animation is complete. +- (void)calloutViewDidDisappear:(MGLSMCalloutView *)calloutView; + +NS_ASSUME_NONNULL_END +@end diff --git a/platform/ios/vendor/SMCalloutView/SMCalloutView.m b/platform/ios/vendor/SMCalloutView/SMCalloutView.m new file mode 100755 index 0000000000..9631ca0367 --- /dev/null +++ b/platform/ios/vendor/SMCalloutView/SMCalloutView.m @@ -0,0 +1,851 @@ +#import "SMCalloutView.h" + +// +// UIView frame helpers - we do a lot of UIView frame fiddling in this class; these functions help keep things readable. +// + +@interface UIView (SMFrameAdditions) +@property (nonatomic, assign) CGPoint frameOrigin; +@property (nonatomic, assign) CGSize frameSize; +@property (nonatomic, assign) CGFloat frameX, frameY, frameWidth, frameHeight; // normal rect properties +@property (nonatomic, assign) CGFloat frameLeft, frameTop, frameRight, frameBottom; // these will stretch/shrink the rect +@end + +// +// Callout View. +// + +#define CALLOUT_DEFAULT_CONTAINER_HEIGHT 44 // height of just the main portion without arrow +#define CALLOUT_SUB_DEFAULT_CONTAINER_HEIGHT 52 // height of just the main portion without arrow (when subtitle is present) +#define CALLOUT_MIN_WIDTH 61 // minimum width of system callout +#define TITLE_HMARGIN 12 // the title/subtitle view's normal horizontal margin from the edges of our callout view or from the accessories +#define TITLE_TOP 11 // the top of the title view when no subtitle is present +#define TITLE_SUB_TOP 4 // the top of the title view when a subtitle IS present +#define TITLE_HEIGHT 21 // title height, fixed +#define SUBTITLE_TOP 28 // the top of the subtitle, when present +#define SUBTITLE_HEIGHT 15 // subtitle height, fixed +#define BETWEEN_ACCESSORIES_MARGIN 7 // margin between accessories when no title/subtitle is present +#define TOP_ANCHOR_MARGIN 13 // all the above measurements assume a bottom anchor! if we're pointing "up" we'll need to add this top margin to everything. +#define COMFORTABLE_MARGIN 10 // when we try to reposition content to be visible, we'll consider this margin around your target rect + +NSTimeInterval const kMGLSMCalloutViewRepositionDelayForUIScrollView = 1.0/3.0; + +@interface MGLSMCalloutView () +@property (nonatomic, strong) UIButton *containerView; // for masking and interaction +@property (nonatomic, strong) UILabel *titleLabel, *subtitleLabel; +@property (nonatomic, assign) MGLSMCalloutArrowDirection currentArrowDirection; +@property (nonatomic, assign) BOOL popupCancelled; +@end + +@implementation MGLSMCalloutView + ++ (MGLSMCalloutView *)platformCalloutView { + // MGL: Mapbox does not need or include the custom flavor, so this is modified to just use SMCalloutView. + return [MGLSMCalloutView new]; +} + +- (id)initWithFrame:(CGRect)frame { + if (self = [super initWithFrame:frame]) { + self.permittedArrowDirection = MGLSMCalloutArrowDirectionDown; + self.presentAnimation = MGLSMCalloutAnimationBounce; + self.dismissAnimation = MGLSMCalloutAnimationFade; + self.backgroundColor = [UIColor clearColor]; + self.containerView = [UIButton new]; + self.containerView.isAccessibilityElement = NO; + self.isAccessibilityElement = NO; + self.contentViewInset = UIEdgeInsetsMake(12, 12, 12, 12); + + [self.containerView addTarget:self action:@selector(highlightIfNecessary) forControlEvents:UIControlEventTouchDown | UIControlEventTouchDragInside]; + [self.containerView addTarget:self action:@selector(unhighlightIfNecessary) forControlEvents:UIControlEventTouchDragOutside | UIControlEventTouchCancel | UIControlEventTouchUpOutside | UIControlEventTouchUpInside]; + [self.containerView addTarget:self action:@selector(calloutClicked) forControlEvents:UIControlEventTouchUpInside]; + } + return self; +} + +- (BOOL)supportsHighlighting { + if (![self.delegate respondsToSelector:@selector(calloutViewClicked:)]) + return NO; + if ([self.delegate respondsToSelector:@selector(calloutViewShouldHighlight:)]) + return [self.delegate calloutViewShouldHighlight:self]; + return YES; +} + +- (void)highlightIfNecessary { if (self.supportsHighlighting) self.backgroundView.highlighted = YES; } +- (void)unhighlightIfNecessary { if (self.supportsHighlighting) self.backgroundView.highlighted = NO; } + +- (void)calloutClicked { + if ([self.delegate respondsToSelector:@selector(calloutViewClicked:)]) + [self.delegate calloutViewClicked:self]; +} + +- (UIView *)titleViewOrDefault { + if (self.titleView) + // if you have a custom title view defined, return that. + return self.titleView; + else { + if (!self.titleLabel) { + // create a default titleView + self.titleLabel = [UILabel new]; + self.titleLabel.frameHeight = TITLE_HEIGHT; + self.titleLabel.opaque = NO; + self.titleLabel.backgroundColor = [UIColor clearColor]; + self.titleLabel.font = [UIFont systemFontOfSize:17]; + self.titleLabel.textColor = [UIColor blackColor]; + } + return self.titleLabel; + } +} + +- (UIView *)subtitleViewOrDefault { + if (self.subtitleView) + // if you have a custom subtitle view defined, return that. + return self.subtitleView; + else { + if (!self.subtitleLabel) { + // create a default subtitleView + self.subtitleLabel = [UILabel new]; + self.subtitleLabel.frameHeight = SUBTITLE_HEIGHT; + self.subtitleLabel.opaque = NO; + self.subtitleLabel.backgroundColor = [UIColor clearColor]; + self.subtitleLabel.font = [UIFont systemFontOfSize:12]; + self.subtitleLabel.textColor = [UIColor blackColor]; + } + return self.subtitleLabel; + } +} + +- (MGLSMCalloutBackgroundView *)backgroundView { + // create our default background on first access only if it's nil, since you might have set your own background anyway. + return _backgroundView ? _backgroundView : (_backgroundView = [self defaultBackgroundView]); +} + +- (MGLSMCalloutBackgroundView *)defaultBackgroundView { + return [MGLSMCalloutMaskedBackgroundView new]; +} + +- (void)rebuildSubviews { + // remove and re-add our appropriate subviews in the appropriate order + [self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; + [self.containerView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; + [self setNeedsDisplay]; + + [self addSubview:self.backgroundView]; + [self addSubview:self.containerView]; + + if (self.contentView) { + [self.containerView addSubview:self.contentView]; + } + else { + if (self.titleViewOrDefault) [self.containerView addSubview:self.titleViewOrDefault]; + if (self.subtitleViewOrDefault) [self.containerView addSubview:self.subtitleViewOrDefault]; + } + if (self.leftAccessoryView) [self.containerView addSubview:self.leftAccessoryView]; + if (self.rightAccessoryView) [self.containerView addSubview:self.rightAccessoryView]; +} + +// Accessory margins. Accessories are centered vertically when shorter +// than the callout, otherwise they grow from the upper corner. + +- (CGFloat)leftAccessoryVerticalMargin { + if (self.leftAccessoryView.frameHeight < self.calloutContainerHeight) + return roundf((self.calloutContainerHeight - self.leftAccessoryView.frameHeight) / 2); + else + return 0; +} + +- (CGFloat)leftAccessoryHorizontalMargin { + return fminf(self.leftAccessoryVerticalMargin, TITLE_HMARGIN); +} + +- (CGFloat)rightAccessoryVerticalMargin { + if (self.rightAccessoryView.frameHeight < self.calloutContainerHeight) + return roundf((self.calloutContainerHeight - self.rightAccessoryView.frameHeight) / 2); + else + return 0; +} + +- (CGFloat)rightAccessoryHorizontalMargin { + return fminf(self.rightAccessoryVerticalMargin, TITLE_HMARGIN); +} + +- (CGFloat)innerContentMarginLeft { + if (self.leftAccessoryView) + return self.leftAccessoryHorizontalMargin + self.leftAccessoryView.frameWidth + TITLE_HMARGIN; + else + return self.contentViewInset.left; +} + +- (CGFloat)innerContentMarginRight { + if (self.rightAccessoryView) + return self.rightAccessoryHorizontalMargin + self.rightAccessoryView.frameWidth + TITLE_HMARGIN; + else + return self.contentViewInset.right; +} + +- (CGFloat)calloutHeight { + return self.calloutContainerHeight + self.backgroundView.anchorHeight; +} + +- (CGFloat)calloutContainerHeight { + if (self.contentView) + return self.contentView.frameHeight + self.contentViewInset.bottom + self.contentViewInset.top; + else if (self.subtitleView || self.subtitle.length > 0) + return CALLOUT_SUB_DEFAULT_CONTAINER_HEIGHT; + else + return CALLOUT_DEFAULT_CONTAINER_HEIGHT; +} + +- (CGSize)sizeThatFits:(CGSize)size { + + // calculate how much non-negotiable space we need to reserve for margin and accessories + CGFloat margin = self.innerContentMarginLeft + self.innerContentMarginRight; + + // how much room is left for text? + CGFloat availableWidthForText = size.width - margin - 1; + + // no room for text? then we'll have to squeeze into the given size somehow. + if (availableWidthForText < 0) + availableWidthForText = 0; + + CGSize preferredTitleSize = [self.titleViewOrDefault sizeThatFits:CGSizeMake(availableWidthForText, TITLE_HEIGHT)]; + CGSize preferredSubtitleSize = [self.subtitleViewOrDefault sizeThatFits:CGSizeMake(availableWidthForText, SUBTITLE_HEIGHT)]; + + // total width we'd like + CGFloat preferredWidth; + + if (self.contentView) { + + // if we have a content view, then take our preferred size directly from that + preferredWidth = self.contentView.frameWidth + margin; + } + else if (preferredTitleSize.width >= 0.000001 || preferredSubtitleSize.width >= 0.000001) { + + // if we have a title or subtitle, then our assumed margins are valid, and we can apply them + preferredWidth = fmaxf(preferredTitleSize.width, preferredSubtitleSize.width) + margin; + } + else { + // ok we have no title or subtitle to speak of. In this case, the system callout would actually not display + // at all! But we can handle it. + preferredWidth = self.leftAccessoryView.frameWidth + self.rightAccessoryView.frameWidth + self.leftAccessoryHorizontalMargin + self.rightAccessoryHorizontalMargin; + + if (self.leftAccessoryView && self.rightAccessoryView) + preferredWidth += BETWEEN_ACCESSORIES_MARGIN; + } + + // ensure we're big enough to fit our graphics! + preferredWidth = fmaxf(preferredWidth, CALLOUT_MIN_WIDTH); + + // ask to be smaller if we have space, otherwise we'll fit into what we have by truncating the title/subtitle. + return CGSizeMake(fminf(preferredWidth, size.width), self.calloutHeight); +} + +- (CGSize)offsetToContainRect:(CGRect)innerRect inRect:(CGRect)outerRect { + CGFloat nudgeRight = fmaxf(0, CGRectGetMinX(outerRect) - CGRectGetMinX(innerRect)); + CGFloat nudgeLeft = fminf(0, CGRectGetMaxX(outerRect) - CGRectGetMaxX(innerRect)); + CGFloat nudgeTop = fmaxf(0, CGRectGetMinY(outerRect) - CGRectGetMinY(innerRect)); + CGFloat nudgeBottom = fminf(0, CGRectGetMaxY(outerRect) - CGRectGetMaxY(innerRect)); + return CGSizeMake(nudgeLeft ? nudgeLeft : nudgeRight, nudgeTop ? nudgeTop : nudgeBottom); +} + +- (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]; +} + +- (void)presentCalloutFromRect:(CGRect)rect inLayer:(CALayer *)layer constrainedToLayer:(CALayer *)constrainedLayer animated:(BOOL)animated { + [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 { + + // Sanity check: dismiss this callout immediately if it's displayed somewhere + if (self.layer.superlayer) [self dismissCalloutAnimated:NO]; + + // cancel all animations that may be in progress + [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); + + constrainedRect = CGRectInset(constrainedRect, COMFORTABLE_MARGIN, COMFORTABLE_MARGIN); + + // form our subviews based on our content set so far + [self rebuildSubviews]; + + // apply title/subtitle (if present + self.titleLabel.text = self.title; + self.subtitleLabel.text = self.subtitle; + + // size the callout to fit the width constraint as best as possible + self.frameSize = [self sizeThatFits:CGSizeMake(constrainedRect.size.width, self.calloutHeight)]; + + // how much room do we have in the constraint box, both above and below our target rect? + CGFloat topSpace = CGRectGetMinY(rect) - CGRectGetMinY(constrainedRect); + CGFloat bottomSpace = CGRectGetMaxY(constrainedRect) - CGRectGetMaxY(rect); + + // we prefer to point our arrow down. + MGLSMCalloutArrowDirection bestDirection = MGLSMCalloutArrowDirectionDown; + + // we'll point it up though if that's the only option you gave us. + if (self.permittedArrowDirection == MGLSMCalloutArrowDirectionUp) + bestDirection = MGLSMCalloutArrowDirectionUp; + + // or, if we don't have enough space on the top and have more space on the bottom, and you + // gave us a choice, then pointing up is the better option. + if (self.permittedArrowDirection == MGLSMCalloutArrowDirectionAny && topSpace < self.calloutHeight && bottomSpace > topSpace) + bestDirection = MGLSMCalloutArrowDirectionUp; + + self.currentArrowDirection = bestDirection; + + // we want to point directly at the horizontal center of the given rect. calculate our "anchor point" in terms of our + // target view's coordinate system. make sure to offset the anchor point as requested if necessary. + CGFloat anchorX = self.calloutOffset.x + CGRectGetMidX(rect); + CGFloat anchorY = self.calloutOffset.y + (bestDirection == MGLSMCalloutArrowDirectionDown ? CGRectGetMinY(rect) : CGRectGetMaxY(rect)); + + // we prefer to sit centered directly above our anchor + CGFloat calloutX = roundf(anchorX - self.frameWidth / 2); + + // but not if it's going to get too close to the edge of our constraints + if (calloutX < constrainedRect.origin.x) + calloutX = constrainedRect.origin.x; + + if (calloutX > constrainedRect.origin.x+constrainedRect.size.width-self.frameWidth) + calloutX = constrainedRect.origin.x+constrainedRect.size.width-self.frameWidth; + + // what's the farthest to the left and right that we could point to, given our background image constraints? + CGFloat minPointX = calloutX + self.backgroundView.anchorMargin; + CGFloat maxPointX = calloutX + self.frameWidth - self.backgroundView.anchorMargin; + + // we may need to scoot over to the left or right to point at the correct spot + CGFloat adjustX = 0; + if (anchorX < minPointX) adjustX = anchorX - minPointX; + if (anchorX > maxPointX) adjustX = anchorX - maxPointX; + + // add the callout to the given layer (or view if possible, to receive touch events) + if (view) + [view addSubview:self]; + else + [layer addSublayer:self.layer]; + + CGPoint calloutOrigin = { + .x = calloutX + adjustX, + .y = bestDirection == MGLSMCalloutArrowDirectionDown ? (anchorY - self.calloutHeight) : anchorY + }; + + self.frameOrigin = calloutOrigin; + + // now set the *actual* anchor point for our layer so that our "popup" animation starts from this point. + CGPoint anchorPoint = [layer convertPoint:CGPointMake(anchorX, anchorY) toLayer:self.layer]; + + // pass on the anchor point to our background view so it knows where to draw the arrow + self.backgroundView.arrowPoint = anchorPoint; + + // adjust it to unit coordinates for the actual layer.anchorPoint property + anchorPoint.x /= self.frameWidth; + anchorPoint.y /= self.frameHeight; + self.layer.anchorPoint = anchorPoint; + + // setting the anchor point moves the view a bit, so we need to reset + self.frameOrigin = calloutOrigin; + + // make sure our frame is not on half-pixels or else we may be blurry! + CGFloat scale = [UIScreen mainScreen].scale; + self.frameX = floorf(self.frameX*scale)/scale; + self.frameY = floorf(self.frameY*scale)/scale; + + // layout now so we can immediately start animating to the final position if needed + [self setNeedsLayout]; + [self layoutIfNeeded]; + + // if we're outside the bounds of our constraint rect, we'll give our delegate an opportunity to shift us into position. + // consider both our size and the size of our target rect (which we'll assume to be the size of the content you want to scroll into view. + CGRect contentRect = CGRectUnion(self.frame, rect); + CGSize offset = [self offsetToContainRect:contentRect inRect:constrainedRect]; + + NSTimeInterval delay = 0; + self.popupCancelled = NO; // reset this before calling our delegate below + + if ([self.delegate respondsToSelector:@selector(calloutView:delayForRepositionWithSize:)] && !CGSizeEqualToSize(offset, CGSizeZero)) + delay = [self.delegate calloutView:(id)self delayForRepositionWithSize:offset]; + + // there's a chance that user code in the delegate method may have called -dismissCalloutAnimated to cancel things; if that + // happened then we need to bail! + if (self.popupCancelled) return; + + // now we want to mask our contents to our background view (if requested) to match the iOS 7 style + self.containerView.layer.mask = self.backgroundView.contentMask; + + // if we need to delay, we don't want to be visible while we're delaying, so hide us in preparation for our popup + self.hidden = YES; + + // create the appropriate animation, even if we're not animated + CAAnimation *animation = [self animationWithType:self.presentAnimation presenting:YES]; + + // nuke the duration if no animation requested - we'll still need to "run" the animation to get delays and callbacks + if (!animated) + animation.duration = 0.0000001; // can't be zero or the animation won't "run" + + animation.beginTime = CACurrentMediaTime() + delay; + animation.delegate = self; + + [self.layer addAnimation:animation forKey:@"present"]; +} + +- (void)animationDidStart:(CAAnimation *)anim { + BOOL presenting = [[anim valueForKey:@"presenting"] boolValue]; + + if (presenting) { + if ([_delegate respondsToSelector:@selector(calloutViewWillAppear:)]) + [_delegate calloutViewWillAppear:(id)self]; + + // ok, animation is on, let's make ourselves visible! + self.hidden = NO; + } + else if (!presenting) { + if ([_delegate respondsToSelector:@selector(calloutViewWillDisappear:)]) + [_delegate calloutViewWillDisappear:(id)self]; + } +} + +- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)finished { + BOOL presenting = [[anim valueForKey:@"presenting"] boolValue]; + + if (presenting && finished) { + if ([_delegate respondsToSelector:@selector(calloutViewDidAppear:)]) + [_delegate calloutViewDidAppear:(id)self]; + } + else if (!presenting && finished) { + + [self removeFromParent]; + [self.layer removeAnimationForKey:@"dismiss"]; + + if ([_delegate respondsToSelector:@selector(calloutViewDidDisappear:)]) + [_delegate calloutViewDidDisappear:(id)self]; + } +} + +- (void)dismissCalloutAnimated:(BOOL)animated { + + // cancel all animations that may be in progress + [self.layer removeAnimationForKey:@"present"]; + [self.layer removeAnimationForKey:@"dismiss"]; + + self.popupCancelled = YES; + + if (animated) { + CAAnimation *animation = [self animationWithType:self.dismissAnimation presenting:NO]; + animation.delegate = self; + [self.layer addAnimation:animation forKey:@"dismiss"]; + } + else { + [self removeFromParent]; + } +} + +- (void)removeFromParent { + if (self.superview) + [self removeFromSuperview]; + else { + // removing a layer from a superlayer causes an implicit fade-out animation that we wish to disable. + [CATransaction begin]; + [CATransaction setDisableActions:YES]; + [self.layer removeFromSuperlayer]; + [CATransaction commit]; + } +} + +- (CAAnimation *)animationWithType:(MGLSMCalloutAnimation)type presenting:(BOOL)presenting { + CAAnimation *animation = nil; + + if (type == MGLSMCalloutAnimationBounce) { + + CABasicAnimation *fade = [CABasicAnimation animationWithKeyPath:@"opacity"]; + fade.duration = 0.23; + fade.fromValue = presenting ? @0.0 : @1.0; + fade.toValue = presenting ? @1.0 : @0.0; + fade.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; + + CABasicAnimation *bounce = [CABasicAnimation animationWithKeyPath:@"transform.scale"]; + bounce.duration = 0.23; + bounce.fromValue = presenting ? @0.7 : @1.0; + bounce.toValue = presenting ? @1.0 : @0.7; + bounce.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.59367:0.12066:0.18878:1.5814]; + + CAAnimationGroup *group = [CAAnimationGroup animation]; + group.animations = @[fade, bounce]; + group.duration = 0.23; + + animation = group; + } + else if (type == MGLSMCalloutAnimationFade) { + CABasicAnimation *fade = [CABasicAnimation animationWithKeyPath:@"opacity"]; + fade.duration = 1.0/3.0; + fade.fromValue = presenting ? @0.0 : @1.0; + fade.toValue = presenting ? @1.0 : @0.0; + animation = fade; + } + else if (type == MGLSMCalloutAnimationStretch) { + CABasicAnimation *stretch = [CABasicAnimation animationWithKeyPath:@"transform.scale"]; + stretch.duration = 0.1; + stretch.fromValue = presenting ? @0.0 : @1.0; + stretch.toValue = presenting ? @1.0 : @0.0; + animation = stretch; + } + + // CAAnimation is KVC compliant, so we can store whether we're presenting for lookup in our delegate methods + [animation setValue:@(presenting) forKey:@"presenting"]; + + animation.fillMode = kCAFillModeForwards; + animation.removedOnCompletion = NO; + return animation; +} + +- (void)layoutSubviews { + + self.containerView.frame = self.bounds; + self.backgroundView.frame = self.bounds; + + // if we're pointing up, we'll need to push almost everything down a bit + CGFloat dy = self.currentArrowDirection == MGLSMCalloutArrowDirectionUp ? TOP_ANCHOR_MARGIN : 0; + + self.titleViewOrDefault.frameX = self.innerContentMarginLeft; + self.titleViewOrDefault.frameY = (self.subtitleView || self.subtitle.length ? TITLE_SUB_TOP : TITLE_TOP) + dy; + self.titleViewOrDefault.frameWidth = self.frameWidth - self.innerContentMarginLeft - self.innerContentMarginRight; + + self.subtitleViewOrDefault.frameX = self.titleViewOrDefault.frameX; + self.subtitleViewOrDefault.frameY = SUBTITLE_TOP + dy; + self.subtitleViewOrDefault.frameWidth = self.titleViewOrDefault.frameWidth; + + self.leftAccessoryView.frameX = self.leftAccessoryHorizontalMargin; + self.leftAccessoryView.frameY = self.leftAccessoryVerticalMargin + dy; + + self.rightAccessoryView.frameX = self.frameWidth - self.rightAccessoryHorizontalMargin - self.rightAccessoryView.frameWidth; + self.rightAccessoryView.frameY = self.rightAccessoryVerticalMargin + dy; + + if (self.contentView) { + self.contentView.frameX = self.innerContentMarginLeft; + self.contentView.frameY = self.contentViewInset.top + dy; + } +} + +#pragma mark - Accessibility + +- (NSInteger)accessibilityElementCount { + return (!!self.leftAccessoryView + !!self.titleViewOrDefault + + !!self.subtitleViewOrDefault + !!self.rightAccessoryView); +} + +- (id)accessibilityElementAtIndex:(NSInteger)index { + if (index == 0) { + return self.leftAccessoryView ? self.leftAccessoryView : self.titleViewOrDefault; + } + if (index == 1) { + return self.leftAccessoryView ? self.titleViewOrDefault : self.subtitleViewOrDefault; + } + if (index == 2) { + return self.leftAccessoryView ? self.subtitleViewOrDefault : self.rightAccessoryView; + } + if (index == 3) { + return self.leftAccessoryView ? self.rightAccessoryView : nil; + } + return nil; +} + +- (NSInteger)indexOfAccessibilityElement:(id)element { + if (element == nil) return NSNotFound; + if (element == self.leftAccessoryView) return 0; + if (element == self.titleViewOrDefault) { + return self.leftAccessoryView ? 1 : 0; + } + if (element == self.subtitleViewOrDefault) { + return self.leftAccessoryView ? 2 : 1; + } + if (element == self.rightAccessoryView) { + return self.leftAccessoryView ? 3 : 2; + } + return NSNotFound; +} + +@end + +// import this known "private API" from SMCalloutBackgroundView +@interface MGLSMCalloutBackgroundView (EmbeddedImages) ++ (UIImage *)embeddedImageNamed:(NSString *)name; +@end + +// +// Callout Background View. +// + +@interface MGLSMCalloutMaskedBackgroundView () +@property (nonatomic, strong) UIView *containerView, *containerBorderView, *arrowView; +@property (nonatomic, strong) UIImageView *arrowImageView, *arrowHighlightedImageView, *arrowBorderView; +@end + +static UIImage *blackArrowImage = nil, *whiteArrowImage = nil, *grayArrowImage = nil; + +@implementation MGLSMCalloutMaskedBackgroundView + +- (id)initWithFrame:(CGRect)frame { + if (self = [super initWithFrame:frame]) { + + // Here we're mimicking the very particular (and odd) structure of the system callout view. + // The hierarchy and view/layer values were discovered by inspecting map kit using Reveal.app + + self.containerView = [UIView new]; + self.containerView.backgroundColor = [UIColor whiteColor]; + self.containerView.alpha = 0.96; + self.containerView.layer.cornerRadius = 8; + self.containerView.layer.shadowRadius = 30; + self.containerView.layer.shadowOpacity = 0.1; + + self.containerBorderView = [UIView new]; + self.containerBorderView.layer.borderColor = [UIColor colorWithWhite:0 alpha:0.1].CGColor; + self.containerBorderView.layer.borderWidth = 0.5; + self.containerBorderView.layer.cornerRadius = 8.5; + + if (!blackArrowImage) { + blackArrowImage = [MGLSMCalloutBackgroundView embeddedImageNamed:@"CalloutArrow"]; + whiteArrowImage = [self image:blackArrowImage withColor:[UIColor whiteColor]]; + grayArrowImage = [self image:blackArrowImage withColor:[UIColor colorWithWhite:0.85 alpha:1]]; + } + + self.anchorHeight = 13; + self.anchorMargin = 27; + + self.arrowView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, blackArrowImage.size.width, blackArrowImage.size.height)]; + self.arrowView.alpha = 0.96; + self.arrowImageView = [[UIImageView alloc] initWithImage:whiteArrowImage]; + self.arrowHighlightedImageView = [[UIImageView alloc] initWithImage:grayArrowImage]; + self.arrowHighlightedImageView.hidden = YES; + self.arrowBorderView = [[UIImageView alloc] initWithImage:blackArrowImage]; + self.arrowBorderView.alpha = 0.1; + self.arrowBorderView.frameY = 0.5; + + [self addSubview:self.containerView]; + [self.containerView addSubview:self.containerBorderView]; + [self addSubview:self.arrowView]; + [self.arrowView addSubview:self.arrowBorderView]; + [self.arrowView addSubview:self.arrowImageView]; + [self.arrowView addSubview:self.arrowHighlightedImageView]; + } + return self; +} + +// Make sure we relayout our images when our arrow point changes! +- (void)setArrowPoint:(CGPoint)arrowPoint { + [super setArrowPoint:arrowPoint]; + [self setNeedsLayout]; +} + +- (void)setHighlighted:(BOOL)highlighted { + [super setHighlighted:highlighted]; + self.containerView.backgroundColor = highlighted ? [UIColor colorWithWhite:0.85 alpha:1] : [UIColor whiteColor]; + self.arrowImageView.hidden = highlighted; + self.arrowHighlightedImageView.hidden = !highlighted; +} + +- (UIImage *)image:(UIImage *)image withColor:(UIColor *)color { + + UIGraphicsBeginImageContextWithOptions(image.size, NO, 0); + CGRect imageRect = (CGRect){.size=image.size}; + CGContextRef c = UIGraphicsGetCurrentContext(); + CGContextTranslateCTM(c, 0, image.size.height); + CGContextScaleCTM(c, 1, -1); + CGContextClipToMask(c, imageRect, image.CGImage); + [color setFill]; + CGContextFillRect(c, imageRect); + UIImage *whiteImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return whiteImage; +} + +- (void)layoutSubviews { + + BOOL pointingUp = self.arrowPoint.y < self.frameHeight/2; + + // if we're pointing up, we'll need to push almost everything down a bit + CGFloat dy = pointingUp ? TOP_ANCHOR_MARGIN : 0; + + self.containerView.frame = CGRectMake(0, dy, self.frameWidth, self.frameHeight - self.arrowView.frameHeight + 0.5); + self.containerBorderView.frame = CGRectInset(self.containerView.bounds, -0.5, -0.5); + + self.arrowView.frameX = roundf(self.arrowPoint.x - self.arrowView.frameWidth / 2); + + if (pointingUp) { + self.arrowView.frameY = 1; + self.arrowView.transform = CGAffineTransformMakeRotation(M_PI); + } + else { + self.arrowView.frameY = self.containerView.frameHeight - 0.5; + self.arrowView.transform = CGAffineTransformIdentity; + } +} + +- (CALayer *)contentMask { + + UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0); + + [self.layer renderInContext:UIGraphicsGetCurrentContext()]; + + UIImage *maskImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + CALayer *layer = [CALayer layer]; + layer.frame = self.bounds; + layer.contents = (id)maskImage.CGImage; + return layer; +} + +@end + +@implementation MGLSMCalloutBackgroundView + ++ (NSData *)dataWithBase64EncodedString:(NSString *)string { + // + // NSData+Base64.m + // + // Version 1.0.2 + // + // Created by Nick Lockwood on 12/01/2012. + // Copyright (C) 2012 Charcoal Design + // + // Distributed under the permissive zlib License + // Get the latest version from here: + // + // https://github.com/nicklockwood/Base64 + // + // This software is provided 'as-is', without any express or implied + // warranty. In no event will the authors be held liable for any damages + // arising from the use of this software. + // + // Permission is granted to anyone to use this software for any purpose, + // including commercial applications, and to alter it and redistribute it + // freely, subject to the following restrictions: + // + // 1. The origin of this software must not be misrepresented; you must not + // claim that you wrote the original software. If you use this software + // in a product, an acknowledgment in the product documentation would be + // appreciated but is not required. + // + // 2. Altered source versions must be plainly marked as such, and must not be + // misrepresented as being the original software. + // + // 3. This notice may not be removed or altered from any source distribution. + // + const char lookup[] = { + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 62, 99, 99, 99, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 99, 99, 99, 99, 99, 99, + 99, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 99, 99, 99, 99, 99, + 99, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 99, 99, 99, 99, 99 + }; + + NSData *inputData = [string dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES]; + long long inputLength = [inputData length]; + const unsigned char *inputBytes = [inputData bytes]; + + long long maxOutputLength = (inputLength / 4 + 1) * 3; + NSMutableData *outputData = [NSMutableData dataWithLength:(NSUInteger)maxOutputLength]; + unsigned char *outputBytes = (unsigned char *)[outputData mutableBytes]; + + int accumulator = 0; + long long outputLength = 0; + unsigned char accumulated[] = {0, 0, 0, 0}; + for (long long i = 0; i < inputLength; i++) { + unsigned char decoded = lookup[inputBytes[i] & 0x7F]; + if (decoded != 99) { + accumulated[accumulator] = decoded; + if (accumulator == 3) { + outputBytes[outputLength++] = (accumulated[0] << 2) | (accumulated[1] >> 4); + outputBytes[outputLength++] = (accumulated[1] << 4) | (accumulated[2] >> 2); + outputBytes[outputLength++] = (accumulated[2] << 6) | accumulated[3]; + } + accumulator = (accumulator + 1) % 4; + } + } + + //handle left-over data + if (accumulator > 0) outputBytes[outputLength] = (accumulated[0] << 2) | (accumulated[1] >> 4); + if (accumulator > 1) outputBytes[++outputLength] = (accumulated[1] << 4) | (accumulated[2] >> 2); + if (accumulator > 2) outputLength++; + + //truncate data to match actual output length + outputData.length = (NSUInteger)outputLength; + return outputLength? outputData: nil; +} + ++ (UIImage *)embeddedImageNamed:(NSString *)name { + CGFloat screenScale = [UIScreen mainScreen].scale; + if (screenScale > 1.0) { + name = [name stringByAppendingString:@"_2x"]; + screenScale = 2.0; + } + + SEL selector = NSSelectorFromString(name); + + if (![(id)self respondsToSelector:selector]) { + NSLog(@"Could not find an embedded image. Ensure that you've added a class-level method named +%@", name); + return nil; + } + + // We need to hush the compiler here - but we know what we're doing! + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Warc-performSelector-leaks" + NSString *base64String = [(id)self performSelector:selector]; + #pragma clang diagnostic pop + + UIImage *rawImage = [UIImage imageWithData:[self dataWithBase64EncodedString:base64String]]; + return [UIImage imageWithCGImage:rawImage.CGImage scale:screenScale orientation:UIImageOrientationUp]; +} + ++ (NSString *)CalloutArrow { return @"iVBORw0KGgoAAAANSUhEUgAAACcAAAANCAYAAAAqlHdlAAAAHGlET1QAAAACAAAAAAAAAAcAAAAoAAAABwAAAAYAAADJEgYpIwAAAJVJREFUOBFiYIAAdn5+fkFOTkE5Dg5eW05O3lJOTr6zQPyfDhhoD28pxF5BOZA7gE5ih7oLN8XJyR8MdNwrGjkQaC5/MG7biZDh4OBXBDruLpUdeBdkLhHWE1bCzs6nAnTcUyo58DnIPMK2kqAC6DALIP5JoQNB+i1IsJZ4pcBEm0iJ40D6ibeNDJVAx00k04ETSbUOAAAA//+SwicfAAAAe0lEQVRjYCAdMHNy8u7l5OT7Tzzm3Qu0hpl0q8jQwcPDIwp02B0iHXeHl5dXhAxryNfCzc2tC3TcJwIO/ARSR74tFOjk4uL1BzruHw4H/gPJU2A85Vq5uPjTgY77g+bAPyBxyk2nggkcHPxOnJz8B4AOfAGiQXwqGMsAACGK1kPPMHNBAAAAAElFTkSuQmCC"; } + ++ (NSString *)CalloutArrow_2x { return @"iVBORw0KGgoAAAANSUhEUgAAAE4AAAAaCAYAAAAZtWr8AAAACXBIWXMAABYlAAAWJQFJUiTwAAAAHGlET1QAAAACAAAAAAAAAA0AAAAoAAAADQAAAA0AAAFMRh0LGwAAARhJREFUWAnclbENwjAQRZ0mih2fDYgsQEVDxQZMgKjpWYAJkBANI8AGDIEoM0WkzBDRAf8klB44g0OkU1zE3/+9RIpS7VVY730/y/woTWlsjJ9iPcN9pbXfY85auyvm/qcDNmb0e2Z+sk/ZBTthN0oVttX12mJIWeaWEFf+kbySmZQa0msu3nzaGJprTXV3BVLNDG/if7bNOTeAvFP35NGJu39GL7Abb27bFXncVQBZLgJf3jp+ebSWIxZMgrxdvPJoJ4gqHpXgV36ITR46HUGaiNMKB6YQd4lI3gV8qTBjmDhrbQFxVQTyKu4ShjJQap7nE4hrfiiv4Q6B8MLGat1bQNztB/JwZm8Rli5wujFu821xfGZgLPUAAAD//4wvm4gAAAD7SURBVOWXMQ6CMBiFgaFpi6VyBEedXJy4hMQTeBSvRDgJEySegI3EQWOivkZnqUB/k0LyL7R9L++D9G+DwP0TCZGUqCdRlYgUuY9F4JCmqQa0hgBcY7wIItFZMLZYS5l0ruAZbXhs6BIROgmhcoB7OIAHTZUTRqG3wp9xmhqc0aRPQu8YAlwxIbwCEUL6GH9wfDcLXY2HpyvvmkHf9+BcrwCuHQGvNRp9Pl6OY0PPAO42AB7WqMxLKLahpFR7gLv/AA9zPe+gtvAMCIC7WMC7CqEPtrqzmBfHyy3A1V/g1Th27GYBY0BIxrk6Ap65254/VZp30GID9JwteQEZrVMWXqGn8gAAAABJRU5ErkJggg=="; } + +@end + +// +// Our UIView frame helpers implementation +// + +@implementation UIView (SMFrameAdditions) + +- (CGPoint)frameOrigin { return self.frame.origin; } +- (void)setFrameOrigin:(CGPoint)origin { self.frame = (CGRect){ .origin=origin, .size=self.frame.size }; } + +- (CGFloat)frameX { return self.frame.origin.x; } +- (void)setFrameX:(CGFloat)x { self.frame = (CGRect){ .origin.x=x, .origin.y=self.frame.origin.y, .size=self.frame.size }; } + +- (CGFloat)frameY { return self.frame.origin.y; } +- (void)setFrameY:(CGFloat)y { self.frame = (CGRect){ .origin.x=self.frame.origin.x, .origin.y=y, .size=self.frame.size }; } + +- (CGSize)frameSize { return self.frame.size; } +- (void)setFrameSize:(CGSize)size { self.frame = (CGRect){ .origin=self.frame.origin, .size=size }; } + +- (CGFloat)frameWidth { return self.frame.size.width; } +- (void)setFrameWidth:(CGFloat)width { self.frame = (CGRect){ .origin=self.frame.origin, .size.width=width, .size.height=self.frame.size.height }; } + +- (CGFloat)frameHeight { return self.frame.size.height; } +- (void)setFrameHeight:(CGFloat)height { self.frame = (CGRect){ .origin=self.frame.origin, .size.width=self.frame.size.width, .size.height=height }; } + +- (CGFloat)frameLeft { return self.frame.origin.x; } +- (void)setFrameLeft:(CGFloat)left { self.frame = (CGRect){ .origin.x=left, .origin.y=self.frame.origin.y, .size.width=fmaxf(self.frame.origin.x+self.frame.size.width-left,0), .size.height=self.frame.size.height }; } + +- (CGFloat)frameTop { return self.frame.origin.y; } +- (void)setFrameTop:(CGFloat)top { self.frame = (CGRect){ .origin.x=self.frame.origin.x, .origin.y=top, .size.width=self.frame.size.width, .size.height=fmaxf(self.frame.origin.y+self.frame.size.height-top,0) }; } + +- (CGFloat)frameRight { return self.frame.origin.x + self.frame.size.width; } +- (void)setFrameRight:(CGFloat)right { self.frame = (CGRect){ .origin=self.frame.origin, .size.width=fmaxf(right-self.frame.origin.x,0), .size.height=self.frame.size.height }; } + +- (CGFloat)frameBottom { return self.frame.origin.y + self.frame.size.height; } +- (void)setFrameBottom:(CGFloat)bottom { self.frame = (CGRect){ .origin=self.frame.origin, .size.width=self.frame.size.width, .size.height=fmaxf(bottom-self.frame.origin.y,0) }; } + +@end -- cgit v1.2.1 From 56446c5d17ac74aa9bf39bdd3c8d1e9629d8fd41 Mon Sep 17 00:00:00 2001 From: Tobrun Van Nuland Date: Tue, 26 Sep 2017 10:57:58 +0200 Subject: [android] - add additional Android Animator examples --- .../activity/camera/CameraAnimatorActivity.java | 183 ++++++++++++++++++--- .../main/res/layout/activity_camera_animator.xml | 4 +- .../src/main/res/menu/menu_animator.xml | 20 +++ .../src/main/res/values/actions.xml | 4 + 4 files changed, 184 insertions(+), 27 deletions(-) create mode 100644 platform/android/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_animator.xml diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/camera/CameraAnimatorActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/camera/CameraAnimatorActivity.java index cc44ac9715..c8c5c6bd37 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/camera/CameraAnimatorActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/camera/CameraAnimatorActivity.java @@ -5,13 +5,20 @@ import android.animation.AnimatorSet; import android.animation.TypeEvaluator; import android.animation.ValueAnimator; import android.os.Bundle; +import android.support.v4.util.LongSparseArray; import android.support.v4.view.animation.FastOutLinearInInterpolator; import android.support.v4.view.animation.FastOutSlowInInterpolator; +import android.support.v4.view.animation.PathInterpolatorCompat; import android.support.v7.app.AppCompatActivity; +import android.view.Menu; +import android.view.MenuItem; import android.view.View; import android.view.animation.AnticipateOvershootInterpolator; +import android.view.animation.BounceInterpolator; +import android.view.animation.Interpolator; import com.mapbox.mapboxsdk.camera.CameraPosition; +import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.maps.MapView; import com.mapbox.mapboxsdk.maps.MapboxMap; @@ -24,6 +31,51 @@ import com.mapbox.mapboxsdk.testapp.R; public class CameraAnimatorActivity extends AppCompatActivity implements OnMapReadyCallback { private static final double ANIMATION_DELAY_FACTOR = 1.5; + private static final LatLng START_LAT_LNG = new LatLng(37.787947, -122.407432); + + private final LongSparseArray animators = new LongSparseArray() { + { + put(R.id.menu_action_accelerate_decelerate_interpolator, new AnimatorBuilder() { + @Override + public Animator build() { + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether( + createLatLngAnimator(START_LAT_LNG, new LatLng(37.826715, -122.422795)), + obtainExampleInterpolator(new FastOutSlowInInterpolator(), 2500) + ); + return animatorSet; + } + }); + + put(R.id.menu_action_bounce_interpolator, new AnimatorBuilder() { + @Override + public Animator build() { + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether( + createLatLngAnimator(START_LAT_LNG, new LatLng(37.787947, -122.407432)), + obtainExampleInterpolator(new BounceInterpolator(), 3750) + ); + return animatorSet; + } + }); + + put(R.id.menu_action_anticipate_overshoot_interpolator, new AnimatorBuilder() { + @Override + public Animator build() { + return obtainExampleInterpolator(new AnticipateOvershootInterpolator(), 2500); + } + }); + + put(R.id.menu_action_path_interpolator, new AnimatorBuilder() { + @Override + public Animator build() { + return obtainExampleInterpolator( + PathInterpolatorCompat.create(.22f, .68f, 0, 1.71f), 2500); + } + }); + } + }; + private MapView mapView; private MapboxMap mapboxMap; @@ -32,7 +84,6 @@ public class CameraAnimatorActivity extends AppCompatActivity implements OnMapRe protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_camera_animator); - mapView = (MapView) findViewById(R.id.mapView); if (mapView != null) { mapView.onCreate(savedInstanceState); @@ -43,27 +94,42 @@ public class CameraAnimatorActivity extends AppCompatActivity implements OnMapRe @Override public void onMapReady(final MapboxMap map) { mapboxMap = map; + initFab(); + } + + private void initFab() { findViewById(R.id.fab).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { view.setVisibility(View.GONE); - createAnimator(mapboxMap.getCameraPosition()).start(); + + CameraPosition animatedPosition = new CameraPosition.Builder() + .target(new LatLng(37.789992, -122.402214)) + .tilt(60) + .zoom(14.5f) + .bearing(135) + .build(); + + createExampleAnimator(mapboxMap.getCameraPosition(), animatedPosition).start(); } }); } - private Animator createAnimator(CameraPosition currentPosition) { + // + // Animator API used for the animation on the FAB + // + + private Animator createExampleAnimator(CameraPosition currentPosition, CameraPosition targetPosition) { AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.play(createLatLngAnimator(currentPosition.target)); - animatorSet.play(createZoomAnimator(currentPosition.zoom)); - animatorSet.play(createBearingAnimator(currentPosition.bearing)); - animatorSet.play(createTiltAnimator(currentPosition.tilt)); + animatorSet.play(createLatLngAnimator(currentPosition.target, targetPosition.target)); + animatorSet.play(createZoomAnimator(currentPosition.zoom, targetPosition.zoom)); + animatorSet.play(createBearingAnimator(currentPosition.bearing, targetPosition.bearing)); + animatorSet.play(createTiltAnimator(currentPosition.tilt, targetPosition.tilt)); return animatorSet; } - private Animator createLatLngAnimator(LatLng currentPosition) { - LatLng target = new LatLng(37.789992, -122.402214); - ValueAnimator latLngAnimator = ValueAnimator.ofObject(new LatLngEvaluator(), currentPosition, target); + private Animator createLatLngAnimator(LatLng currentPosition, LatLng targetPosition) { + ValueAnimator latLngAnimator = ValueAnimator.ofObject(new LatLngEvaluator(), currentPosition, targetPosition); latLngAnimator.setDuration((long) (1000 * ANIMATION_DELAY_FACTOR)); latLngAnimator.setInterpolator(new FastOutSlowInInterpolator()); latLngAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @@ -75,8 +141,8 @@ public class CameraAnimatorActivity extends AppCompatActivity implements OnMapRe return latLngAnimator; } - private Animator createZoomAnimator(double currentZoom) { - ValueAnimator zoomAnimator = ValueAnimator.ofFloat((float) currentZoom, 14.5f); + private Animator createZoomAnimator(double currentZoom, double targetZoom) { + ValueAnimator zoomAnimator = ValueAnimator.ofFloat((float) currentZoom, (float) targetZoom); zoomAnimator.setDuration((long) (2200 * ANIMATION_DELAY_FACTOR)); zoomAnimator.setStartDelay((long) (600 * ANIMATION_DELAY_FACTOR)); zoomAnimator.setInterpolator(new AnticipateOvershootInterpolator()); @@ -89,8 +155,8 @@ public class CameraAnimatorActivity extends AppCompatActivity implements OnMapRe return zoomAnimator; } - private Animator createBearingAnimator(double currentBearing) { - ValueAnimator bearingAnimator = ValueAnimator.ofFloat((float) currentBearing, 135); + private Animator createBearingAnimator(double currentBearing, double targetBearing) { + ValueAnimator bearingAnimator = ValueAnimator.ofFloat((float) currentBearing, (float) targetBearing); bearingAnimator.setDuration((long) (1000 * ANIMATION_DELAY_FACTOR)); bearingAnimator.setStartDelay((long) (1000 * ANIMATION_DELAY_FACTOR)); bearingAnimator.setInterpolator(new FastOutLinearInInterpolator()); @@ -103,8 +169,8 @@ public class CameraAnimatorActivity extends AppCompatActivity implements OnMapRe return bearingAnimator; } - private Animator createTiltAnimator(double currentTilt) { - ValueAnimator tiltAnimator = ValueAnimator.ofFloat((float) currentTilt, 60); + private Animator createTiltAnimator(double currentTilt, double targetTilt) { + ValueAnimator tiltAnimator = ValueAnimator.ofFloat((float) currentTilt, (float) targetTilt); tiltAnimator.setDuration((long) (1000 * ANIMATION_DELAY_FACTOR)); tiltAnimator.setStartDelay((long) (1500 * ANIMATION_DELAY_FACTOR)); tiltAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @@ -116,20 +182,66 @@ public class CameraAnimatorActivity extends AppCompatActivity implements OnMapRe return tiltAnimator; } - private static class LatLngEvaluator implements TypeEvaluator { + // + // Interpolator examples + // - private final LatLng latLng = new LatLng(); + private Animator obtainExampleInterpolator(int menuItemId) { + return animators.get(menuItemId).build(); + } - @Override - public LatLng evaluate(float fraction, LatLng startValue, LatLng endValue) { - latLng.setLatitude(startValue.getLatitude() - + ((endValue.getLatitude() - startValue.getLatitude()) * fraction)); - latLng.setLongitude(startValue.getLongitude() - + ((endValue.getLongitude() - startValue.getLongitude()) * fraction)); - return latLng; + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_animator, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (mapboxMap == null) { + return false; } + findViewById(R.id.fab).setVisibility(View.GONE); + resetCameraPosition(); + playAnimation(item.getItemId()); + return super.onOptionsItemSelected(item); } + private void resetCameraPosition() { + mapboxMap.moveCamera(CameraUpdateFactory.newCameraPosition( + new CameraPosition.Builder() + .target(START_LAT_LNG) + .zoom(11) + .bearing(0) + .tilt(0) + .build() + )); + } + + private void playAnimation(int itemId) { + Animator animator = obtainExampleInterpolator(itemId); + if (animator != null) { + animator.start(); + } + } + + private Animator obtainExampleInterpolator(Interpolator interpolator, long duration) { + ValueAnimator zoomAnimator = ValueAnimator.ofFloat(11.0f, 16.0f); + zoomAnimator.setDuration((long) (duration * ANIMATION_DELAY_FACTOR)); + zoomAnimator.setInterpolator(interpolator); + zoomAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + mapboxMap.setZoom((Float) animation.getAnimatedValue()); + } + }); + return zoomAnimator; + } + + // + // MapView lifecycle + // + @Override protected void onStart() { super.onStart(); @@ -171,4 +283,25 @@ public class CameraAnimatorActivity extends AppCompatActivity implements OnMapRe super.onLowMemory(); mapView.onLowMemory(); } + + /** + * Helper class to evaluate LatLng objects with a ValueAnimator + */ + private static class LatLngEvaluator implements TypeEvaluator { + + private final LatLng latLng = new LatLng(); + + @Override + public LatLng evaluate(float fraction, LatLng startValue, LatLng endValue) { + latLng.setLatitude(startValue.getLatitude() + + ((endValue.getLatitude() - startValue.getLatitude()) * fraction)); + latLng.setLongitude(startValue.getLongitude() + + ((endValue.getLongitude() - startValue.getLongitude()) * fraction)); + return latLng; + } + } + + interface AnimatorBuilder { + Animator build(); + } } diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_camera_animator.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_camera_animator.xml index d4933bfb9a..cb14aab91f 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_camera_animator.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_camera_animator.xml @@ -10,8 +10,8 @@ android:id="@id/mapView" android:layout_width="match_parent" android:layout_height="match_parent" - app:mapbox_cameraTargetLat="37.774913" - app:mapbox_cameraTargetLng="-122.419368" + app:mapbox_cameraTargetLat="37.787947" + app:mapbox_cameraTargetLng="-122.407432" app:mapbox_cameraZoom="11" app:mapbox_styleUrl="@string/mapbox_style_mapbox_streets"/> diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_animator.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_animator.xml new file mode 100644 index 0000000000..db5a62d2cb --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_animator.xml @@ -0,0 +1,20 @@ + +

+ + + + + diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/actions.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/actions.xml index 4ca19def71..416b9a8f86 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/actions.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/actions.xml @@ -12,6 +12,10 @@ Reset location source to null Toggle icon overlap Change location + Accelerate/Decelerate interpolator + Bounce interpolator + Anticipate/Overshoot interpolator + PathInterpolator interpolator Move Ease Animate -- cgit v1.2.1 From 78ea88d757ecf3a0a75b786ae343d746617ba46b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Mon, 9 Oct 2017 12:59:27 +0200 Subject: [core] make forcing cache/network only more explicit Previously, we used the existence of a `prior*` field in the Resource object as an indication for whether we should consult the cache or not. However, this is prone to error, since a failed cache lookup won't set any prior fields. Therefore, we manually set `priorExpires` to 0. This in turn triggered another bug where generated wrong expiration timestamps when the server response we got was expired (or expired between sending and receiving). This commit changes the flags so that we can now explicitly request CacheOnly/NetworkOnly (or All) loading methods, rather than the implicit Optional/Required naming scheme. --- cmake/core-files.cmake | 1 + include/mbgl/storage/default_file_source.hpp | 2 +- include/mbgl/storage/file_source.hpp | 6 +- include/mbgl/storage/resource.hpp | 43 ++++- include/mbgl/tile/tile_necessity.hpp | 15 ++ platform/default/default_file_source.cpp | 50 +++--- src/mbgl/algorithm/update_renderables.hpp | 25 +-- src/mbgl/annotation/annotation_tile.cpp | 3 - src/mbgl/annotation/annotation_tile.hpp | 2 - src/mbgl/renderer/tile_pyramid.cpp | 4 +- src/mbgl/storage/resource.cpp | 4 +- src/mbgl/style/sources/image_source.cpp | 2 +- src/mbgl/tile/geojson_tile.cpp | 2 - src/mbgl/tile/geojson_tile.hpp | 2 - src/mbgl/tile/raster_tile.cpp | 8 +- src/mbgl/tile/raster_tile.hpp | 7 +- src/mbgl/tile/tile.cpp | 2 +- src/mbgl/tile/tile.hpp | 14 +- src/mbgl/tile/tile_loader.hpp | 12 +- src/mbgl/tile/tile_loader_impl.hpp | 58 ++++--- src/mbgl/tile/vector_tile.cpp | 8 +- src/mbgl/tile/vector_tile.hpp | 7 +- test/algorithm/mock.hpp | 2 +- test/algorithm/update_renderables.test.cpp | 230 +++++++++++++-------------- test/storage/default_file_source.test.cpp | 26 +-- 25 files changed, 284 insertions(+), 251 deletions(-) create mode 100644 include/mbgl/tile/tile_necessity.hpp diff --git a/cmake/core-files.cmake b/cmake/core-files.cmake index 2eadc747d1..54b4079cff 100644 --- a/cmake/core-files.cmake +++ b/cmake/core-files.cmake @@ -489,6 +489,7 @@ set(MBGL_CORE_FILES # tile include/mbgl/tile/tile_id.hpp + include/mbgl/tile/tile_necessity.hpp src/mbgl/tile/geojson_tile.cpp src/mbgl/tile/geojson_tile.hpp src/mbgl/tile/geojson_tile_data.hpp diff --git a/include/mbgl/storage/default_file_source.hpp b/include/mbgl/storage/default_file_source.hpp index 91e442cf85..b9c8de5052 100644 --- a/include/mbgl/storage/default_file_source.hpp +++ b/include/mbgl/storage/default_file_source.hpp @@ -34,7 +34,7 @@ public: uint64_t maximumCacheSize = util::DEFAULT_MAX_CACHE_SIZE); ~DefaultFileSource() override; - bool supportsOptionalRequests() const override { + bool supportsCacheOnlyRequests() const override { return true; } diff --git a/include/mbgl/storage/file_source.hpp b/include/mbgl/storage/file_source.hpp index 404c683fdb..0709a1c245 100644 --- a/include/mbgl/storage/file_source.hpp +++ b/include/mbgl/storage/file_source.hpp @@ -24,11 +24,11 @@ public: // not be executed. virtual std::unique_ptr request(const Resource&, Callback) = 0; - // When a file source supports optional requests, it must return true. - // Optional requests are requests that aren't as urgent, but could be useful, e.g. + // When a file source supports consulting a local cache only, it must return true. + // Cache-only requests are requests that aren't as urgent, but could be useful, e.g. // to cover part of the map while loading. The FileSource should only do cheap actions to // retrieve the data, e.g. load it from a cache, but not from the internet. - virtual bool supportsOptionalRequests() const { + virtual bool supportsCacheOnlyRequests() const { return false; } }; diff --git a/include/mbgl/storage/resource.hpp b/include/mbgl/storage/resource.hpp index 5d44f4869f..318fa389f4 100644 --- a/include/mbgl/storage/resource.hpp +++ b/include/mbgl/storage/resource.hpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include @@ -30,18 +32,28 @@ public: int8_t z; }; - enum Necessity : bool { - Optional = false, - Required = true, + enum class LoadingMethod : uint8_t { + None = 0b00, + Cache = 0b01, + Network = 0b10, + + CacheOnly = Cache, + NetworkOnly = Network, + All = Cache | Network, }; - Resource(Kind kind_, std::string url_, optional tileData_ = {}, Necessity necessity_ = Required) + Resource(Kind kind_, + std::string url_, + optional tileData_ = {}, + LoadingMethod loadingMethod_ = LoadingMethod::All) : kind(kind_), - necessity(necessity_), + loadingMethod(loadingMethod_), url(std::move(url_)), tileData(std::move(tileData_)) { } + bool hasLoadingMethod(LoadingMethod method); + static Resource style(const std::string& url); static Resource source(const std::string& url); static Resource tile(const std::string& urlTemplate, @@ -50,7 +62,7 @@ public: int32_t y, int8_t z, Tileset::Scheme scheme, - Necessity = Required); + LoadingMethod = LoadingMethod::All); static Resource glyphs(const std::string& urlTemplate, const FontStack& fontStack, const std::pair& glyphRange); @@ -59,7 +71,7 @@ public: static Resource image(const std::string& url); Kind kind; - Necessity necessity; + LoadingMethod loadingMethod; std::string url; // Includes auxiliary data if this is a tile request. @@ -71,4 +83,21 @@ public: std::shared_ptr priorData; }; + +MBGL_CONSTEXPR Resource::LoadingMethod operator|(Resource::LoadingMethod a, Resource::LoadingMethod b) { + return Resource::LoadingMethod(mbgl::underlying_type(a) | mbgl::underlying_type(b)); +} + +MBGL_CONSTEXPR Resource::LoadingMethod& operator|=(Resource::LoadingMethod& a, Resource::LoadingMethod b) { + return (a = a | b); +} + +MBGL_CONSTEXPR Resource::LoadingMethod operator&(Resource::LoadingMethod a, Resource::LoadingMethod b) { + return Resource::LoadingMethod(mbgl::underlying_type(a) & mbgl::underlying_type(b)); +} + +inline bool Resource::hasLoadingMethod(Resource::LoadingMethod method) { + return (loadingMethod & method) != Resource::LoadingMethod::None; +} + } // namespace mbgl diff --git a/include/mbgl/tile/tile_necessity.hpp b/include/mbgl/tile/tile_necessity.hpp new file mode 100644 index 0000000000..e51bf51d10 --- /dev/null +++ b/include/mbgl/tile/tile_necessity.hpp @@ -0,0 +1,15 @@ +#pragma once + +namespace mbgl { + +// Tiles can have two states: optional or required. +// - optional means that only low-cost actions should be taken to obtain the data +// (e.g. load from cache, but accept stale data) +// - required means that every effort should be taken to obtain the data (e.g. load +// from internet and keep the data fresh if it expires) +enum class TileNecessity : bool { + Optional = false, + Required = true, +}; + +} // namespace mbgl diff --git a/platform/default/default_file_source.cpp b/platform/default/default_file_source.cpp index 3fdb03e6b4..608b782ab9 100644 --- a/platform/default/default_file_source.cpp +++ b/platform/default/default_file_source.cpp @@ -126,47 +126,41 @@ public: tasks[req] = localFileSource->request(resource, callback); } else { // Try the offline database - const bool hasPrior = resource.priorEtag || resource.priorModified || - resource.priorExpires || resource.priorData; - if (!hasPrior || resource.necessity == Resource::Optional) { + if (resource.hasLoadingMethod(Resource::LoadingMethod::Cache)) { auto offlineResponse = offlineDatabase->get(resource); - if (resource.necessity == Resource::Optional && !offlineResponse) { - // Ensure there's always a response that we can send, so the caller knows that - // there's no optional data available in the cache. - offlineResponse.emplace(); - offlineResponse->noContent = true; - offlineResponse->error = std::make_unique( - Response::Error::Reason::NotFound, "Not found in offline database"); - } - - if (offlineResponse) { + if (resource.loadingMethod == Resource::LoadingMethod::CacheOnly) { + if (!offlineResponse) { + // Ensure there's always a response that we can send, so the caller knows that + // there's no optional data available in the cache, when it's the only place + // we're supposed to load from. + offlineResponse.emplace(); + offlineResponse->noContent = true; + offlineResponse->error = std::make_unique( + Response::Error::Reason::NotFound, "Not found in offline database"); + } else if (!offlineResponse->isUsable()) { + // Don't return resources the server requested not to show when they're stale. + // Even if we can't directly use the response, we may still use it to send a + // conditional HTTP request, which is why we're saving it above. + offlineResponse->error = std::make_unique( + Response::Error::Reason::NotFound, "Cached resource is unusable"); + } + callback(*offlineResponse); + } else if (offlineResponse) { + // Copy over the fields so that we can use them when making a refresh request. resource.priorModified = offlineResponse->modified; resource.priorExpires = offlineResponse->expires; resource.priorEtag = offlineResponse->etag; + resource.priorData = offlineResponse->data; - // Don't return resources the server requested not to show when they're stale. - // Even if we can't directly use the response, we may still use it to send a - // conditional HTTP request. if (offlineResponse->isUsable()) { callback(*offlineResponse); - } else if (resource.necessity == Resource::Optional) { - // Instead of the data that we got, return a not found error so that - // underlying implementations know about the fact that we couldn't find - // usable cache data. - offlineResponse->error = std::make_unique( - Response::Error::Reason::NotFound, "Cached resource is unusable"); - callback(*offlineResponse); - } else { - // Since we can't return the data immediately, we'll have to hold on so that - // we can return it later in case we get a 304 Not Modified response. - resource.priorData = offlineResponse->data; } } } // Get from the online file source - if (resource.necessity == Resource::Required) { + if (resource.hasLoadingMethod(Resource::LoadingMethod::Network)) { tasks[req] = onlineFileSource.request(resource, [=] (Response onlineResponse) mutable { this->offlineDatabase->put(resource, onlineResponse); callback(onlineResponse); diff --git a/src/mbgl/algorithm/update_renderables.hpp b/src/mbgl/algorithm/update_renderables.hpp index 0c2266ff47..c583b6b2b6 100644 --- a/src/mbgl/algorithm/update_renderables.hpp +++ b/src/mbgl/algorithm/update_renderables.hpp @@ -1,8 +1,8 @@ #pragma once #include +#include #include -#include #include @@ -40,15 +40,15 @@ void updateRenderables(GetTileFn getTile, // if (source has the tile and bucket is loaded) { if (tile->isRenderable()) { - retainTile(*tile, Resource::Necessity::Required); + retainTile(*tile, TileNecessity::Required); renderTile(idealRenderTileID, *tile); } else { // We are now attempting to load child and parent tiles. - bool parentHasTriedOptional = tile->hasTriedOptional(); + bool parentHasTriedOptional = tile->hasTriedCache(); bool parentIsLoaded = tile->isLoaded(); // The tile isn't loaded yet, but retain it anyway because it's an ideal tile. - retainTile(*tile, Resource::Necessity::Required); + retainTile(*tile, TileNecessity::Required); covered = true; overscaledZ = dataTileZoom + 1; if (overscaledZ > zoomRange.max) { @@ -56,7 +56,7 @@ void updateRenderables(GetTileFn getTile, const auto childDataTileID = idealDataTileID.scaledTo(overscaledZ); tile = getTile(childDataTileID); if (tile && tile->isRenderable()) { - retainTile(*tile, Resource::Necessity::Optional); + retainTile(*tile, TileNecessity::Optional); renderTile(idealRenderTileID, *tile); } else { covered = false; @@ -67,7 +67,7 @@ void updateRenderables(GetTileFn getTile, const OverscaledTileID childDataTileID(overscaledZ, idealRenderTileID.wrap, childTileID); tile = getTile(childDataTileID); if (tile && tile->isRenderable()) { - retainTile(*tile, Resource::Necessity::Optional); + retainTile(*tile, TileNecessity::Optional); renderTile(childDataTileID.toUnwrapped(), *tile); } else { // At least one child tile doesn't exist, so we are going to look for @@ -97,12 +97,19 @@ void updateRenderables(GetTileFn getTile, } if (tile) { - retainTile(*tile, parentIsLoaded ? Resource::Necessity::Required - : Resource::Necessity::Optional); + if (!parentIsLoaded) { + // We haven't completed loading the child, so we only do an optional + // (cache) request in an attempt to quickly load data that we can show. + retainTile(*tile, TileNecessity::Optional); + } else { + // Now that we've checked the child and know for sure that we can't load + // it, we attempt to load the parent from the network. + retainTile(*tile, TileNecessity::Required); + } // Save the current values, since they're the parent of the next iteration // of the parent tile ascent loop. - parentHasTriedOptional = tile->hasTriedOptional(); + parentHasTriedOptional = tile->hasTriedCache(); parentIsLoaded = tile->isLoaded(); if (tile->isRenderable()) { diff --git a/src/mbgl/annotation/annotation_tile.cpp b/src/mbgl/annotation/annotation_tile.cpp index 0596d60f4f..d405418a45 100644 --- a/src/mbgl/annotation/annotation_tile.cpp +++ b/src/mbgl/annotation/annotation_tile.cpp @@ -19,9 +19,6 @@ AnnotationTile::~AnnotationTile() { annotationManager.removeTile(*this); } -void AnnotationTile::setNecessity(Necessity) { -} - class AnnotationTileFeatureData { public: AnnotationTileFeatureData(const AnnotationID id_, diff --git a/src/mbgl/annotation/annotation_tile.hpp b/src/mbgl/annotation/annotation_tile.hpp index 88505c50e3..a4d1e66802 100644 --- a/src/mbgl/annotation/annotation_tile.hpp +++ b/src/mbgl/annotation/annotation_tile.hpp @@ -14,8 +14,6 @@ public: AnnotationTile(const OverscaledTileID&, const TileParameters&); ~AnnotationTile() override; - void setNecessity(Necessity) final; - private: AnnotationManager& annotationManager; }; diff --git a/src/mbgl/renderer/tile_pyramid.cpp b/src/mbgl/renderer/tile_pyramid.cpp index 6cd9bd9ebd..3e2311089d 100644 --- a/src/mbgl/renderer/tile_pyramid.cpp +++ b/src/mbgl/renderer/tile_pyramid.cpp @@ -121,7 +121,7 @@ void TilePyramid::update(const std::vector>& layer // we're actively using, e.g. as a replacement for tile that aren't loaded yet. std::set retain; - auto retainTileFn = [&](Tile& tile, Resource::Necessity necessity) -> void { + auto retainTileFn = [&](Tile& tile, TileNecessity necessity) -> void { if (retain.emplace(tile.id).second) { tile.setNecessity(necessity); } @@ -179,7 +179,7 @@ void TilePyramid::update(const std::vector>& layer while (tilesIt != tiles.end()) { if (retainIt == retain.end() || tilesIt->first < *retainIt) { if (!needsRelayout) { - tilesIt->second->setNecessity(Tile::Necessity::Optional); + tilesIt->second->setNecessity(TileNecessity::Optional); cache.add(tilesIt->first, std::move(tilesIt->second)); } tiles.erase(tilesIt++); diff --git a/src/mbgl/storage/resource.cpp b/src/mbgl/storage/resource.cpp index 94bba7f8bf..e75302b986 100644 --- a/src/mbgl/storage/resource.cpp +++ b/src/mbgl/storage/resource.cpp @@ -95,7 +95,7 @@ Resource Resource::tile(const std::string& urlTemplate, int32_t y, int8_t z, Tileset::Scheme scheme, - Necessity necessity) { + LoadingMethod loadingMethod) { bool supportsRatio = urlTemplate.find("{ratio}") != std::string::npos; if (scheme == Tileset::Scheme::TMS) { y = (1 << z) - y - 1; @@ -131,7 +131,7 @@ Resource Resource::tile(const std::string& urlTemplate, y, z }, - necessity + loadingMethod }; } diff --git a/src/mbgl/style/sources/image_source.cpp b/src/mbgl/style/sources/image_source.cpp index 9b60ba1a48..fa268da0ef 100644 --- a/src/mbgl/style/sources/image_source.cpp +++ b/src/mbgl/style/sources/image_source.cpp @@ -59,7 +59,7 @@ void ImageSource::loadDescription(FileSource& fileSource) { if (req || loaded) { return; } - const Resource imageResource { Resource::Image, *url, {}, Resource::Necessity::Required }; + const Resource imageResource { Resource::Image, *url, {} }; req = fileSource.request(imageResource, [this](Response res) { if (res.error) { diff --git a/src/mbgl/tile/geojson_tile.cpp b/src/mbgl/tile/geojson_tile.cpp index ee4989462c..d648d2e5ff 100644 --- a/src/mbgl/tile/geojson_tile.cpp +++ b/src/mbgl/tile/geojson_tile.cpp @@ -17,8 +17,6 @@ GeoJSONTile::GeoJSONTile(const OverscaledTileID& overscaledTileID, void GeoJSONTile::updateData(mapbox::geometry::feature_collection features) { setData(std::make_unique(std::move(features))); } - -void GeoJSONTile::setNecessity(Necessity) {} void GeoJSONTile::querySourceFeatures( std::vector& result, diff --git a/src/mbgl/tile/geojson_tile.hpp b/src/mbgl/tile/geojson_tile.hpp index d8a0a379d7..270406267c 100644 --- a/src/mbgl/tile/geojson_tile.hpp +++ b/src/mbgl/tile/geojson_tile.hpp @@ -15,8 +15,6 @@ public: mapbox::geometry::feature_collection); void updateData(mapbox::geometry::feature_collection); - - void setNecessity(Necessity) final; void querySourceFeatures( std::vector& result, diff --git a/src/mbgl/tile/raster_tile.cpp b/src/mbgl/tile/raster_tile.cpp index 2a3c9eeb0e..85fcea77b7 100644 --- a/src/mbgl/tile/raster_tile.cpp +++ b/src/mbgl/tile/raster_tile.cpp @@ -32,12 +32,12 @@ void RasterTile::setError(std::exception_ptr err) { observer->onTileError(*this, err); } -void RasterTile::setData(std::shared_ptr data, - optional modified_, - optional expires_) { +void RasterTile::setMetadata(optional modified_, optional expires_) { modified = modified_; expires = expires_; +} +void RasterTile::setData(std::shared_ptr data) { pending = true; ++correlationID; worker.invoke(&RasterTileWorker::parse, data, correlationID); @@ -77,7 +77,7 @@ void RasterTile::setMask(TileMask&& mask) { } } -void RasterTile::setNecessity(Necessity necessity) { +void RasterTile::setNecessity(TileNecessity necessity) { loader.setNecessity(necessity); } diff --git a/src/mbgl/tile/raster_tile.hpp b/src/mbgl/tile/raster_tile.hpp index 2cb64e8ed7..192769ed8f 100644 --- a/src/mbgl/tile/raster_tile.hpp +++ b/src/mbgl/tile/raster_tile.hpp @@ -22,12 +22,11 @@ public: const Tileset&); ~RasterTile() final; - void setNecessity(Necessity) final; + void setNecessity(TileNecessity) final; void setError(std::exception_ptr); - void setData(std::shared_ptr data, - optional modified_, - optional expires_); + void setMetadata(optional modified, optional expires); + void setData(std::shared_ptr data); void cancel() override; diff --git a/src/mbgl/tile/tile.cpp b/src/mbgl/tile/tile.cpp index 7d7eb0b3fc..f36a472e72 100644 --- a/src/mbgl/tile/tile.cpp +++ b/src/mbgl/tile/tile.cpp @@ -18,7 +18,7 @@ void Tile::setObserver(TileObserver* observer_) { observer = observer_; } -void Tile::setTriedOptional() { +void Tile::setTriedCache() { triedOptional = true; observer->onTileChanged(*this); } diff --git a/src/mbgl/tile/tile.hpp b/src/mbgl/tile/tile.hpp index 39cc0de8bd..8be7c4d862 100644 --- a/src/mbgl/tile/tile.hpp +++ b/src/mbgl/tile/tile.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -38,14 +39,7 @@ public: void setObserver(TileObserver* observer); - // Tiles can have two states: optional or required. - // - optional means that only low-cost actions should be taken to obtain the data - // (e.g. load from cache, but accept stale data) - // - required means that every effort should be taken to obtain the data (e.g. load - // from internet and keep the data fresh if it expires) - using Necessity = Resource::Necessity; - - virtual void setNecessity(Necessity) = 0; + virtual void setNecessity(TileNecessity) {} // Mark this tile as no longer needed and cancel any pending work. virtual void cancel() = 0; @@ -68,11 +62,11 @@ public: std::vector& result, const SourceQueryOptions&); - void setTriedOptional(); + void setTriedCache(); // Returns true when the tile source has received a first response, regardless of whether a load // error occurred or actual data was loaded. - bool hasTriedOptional() const { + bool hasTriedCache() const { return triedOptional; } diff --git a/src/mbgl/tile/tile_loader.hpp b/src/mbgl/tile/tile_loader.hpp index bc408ebaf6..92ca74330f 100644 --- a/src/mbgl/tile/tile_loader.hpp +++ b/src/mbgl/tile/tile_loader.hpp @@ -21,12 +21,10 @@ public: const Tileset&); ~TileLoader(); - using Necessity = Resource::Necessity; - - void setNecessity(Necessity newNecessity) { + void setNecessity(TileNecessity newNecessity) { if (newNecessity != necessity) { necessity = newNecessity; - if (necessity == Necessity::Required) { + if (necessity == TileNecessity::Required) { makeRequired(); } else { makeOptional(); @@ -45,12 +43,12 @@ private: // an up-to-date version or load new data void makeOptional(); - void loadOptional(); + void loadFromCache(); void loadedData(const Response&); - void loadRequired(); + void loadFromNetwork(); T& tile; - Necessity necessity; + TileNecessity necessity; Resource resource; FileSource& fileSource; std::unique_ptr request; diff --git a/src/mbgl/tile/tile_loader_impl.hpp b/src/mbgl/tile/tile_loader_impl.hpp index 598ec32c10..1b29638269 100644 --- a/src/mbgl/tile/tile_loader_impl.hpp +++ b/src/mbgl/tile/tile_loader_impl.hpp @@ -15,32 +15,31 @@ TileLoader::TileLoader(T& tile_, const TileParameters& parameters, const Tileset& tileset) : tile(tile_), - necessity(Necessity::Optional), + necessity(TileNecessity::Optional), resource(Resource::tile( tileset.tiles.at(0), parameters.pixelRatio, id.canonical.x, id.canonical.y, id.canonical.z, - tileset.scheme)), + tileset.scheme, + Resource::LoadingMethod::CacheOnly)), fileSource(parameters.fileSource) { assert(!request); - if (fileSource.supportsOptionalRequests()) { + if (fileSource.supportsCacheOnlyRequests()) { // When supported, the first request is always optional, even if the TileLoader // is marked as required. That way, we can let the first optional request continue // to load when the TileLoader is later changed from required to optional. If we // started out with a required request, we'd have to cancel everything, including the // initial optional part of the request. - loadOptional(); + loadFromCache(); + } else if (necessity == TileNecessity::Required) { + // When the file source doesn't support cache-only requests, and we definiitely need this + // data, we can start out with a network request immediately. + loadFromNetwork(); } else { - // When the FileSource doesn't support optional requests, we do nothing until the + // When the FileSource doesn't support cache-only requests, we do nothing until the // data is definitely required. - if (necessity == Necessity::Required) { - loadRequired(); - } else { - // We're using this field to check whether the pending request is optional or required. - resource.necessity = Resource::Optional; - } } } @@ -48,29 +47,31 @@ template TileLoader::~TileLoader() = default; template -void TileLoader::loadOptional() { +void TileLoader::loadFromCache() { assert(!request); - resource.necessity = Resource::Optional; + resource.loadingMethod = Resource::LoadingMethod::CacheOnly; request = fileSource.request(resource, [this](Response res) { request.reset(); - tile.setTriedOptional(); + tile.setTriedCache(); if (res.error && res.error->reason == Response::Error::Reason::NotFound) { - // When the optional request could not be satisfied, don't treat it as an error. - // Instead, we make sure that the next request knows that there has been an optional - // request before by setting one of the prior* fields. + // When the cache-only request could not be satisfied, don't treat it as an error. + // A cache lookup could still return data, _and_ an error, in particular when we were + // able to find the data, but it is expired and the Cache-Control headers indicated that + // we aren't allowed to use expired responses. In this case, we still get the data which + // we can use in our conditional network request. resource.priorModified = res.modified; - resource.priorExpires = Timestamp{ Seconds::zero() }; + resource.priorExpires = res.expires; resource.priorEtag = res.etag; resource.priorData = res.data; } else { loadedData(res); } - if (necessity == Necessity::Required) { - loadRequired(); + if (necessity == TileNecessity::Required) { + loadFromNetwork(); } }); } @@ -78,14 +79,15 @@ void TileLoader::loadOptional() { template void TileLoader::makeRequired() { if (!request) { - loadRequired(); + loadFromNetwork(); } } template void TileLoader::makeOptional() { - if (resource.necessity == Resource::Required && request) { - // Abort a potential HTTP request. + if (resource.loadingMethod == Resource::LoadingMethod::NetworkOnly && request) { + // Abort the current request, but only when we know that we're specifically querying for a + // network resource only. request.reset(); } } @@ -98,19 +100,23 @@ void TileLoader::loadedData(const Response& res) { resource.priorExpires = res.expires; // Do not notify the tile; when we get this message, it already has the current // version of the data. + tile.setMetadata(res.modified, res.expires); } else { resource.priorModified = res.modified; resource.priorExpires = res.expires; resource.priorEtag = res.etag; - tile.setData(res.noContent ? nullptr : res.data, res.modified, res.expires); + tile.setMetadata(res.modified, res.expires); + tile.setData(res.noContent ? nullptr : res.data); } } template -void TileLoader::loadRequired() { +void TileLoader::loadFromNetwork() { assert(!request); - resource.necessity = Resource::Required; + // Instead of using Resource::LoadingMethod::All, we're first doing a CacheOnly, and then a + // NetworkOnly request. + resource.loadingMethod = Resource::LoadingMethod::NetworkOnly; request = fileSource.request(resource, [this](Response res) { loadedData(res); }); } diff --git a/src/mbgl/tile/vector_tile.cpp b/src/mbgl/tile/vector_tile.cpp index e2e700b7b7..0756d3e526 100644 --- a/src/mbgl/tile/vector_tile.cpp +++ b/src/mbgl/tile/vector_tile.cpp @@ -12,16 +12,16 @@ VectorTile::VectorTile(const OverscaledTileID& id_, : GeometryTile(id_, sourceID_, parameters), loader(*this, id_, parameters, tileset) { } -void VectorTile::setNecessity(Necessity necessity) { +void VectorTile::setNecessity(TileNecessity necessity) { loader.setNecessity(necessity); } -void VectorTile::setData(std::shared_ptr data_, - optional modified_, - optional expires_) { +void VectorTile::setMetadata(optional modified_, optional expires_) { modified = modified_; expires = expires_; +} +void VectorTile::setData(std::shared_ptr data_) { GeometryTile::setData(data_ ? std::make_unique(data_) : nullptr); } diff --git a/src/mbgl/tile/vector_tile.hpp b/src/mbgl/tile/vector_tile.hpp index 566cde4f37..7dae414fef 100644 --- a/src/mbgl/tile/vector_tile.hpp +++ b/src/mbgl/tile/vector_tile.hpp @@ -15,10 +15,9 @@ public: const TileParameters&, const Tileset&); - void setNecessity(Necessity) final; - void setData(std::shared_ptr data, - optional modified, - optional expires); + void setNecessity(TileNecessity) final; + void setMetadata(optional modified, optional expires); + void setData(std::shared_ptr data); private: TileLoader loader; diff --git a/test/algorithm/mock.hpp b/test/algorithm/mock.hpp index d87f55343b..b8eb020105 100644 --- a/test/algorithm/mock.hpp +++ b/test/algorithm/mock.hpp @@ -26,7 +26,7 @@ struct MockBucket {}; struct MockTileData { MockTileData(const mbgl::OverscaledTileID& tileID_) : tileID(tileID_) {} - bool hasTriedOptional() const { + bool hasTriedCache() const { return triedOptional; } diff --git a/test/algorithm/update_renderables.test.cpp b/test/algorithm/update_renderables.test.cpp index 2d37992579..7d6a0fcb13 100644 --- a/test/algorithm/update_renderables.test.cpp +++ b/test/algorithm/update_renderables.test.cpp @@ -45,7 +45,7 @@ std::ostream& operator<<(std::ostream& os, const CreateTileDataAction& action) { struct RetainTileDataAction { const OverscaledTileID tileID; - const Resource::Necessity necessity; + const TileNecessity necessity; bool operator==(const RetainTileDataAction& rhs) const { return tileID == rhs.tileID && necessity == rhs.necessity; @@ -56,7 +56,7 @@ std::ostream& operator<<(std::ostream& os, const RetainTileDataAction& action) { return os << "RetainTileDataAction{ { " << int(action.tileID.overscaledZ) << ", " << int(action.tileID.wrap) << ", { " << int(action.tileID.canonical.z) << ", " << action.tileID.canonical.x << ", " << action.tileID.canonical.y << " } }, " - << (action.necessity == Resource::Necessity::Required ? "Required" : "Optional") << " }\n"; + << (action.necessity == TileNecessity::Required ? "Required" : "Optional") << " }\n"; } struct RenderTileAction { @@ -102,7 +102,7 @@ auto createTileDataFn(ActionLog& log, T& dataTiles) { template auto retainTileDataFn(ActionLog& log) { - return [&](auto& tileData, Resource::Necessity necessity) { + return [&](auto& tileData, TileNecessity necessity) { log.emplace_back(RetainTileDataAction{ tileData.tileID, necessity }); }; } @@ -132,7 +132,7 @@ TEST(UpdateRenderables, SingleTile) { source.idealTiles, source.zoomRange, 1); EXPECT_EQ(ActionLog({ GetTileDataAction{ { 1, 0, { 1, 1, 1 } }, Found }, // found ideal tile - RetainTileDataAction{ { 1, 0, { 1, 1, 1 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 1, 0, { 1, 1, 1 } }, TileNecessity::Required }, // RenderTileAction{ { 1, 1, 1 }, *tile_1_1_1_1 }, // render ideal tile }), log); @@ -143,7 +143,7 @@ TEST(UpdateRenderables, SingleTile) { source.idealTiles, source.zoomRange, 1); EXPECT_EQ(ActionLog({ GetTileDataAction{ { 1, 0, { 1, 1, 1 } }, Found }, // found ideal tile - RetainTileDataAction{ { 1, 0, { 1, 1, 1 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 1, 0, { 1, 1, 1 } }, TileNecessity::Required }, // RenderTileAction{ { 1, 1, 1 }, *tile_1_1_1_1 }, // render ideal tile }), log); @@ -156,7 +156,7 @@ TEST(UpdateRenderables, SingleTile) { EXPECT_EQ(ActionLog({ GetTileDataAction{ { 1, 0, { 1, 0, 1 } }, NotFound }, // missing ideal tile CreateTileDataAction{ { 1, 0, { 1, 0, 1 } } }, // create ideal tile - RetainTileDataAction{ { 1, 0, { 1, 0, 1 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 1 } }, TileNecessity::Required }, // GetTileDataAction{ { 2, 0, { 2, 0, 2 } }, NotFound }, // four child tiles GetTileDataAction{ { 2, 0, { 2, 0, 3 } }, NotFound }, // ... GetTileDataAction{ { 2, 0, { 2, 1, 2 } }, NotFound }, // ... @@ -164,7 +164,7 @@ TEST(UpdateRenderables, SingleTile) { GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, NotFound }, // parent tile GetTileDataAction{ { 1, 0, { 1, 1, 1 } }, Found }, // found ideal tile - RetainTileDataAction{ { 1, 0, { 1, 1, 1 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 1, 0, { 1, 1, 1 } }, TileNecessity::Required }, // RenderTileAction{ { 1, 1, 1 }, *tile_1_1_1_1 }, // render found tile }), log); @@ -176,17 +176,17 @@ TEST(UpdateRenderables, SingleTile) { source.idealTiles, source.zoomRange, 1); EXPECT_EQ(ActionLog({ GetTileDataAction{ { 1, 0, { 1, 0, 1 } }, Found }, // missing ideal tile - RetainTileDataAction{ { 1, 0, { 1, 0, 1 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 1 } }, TileNecessity::Required }, // GetTileDataAction{ { 2, 0, { 2, 0, 2 } }, NotFound }, // four child tiles GetTileDataAction{ { 2, 0, { 2, 0, 3 } }, NotFound }, // ... GetTileDataAction{ { 2, 0, { 2, 1, 2 } }, NotFound }, // ... GetTileDataAction{ { 2, 0, { 2, 1, 3 } }, NotFound }, // ... GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, NotFound }, // parent tile CreateTileDataAction{ { 0, 0, { 0, 0, 0 } } }, // load parent tile - RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, TileNecessity::Optional }, // GetTileDataAction{ { 1, 0, { 1, 1, 1 } }, Found }, // found ideal tile - RetainTileDataAction{ { 1, 0, { 1, 1, 1 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 1, 0, { 1, 1, 1 } }, TileNecessity::Required }, // RenderTileAction{ { 1, 1, 1 }, *tile_1_1_1_1 }, // render found tile }), log); @@ -199,11 +199,11 @@ TEST(UpdateRenderables, SingleTile) { source.idealTiles, source.zoomRange, 1); EXPECT_EQ(ActionLog({ GetTileDataAction{ { 1, 0, { 1, 0, 1 } }, Found }, // newly added tile - RetainTileDataAction{ { 1, 0, { 1, 0, 1 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 1 } }, TileNecessity::Required }, // RenderTileAction{ { 1, 0, 1 }, *tile_1_1_0_1 }, // render ideal tile GetTileDataAction{ { 1, 0, { 1, 1, 1 } }, Found }, // ideal tile - RetainTileDataAction{ { 1, 0, { 1, 1, 1 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 1, 0, { 1, 1, 1 } }, TileNecessity::Required }, // RenderTileAction{ { 1, 1, 1 }, *tile_1_1_1_1 }, // render found tile }), log); @@ -218,21 +218,21 @@ TEST(UpdateRenderables, SingleTile) { source.idealTiles, source.zoomRange, 1); EXPECT_EQ(ActionLog({ GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, Found }, // found tile, not ready - RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // four child tiles GetTileDataAction{ { 2, 0, { 2, 0, 1 } }, NotFound }, // ... GetTileDataAction{ { 2, 0, { 2, 1, 0 } }, NotFound }, // ... GetTileDataAction{ { 2, 0, { 2, 1, 1 } }, NotFound }, // ... GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, Found }, // parent tile - RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, TileNecessity::Optional }, // // optional parent tile was already created before, but is not renderable GetTileDataAction{ { 1, 0, { 1, 0, 1 } }, Found }, // ideal tile - RetainTileDataAction{ { 1, 0, { 1, 0, 1 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 1 } }, TileNecessity::Required }, // RenderTileAction{ { 1, 0, 1 }, *tile_1_1_0_1 }, // render ideal tile GetTileDataAction{ { 1, 0, { 1, 1, 1 } }, Found }, // ideal tile - RetainTileDataAction{ { 1, 0, { 1, 1, 1 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 1, 0, { 1, 1, 1 } }, TileNecessity::Required }, // RenderTileAction{ { 1, 1, 1 }, *tile_1_1_1_1 }, // render ideal tile }), log); @@ -244,15 +244,15 @@ TEST(UpdateRenderables, SingleTile) { source.idealTiles, source.zoomRange, 1); EXPECT_EQ(ActionLog({ GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, Found }, // found tile, now ready - RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, TileNecessity::Required }, // RenderTileAction{ { 1, 0, 0 }, *tile_1_1_0_0 }, // GetTileDataAction{ { 1, 0, { 1, 0, 1 } }, Found }, // ideal tile - RetainTileDataAction{ { 1, 0, { 1, 0, 1 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 1 } }, TileNecessity::Required }, // RenderTileAction{ { 1, 0, 1 }, *tile_1_1_0_1 }, // GetTileDataAction{ { 1, 0, { 1, 1, 1 } }, Found }, // ideal tile - RetainTileDataAction{ { 1, 0, { 1, 1, 1 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 1, 0, { 1, 1, 1 } }, TileNecessity::Required }, // RenderTileAction{ { 1, 1, 1 }, *tile_1_1_1_1 }, // }), log); @@ -278,24 +278,24 @@ TEST(UpdateRenderables, UseParentTile) { EXPECT_EQ(ActionLog({ GetTileDataAction{ { 1, 0, { 1, 0, 1 } }, NotFound }, // missing ideal tile CreateTileDataAction{ { 1, 0, { 1, 0, 1 } } }, // - RetainTileDataAction{ { 1, 0, { 1, 0, 1 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 1 } }, TileNecessity::Required }, // GetTileDataAction{ { 2, 0, { 2, 0, 2 } }, NotFound }, // child tile GetTileDataAction{ { 2, 0, { 2, 0, 3 } }, NotFound }, // ... GetTileDataAction{ { 2, 0, { 2, 1, 2 } }, NotFound }, // ... GetTileDataAction{ { 2, 0, { 2, 1, 3 } }, NotFound }, // ... GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, Found }, // parent found! - RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, TileNecessity::Optional }, // RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, // render parent GetTileDataAction{ { 1, 0, { 1, 1, 0 } }, NotFound }, // missing ideal tile CreateTileDataAction{ { 1, 0, { 1, 1, 0 } } }, // - RetainTileDataAction{ { 1, 0, { 1, 1, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 1, 0, { 1, 1, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 2, 0, { 2, 2, 0 } }, NotFound }, // child tile GetTileDataAction{ { 2, 0, { 2, 2, 1 } }, NotFound }, // ... GetTileDataAction{ { 2, 0, { 2, 3, 0 } }, NotFound }, // ... GetTileDataAction{ { 2, 0, { 2, 3, 1 } }, NotFound }, // ... GetTileDataAction{ { 1, 0, { 1, 1, 1 } }, NotFound }, // missing tile CreateTileDataAction{ { 1, 0, { 1, 1, 1 } } }, // - RetainTileDataAction{ { 1, 0, { 1, 1, 1 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 1, 0, { 1, 1, 1 } }, TileNecessity::Required }, // GetTileDataAction{ { 2, 0, { 2, 2, 2 } }, NotFound }, // child tile GetTileDataAction{ { 2, 0, { 2, 2, 3 } }, NotFound }, // ... GetTileDataAction{ { 2, 0, { 2, 3, 2 } }, NotFound }, // ... @@ -321,7 +321,7 @@ TEST(UpdateRenderables, DontUseWrongParentTile) { EXPECT_EQ(ActionLog({ GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // missing ideal tile CreateTileDataAction{ { 2, 0, { 2, 0, 0 } } }, // - RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 3, 0, { 3, 0, 0 } }, NotFound }, // child tile GetTileDataAction{ { 3, 0, { 3, 0, 1 } }, NotFound }, // ... GetTileDataAction{ { 3, 0, { 3, 1, 0 } }, NotFound }, // ... @@ -338,14 +338,14 @@ TEST(UpdateRenderables, DontUseWrongParentTile) { source.idealTiles, source.zoomRange, 2); EXPECT_EQ(ActionLog({ GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, Found }, // non-ready ideal tile - RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 3, 0, { 3, 0, 0 } }, NotFound }, // child tile GetTileDataAction{ { 3, 0, { 3, 0, 1 } }, NotFound }, // ... GetTileDataAction{ { 3, 0, { 3, 1, 0 } }, NotFound }, // ... GetTileDataAction{ { 3, 0, { 3, 1, 1 } }, NotFound }, // ... GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, NotFound }, // parent tile, missing CreateTileDataAction{ { 1, 0, { 1, 0, 0 } } }, // find optional parent - RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, TileNecessity::Optional }, // GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, NotFound }, // parent tile, missing }), log); @@ -357,25 +357,25 @@ TEST(UpdateRenderables, DontUseWrongParentTile) { source.idealTiles, source.zoomRange, 2); EXPECT_EQ(ActionLog({ GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, Found }, // non-ready ideal tile - RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, TileNecessity::Required }, // // this tile was added by the previous invocation of updateRenderables GetTileDataAction{ { 3, 0, { 3, 0, 0 } }, NotFound }, // child tile GetTileDataAction{ { 3, 0, { 3, 0, 1 } }, NotFound }, // ... GetTileDataAction{ { 3, 0, { 3, 1, 0 } }, NotFound }, // ... GetTileDataAction{ { 3, 0, { 3, 1, 1 } }, NotFound }, // ... GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, Found }, // parent tile not ready - RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, TileNecessity::Optional }, // GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, NotFound }, // missing parent tile GetTileDataAction{ { 2, 0, { 2, 2, 0 } }, NotFound }, // missing ideal tile CreateTileDataAction{ { 2, 0, { 2, 2, 0 } } }, // - RetainTileDataAction{ { 2, 0, { 2, 2, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 2, 0, { 2, 2, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 3, 0, { 3, 4, 0 } }, NotFound }, // child tile GetTileDataAction{ { 3, 0, { 3, 4, 1 } }, NotFound }, // ... GetTileDataAction{ { 3, 0, { 3, 5, 0 } }, NotFound }, // ... GetTileDataAction{ { 3, 0, { 3, 5, 1 } }, NotFound }, // ... GetTileDataAction{ { 1, 0, { 1, 1, 0 } }, Found }, // found parent tile - RetainTileDataAction{ { 1, 0, { 1, 1, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 1, 0, { 1, 1, 0 } }, TileNecessity::Optional }, // RenderTileAction{ { 1, 1, 0 }, *tile_1_1_1_0 }, // render parent tile }), log); @@ -402,13 +402,13 @@ TEST(UpdateRenderables, UseParentTileWhenChildNotReady) { source.idealTiles, source.zoomRange, 1); EXPECT_EQ(ActionLog({ GetTileDataAction{ { 1, 0, { 1, 0, 1 } }, Found }, // found, but not ready - RetainTileDataAction{ { 1, 0, { 1, 0, 1 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 1 } }, TileNecessity::Required }, // GetTileDataAction{ { 2, 0, { 2, 0, 2 } }, NotFound }, // child tile GetTileDataAction{ { 2, 0, { 2, 0, 3 } }, NotFound }, // ... GetTileDataAction{ { 2, 0, { 2, 1, 2 } }, NotFound }, // ... GetTileDataAction{ { 2, 0, { 2, 1, 3 } }, NotFound }, // ... GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, Found }, // parent tile, ready - RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, TileNecessity::Optional }, // RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, // render parent tile }), log); @@ -420,7 +420,7 @@ TEST(UpdateRenderables, UseParentTileWhenChildNotReady) { source.idealTiles, source.zoomRange, 1); EXPECT_EQ(ActionLog({ GetTileDataAction{ { 1, 0, { 1, 0, 1 } }, Found }, // found and ready - RetainTileDataAction{ { 1, 0, { 1, 0, 1 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 1 } }, TileNecessity::Required }, // RenderTileAction{ { 1, 0, 1 }, *tile_1_1_0_1 }, // render ideal tile }), log); @@ -448,17 +448,17 @@ TEST(UpdateRenderables, UseOverlappingParentTile) { EXPECT_EQ(ActionLog({ GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, NotFound }, // ideal tile not found CreateTileDataAction{ { 1, 0, { 1, 0, 0 } } }, // - RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // child tile GetTileDataAction{ { 2, 0, { 2, 0, 1 } }, NotFound }, // ... GetTileDataAction{ { 2, 0, { 2, 1, 0 } }, NotFound }, // ... GetTileDataAction{ { 2, 0, { 2, 1, 1 } }, NotFound }, // ... GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, Found }, // parent tile found - RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, TileNecessity::Optional }, // RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, // GetTileDataAction{ { 1, 0, { 1, 0, 1 } }, Found }, // ideal tile found - RetainTileDataAction{ { 1, 0, { 1, 0, 1 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 1 } }, TileNecessity::Required }, // RenderTileAction{ { 1, 0, 1 }, *tile_1_1_0_1 }, // }), log); @@ -484,13 +484,13 @@ TEST(UpdateRenderables, UseChildTiles) { EXPECT_EQ(ActionLog({ GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, NotFound }, // ideal tile, missing CreateTileDataAction{ { 0, 0, { 0, 0, 0 } } }, // - RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, Found }, // child tile found - RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, TileNecessity::Optional }, // RenderTileAction{ { 1, 0, 0 }, *tile_1_1_0_0 }, // render child tile GetTileDataAction{ { 1, 0, { 1, 0, 1 } }, NotFound }, // child tile not found GetTileDataAction{ { 1, 0, { 1, 1, 0 } }, Found }, // child tile found - RetainTileDataAction{ { 1, 0, { 1, 1, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 1, 0, { 1, 1, 0 } }, TileNecessity::Optional }, // RenderTileAction{ { 1, 1, 0 }, *tile_1_1_1_0 }, // render child tile GetTileDataAction{ { 1, 0, { 1, 1, 1 } }, NotFound }, // child tile not found // no parent tile of 0 to consider @@ -518,15 +518,15 @@ TEST(UpdateRenderables, PreferChildTiles) { EXPECT_EQ(ActionLog({ GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, NotFound }, // ideal tile, not found CreateTileDataAction{ { 1, 0, { 1, 0, 0 } } }, // - RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, Found }, // child tile, found - RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, TileNecessity::Optional }, // RenderTileAction{ { 2, 0, 0 }, *tile_2_2_0_0 }, // GetTileDataAction{ { 2, 0, { 2, 0, 1 } }, NotFound }, // child tile, not found GetTileDataAction{ { 2, 0, { 2, 1, 0 } }, NotFound }, // ... GetTileDataAction{ { 2, 0, { 2, 1, 1 } }, NotFound }, // ... GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, Found }, // parent tile, found - RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, TileNecessity::Optional }, // RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, // }), log); @@ -541,17 +541,17 @@ TEST(UpdateRenderables, PreferChildTiles) { EXPECT_EQ(ActionLog({ GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, Found }, // ideal tile, not ready // ideal tile was added in previous invocation, but is not yet ready - RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, Found }, // child tile, found - RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, TileNecessity::Optional }, // RenderTileAction{ { 2, 0, 0 }, *tile_2_2_0_0 }, // GetTileDataAction{ { 2, 0, { 2, 0, 1 } }, Found }, // ... - RetainTileDataAction{ { 2, 0, { 2, 0, 1 } }, Resource::Necessity::Optional }, // ... + RetainTileDataAction{ { 2, 0, { 2, 0, 1 } }, TileNecessity::Optional }, // ... RenderTileAction{ { 2, 0, 1 }, *tile_2_2_0_1 }, // GetTileDataAction{ { 2, 0, { 2, 1, 0 } }, NotFound }, // child tile, not found GetTileDataAction{ { 2, 0, { 2, 1, 1 } }, NotFound }, // ... GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, Found }, // parent tile, found - RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, TileNecessity::Optional }, // RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, // }), log); @@ -564,19 +564,19 @@ TEST(UpdateRenderables, PreferChildTiles) { EXPECT_EQ(ActionLog({ GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, Found }, // ideal tile, not ready // ideal tile was added in first invocation, but is not yet ready - RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, Found }, // child tile, found - RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, TileNecessity::Optional }, // RenderTileAction{ { 2, 0, 0 }, *tile_2_2_0_0 }, // GetTileDataAction{ { 2, 0, { 2, 0, 1 } }, Found }, // ... - RetainTileDataAction{ { 2, 0, { 2, 0, 1 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 2, 0, { 2, 0, 1 } }, TileNecessity::Optional }, // RenderTileAction{ { 2, 0, 1 }, *tile_2_2_0_1 }, // GetTileDataAction{ { 2, 0, { 2, 1, 0 } }, Found }, // ... - RetainTileDataAction{ { 2, 0, { 2, 1, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 2, 0, { 2, 1, 0 } }, TileNecessity::Optional }, // RenderTileAction{ { 2, 1, 0 }, *tile_2_2_1_0 }, // GetTileDataAction{ { 2, 0, { 2, 1, 1 } }, NotFound }, // child tile, not found GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, Found }, // parent tile, found - RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, TileNecessity::Optional }, // RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, // }), log); @@ -590,18 +590,18 @@ TEST(UpdateRenderables, PreferChildTiles) { EXPECT_EQ(ActionLog({ GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, Found }, // ideal tile, not ready // ideal tile was added in first invocation, but is not yet ready - RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, Found }, // child tile, found - RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, TileNecessity::Optional }, // RenderTileAction{ { 2, 0, 0 }, *tile_2_2_0_0 }, // GetTileDataAction{ { 2, 0, { 2, 0, 1 } }, Found }, // ... - RetainTileDataAction{ { 2, 0, { 2, 0, 1 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 2, 0, { 2, 0, 1 } }, TileNecessity::Optional }, // RenderTileAction{ { 2, 0, 1 }, *tile_2_2_0_1 }, // GetTileDataAction{ { 2, 0, { 2, 1, 0 } }, Found }, // ... - RetainTileDataAction{ { 2, 0, { 2, 1, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 2, 0, { 2, 1, 0 } }, TileNecessity::Optional }, // RenderTileAction{ { 2, 1, 0 }, *tile_2_2_1_0 }, // GetTileDataAction{ { 2, 0, { 2, 1, 1 } }, Found }, // ... - RetainTileDataAction{ { 2, 0, { 2, 1, 1 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 2, 0, { 2, 1, 1 } }, TileNecessity::Optional }, // RenderTileAction{ { 2, 1, 1 }, *tile_2_2_1_1 }, // }), log); @@ -628,15 +628,15 @@ TEST(UpdateRenderables, UseParentAndChildTiles) { EXPECT_EQ(ActionLog({ GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, NotFound }, // ideal tile, missing CreateTileDataAction{ { 1, 0, { 1, 0, 0 } } }, // - RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, Found }, // child tile - RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, TileNecessity::Optional }, // RenderTileAction{ { 2, 0, 0 }, *tile_2_2_0_0 }, // GetTileDataAction{ { 2, 0, { 2, 0, 1 } }, NotFound }, // GetTileDataAction{ { 2, 0, { 2, 1, 0 } }, NotFound }, // GetTileDataAction{ { 2, 0, { 2, 1, 1 } }, NotFound }, // GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, Found }, // parent tile - RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, TileNecessity::Optional }, // RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, // }), log); @@ -648,13 +648,13 @@ TEST(UpdateRenderables, UseParentAndChildTiles) { source.idealTiles, source.zoomRange, 1); EXPECT_EQ(ActionLog({ GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, Found }, // ideal tile, not ready - RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // GetTileDataAction{ { 2, 0, { 2, 0, 1 } }, NotFound }, // GetTileDataAction{ { 2, 0, { 2, 1, 0 } }, NotFound }, // GetTileDataAction{ { 2, 0, { 2, 1, 1 } }, NotFound }, // GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, Found }, // parent tile - RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, TileNecessity::Optional }, // RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, // }), log); @@ -679,7 +679,7 @@ TEST(UpdateRenderables, DontUseTilesLowerThanMinzoom) { EXPECT_EQ(ActionLog({ GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // ideal tile, missing CreateTileDataAction{ { 2, 0, { 2, 0, 0 } } }, // - RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 3, 0, { 3, 0, 0 } }, NotFound }, // GetTileDataAction{ { 3, 0, { 3, 0, 1 } }, NotFound }, // GetTileDataAction{ { 3, 0, { 3, 1, 0 } }, NotFound }, // @@ -709,7 +709,7 @@ TEST(UpdateRenderables, UseOverzoomedTileAfterMaxzoom) { ActionLog({ GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // ideal tile, missing CreateTileDataAction{ { 2, 0, { 2, 0, 0 } } }, // - RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 3, 0, { 2, 0, 0 } }, NotFound }, // overzoomed tile, not children! GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, NotFound }, // GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, NotFound }, // @@ -724,11 +724,11 @@ TEST(UpdateRenderables, UseOverzoomedTileAfterMaxzoom) { EXPECT_EQ( ActionLog({ GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, Found }, // ideal tile, missing - RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 3, 0, { 2, 0, 0 } }, NotFound }, // overzoomed tile, not children! GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, NotFound }, // CreateTileDataAction{ { 1, 0, { 1, 0, 0 } } }, // - RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, TileNecessity::Optional }, // GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, NotFound }, // }), log); @@ -742,10 +742,10 @@ TEST(UpdateRenderables, UseOverzoomedTileAfterMaxzoom) { EXPECT_EQ(ActionLog({ GetTileDataAction{ { 3, 0, { 2, 0, 0 } }, NotFound }, // ideal tile, missing CreateTileDataAction{ { 3, 0, { 2, 0, 0 } } }, // - RetainTileDataAction{ { 3, 0, { 2, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 3, 0, { 2, 0, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 4, 0, { 2, 0, 0 } }, NotFound }, // GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, Found }, // - RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, TileNecessity::Optional }, // RenderTileAction{ { 2, 0, 0 }, *tile_2_2_0_0 }, // }), log); @@ -758,7 +758,7 @@ TEST(UpdateRenderables, UseOverzoomedTileAfterMaxzoom) { source.idealTiles, source.zoomRange, 3); EXPECT_EQ(ActionLog({ GetTileDataAction{ { 3, 0, { 2, 0, 0 } }, Found }, // - RetainTileDataAction{ { 3, 0, { 2, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 3, 0, { 2, 0, 0 } }, TileNecessity::Required }, // RenderTileAction{ { 2, 0, 0 }, *tile_3_2_0_0 }, // }), log); @@ -769,7 +769,7 @@ TEST(UpdateRenderables, UseOverzoomedTileAfterMaxzoom) { source.idealTiles, source.zoomRange, 2); EXPECT_EQ(ActionLog({ GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, Found }, // - RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, TileNecessity::Required }, // RenderTileAction{ { 2, 0, 0 }, *tile_2_2_0_0 }, // }), log); @@ -785,9 +785,9 @@ TEST(UpdateRenderables, UseOverzoomedTileAfterMaxzoom) { EXPECT_EQ(ActionLog({ GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // CreateTileDataAction{ { 2, 0, { 2, 0, 0 } } }, // - RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 3, 0, { 2, 0, 0 } }, Found }, // use overzoomed tile! - RetainTileDataAction{ { 3, 0, { 2, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 3, 0, { 2, 0, 0 } }, TileNecessity::Optional }, // RenderTileAction{ { 2, 0, 0 }, *tile_3_2_0_0 }, // }), log); @@ -811,7 +811,7 @@ TEST(UpdateRenderables, AscendToNonOverzoomedTiles) { source.idealTiles, source.zoomRange, 3); EXPECT_EQ(ActionLog({ GetTileDataAction{ { 3, 0, { 2, 0, 0 } }, Found }, // - RetainTileDataAction{ { 3, 0, { 2, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 3, 0, { 2, 0, 0 } }, TileNecessity::Required }, // RenderTileAction{ { 2, 0, 0 }, *tile_3_2_0_0 }, // }), log); @@ -827,10 +827,10 @@ TEST(UpdateRenderables, AscendToNonOverzoomedTiles) { EXPECT_EQ(ActionLog({ GetTileDataAction{ { 3, 0, { 2, 0, 0 } }, NotFound }, // CreateTileDataAction{ { 3, 0, { 2, 0, 0 } } }, // - RetainTileDataAction{ { 3, 0, { 2, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 3, 0, { 2, 0, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 4, 0, { 2, 0, 0 } }, NotFound }, // prefer using a child first GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, Found }, // - RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, TileNecessity::Optional }, // RenderTileAction{ { 2, 0, 0 }, *tile_2_2_0_0 }, // }), log); @@ -845,11 +845,11 @@ TEST(UpdateRenderables, AscendToNonOverzoomedTiles) { source.idealTiles, source.zoomRange, 3); EXPECT_EQ(ActionLog({ GetTileDataAction{ { 3, 0, { 2, 0, 0 } }, Found }, // ideal tile, not ready - RetainTileDataAction{ { 3, 0, { 2, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 3, 0, { 2, 0, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 4, 0, { 2, 0, 0 } }, NotFound }, // GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, Found }, // - RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, TileNecessity::Optional }, // RenderTileAction{ { 1, 0, 0 }, *tile_1_1_0_0 }, // }), log); @@ -861,13 +861,13 @@ TEST(UpdateRenderables, AscendToNonOverzoomedTiles) { source.idealTiles, source.zoomRange, 3); EXPECT_EQ(ActionLog({ GetTileDataAction{ { 3, 0, { 2, 0, 0 } }, Found }, // ideal tile, not ready - RetainTileDataAction{ { 3, 0, { 2, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 3, 0, { 2, 0, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 4, 0, { 2, 0, 0 } }, NotFound }, // GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // CreateTileDataAction{ { 2, 0, { 2, 0, 0 } } }, // - RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, TileNecessity::Optional }, // GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, Found }, // - RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, TileNecessity::Optional }, // RenderTileAction{ { 1, 0, 0 }, *tile_1_1_0_0 }, // }), log); @@ -889,7 +889,7 @@ TEST(UpdateRenderables, DoNotAscendMultipleTimesIfNotFound) { EXPECT_EQ(ActionLog({ GetTileDataAction{ { 8, 0, { 8, 0, 0 } }, NotFound }, // ideal tile CreateTileDataAction{ { 8, 0, { 8, 0, 0 } } }, // - RetainTileDataAction{ { 8, 0, { 8, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 8, 0, { 8, 0, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 9, 0, { 9, 0, 0 } }, NotFound }, // child tile GetTileDataAction{ { 9, 0, { 9, 0, 1 } }, NotFound }, // ... GetTileDataAction{ { 9, 0, { 9, 1, 0 } }, NotFound }, // ... @@ -905,7 +905,7 @@ TEST(UpdateRenderables, DoNotAscendMultipleTimesIfNotFound) { GetTileDataAction{ { 8, 0, { 8, 1, 0 } }, NotFound }, // ideal tile CreateTileDataAction{ { 8, 0, { 8, 1, 0 } } }, // - RetainTileDataAction{ { 8, 0, { 8, 1, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 8, 0, { 8, 1, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 9, 0, { 9, 2, 0 } }, NotFound }, // child tile GetTileDataAction{ { 9, 0, { 9, 2, 1 } }, NotFound }, // ... GetTileDataAction{ { 9, 0, { 9, 3, 0 } }, NotFound }, // ... @@ -923,7 +923,7 @@ TEST(UpdateRenderables, DoNotAscendMultipleTimesIfNotFound) { source.idealTiles, source.zoomRange, 8); EXPECT_EQ(ActionLog({ GetTileDataAction{ { 8, 0, { 8, 0, 0 } }, Found }, // ideal tile, not ready - RetainTileDataAction{ { 8, 0, { 8, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 8, 0, { 8, 0, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 9, 0, { 9, 0, 0 } }, NotFound }, // child tile GetTileDataAction{ { 9, 0, { 9, 0, 1 } }, NotFound }, // ... GetTileDataAction{ { 9, 0, { 9, 1, 0 } }, NotFound }, // ... @@ -932,11 +932,11 @@ TEST(UpdateRenderables, DoNotAscendMultipleTimesIfNotFound) { GetTileDataAction{ { 6, 0, { 6, 0, 0 } }, NotFound }, // ... GetTileDataAction{ { 5, 0, { 5, 0, 0 } }, NotFound }, // ... GetTileDataAction{ { 4, 0, { 4, 0, 0 } }, Found }, // stops ascent - RetainTileDataAction{ { 4, 0, { 4, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 4, 0, { 4, 0, 0 } }, TileNecessity::Optional }, // RenderTileAction{ { 4, 0, 0 }, *tile_4_0_0_0 }, // GetTileDataAction{ { 8, 0, { 8, 1, 0 } }, Found }, // ideal tile, not ready - RetainTileDataAction{ { 8, 0, { 8, 1, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 8, 0, { 8, 1, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 9, 0, { 9, 2, 0 } }, NotFound }, // child tile GetTileDataAction{ { 9, 0, { 9, 2, 1 } }, NotFound }, // ... GetTileDataAction{ { 9, 0, { 9, 3, 0 } }, NotFound }, // ... @@ -963,13 +963,13 @@ TEST(UpdateRenderables, DontRetainUnusedNonIdealTiles) { source.idealTiles, source.zoomRange, 2); EXPECT_EQ(ActionLog({ GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, Found }, // ideal tile, not ready - RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 3, 0, { 3, 0, 0 } }, NotFound }, // GetTileDataAction{ { 3, 0, { 3, 0, 1 } }, NotFound }, // GetTileDataAction{ { 3, 0, { 3, 1, 0 } }, NotFound }, // GetTileDataAction{ { 3, 0, { 3, 1, 1 } }, NotFound }, // GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, Found }, // parent tile, not ready - RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, TileNecessity::Optional }, // GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, NotFound }, // }), log); @@ -996,7 +996,7 @@ TEST(UpdateRenderables, WrappedTiles) { EXPECT_EQ(ActionLog({ GetTileDataAction{ { 1, -1, { 1, 1, 0 } }, NotFound }, // ideal tile 1/-1/0 (wrapped to -1) CreateTileDataAction{ { 1, -1, { 1, 1, 0 } } }, // - RetainTileDataAction{ { 1, -1, { 1, 1, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 1, -1, { 1, 1, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 2, -1, { 2, 2, 0 } }, NotFound }, // GetTileDataAction{ { 2, -1, { 2, 2, 1 } }, NotFound }, // GetTileDataAction{ { 2, -1, { 2, 3, 0 } }, NotFound }, // @@ -1005,18 +1005,18 @@ TEST(UpdateRenderables, WrappedTiles) { GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, NotFound }, // ideal tile 1/0/0 CreateTileDataAction{ { 1, 0, { 1, 0, 0 } } }, // - RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 1, 0, { 1, 0, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // GetTileDataAction{ { 2, 0, { 2, 0, 1 } }, NotFound }, // GetTileDataAction{ { 2, 0, { 2, 1, 0 } }, NotFound }, // GetTileDataAction{ { 2, 0, { 2, 1, 1 } }, NotFound }, // GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, Found }, // - RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 0, 0, { 0, 0, 0 } }, TileNecessity::Optional }, // RenderTileAction{ { 0, 0, 0 }, *tile_0_0_0_0 }, // GetTileDataAction{ { 1, 0, { 1, 1, 0 } }, NotFound }, // ideal tile 1/1/0, doesn't match 1/-/1/0 CreateTileDataAction{ { 1, 0, { 1, 1, 0 } } }, - RetainTileDataAction{ { 1, 0, { 1, 1, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 1, 0, { 1, 1, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 2, 0, { 2, 2, 0 } }, NotFound }, // GetTileDataAction{ { 2, 0, { 2, 2, 1 } }, NotFound }, // GetTileDataAction{ { 2, 0, { 2, 3, 0 } }, NotFound }, // @@ -1025,7 +1025,7 @@ TEST(UpdateRenderables, WrappedTiles) { GetTileDataAction{ { 1, 1, { 1, 0, 0 } }, NotFound }, // ideal tile 1/2/0 (wrapped to 1) CreateTileDataAction{ { 1, 1, { 1, 0, 0 } } }, - RetainTileDataAction{ { 1, 1, { 1, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 1, 1, { 1, 0, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 2, 1, { 2, 0, 0 } }, NotFound }, // GetTileDataAction{ { 2, 1, { 2, 0, 1 } }, NotFound }, // GetTileDataAction{ { 2, 1, { 2, 1, 0 } }, NotFound }, // @@ -1050,7 +1050,7 @@ TEST(UpdateRenderables, RepeatedRenderWithMissingOptionals) { EXPECT_EQ(ActionLog({ GetTileDataAction{ { 6, 0, { 6, 0, 0 } }, NotFound }, // ideal tile, not found CreateTileDataAction{ { 6, 0, { 6, 0, 0 } } }, // - RetainTileDataAction{ { 6, 0, { 6, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 6, 0, { 6, 0, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 7, 0, { 7, 0, 0 } }, NotFound }, // children GetTileDataAction{ { 7, 0, { 7, 0, 1 } }, NotFound }, // ... GetTileDataAction{ { 7, 0, { 7, 1, 0 } }, NotFound }, // ... @@ -1070,7 +1070,7 @@ TEST(UpdateRenderables, RepeatedRenderWithMissingOptionals) { source.idealTiles, source.zoomRange, 6); EXPECT_EQ(ActionLog({ GetTileDataAction{ { 6, 0, { 6, 0, 0 } }, Found }, // ideal tile, not ready - RetainTileDataAction{ { 6, 0, { 6, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 6, 0, { 6, 0, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 7, 0, { 7, 0, 0 } }, NotFound }, // children GetTileDataAction{ { 7, 0, { 7, 0, 1 } }, NotFound }, // ... GetTileDataAction{ { 7, 0, { 7, 1, 0 } }, NotFound }, // ... @@ -1091,14 +1091,14 @@ TEST(UpdateRenderables, RepeatedRenderWithMissingOptionals) { source.idealTiles, source.zoomRange, 6); EXPECT_EQ(ActionLog({ GetTileDataAction{ { 6, 0, { 6, 0, 0 } }, Found }, // ideal tile, not ready - RetainTileDataAction{ { 6, 0, { 6, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 6, 0, { 6, 0, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 7, 0, { 7, 0, 0 } }, NotFound }, // children GetTileDataAction{ { 7, 0, { 7, 0, 1 } }, NotFound }, // ... GetTileDataAction{ { 7, 0, { 7, 1, 0 } }, NotFound }, // ... GetTileDataAction{ { 7, 0, { 7, 1, 1 } }, NotFound }, // ... GetTileDataAction{ { 5, 0, { 5, 0, 0 } }, NotFound }, // ascent CreateTileDataAction{ { 5, 0, { 5, 0, 0 } } }, // - RetainTileDataAction{ { 5, 0, { 5, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 5, 0, { 5, 0, 0 } }, TileNecessity::Optional }, // GetTileDataAction{ { 4, 0, { 4, 0, 0 } }, NotFound }, // ... GetTileDataAction{ { 3, 0, { 3, 0, 0 } }, NotFound }, // ... GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // ... @@ -1113,13 +1113,13 @@ TEST(UpdateRenderables, RepeatedRenderWithMissingOptionals) { source.idealTiles, source.zoomRange, 6); EXPECT_EQ(ActionLog({ GetTileDataAction{ { 6, 0, { 6, 0, 0 } }, Found }, // ideal tile, not ready - RetainTileDataAction{ { 6, 0, { 6, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 6, 0, { 6, 0, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 7, 0, { 7, 0, 0 } }, NotFound }, // children GetTileDataAction{ { 7, 0, { 7, 0, 1 } }, NotFound }, // ... GetTileDataAction{ { 7, 0, { 7, 1, 0 } }, NotFound }, // ... GetTileDataAction{ { 7, 0, { 7, 1, 1 } }, NotFound }, // ... GetTileDataAction{ { 5, 0, { 5, 0, 0 } }, Found }, // ascent - RetainTileDataAction{ { 5, 0, { 5, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 5, 0, { 5, 0, 0 } }, TileNecessity::Optional }, // GetTileDataAction{ { 4, 0, { 4, 0, 0 } }, NotFound }, // ... GetTileDataAction{ { 3, 0, { 3, 0, 0 } }, NotFound }, // ... GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // ... @@ -1135,16 +1135,16 @@ TEST(UpdateRenderables, RepeatedRenderWithMissingOptionals) { source.idealTiles, source.zoomRange, 6); EXPECT_EQ(ActionLog({ GetTileDataAction{ { 6, 0, { 6, 0, 0 } }, Found }, // ideal tile, not ready - RetainTileDataAction{ { 6, 0, { 6, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 6, 0, { 6, 0, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 7, 0, { 7, 0, 0 } }, NotFound }, // children GetTileDataAction{ { 7, 0, { 7, 0, 1 } }, NotFound }, // ... GetTileDataAction{ { 7, 0, { 7, 1, 0 } }, NotFound }, // ... GetTileDataAction{ { 7, 0, { 7, 1, 1 } }, NotFound }, // ... GetTileDataAction{ { 5, 0, { 5, 0, 0 } }, Found }, // ascent - RetainTileDataAction{ { 5, 0, { 5, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 5, 0, { 5, 0, 0 } }, TileNecessity::Optional }, // GetTileDataAction{ { 4, 0, { 4, 0, 0 } }, NotFound }, // ... CreateTileDataAction{ { 4, 0, { 4, 0, 0 } } }, // - RetainTileDataAction{ { 4, 0, { 4, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 4, 0, { 4, 0, 0 } }, TileNecessity::Optional }, // GetTileDataAction{ { 3, 0, { 3, 0, 0 } }, NotFound }, // ... GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // ... GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, NotFound }, // ... @@ -1159,18 +1159,18 @@ TEST(UpdateRenderables, RepeatedRenderWithMissingOptionals) { source.idealTiles, source.zoomRange, 6); EXPECT_EQ(ActionLog({ GetTileDataAction{ { 6, 0, { 6, 0, 0 } }, Found }, // ideal tile, not ready - RetainTileDataAction{ { 6, 0, { 6, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 6, 0, { 6, 0, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 7, 0, { 7, 0, 0 } }, NotFound }, // children GetTileDataAction{ { 7, 0, { 7, 0, 1 } }, NotFound }, // ... GetTileDataAction{ { 7, 0, { 7, 1, 0 } }, NotFound }, // ... GetTileDataAction{ { 7, 0, { 7, 1, 1 } }, NotFound }, // ... GetTileDataAction{ { 5, 0, { 5, 0, 0 } }, Found }, // ascent - RetainTileDataAction{ { 5, 0, { 5, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 5, 0, { 5, 0, 0 } }, TileNecessity::Optional }, // GetTileDataAction{ { 4, 0, { 4, 0, 0 } }, Found }, // ... - RetainTileDataAction{ { 4, 0, { 4, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 4, 0, { 4, 0, 0 } }, TileNecessity::Optional }, // GetTileDataAction{ { 3, 0, { 3, 0, 0 } }, NotFound }, // ... CreateTileDataAction{ { 3, 0, { 3, 0, 0 } } }, // - RetainTileDataAction{ { 3, 0, { 3, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 3, 0, { 3, 0, 0 } }, TileNecessity::Optional }, // GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // ... GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, NotFound }, // ... GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, NotFound }, // ... @@ -1184,20 +1184,20 @@ TEST(UpdateRenderables, RepeatedRenderWithMissingOptionals) { source.idealTiles, source.zoomRange, 6); EXPECT_EQ(ActionLog({ GetTileDataAction{ { 6, 0, { 6, 0, 0 } }, Found }, // ideal tile, not ready - RetainTileDataAction{ { 6, 0, { 6, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 6, 0, { 6, 0, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 7, 0, { 7, 0, 0 } }, NotFound }, // children GetTileDataAction{ { 7, 0, { 7, 0, 1 } }, NotFound }, // ... GetTileDataAction{ { 7, 0, { 7, 1, 0 } }, NotFound }, // ... GetTileDataAction{ { 7, 0, { 7, 1, 1 } }, NotFound }, // ... GetTileDataAction{ { 5, 0, { 5, 0, 0 } }, Found }, // ascent - RetainTileDataAction{ { 5, 0, { 5, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 5, 0, { 5, 0, 0 } }, TileNecessity::Optional }, // GetTileDataAction{ { 4, 0, { 4, 0, 0 } }, Found }, // ... - RetainTileDataAction{ { 4, 0, { 4, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 4, 0, { 4, 0, 0 } }, TileNecessity::Optional }, // GetTileDataAction{ { 3, 0, { 3, 0, 0 } }, Found }, // ... - RetainTileDataAction{ { 3, 0, { 3, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 3, 0, { 3, 0, 0 } }, TileNecessity::Optional }, // GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // ... CreateTileDataAction{ { 2, 0, { 2, 0, 0 } } }, // - RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 2, 0, { 2, 0, 0 } }, TileNecessity::Optional }, // GetTileDataAction{ { 1, 0, { 1, 0, 0 } }, NotFound }, // ... GetTileDataAction{ { 0, 0, { 0, 0, 0 } }, NotFound }, // ... }), @@ -1211,17 +1211,17 @@ TEST(UpdateRenderables, RepeatedRenderWithMissingOptionals) { source.idealTiles, source.zoomRange, 6); EXPECT_EQ(ActionLog({ GetTileDataAction{ { 6, 0, { 6, 0, 0 } }, Found }, // ideal tile, not ready - RetainTileDataAction{ { 6, 0, { 6, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 6, 0, { 6, 0, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 7, 0, { 7, 0, 0 } }, NotFound }, // children GetTileDataAction{ { 7, 0, { 7, 0, 1 } }, NotFound }, // ... GetTileDataAction{ { 7, 0, { 7, 1, 0 } }, NotFound }, // ... GetTileDataAction{ { 7, 0, { 7, 1, 1 } }, NotFound }, // ... GetTileDataAction{ { 5, 0, { 5, 0, 0 } }, Found }, // ascent - RetainTileDataAction{ { 5, 0, { 5, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 5, 0, { 5, 0, 0 } }, TileNecessity::Optional }, // GetTileDataAction{ { 4, 0, { 4, 0, 0 } }, Found }, // ... - RetainTileDataAction{ { 4, 0, { 4, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 4, 0, { 4, 0, 0 } }, TileNecessity::Optional }, // GetTileDataAction{ { 3, 0, { 3, 0, 0 } }, Found }, // ... - RetainTileDataAction{ { 3, 0, { 3, 0, 0 } }, Resource::Necessity::Optional }, // + RetainTileDataAction{ { 3, 0, { 3, 0, 0 } }, TileNecessity::Optional }, // RenderTileAction{ { 3, 0, 0 }, *tile_3_3_0_0 }, // }), log); @@ -1246,11 +1246,11 @@ TEST(UpdateRenderables, LoadRequiredIfIdealTileCantBeFound) { source.idealTiles, source.zoomRange, 6); EXPECT_EQ(ActionLog({ GetTileDataAction{ { 6, 0, { 6, 0, 0 } }, Found }, // ideal tile, not found - RetainTileDataAction{ { 6, 0, { 6, 0, 0 } }, Resource::Necessity::Required }, // + RetainTileDataAction{ { 6, 0, { 6, 0, 0 } }, TileNecessity::Required }, // GetTileDataAction{ { 7, 0, { 6, 0, 0 } }, NotFound }, // overzoomed child GetTileDataAction{ { 5, 0, { 5, 0, 0 } }, NotFound }, // ascent CreateTileDataAction{ { 5, 0, { 5, 0, 0 } } }, - RetainTileDataAction{ { 5, 0, { 5, 0, 0 } }, Resource::Necessity::Required }, + RetainTileDataAction{ { 5, 0, { 5, 0, 0 } }, TileNecessity::Required }, GetTileDataAction{ { 4, 0, { 4, 0, 0 } }, NotFound }, // ... GetTileDataAction{ { 3, 0, { 3, 0, 0 } }, NotFound }, // ... GetTileDataAction{ { 2, 0, { 2, 0, 0 } }, NotFound }, // ... diff --git a/test/storage/default_file_source.test.cpp b/test/storage/default_file_source.test.cpp index b5686b5ffe..c11d442270 100644 --- a/test/storage/default_file_source.test.cpp +++ b/test/storage/default_file_source.test.cpp @@ -251,7 +251,7 @@ TEST(DefaultFileSource, OptionalNonExpired) { util::RunLoop loop; DefaultFileSource fs(":memory:", "."); - const Resource optionalResource { Resource::Unknown, "http://127.0.0.1:3000/test", {}, Resource::Optional }; + const Resource optionalResource { Resource::Unknown, "http://127.0.0.1:3000/test", {}, Resource::LoadingMethod::CacheOnly }; using namespace std::chrono_literals; @@ -281,7 +281,7 @@ TEST(DefaultFileSource, OptionalExpired) { util::RunLoop loop; DefaultFileSource fs(":memory:", "."); - const Resource optionalResource { Resource::Unknown, "http://127.0.0.1:3000/test", {}, Resource::Optional }; + const Resource optionalResource { Resource::Unknown, "http://127.0.0.1:3000/test", {}, Resource::LoadingMethod::CacheOnly }; using namespace std::chrono_literals; @@ -327,7 +327,7 @@ TEST(DefaultFileSource, OptionalNotFound) { util::RunLoop loop; DefaultFileSource fs(":memory:", "."); - const Resource optionalResource { Resource::Unknown, "http://127.0.0.1:3000/test", {}, Resource::Optional }; + const Resource optionalResource { Resource::Unknown, "http://127.0.0.1:3000/test", {}, Resource::LoadingMethod::CacheOnly }; using namespace std::chrono_literals; @@ -348,13 +348,13 @@ TEST(DefaultFileSource, OptionalNotFound) { loop.run(); } -// Test that we can make a request with etag data that doesn't first try to load -// from cache like a regular request +// Test that a network only request doesn't attempt to load data from the cache. TEST(DefaultFileSource, TEST_REQUIRES_SERVER(NoCacheRefreshEtagNotModified)) { util::RunLoop loop; DefaultFileSource fs(":memory:", "."); Resource resource { Resource::Unknown, "http://127.0.0.1:3000/revalidate-same" }; + resource.loadingMethod = Resource::LoadingMethod::NetworkOnly; resource.priorEtag.emplace("snowfall"); using namespace std::chrono_literals; @@ -383,13 +383,13 @@ TEST(DefaultFileSource, TEST_REQUIRES_SERVER(NoCacheRefreshEtagNotModified)) { loop.run(); } -// Test that we can make a request with etag data that doesn't first try to load -// from cache like a regular request +// Test that a network only request doesn't attempt to load data from the cache. TEST(DefaultFileSource, TEST_REQUIRES_SERVER(NoCacheRefreshEtagModified)) { util::RunLoop loop; DefaultFileSource fs(":memory:", "."); Resource resource { Resource::Unknown, "http://127.0.0.1:3000/revalidate-same" }; + resource.loadingMethod = Resource::LoadingMethod::NetworkOnly; resource.priorEtag.emplace("sunshine"); using namespace std::chrono_literals; @@ -418,15 +418,13 @@ TEST(DefaultFileSource, TEST_REQUIRES_SERVER(NoCacheRefreshEtagModified)) { loop.run(); } -// Test that we can make a request that doesn't first try to load -// from cache like a regular request. +// Test that a network only request doesn't attempt to load data from the cache. TEST(DefaultFileSource, TEST_REQUIRES_SERVER(NoCacheFull)) { util::RunLoop loop; DefaultFileSource fs(":memory:", "."); Resource resource { Resource::Unknown, "http://127.0.0.1:3000/revalidate-same" }; - // Setting any prior field results in skipping the cache. - resource.priorExpires.emplace(Seconds(0)); + resource.loadingMethod = Resource::LoadingMethod::NetworkOnly; using namespace std::chrono_literals; @@ -461,6 +459,7 @@ TEST(DefaultFileSource, TEST_REQUIRES_SERVER(NoCacheRefreshModifiedNotModified)) DefaultFileSource fs(":memory:", "."); Resource resource { Resource::Unknown, "http://127.0.0.1:3000/revalidate-modified" }; + resource.loadingMethod = Resource::LoadingMethod::NetworkOnly; resource.priorModified.emplace(Seconds(1420070400)); // January 1, 2015 using namespace std::chrono_literals; @@ -496,6 +495,7 @@ TEST(DefaultFileSource, TEST_REQUIRES_SERVER(NoCacheRefreshModifiedModified)) { DefaultFileSource fs(":memory:", "."); Resource resource { Resource::Unknown, "http://127.0.0.1:3000/revalidate-modified" }; + resource.loadingMethod = Resource::LoadingMethod::NetworkOnly; resource.priorModified.emplace(Seconds(1417392000)); // December 1, 2014 using namespace std::chrono_literals; @@ -578,7 +578,7 @@ TEST(DefaultFileSource, TEST_REQUIRES_SERVER(RespondToStaleMustRevalidate)) { DefaultFileSource fs(":memory:", "."); Resource resource { Resource::Unknown, "http://127.0.0.1:3000/revalidate-same" }; - resource.necessity = Resource::Necessity::Optional; + resource.loadingMethod = Resource::LoadingMethod::CacheOnly; // using namespace std::chrono_literals; @@ -621,7 +621,7 @@ TEST(DefaultFileSource, TEST_REQUIRES_SERVER(RespondToStaleMustRevalidate)) { // Now run this request again, with the data we gathered from the previous stale/unusable // request. We're replacing the data so that we can check that the DefaultFileSource doesn't // attempt another database access if we already have the value. - resource.necessity = Resource::Necessity::Required; + resource.loadingMethod = Resource::LoadingMethod::NetworkOnly; resource.priorData = std::make_shared("Prior value"); req = fs.request(resource, [&](Response res) { -- cgit v1.2.1 From 06a18dd5485005ab87420c8e4c609f68984b7469 Mon Sep 17 00:00:00 2001 From: Tobrun Date: Thu, 12 Oct 2017 10:41:25 +0200 Subject: [android] - add make target for ndk-stack --- Makefile | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Makefile b/Makefile index b4863a9b9e..7bcb281985 100644 --- a/Makefile +++ b/Makefile @@ -553,6 +553,11 @@ run-android-ui-test-$1-%: platform/android/configuration.gradle -adb uninstall com.mapbox.mapboxsdk.testapp 2> /dev/null cd platform/android && $(MBGL_ANDROID_GRADLE) -Pmapbox.abis=$2 :MapboxGLAndroidSDKTestApp:connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.class="$$*" +# Symbolicate native stack trace with the specified abi +.PHONY: android-ndk-stack-$1 +android-ndk-stack-$1: platform/android/configuration.gradle + adb logcat | ndk-stack -sym platform/android/MapboxGLAndroidSDK/build/intermediates/cmake/debug/obj/$2/ + endef # Explodes the arguments into individual variables @@ -641,6 +646,10 @@ android-lint-test-app: platform/android/configuration.gradle android-javadoc: platform/android/configuration.gradle cd platform/android && $(MBGL_ANDROID_GRADLE) -Pmapbox.abis=none :MapboxGLAndroidSDK:javadocrelease +# Symbolicate ndk stack traces for the arm-v7 abi +.PHONY: android-ndk-stack +android-ndk-stack: android-ndk-stack-arm-v7 + # Open Android Studio if machine is macos ifeq ($(HOST_PLATFORM), macos) .PHONY: aproj -- cgit v1.2.1 From d7d3d2c7311c5fed79537e39b8c3ea1f954b8ccf Mon Sep 17 00:00:00 2001 From: Asheem Mamoowala Date: Mon, 9 Oct 2017 16:26:22 -0700 Subject: [core] Fix out-of-bounds array access --- src/mbgl/util/i18n.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mbgl/util/i18n.cpp b/src/mbgl/util/i18n.cpp index 16f1d669f3..3e3a68e248 100644 --- a/src/mbgl/util/i18n.cpp +++ b/src/mbgl/util/i18n.cpp @@ -554,7 +554,7 @@ std::u16string verticalizePunctuation(const std::u16string& input) { std::u16string output; for (size_t i = 0; i < input.size(); i++) { - char16_t nextCharCode = i < input.size() ? input[i + 1] : 0; + char16_t nextCharCode = i < input.size() - 1 ? input[i + 1] : 0; char16_t prevCharCode = i ? input[i - 1] : 0; bool canReplacePunctuation = -- cgit v1.2.1 From 10978329f958f5b9e260c525e25598484f623a49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguy=E1=BB=85n?= Date: Thu, 12 Oct 2017 15:17:41 -0700 Subject: [ios, macos] Fixed symbol export warning Fixed a warning issued by check-public-symbols.js in MGLMapSnapshotOptions due to the MGL_EXPORT keyword appearing before the documentation comment. Also copyedited a couple comments in the same header. --- platform/darwin/src/MGLMapSnapshotter.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/platform/darwin/src/MGLMapSnapshotter.h b/platform/darwin/src/MGLMapSnapshotter.h index 615d39bee4..e227f3e306 100644 --- a/platform/darwin/src/MGLMapSnapshotter.h +++ b/platform/darwin/src/MGLMapSnapshotter.h @@ -5,10 +5,10 @@ NS_ASSUME_NONNULL_BEGIN -MGL_EXPORT /** - The options to use when creating images with the `MGLMapsnapshotter`. + The options to use when creating images with the `MGLMapSnapshotter`. */ +MGL_EXPORT @interface MGLMapSnapshotOptions : NSObject /** @@ -63,7 +63,8 @@ MGL_EXPORT /** The scale of the output image. Defaults to the main screen scale. - Minimum is 1. + + The minimum scale is 1. */ @property (nonatomic) CGFloat scale; -- cgit v1.2.1 From d95079a0f94fdc9294c41739fa993eb48ea2971d Mon Sep 17 00:00:00 2001 From: Tobrun Van Nuland Date: Tue, 12 Sep 2017 18:40:25 +0200 Subject: [android] - use FileSource pause and resume when foregrounding/backgrounding app --- .../java/com/mapbox/mapboxsdk/maps/MapView.java | 3 +++ .../mapbox/mapboxsdk/offline/OfflineRegion.java | 9 ++++++++ .../com/mapbox/mapboxsdk/storage/FileSource.java | 24 +++++++++++++++++++++- .../testapp/activity/offline/OfflineActivity.java | 3 +++ platform/android/src/file_source.cpp | 14 ++++++++++--- platform/android/src/file_source.hpp | 4 ++++ 6 files changed, 53 insertions(+), 4 deletions(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java index 260933ffa1..9182b952e6 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 @@ -33,6 +33,7 @@ import com.mapbox.mapboxsdk.maps.widgets.CompassView; import com.mapbox.mapboxsdk.maps.widgets.MyLocationView; import com.mapbox.mapboxsdk.maps.widgets.MyLocationViewSettings; import com.mapbox.mapboxsdk.net.ConnectivityReceiver; +import com.mapbox.mapboxsdk.storage.FileSource; import com.mapbox.services.android.telemetry.MapboxTelemetry; import java.lang.annotation.Retention; @@ -335,6 +336,7 @@ public class MapView extends FrameLayout { @UiThread public void onStart() { ConnectivityReceiver.instance(getContext()).activate(); + FileSource.getInstance(getContext()).activate(); if (mapboxMap != null) { mapboxMap.onStart(); } @@ -367,6 +369,7 @@ public class MapView extends FrameLayout { public void onStop() { mapboxMap.onStop(); ConnectivityReceiver.instance(getContext()).deactivate(); + FileSource.getInstance(getContext()).deactivate(); } /** diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java index f210729037..1b9a156352 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegion.java @@ -300,10 +300,19 @@ public class OfflineRegion { /** * Pause or resume downloading of regional resources. + *

+ * After a download has been completed, you are required to reset the state of the region to STATE_INACTIVE. + *

* * @param state the download state */ public void setDownloadState(@DownloadState int state) { + if (state == STATE_ACTIVE) { + fileSource.activate(); + } else { + fileSource.deactivate(); + } + this.state = state; setOfflineRegionDownloadState(state); } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/storage/FileSource.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/storage/FileSource.java index a968cdf192..41dc449b50 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/storage/FileSource.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/storage/FileSource.java @@ -72,7 +72,7 @@ public class FileSource { MapboxConstants.KEY_META_DATA_SET_STORAGE_EXTERNAL, MapboxConstants.DEFAULT_SET_STORAGE_EXTERNAL); } catch (PackageManager.NameNotFoundException exception) { - Timber.e(exception,"Failed to read the package metadata: "); + Timber.e(exception, "Failed to read the package metadata: "); } catch (Exception exception) { Timber.e(exception, "Failed to read the storage key: "); } @@ -119,17 +119,39 @@ public class FileSource { } private long nativePtr; + private long activeCounter; + private boolean wasPaused; private FileSource(String cachePath, AssetManager assetManager) { initialize(Mapbox.getAccessToken(), cachePath, assetManager); } + public void activate() { + activeCounter++; + if (activeCounter == 1 && wasPaused) { + wasPaused = false; + resume(); + } + } + + public void deactivate() { + activeCounter--; + if (activeCounter == 0) { + wasPaused = true; + pause(); + } + } + public native void setAccessToken(@NonNull String accessToken); public native String getAccessToken(); public native void setApiBaseUrl(String baseUrl); + private native void resume(); + + private native void pause(); + /** * Sets a callback for transforming URLs requested from the internet *

diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/OfflineActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/OfflineActivity.java index 5bffd4d930..3a59e0628d 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/OfflineActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/offline/OfflineActivity.java @@ -264,6 +264,7 @@ public class OfflineActivity extends AppCompatActivity if (status.isComplete()) { // Download complete endProgress("Region downloaded successfully."); + offlineRegion.setDownloadState(OfflineRegion.STATE_INACTIVE); offlineRegion.setObserver(null); return; } else if (status.isRequiredResourceCountPrecise()) { @@ -281,11 +282,13 @@ public class OfflineActivity extends AppCompatActivity @Override public void onError(OfflineRegionError error) { Timber.e("onError: %s, %s", error.getReason(), error.getMessage()); + offlineRegion.setDownloadState(OfflineRegion.STATE_INACTIVE); } @Override public void mapboxTileCountLimitExceeded(long limit) { Timber.e("Mapbox tile count limit exceeded: %s", limit); + offlineRegion.setDownloadState(OfflineRegion.STATE_INACTIVE); } }); diff --git a/platform/android/src/file_source.cpp b/platform/android/src/file_source.cpp index 262e3d3c6a..a576661a4f 100644 --- a/platform/android/src/file_source.cpp +++ b/platform/android/src/file_source.cpp @@ -8,8 +8,6 @@ #include "asset_manager_file_source.hpp" #include "jni/generic_global_ref_deleter.hpp" -#include - namespace mbgl { namespace android { @@ -64,6 +62,14 @@ void FileSource::setResourceTransform(jni::JNIEnv& env, jni::Objectresume(); +} + +void FileSource::pause(jni::JNIEnv&) { + fileSource->pause(); +} + jni::Class FileSource::javaClass; FileSource* FileSource::getNativePeer(jni::JNIEnv& env, jni::Object jFileSource) { @@ -93,7 +99,9 @@ void FileSource::registerNative(jni::JNIEnv& env) { METHOD(&FileSource::getAccessToken, "getAccessToken"), METHOD(&FileSource::setAccessToken, "setAccessToken"), METHOD(&FileSource::setAPIBaseUrl, "setApiBaseUrl"), - METHOD(&FileSource::setResourceTransform, "setResourceTransform") + METHOD(&FileSource::setResourceTransform, "setResourceTransform"), + METHOD(&FileSource::resume, "resume"), + METHOD(&FileSource::pause, "pause") ); } diff --git a/platform/android/src/file_source.hpp b/platform/android/src/file_source.hpp index 4abe352bff..2933aedf86 100644 --- a/platform/android/src/file_source.hpp +++ b/platform/android/src/file_source.hpp @@ -41,6 +41,10 @@ public: void setResourceTransform(jni::JNIEnv&, jni::Object); + void resume(jni::JNIEnv&); + + void pause(jni::JNIEnv&); + static jni::Class javaClass; static FileSource* getNativePeer(jni::JNIEnv&, jni::Object); -- cgit v1.2.1 From 0cd646180e787a59e600cb184b189a801e261c5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Fri, 13 Oct 2017 17:53:30 -0700 Subject: [core] don't generate clip IDs for buckets/layers that don't need to be clipped --- src/mbgl/algorithm/generate_clip_ids_impl.hpp | 2 +- src/mbgl/renderer/render_tile.hpp | 1 + src/mbgl/renderer/renderer_impl.cpp | 7 +++++++ test/algorithm/generate_clip_ids.test.cpp | 7 +++++-- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/mbgl/algorithm/generate_clip_ids_impl.hpp b/src/mbgl/algorithm/generate_clip_ids_impl.hpp index db62214220..6a316dcdad 100644 --- a/src/mbgl/algorithm/generate_clip_ids_impl.hpp +++ b/src/mbgl/algorithm/generate_clip_ids_impl.hpp @@ -17,7 +17,7 @@ void ClipIDGenerator::update(std::vector> ren const auto end = renderables.end(); for (auto it = renderables.begin(); it != end; it++) { auto& renderable = it->get(); - if (!renderable.used) { + if (!renderable.used || !renderable.needsClipping) { continue; } diff --git a/src/mbgl/renderer/render_tile.hpp b/src/mbgl/renderer/render_tile.hpp index b498972f5c..3db02393d2 100644 --- a/src/mbgl/renderer/render_tile.hpp +++ b/src/mbgl/renderer/render_tile.hpp @@ -28,6 +28,7 @@ public: mat4 matrix; mat4 nearClippedMatrix; bool used = false; + bool needsClipping = false; mat4 translatedMatrix(const std::array& translate, style::TranslateAnchorType anchor, diff --git a/src/mbgl/renderer/renderer_impl.cpp b/src/mbgl/renderer/renderer_impl.cpp index 1a828b80a3..6a8c18792e 100644 --- a/src/mbgl/renderer/renderer_impl.cpp +++ b/src/mbgl/renderer/renderer_impl.cpp @@ -370,6 +370,13 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { if (bucket) { sortedTilesForInsertion.emplace_back(tile); tile.used = true; + + // We only need clipping when we're _not_ drawing a symbol layer. The only exception + // for symbol layers is when we're rendering still images. See render_symbol_layer.cpp + // for the exception we make there. + if (!symbolLayer || parameters.mapMode == MapMode::Still) { + tile.needsClipping = true; + } } } layer->setRenderTiles(std::move(sortedTilesForInsertion)); diff --git a/test/algorithm/generate_clip_ids.test.cpp b/test/algorithm/generate_clip_ids.test.cpp index 1ebdccb99e..9dc86305af 100644 --- a/test/algorithm/generate_clip_ids.test.cpp +++ b/test/algorithm/generate_clip_ids.test.cpp @@ -8,13 +8,16 @@ struct Renderable { UnwrappedTileID id; ClipID clip; bool used; + bool needsClipping; Renderable(UnwrappedTileID id_, ClipID clip_, - bool used_ = true) + bool used_ = true, + bool needsClipping_ = true) : id(std::move(id_)), clip(std::move(clip_)), - used(used_) {} + used(used_), + needsClipping(needsClipping_) {} bool operator==(const Renderable& rhs) const { return id == rhs.id && clip == rhs.clip; -- cgit v1.2.1 From 9c12a311f2ba2b26a44a6e13edd38bf8050e5d16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Tue, 17 Oct 2017 14:00:52 -0700 Subject: [core] correctly alter sprite URLs when attaching sprite.png/sprite.json filenames --- src/mbgl/storage/resource.cpp | 18 ++++++++++-------- test/storage/resource.test.cpp | 8 ++++++++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/mbgl/storage/resource.cpp b/src/mbgl/storage/resource.cpp index e75302b986..207dd2ee69 100644 --- a/src/mbgl/storage/resource.cpp +++ b/src/mbgl/storage/resource.cpp @@ -61,17 +61,19 @@ Resource Resource::image(const std::string& url) { } Resource Resource::spriteImage(const std::string& base, float pixelRatio) { - return Resource { - Resource::Kind::SpriteImage, - base + (pixelRatio > 1 ? "@2x" : "") + ".png" - }; + util::URL url(base); + return Resource{ Resource::Kind::SpriteImage, + base.substr(0, url.path.first + url.path.second) + + (pixelRatio > 1 ? "@2x" : "") + ".png" + + base.substr(url.query.first, url.query.second) }; } Resource Resource::spriteJSON(const std::string& base, float pixelRatio) { - return Resource { - Resource::Kind::SpriteJSON, - base + (pixelRatio > 1 ? "@2x" : "") + ".json" - }; + util::URL url(base); + return Resource{ Resource::Kind::SpriteJSON, + base.substr(0, url.path.first + url.path.second) + + (pixelRatio > 1 ? "@2x" : "") + ".json" + + base.substr(url.query.first, url.query.second) }; } Resource Resource::glyphs(const std::string& urlTemplate, const FontStack& fontStack, const std::pair& glyphRange) { diff --git a/test/storage/resource.test.cpp b/test/storage/resource.test.cpp index 5a27aa98a5..083134c4d0 100644 --- a/test/storage/resource.test.cpp +++ b/test/storage/resource.test.cpp @@ -115,6 +115,10 @@ TEST(Resource, SpriteImage) { Resource resource = Resource::spriteImage("http://example.com/sprite", 2.0); EXPECT_EQ(Resource::Kind::SpriteImage, resource.kind); EXPECT_EQ("http://example.com/sprite@2x.png", resource.url); + + Resource paramResource = Resource::spriteImage("http://example.com/sprite?query=true", 2.0); + EXPECT_EQ(Resource::Kind::SpriteImage, paramResource.kind); + EXPECT_EQ("http://example.com/sprite@2x.png?query=true", paramResource.url); } TEST(Resource, Image) { @@ -129,4 +133,8 @@ TEST(Resource, SpriteJSON) { Resource resource = Resource::spriteJSON("http://example.com/sprite", 2.0); EXPECT_EQ(Resource::Kind::SpriteJSON, resource.kind); EXPECT_EQ("http://example.com/sprite@2x.json", resource.url); + + Resource paramResource = Resource::spriteJSON("http://example.com/sprite?query=true", 2.0); + EXPECT_EQ(Resource::Kind::SpriteJSON, paramResource.kind); + EXPECT_EQ("http://example.com/sprite@2x.json?query=true", paramResource.url); } -- cgit v1.2.1 From fe6e0c360e9d2bcab975f7f469a5ad5537cf6a9c Mon Sep 17 00:00:00 2001 From: Tobrun Van Nuland Date: Tue, 17 Oct 2017 17:35:20 +0200 Subject: [android] - removed unused gradle plugins --- .../android/MapboxGLAndroidSDKTestApp/build.gradle | 3 -- .../gradle-device-farm.gradle | 43 ---------------------- .../MapboxGLAndroidSDKTestApp/gradle-spoon.gradle | 8 ---- platform/android/build.gradle | 2 - platform/android/dependencies.gradle | 1 - 5 files changed, 57 deletions(-) delete mode 100644 platform/android/MapboxGLAndroidSDKTestApp/gradle-device-farm.gradle delete mode 100644 platform/android/MapboxGLAndroidSDKTestApp/gradle-spoon.gradle diff --git a/platform/android/MapboxGLAndroidSDKTestApp/build.gradle b/platform/android/MapboxGLAndroidSDKTestApp/build.gradle index 67939b5144..0bac90d2c2 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/build.gradle +++ b/platform/android/MapboxGLAndroidSDKTestApp/build.gradle @@ -70,7 +70,6 @@ dependencies { } // Testing dependencies - androidTestCompile rootProject.ext.dep.testSpoonRunner androidTestCompile rootProject.ext.dep.supportAnnotations androidTestCompile rootProject.ext.dep.testRunner androidTestCompile rootProject.ext.dep.testRules @@ -80,8 +79,6 @@ dependencies { apply from: 'gradle-make.gradle' apply from: 'gradle-config.gradle' -apply from: 'gradle-device-farm.gradle' -apply from: 'gradle-spoon.gradle' apply from: 'gradle-checkstyle.gradle' apply from: '../gradle-lint.gradle' diff --git a/platform/android/MapboxGLAndroidSDKTestApp/gradle-device-farm.gradle b/platform/android/MapboxGLAndroidSDKTestApp/gradle-device-farm.gradle deleted file mode 100644 index 5cb5d75bbb..0000000000 --- a/platform/android/MapboxGLAndroidSDKTestApp/gradle-device-farm.gradle +++ /dev/null @@ -1,43 +0,0 @@ -apply plugin: 'devicefarm' - -def getAccessKeyDeviceFarm() { - if (project.hasProperty('AWS_ACCESS_KEY_ID_DEVICE_FARM')) { - return AWS_ACCESS_KEY_ID_DEVICE_FARM - } else { - println("Could not locate AWS_ACCESS_KEY_ID_DEVICE_FARM in gradle.properties") - return "" - } -} - -def getSecretAccessKeyDeviceFarm() { - if (project.hasProperty('AWS_SECRET_ACCESS_KEY_DEVICE_FARM')) { - return AWS_SECRET_ACCESS_KEY_DEVICE_FARM - } else { - println("Could not locate AWS_SECRET_ACCESS_KEY_DEVICE_FARM in gradle.properties") - return "" - } -} - -devicefarm { - - projectName "Mapbox GL Android" // required: Must already exists. - devicePool "sanity" // optional: Defaults to "Top Devices" - - authentication { - accessKey getAccessKeyDeviceFarm() - secretKey getSecretAccessKeyDeviceFarm() - } - - devicestate { - wifi "on" - bluetooth "off" - gps "on" - nfc "on" - latitude 47.6204 // default - longitude - 122.3491 // default - } - - instrumentation { - - } -} diff --git a/platform/android/MapboxGLAndroidSDKTestApp/gradle-spoon.gradle b/platform/android/MapboxGLAndroidSDKTestApp/gradle-spoon.gradle deleted file mode 100644 index 7a5b966443..0000000000 --- a/platform/android/MapboxGLAndroidSDKTestApp/gradle-spoon.gradle +++ /dev/null @@ -1,8 +0,0 @@ -println "configuring spoon" -apply plugin: 'spoon' -spoon { - // Spoon: distributes instrumentation tests to all your Androids - // for more options see https://github.com/stanfy/spoon-gradle-plugin - grantAllPermissions = true - debug = true -} diff --git a/platform/android/build.gradle b/platform/android/build.gradle index e298b84da8..f3301d0b5a 100644 --- a/platform/android/build.gradle +++ b/platform/android/build.gradle @@ -4,8 +4,6 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:2.3.1' - classpath 'com.amazonaws:aws-devicefarm-gradle-plugin:1.2' - classpath 'com.stanfy.spoon:spoon-gradle-plugin:1.2.1' } } diff --git a/platform/android/dependencies.gradle b/platform/android/dependencies.gradle index 521084559f..0a8278a7b7 100644 --- a/platform/android/dependencies.gradle +++ b/platform/android/dependencies.gradle @@ -27,7 +27,6 @@ ext { mockito : 'org.mockito:mockito-core:2.10.0', // instrumentation test - testSpoonRunner : 'com.squareup.spoon:spoon-client:1.7.1', testRunner : "com.android.support.test:runner:${testRunnerVersion}", testRules : "com.android.support.test:rules:${testRunnerVersion}", testEspressoCore : "com.android.support.test.espresso:espresso-core:${espressoVersion}", -- cgit v1.2.1 From a221b459e99dd15e0731d79622df815a0f86613b Mon Sep 17 00:00:00 2001 From: Tobrun Van Nuland Date: Wed, 18 Oct 2017 10:55:45 +0200 Subject: [android] - execute notifying listeners when not idle --- .../mapbox/mapboxsdk/maps/CameraChangeDispatcher.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CameraChangeDispatcher.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CameraChangeDispatcher.java index e3ebc74ae3..cf780dcc3f 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CameraChangeDispatcher.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CameraChangeDispatcher.java @@ -25,18 +25,22 @@ class CameraChangeDispatcher implements MapboxMap.OnCameraMoveStartedListener, M private OnCameraMoveListener onCameraMoveListener; private OnCameraIdleListener onCameraIdleListener; + @Deprecated void setOnCameraMoveStartedListener(OnCameraMoveStartedListener onCameraMoveStartedListener) { this.onCameraMoveStartedListener = onCameraMoveStartedListener; } + @Deprecated void setOnCameraMoveCanceledListener(OnCameraMoveCanceledListener onCameraMoveCanceledListener) { this.onCameraMoveCanceledListener = onCameraMoveCanceledListener; } + @Deprecated void setOnCameraMoveListener(OnCameraMoveListener onCameraMoveListener) { this.onCameraMoveListener = onCameraMoveListener; } + @Deprecated void setOnCameraIdleListener(OnCameraIdleListener onCameraIdleListener) { this.onCameraIdleListener = onCameraIdleListener; } @@ -46,12 +50,14 @@ class CameraChangeDispatcher implements MapboxMap.OnCameraMoveStartedListener, M if (!idle) { return; } - idle = false; + + // deprecated API if (onCameraMoveStartedListener != null) { onCameraMoveStartedListener.onCameraMoveStarted(reason); } + // new API if (!onCameraMoveStartedListenerList.isEmpty()) { for (OnCameraMoveStartedListener cameraMoveStartedListener : onCameraMoveStartedListenerList) { cameraMoveStartedListener.onCameraMoveStarted(reason); @@ -61,11 +67,13 @@ class CameraChangeDispatcher implements MapboxMap.OnCameraMoveStartedListener, M @Override public void onCameraMove() { + // deprecated API if (onCameraMoveListener != null && !idle) { onCameraMoveListener.onCameraMove(); } - if (!onCameraMoveListenerList.isEmpty()) { + // new API + if (!onCameraMoveListenerList.isEmpty() && !idle) { for (OnCameraMoveListener cameraMoveListener : onCameraMoveListenerList) { cameraMoveListener.onCameraMove(); } @@ -74,11 +82,13 @@ class CameraChangeDispatcher implements MapboxMap.OnCameraMoveStartedListener, M @Override public void onCameraMoveCanceled() { + // deprecated API if (onCameraMoveCanceledListener != null && !idle) { onCameraMoveCanceledListener.onCameraMoveCanceled(); } - if (!onCameraMoveCanceledListenerList.isEmpty()) { + // new API + if (!onCameraMoveCanceledListenerList.isEmpty() && !idle) { for (OnCameraMoveCanceledListener cameraMoveCanceledListener : onCameraMoveCanceledListenerList) { cameraMoveCanceledListener.onCameraMoveCanceled(); } @@ -89,10 +99,12 @@ class CameraChangeDispatcher implements MapboxMap.OnCameraMoveStartedListener, M public void onCameraIdle() { if (!idle) { idle = true; + // deprecated API if (onCameraIdleListener != null) { onCameraIdleListener.onCameraIdle(); } + // new API if (!onCameraIdleListenerList.isEmpty()) { for (OnCameraIdleListener cameraIdleListener : onCameraIdleListenerList) { cameraIdleListener.onCameraIdle(); -- cgit v1.2.1 From 24fc48982a635578db3f2262b9f56a27c53688cb Mon Sep 17 00:00:00 2001 From: Tobrun Van Nuland Date: Wed, 18 Oct 2017 13:06:36 +0200 Subject: [android] - wire up MapZoomButtonController with camera change events --- .../main/java/com/mapbox/mapboxsdk/maps/MapView.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java index 9182b952e6..309090ed3f 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 @@ -191,8 +191,10 @@ public class MapView extends FrameLayout { annotationManager, cameraChangeDispatcher); mapKeyListener = new MapKeyListener(transform, trackingSettings, uiSettings); + // overlain zoom buttons mapZoomButtonController = new MapZoomButtonController(new ZoomButtonsController(this)); - MapZoomControllerListener zoomListener = new MapZoomControllerListener(mapGestureDetector, uiSettings, transform); + MapZoomControllerListener zoomListener = new MapZoomControllerListener(mapGestureDetector, uiSettings, transform, + cameraChangeDispatcher, getWidth(), getHeight()); mapZoomButtonController.bind(uiSettings, zoomListener); compassView.injectCompassAnimationListener(createCompassAnimationListener(cameraChangeDispatcher)); @@ -880,16 +882,23 @@ public class MapView extends FrameLayout { } } - private class MapZoomControllerListener implements ZoomButtonsController.OnZoomListener { + private static class MapZoomControllerListener implements ZoomButtonsController.OnZoomListener { private final MapGestureDetector mapGestureDetector; private final UiSettings uiSettings; private final Transform transform; + private final CameraChangeDispatcher cameraChangeDispatcher; + private final float mapWidth; + private final float mapHeight; - MapZoomControllerListener(MapGestureDetector detector, UiSettings uiSettings, Transform transform) { + MapZoomControllerListener(MapGestureDetector detector, UiSettings uiSettings, Transform transform, + CameraChangeDispatcher dispatcher, float mapWidth, float mapHeight) { this.mapGestureDetector = detector; this.uiSettings = uiSettings; this.transform = transform; + this.cameraChangeDispatcher = dispatcher; + this.mapWidth = mapWidth; + this.mapHeight = mapHeight; } // Not used @@ -902,6 +911,7 @@ public class MapView extends FrameLayout { @Override public void onZoom(boolean zoomIn) { if (uiSettings.isZoomGesturesEnabled()) { + cameraChangeDispatcher.onCameraMoveStarted(CameraChangeDispatcher.REASON_API_ANIMATION); onZoom(zoomIn, mapGestureDetector.getFocalPoint()); } } @@ -910,7 +920,7 @@ public class MapView extends FrameLayout { if (focalPoint != null) { transform.zoom(zoomIn, focalPoint); } else { - PointF centerPoint = new PointF(getMeasuredWidth() / 2, getMeasuredHeight() / 2); + PointF centerPoint = new PointF(mapWidth / 2, mapHeight / 2); transform.zoom(zoomIn, centerPoint); } } -- cgit v1.2.1 From b7b8baef9d30d31a757dbbc0fbb8cb2c5cf3585e Mon Sep 17 00:00:00 2001 From: Fabian Guerra Soto Date: Wed, 18 Oct 2017 15:18:54 -0400 Subject: [ios, macos] Add selection support to MGLMultiPoint annotations. (#9984) * [ios, macos] Add selection support to MGLMultiPoint annotations. * [ios, macos] Update changelogs. --- include/mbgl/renderer/renderer.hpp | 2 + platform/ios/CHANGELOG.md | 1 + platform/ios/app/MBXViewController.m | 1 + platform/ios/src/MGLMapView.mm | 107 ++++++++++++++++---------- platform/macos/CHANGELOG.md | 1 + platform/macos/src/MGLMapView.mm | 75 ++++++++++++------ src/mbgl/annotation/annotation_manager.cpp | 1 + src/mbgl/annotation/annotation_manager.hpp | 1 + src/mbgl/annotation/shape_annotation_impl.cpp | 3 +- src/mbgl/renderer/renderer.cpp | 15 ++++ src/mbgl/renderer/renderer_impl.cpp | 19 +++++ src/mbgl/renderer/renderer_impl.hpp | 3 + 12 files changed, 165 insertions(+), 64 deletions(-) diff --git a/include/mbgl/renderer/renderer.hpp b/include/mbgl/renderer/renderer.hpp index be8abb2c29..23d2451a2e 100644 --- a/include/mbgl/renderer/renderer.hpp +++ b/include/mbgl/renderer/renderer.hpp @@ -40,6 +40,8 @@ public: std::vector queryRenderedFeatures(const ScreenBox& box, const RenderedQueryOptions& options = {}) const; std::vector querySourceFeatures(const std::string& sourceID, const SourceQueryOptions& options = {}) const; AnnotationIDs queryPointAnnotations(const ScreenBox& box) const; + AnnotationIDs queryShapeAnnotations(const ScreenBox& box) const; + AnnotationIDs getAnnotationIDs(const std::vector&) const; // Debug void dumpDebugLogs(); diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 82de619d7b..3dd46f3e93 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -30,6 +30,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT * Added an `overlays` property to `MGLMapView`. ([#8617](https://github.com/mapbox/mapbox-gl-native/pull/8617)) * Selecting an annotation no longer sets the user tracking mode to `MGLUserTrackingModeNone`. ([#10094](https://github.com/mapbox/mapbox-gl-native/pull/10094)) * Added `-[MGLMapView cameraThatFitsShape:direction:edgePadding:]` to get a camera with zoom level and center coordinate computed to fit a shape. ([#10107](https://github.com/mapbox/mapbox-gl-native/pull/10107)) +* Added support selection of shape and polyline annotations.([#9984](https://github.com/mapbox/mapbox-gl-native/pull/9984)) ### Other changes diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m index 07838bc6bd..5bccb64608 100644 --- a/platform/ios/app/MBXViewController.m +++ b/platform/ios/app/MBXViewController.m @@ -827,6 +827,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { } MGLPolygon *polygon = [MGLPolygon polygonWithCoordinates:polygonCoordinates count:[stateCoordinatePairs count]]; + polygon.title = feature[@"properties"][@"NAME"]; [self.mapView addAnnotation:polygon]; diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm index ac608dd074..4112df6e76 100644 --- a/platform/ios/src/MGLMapView.mm +++ b/platform/ios/src/MGLMapView.mm @@ -3257,6 +3257,12 @@ public: } std::vector annotationTags = [self annotationTagsInRect:rect]; + std::vector shapeAnnotationTags = [self shapeAnnotationTagsInRect:rect]; + + if (shapeAnnotationTags.size()) { + annotationTags.insert(annotationTags.end(), shapeAnnotationTags.begin(), shapeAnnotationTags.end()); + } + if (annotationTags.size()) { NSMutableArray *annotations = [NSMutableArray arrayWithCapacity:annotationTags.size()]; @@ -3762,6 +3768,12 @@ public: queryRect = CGRectInset(queryRect, -MGLAnnotationImagePaddingForHitTest, -MGLAnnotationImagePaddingForHitTest); std::vector nearbyAnnotations = [self annotationTagsInRect:queryRect]; + BOOL queryingShapeAnnotations = NO; + + if (!nearbyAnnotations.size()) { + nearbyAnnotations = [self shapeAnnotationTagsInRect:queryRect]; + queryingShapeAnnotations = YES; + } if (nearbyAnnotations.size()) { @@ -3769,54 +3781,56 @@ public: CGRect hitRect = CGRectInset({ point, CGSizeZero }, -MGLAnnotationImagePaddingForHitTest, -MGLAnnotationImagePaddingForHitTest); - - // Filter out any annotation whose image or view is unselectable or for which - // hit testing fails. - auto end = std::remove_if(nearbyAnnotations.begin(), nearbyAnnotations.end(), - [&](const MGLAnnotationTag annotationTag) - { - id annotation = [self annotationWithTag:annotationTag]; - NSAssert(annotation, @"Unknown annotation found nearby tap"); - if ( ! annotation) - { - return true; - } - - MGLAnnotationContext annotationContext = _annotationContextsByAnnotationTag.at(annotationTag); - CGRect annotationRect; - - MGLAnnotationView *annotationView = annotationContext.annotationView; - if (annotationView) - { - if ( ! annotationView.enabled) + + if (!queryingShapeAnnotations) { + // Filter out any annotation whose image or view is unselectable or for which + // hit testing fails. + auto end = std::remove_if(nearbyAnnotations.begin(), nearbyAnnotations.end(), [&](const MGLAnnotationTag annotationTag) { + id annotation = [self annotationWithTag:annotationTag]; + NSAssert(annotation, @"Unknown annotation found nearby tap"); + if ( ! annotation) { return true; } - CGPoint calloutAnchorPoint = [self convertCoordinate:annotation.coordinate toPointToView:self]; - CGRect frame = CGRectInset({ calloutAnchorPoint, CGSizeZero }, -CGRectGetWidth(annotationView.frame) / 2, -CGRectGetHeight(annotationView.frame) / 2); - annotationRect = UIEdgeInsetsInsetRect(frame, annotationView.alignmentRectInsets); - } - else - { - MGLAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag]; - if ( ! annotationImage.enabled) + MGLAnnotationContext annotationContext = _annotationContextsByAnnotationTag.at(annotationTag); + CGRect annotationRect; + + MGLAnnotationView *annotationView = annotationContext.annotationView; + + if (annotationView) { - return true; + if ( ! annotationView.enabled) + { + return true; + } + + CGPoint calloutAnchorPoint = [self convertCoordinate:annotation.coordinate toPointToView:self]; + CGRect frame = CGRectInset({ calloutAnchorPoint, CGSizeZero }, -CGRectGetWidth(annotationView.frame) / 2, -CGRectGetHeight(annotationView.frame) / 2); + annotationRect = UIEdgeInsetsInsetRect(frame, annotationView.alignmentRectInsets); } + else + { + MGLAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag]; + if ( ! annotationImage.enabled) + { + return true; + } - MGLAnnotationImage *fallbackAnnotationImage = [self dequeueReusableAnnotationImageWithIdentifier:MGLDefaultStyleMarkerSymbolName]; - UIImage *fallbackImage = fallbackAnnotationImage.image; + MGLAnnotationImage *fallbackAnnotationImage = [self dequeueReusableAnnotationImageWithIdentifier:MGLDefaultStyleMarkerSymbolName]; + UIImage *fallbackImage = fallbackAnnotationImage.image; - annotationRect = [self frameOfImage:annotationImage.image ?: fallbackImage centeredAtCoordinate:annotation.coordinate]; - } + annotationRect = [self frameOfImage:annotationImage.image ?: fallbackImage centeredAtCoordinate:annotation.coordinate]; + } - // Filter out the annotation if the fattened finger didn’t land - // within the image’s alignment rect. - return !!!CGRectIntersectsRect(annotationRect, hitRect); - }); + // Filter out the annotation if the fattened finger didn’t land + // within the image’s alignment rect. + return !!!CGRectIntersectsRect(annotationRect, hitRect); + }); + + nearbyAnnotations.resize(std::distance(nearbyAnnotations.begin(), end)); + } - nearbyAnnotations.resize(std::distance(nearbyAnnotations.begin(), end)); } MGLAnnotationTag hitAnnotationTag = MGLAnnotationTagNotFound; @@ -3899,6 +3913,14 @@ public: }); } +- (std::vector)shapeAnnotationTagsInRect:(CGRect)rect +{ + return _rendererFrontend->getRenderer()->queryShapeAnnotations({ + { CGRectGetMinX(rect), CGRectGetMinY(rect) }, + { CGRectGetMaxX(rect), CGRectGetMaxY(rect) }, + }); +} + - (id )selectedAnnotation { if (_userLocationAnnotationIsSelected) @@ -3950,8 +3972,6 @@ public: { if ( ! annotation) return; - if ([annotation isKindOfClass:[MGLMultiPoint class]]) return; - if (annotation == self.selectedAnnotation) return; [self deselectAnnotation:self.selectedAnnotation animated:NO]; @@ -4095,6 +4115,13 @@ public: { return CGRectZero; } + + if ([annotation isKindOfClass:[MGLMultiPoint class]]) { + CLLocationCoordinate2D origin = annotation.coordinate; + CGPoint originPoint = [self convertCoordinate:origin toPointToView:self]; + return CGRectMake(originPoint.x, originPoint.y, MGLAnnotationImagePaddingForHitTest, MGLAnnotationImagePaddingForHitTest); + + } UIImage *image = [self imageOfAnnotationWithTag:annotationTag].image; if ( ! image) { diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md index 31b3540a7c..4b3292c600 100644 --- a/platform/macos/CHANGELOG.md +++ b/platform/macos/CHANGELOG.md @@ -27,6 +27,7 @@ * Increased the default maximum zoom level from 20 to 22. ([#9835](https://github.com/mapbox/mapbox-gl-native/pull/9835)) * Added an `overlays` property to `MGLMapView`. ([#8617](https://github.com/mapbox/mapbox-gl-native/pull/8617)) * Added `-[MGLMapView cameraThatFitsShape:direction:edgePadding:]` to get a camera with zoom level and center coordinate computed to fit a shape. ([#10107](https://github.com/mapbox/mapbox-gl-native/pull/10107)) +* Added support selection of shape and polyline annotations.([#9984](https://github.com/mapbox/mapbox-gl-native/pull/9984)) ### Other changes diff --git a/platform/macos/src/MGLMapView.mm b/platform/macos/src/MGLMapView.mm index 0aa5bdc9db..af8ddbbe11 100644 --- a/platform/macos/src/MGLMapView.mm +++ b/platform/macos/src/MGLMapView.mm @@ -1791,6 +1791,12 @@ public: } std::vector annotationTags = [self annotationTagsInRect:rect]; + std::vector shapeAnnotationTags = [self shapeAnnotationTagsInRect:rect]; + + if (shapeAnnotationTags.size()) { + annotationTags.insert(annotationTags.end(), shapeAnnotationTags.begin(), shapeAnnotationTags.end()); + } + if (annotationTags.size()) { NSMutableArray *annotations = [NSMutableArray arrayWithCapacity:annotationTags.size()]; @@ -2050,35 +2056,43 @@ public: queryRect = NSInsetRect(queryRect, -MGLAnnotationImagePaddingForHitTest, -MGLAnnotationImagePaddingForHitTest); std::vector nearbyAnnotations = [self annotationTagsInRect:queryRect]; + BOOL queryingShapeAnnotations = NO; + + if (!nearbyAnnotations.size()) { + nearbyAnnotations = [self shapeAnnotationTagsInRect:queryRect]; + queryingShapeAnnotations = YES; + } if (nearbyAnnotations.size()) { // Assume that the user is fat-fingering an annotation. NSRect hitRect = NSInsetRect({ point, NSZeroSize }, -MGLAnnotationImagePaddingForHitTest, -MGLAnnotationImagePaddingForHitTest); - - // Filter out any annotation whose image is unselectable or for which - // hit testing fails. - auto end = std::remove_if(nearbyAnnotations.begin(), nearbyAnnotations.end(), [&](const MGLAnnotationTag annotationTag) { - id annotation = [self annotationWithTag:annotationTag]; - NSAssert(annotation, @"Unknown annotation found nearby click"); - if (!annotation) { - return true; - } - - MGLAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag]; - if (!annotationImage.selectable) { - return true; - } - - // Filter out the annotation if the fattened finger didn’t land on a - // translucent or opaque pixel in the image. - NSRect annotationRect = [self frameOfImage:annotationImage.image - centeredAtCoordinate:annotation.coordinate]; - return !!![annotationImage.image hitTestRect:hitRect withImageDestinationRect:annotationRect - context:nil hints:nil flipped:NO]; - }); - nearbyAnnotations.resize(std::distance(nearbyAnnotations.begin(), end)); + + if (!queryingShapeAnnotations) { + // Filter out any annotation whose image is unselectable or for which + // hit testing fails. + auto end = std::remove_if(nearbyAnnotations.begin(), nearbyAnnotations.end(), [&](const MGLAnnotationTag annotationTag) { + id annotation = [self annotationWithTag:annotationTag]; + NSAssert(annotation, @"Unknown annotation found nearby click"); + if (!annotation) { + return true; + } + + MGLAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag]; + if (!annotationImage.selectable) { + return true; + } + + // Filter out the annotation if the fattened finger didn’t land on a + // translucent or opaque pixel in the image. + NSRect annotationRect = [self frameOfImage:annotationImage.image + centeredAtCoordinate:annotation.coordinate]; + return !!![annotationImage.image hitTestRect:hitRect withImageDestinationRect:annotationRect + context:nil hints:nil flipped:NO]; + }); + nearbyAnnotations.resize(std::distance(nearbyAnnotations.begin(), end)); + } } MGLAnnotationTag hitAnnotationTag = MGLAnnotationTagNotFound; @@ -2149,6 +2163,14 @@ public: }); } +- (std::vector)shapeAnnotationTagsInRect:(NSRect)rect { + // Cocoa origin is at the lower-left corner. + return _rendererFrontend->getRenderer()->queryShapeAnnotations({ + { NSMinX(rect), NSHeight(self.bounds) - NSMaxY(rect) }, + { NSMaxX(rect), NSHeight(self.bounds) - NSMinY(rect) }, + }); +} + - (id )selectedAnnotation { if ( ! _annotationContextsByAnnotationTag.count(_selectedAnnotationTag) || _selectedAnnotationTag == MGLAnnotationTagNotFound) { @@ -2314,6 +2336,13 @@ public: if (!annotation) { return NSZeroRect; } + if ([annotation isKindOfClass:[MGLMultiPoint class]]) { + CLLocationCoordinate2D origin = annotation.coordinate; + CGPoint originPoint = [self convertCoordinate:origin toPointToView:self]; + return CGRectMake(originPoint.x, originPoint.y, MGLAnnotationImagePaddingForHitTest, MGLAnnotationImagePaddingForHitTest); + + } + NSImage *image = [self imageOfAnnotationWithTag:annotationTag].image; if (!image) { image = [self dequeueReusableAnnotationImageWithIdentifier:MGLDefaultStyleMarkerSymbolName].image; diff --git a/src/mbgl/annotation/annotation_manager.cpp b/src/mbgl/annotation/annotation_manager.cpp index 1f2d01e9eb..a4d53bbd3f 100644 --- a/src/mbgl/annotation/annotation_manager.cpp +++ b/src/mbgl/annotation/annotation_manager.cpp @@ -18,6 +18,7 @@ using namespace style; const std::string AnnotationManager::SourceID = "com.mapbox.annotations"; const std::string AnnotationManager::PointLayerID = "com.mapbox.annotations.points"; +const std::string AnnotationManager::ShapeLayerID = "com.mapbox.annotations.shape."; AnnotationManager::AnnotationManager(Style& style_) : style(style_) { diff --git a/src/mbgl/annotation/annotation_manager.hpp b/src/mbgl/annotation/annotation_manager.hpp index a028a8f1ba..22b25cd2ac 100644 --- a/src/mbgl/annotation/annotation_manager.hpp +++ b/src/mbgl/annotation/annotation_manager.hpp @@ -46,6 +46,7 @@ public: static const std::string SourceID; static const std::string PointLayerID; + static const std::string ShapeLayerID; private: void add(const AnnotationID&, const SymbolAnnotation&, const uint8_t); diff --git a/src/mbgl/annotation/shape_annotation_impl.cpp b/src/mbgl/annotation/shape_annotation_impl.cpp index 0c1a631ad8..9288159b6a 100644 --- a/src/mbgl/annotation/shape_annotation_impl.cpp +++ b/src/mbgl/annotation/shape_annotation_impl.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -15,7 +16,7 @@ namespace geojsonvt = mapbox::geojsonvt; ShapeAnnotationImpl::ShapeAnnotationImpl(const AnnotationID id_, const uint8_t maxZoom_) : id(id_), maxZoom(maxZoom_), - layerID("com.mapbox.annotations.shape." + util::toString(id)) { + layerID(AnnotationManager::ShapeLayerID + util::toString(id)) { } void ShapeAnnotationImpl::updateTileData(const CanonicalTileID& tileID, AnnotationTileData& data) { diff --git a/src/mbgl/renderer/renderer.cpp b/src/mbgl/renderer/renderer.cpp index e915f5e146..8953b419f7 100644 --- a/src/mbgl/renderer/renderer.cpp +++ b/src/mbgl/renderer/renderer.cpp @@ -57,6 +57,21 @@ AnnotationIDs Renderer::queryPointAnnotations(const ScreenBox& box) const { RenderedQueryOptions options; options.layerIDs = {{ AnnotationManager::PointLayerID }}; auto features = queryRenderedFeatures(box, options); + return getAnnotationIDs(features); +} + +AnnotationIDs Renderer::queryShapeAnnotations(const ScreenBox& box) const { + auto features = impl->queryShapeAnnotations({ + box.min, + {box.max.x, box.min.y}, + box.max, + {box.min.x, box.max.y}, + box.min + }); + return getAnnotationIDs(features); +} + +AnnotationIDs Renderer::getAnnotationIDs(const std::vector& features) const { std::set set; for (auto &feature : features) { assert(feature.id); diff --git a/src/mbgl/renderer/renderer_impl.cpp b/src/mbgl/renderer/renderer_impl.cpp index 6a8c18792e..7339756e52 100644 --- a/src/mbgl/renderer/renderer_impl.cpp +++ b/src/mbgl/renderer/renderer_impl.cpp @@ -629,6 +629,10 @@ std::vector Renderer::Impl::queryRenderedFeatures(const ScreenLineStrin } } + return queryRenderedFeatures(geometry, options, layers); +} + +std::vector Renderer::Impl::queryRenderedFeatures(const ScreenLineString& geometry, const RenderedQueryOptions& options, const std::vector& layers) const { std::unordered_set sourceIDs; for (const RenderLayer* layer : layers) { sourceIDs.emplace(layer->baseImpl->source); @@ -663,6 +667,21 @@ std::vector Renderer::Impl::queryRenderedFeatures(const ScreenLineStrin return result; } +std::vector Renderer::Impl::queryShapeAnnotations(const ScreenLineString& geometry) const { + std::vector shapeAnnotationLayers; + RenderedQueryOptions options; + for (const auto& layerImpl : *layerImpls) { + if (std::mismatch(layerImpl->id.begin(), layerImpl->id.end(), + AnnotationManager::ShapeLayerID.begin(), AnnotationManager::ShapeLayerID.end()).second == AnnotationManager::ShapeLayerID.end()) { + if (const RenderLayer* layer = getRenderLayer(layerImpl->id)) { + shapeAnnotationLayers.emplace_back(layer); + } + } + } + + return queryRenderedFeatures(geometry, options, shapeAnnotationLayers); +} + std::vector Renderer::Impl::querySourceFeatures(const std::string& sourceID, const SourceQueryOptions& options) const { const RenderSource* source = getRenderSource(sourceID); if (!source) return {}; diff --git a/src/mbgl/renderer/renderer_impl.hpp b/src/mbgl/renderer/renderer_impl.hpp index 30e7f70722..db2a6e7a74 100644 --- a/src/mbgl/renderer/renderer_impl.hpp +++ b/src/mbgl/renderer/renderer_impl.hpp @@ -49,6 +49,7 @@ public: std::vector queryRenderedFeatures(const ScreenLineString&, const RenderedQueryOptions&) const; std::vector querySourceFeatures(const std::string& sourceID, const SourceQueryOptions&) const; + std::vector queryShapeAnnotations(const ScreenLineString&) const; void onLowMemory(); void dumDebugLogs(); @@ -61,6 +62,8 @@ private: RenderLayer* getRenderLayer(const std::string& id); const RenderLayer* getRenderLayer(const std::string& id) const; + + std::vector queryRenderedFeatures(const ScreenLineString&, const RenderedQueryOptions&, const std::vector&) const; // GlyphManagerObserver implementation. void onGlyphsError(const FontStack&, const GlyphRange&, std::exception_ptr) override; -- cgit v1.2.1 From aa617e32c23edc770580a61d11c034d01f765645 Mon Sep 17 00:00:00 2001 From: Fabian Guerra Soto Date: Wed, 18 Oct 2017 17:24:33 -0400 Subject: [ios] Bump podspec to v3.7.0-beta.2 (#10230) --- platform/ios/Mapbox-iOS-SDK-nightly-dynamic.podspec | 2 +- platform/ios/Mapbox-iOS-SDK-symbols.podspec | 2 +- platform/ios/Mapbox-iOS-SDK.podspec | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/platform/ios/Mapbox-iOS-SDK-nightly-dynamic.podspec b/platform/ios/Mapbox-iOS-SDK-nightly-dynamic.podspec index 72b378b512..119dc3b837 100644 --- a/platform/ios/Mapbox-iOS-SDK-nightly-dynamic.podspec +++ b/platform/ios/Mapbox-iOS-SDK-nightly-dynamic.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |m| - version = '3.7.0-beta.1' + version = '3.7.0-beta.2' m.name = 'Mapbox-iOS-SDK-nightly-dynamic' m.version = "#{version}-nightly" diff --git a/platform/ios/Mapbox-iOS-SDK-symbols.podspec b/platform/ios/Mapbox-iOS-SDK-symbols.podspec index acda3f277f..a66183083e 100644 --- a/platform/ios/Mapbox-iOS-SDK-symbols.podspec +++ b/platform/ios/Mapbox-iOS-SDK-symbols.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |m| - version = '3.7.0-beta.1' + version = '3.7.0-beta.2' m.name = 'Mapbox-iOS-SDK-symbols' m.version = "#{version}-symbols" diff --git a/platform/ios/Mapbox-iOS-SDK.podspec b/platform/ios/Mapbox-iOS-SDK.podspec index b8cdbe0580..d12538fe23 100644 --- a/platform/ios/Mapbox-iOS-SDK.podspec +++ b/platform/ios/Mapbox-iOS-SDK.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |m| - version = '3.7.0-beta.1' + version = '3.7.0-beta.2' m.name = 'Mapbox-iOS-SDK' m.version = version -- cgit v1.2.1 From efcb9ee23129c6c1a863ab4908960dd46328248c Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Wed, 18 Oct 2017 17:45:26 -0400 Subject: [ios, build] Force Bitrise to install pip --- platform/ios/bitrise.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/platform/ios/bitrise.yml b/platform/ios/bitrise.yml index 8eb8b3962b..10f3e35ce8 100644 --- a/platform/ios/bitrise.yml +++ b/platform/ios/bitrise.yml @@ -108,13 +108,9 @@ workflows: #!/bin/bash set -eu -o pipefail brew install cmake + sudo easy_install pip + sudo pip install awscli - is_debug: 'yes' - - script: - title: Configure AWS-CLI - inputs: - - content: |- - #!/bin/bash - pip install awscli - script: title: Build package inputs: -- cgit v1.2.1 From ef18012409b962403666dd23159ed51afd856800 Mon Sep 17 00:00:00 2001 From: Pablo Guardiola Date: Thu, 19 Oct 2017 16:35:00 +0200 Subject: [android] release v5.2.0-beta.2 (#10240) --- platform/android/CHANGELOG.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/platform/android/CHANGELOG.md b/platform/android/CHANGELOG.md index 520a678b73..2072f0c128 100644 --- a/platform/android/CHANGELOG.md +++ b/platform/android/CHANGELOG.md @@ -6,6 +6,19 @@ Mapbox welcomes participation and contributions from everyone. If you'd like to * TBA +## 5.2.0-beta.2 - October 19, 2017 + +- Wire up MapZoomButtonController with camera change events [#10221](https://github.com/mapbox/mapbox-gl-native/pull/10221) +- Execute callbacks only when not idle [#10220](https://github.com/mapbox/mapbox-gl-native/pull/10220) +- Cleanup unused gradle plugins [#10211](https://github.com/mapbox/mapbox-gl-native/pull/10211) +- add FileSource pause/resume [#9977](https://github.com/mapbox/mapbox-gl-native/pull/9977) +- add make target for ndk-stack [#10185](https://github.com/mapbox/mapbox-gl-native/pull/10185) +- Add interpolator examples [#10067](https://github.com/mapbox/mapbox-gl-native/pull/10067) +- Add an UnsatisfiedLinkError safeguard [#10180](https://github.com/mapbox/mapbox-gl-native/pull/10180) +- Hold off handling hover events untill map has been created [#10142](https://github.com/mapbox/mapbox-gl-native/pull/10142) +- Added `MapboxMap.getCameraForGeometry()` to get a camera with zoom level and center coordinate computed to fit a shape [#10107](https://github.com/mapbox/mapbox-gl-native/pull/10107) +- Fine tune gesture zoom & rotation [#10134](https://github.com/mapbox/mapbox-gl-native/pull/10134) + ## 5.2.0-beta.1 - October 6, 2017 * Allow multiple listeners for camera events, deprecate old API [#10141](https://github.com/mapbox/mapbox-gl-native/pull/10141) @@ -59,7 +72,6 @@ Mapbox welcomes participation and contributions from everyone. If you'd like to - build release package once during ci build [#9351](https://github.com/mapbox/mapbox-gl-native/pull/9351) * Add support for ImageSource [#9110](https://github.com/mapbox/mapbox-gl-native/pull/9110) * Increased the default maximum zoom level from 20 to 22. [#9835](https://github.com/mapbox/mapbox-gl-native/pull/9835) -* Added `MapboxMap.getCameraForGeometry()` to get a camera with zoom level and center coordinate computed to fit a shape. ([#10107](https://github.com/mapbox/mapbox-gl-native/pull/10107)) ### 5.1.4 - September 25, 2017 -- cgit v1.2.1 From 6523b16c61d988960e24713d0198685801147c45 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Tue, 3 Oct 2017 15:38:08 -0700 Subject: [ios] Slightly round the corners of the puck arrow --- platform/ios/src/MGLFaux3DUserLocationAnnotationView.m | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m b/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m index 21f6aaa540..63e754d9c6 100644 --- a/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m +++ b/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m @@ -10,7 +10,7 @@ const CGFloat MGLUserLocationAnnotationDotSize = 22.0; const CGFloat MGLUserLocationAnnotationHaloSize = 115.0; const CGFloat MGLUserLocationAnnotationPuckSize = 45.0; -const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuckSize * 0.6; +const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuckSize * 0.5; const CGFloat MGLUserLocationHeadingUpdateThreshold = 0.01; @@ -181,6 +181,10 @@ const CGFloat MGLUserLocationHeadingUpdateThreshold = 0.01; _puckArrow.rasterizationScale = [UIScreen mainScreen].scale; _puckArrow.drawsAsynchronously = YES; + _puckArrow.lineJoin = @"round"; + _puckArrow.lineWidth = 1.f; + _puckArrow.strokeColor = _puckArrow.fillColor; + [self.layer addSublayer:_puckArrow]; } if (self.userLocation.location.course >= 0) -- cgit v1.2.1 From cabf5d2970148d47b9b1ecba02bfa1f36e1f94ac Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Tue, 3 Oct 2017 18:08:13 -0700 Subject: [ios] Avoid drawing view annotations across pixel boundaries --- platform/darwin/src/MGLGeometry.mm | 13 +++++++++++++ platform/darwin/src/MGLGeometry_Private.h | 2 ++ platform/ios/CHANGELOG.md | 1 + platform/ios/src/MGLFaux3DUserLocationAnnotationView.m | 12 +++++++----- platform/ios/src/MGLMapView.mm | 11 +++++------ 5 files changed, 28 insertions(+), 11 deletions(-) diff --git a/platform/darwin/src/MGLGeometry.mm b/platform/darwin/src/MGLGeometry.mm index 1540a3a741..43bf74c407 100644 --- a/platform/darwin/src/MGLGeometry.mm +++ b/platform/darwin/src/MGLGeometry.mm @@ -4,6 +4,10 @@ #import +#if !TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR +#import +#endif + /** Vertical field of view, measured in degrees, for determining the altitude of the viewpoint. @@ -57,3 +61,12 @@ double MGLZoomLevelForAltitude(CLLocationDistance altitude, CGFloat pitch, CLLoc CGFloat mapPixelWidthAtZoom = std::cos(MGLRadiansFromDegrees(latitude)) * mbgl::util::M2PI * mbgl::util::EARTH_RADIUS_M / metersPerPixel; return ::log2(mapPixelWidthAtZoom / mbgl::util::tileSize); } + +CGPoint MGLPointRounded(CGPoint point) { +#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR + CGFloat scaleFactor = [UIScreen instancesRespondToSelector:@selector(nativeScale)] ? [UIScreen mainScreen].nativeScale : [UIScreen mainScreen].scale; +#elif TARGET_OS_MAC + CGFloat scaleFactor = [NSScreen mainScreen].backingScaleFactor; +#endif + return CGPointMake(round(point.x * scaleFactor) / scaleFactor, round(point.y * scaleFactor) / scaleFactor); +} diff --git a/platform/darwin/src/MGLGeometry_Private.h b/platform/darwin/src/MGLGeometry_Private.h index 88fcf5b576..87a19989c1 100644 --- a/platform/darwin/src/MGLGeometry_Private.h +++ b/platform/darwin/src/MGLGeometry_Private.h @@ -138,3 +138,5 @@ NS_INLINE MGLRadianCoordinate2D MGLRadianCoordinateAtDistanceFacingDirection(MGL cos(distance) - sin(coordinate.latitude) * sin(otherLatitude)); return MGLRadianCoordinate2DMake(otherLatitude, otherLongitude); } + +CGPoint MGLPointRounded(CGPoint point); diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 3dd46f3e93..eca338db13 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -31,6 +31,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT * Selecting an annotation no longer sets the user tracking mode to `MGLUserTrackingModeNone`. ([#10094](https://github.com/mapbox/mapbox-gl-native/pull/10094)) * Added `-[MGLMapView cameraThatFitsShape:direction:edgePadding:]` to get a camera with zoom level and center coordinate computed to fit a shape. ([#10107](https://github.com/mapbox/mapbox-gl-native/pull/10107)) * Added support selection of shape and polyline annotations.([#9984](https://github.com/mapbox/mapbox-gl-native/pull/9984)) +* Fixed an issue where view annotations could be slightly misaligned. View annotation placement is now rounded to the nearest pixel. ([#10219](https://github.com/mapbox/mapbox-gl-native/pull/10219)) ### Other changes diff --git a/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m b/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m index 63e754d9c6..1ed3d86ad1 100644 --- a/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m +++ b/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m @@ -175,8 +175,8 @@ const CGFloat MGLUserLocationHeadingUpdateThreshold = 0.01; _puckArrow = [CAShapeLayer layer]; _puckArrow.path = [[self puckArrow] CGPath]; _puckArrow.fillColor = [self.mapView.tintColor CGColor]; - _puckArrow.bounds = CGRectMake(0, 0, MGLUserLocationAnnotationArrowSize, MGLUserLocationAnnotationArrowSize); - _puckArrow.position = CGPointMake(super.bounds.size.width / 2.0, super.bounds.size.height / 2.0); + _puckArrow.bounds = CGRectMake(0, 0, round(MGLUserLocationAnnotationArrowSize), round(MGLUserLocationAnnotationArrowSize)); + _puckArrow.position = CGPointMake(CGRectGetMidX(super.bounds), CGRectGetMidY(super.bounds)); _puckArrow.shouldRasterize = YES; _puckArrow.rasterizationScale = [UIScreen mainScreen].scale; _puckArrow.drawsAsynchronously = YES; @@ -306,7 +306,7 @@ const CGFloat MGLUserLocationHeadingUpdateThreshold = 0.01; [CATransaction setDisableActions:shouldDisableActions]; _accuracyRingLayer.bounds = CGRectMake(0, 0, accuracyRingSize, accuracyRingSize); - _accuracyRingLayer.cornerRadius = accuracyRingSize / 2; + _accuracyRingLayer.cornerRadius = accuracyRingSize / 2.0; // match the halo to the accuracy ring _haloLayer.bounds = _accuracyRingLayer.bounds; @@ -435,9 +435,11 @@ const CGFloat MGLUserLocationHeadingUpdateThreshold = 0.01; - (CALayer *)circleLayerWithSize:(CGFloat)layerSize { + layerSize = round(layerSize); + CALayer *circleLayer = [CALayer layer]; circleLayer.bounds = CGRectMake(0, 0, layerSize, layerSize); - circleLayer.position = CGPointMake(super.bounds.size.width / 2.0, super.bounds.size.height / 2.0); + circleLayer.position = CGPointMake(CGRectGetMidX(super.bounds), CGRectGetMidY(super.bounds)); circleLayer.cornerRadius = layerSize / 2.0; circleLayer.shouldRasterize = YES; circleLayer.rasterizationScale = [UIScreen mainScreen].scale; @@ -460,7 +462,7 @@ const CGFloat MGLUserLocationHeadingUpdateThreshold = 0.01; - (CGFloat)calculateAccuracyRingSize { // diameter in screen points - return self.userLocation.location.horizontalAccuracy / [self.mapView metersPerPointAtLatitude:self.userLocation.coordinate.latitude] * 2.0; + return round(self.userLocation.location.horizontalAccuracy / [self.mapView metersPerPointAtLatitude:self.userLocation.coordinate.latitude] * 2.0); } @end diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm index 4112df6e76..d0d34dbfa9 100644 --- a/platform/ios/src/MGLMapView.mm +++ b/platform/ios/src/MGLMapView.mm @@ -3379,7 +3379,7 @@ public: { annotationViewsForAnnotation[annotationValue] = annotationView; annotationView.annotation = annotation; - annotationView.center = [self convertCoordinate:annotation.coordinate toPointToView:self]; + annotationView.center = MGLPointRounded([self convertCoordinate:annotation.coordinate toPointToView:self]); [newAnnotationViews addObject:annotationView]; MGLAnnotationImage *annotationImage = self.invisibleAnnotationImage; @@ -3805,7 +3805,7 @@ public: return true; } - CGPoint calloutAnchorPoint = [self convertCoordinate:annotation.coordinate toPointToView:self]; + CGPoint calloutAnchorPoint = MGLPointRounded([self convertCoordinate:annotation.coordinate toPointToView:self]); CGRect frame = CGRectInset({ calloutAnchorPoint, CGSizeZero }, -CGRectGetWidth(annotationView.frame) / 2, -CGRectGetHeight(annotationView.frame) / 2); annotationRect = UIEdgeInsetsInsetRect(frame, annotationView.alignmentRectInsets); } @@ -4143,7 +4143,7 @@ public: /// image centered at the given coordinate. - (CGRect)frameOfImage:(UIImage *)image centeredAtCoordinate:(CLLocationCoordinate2D)coordinate { - CGPoint calloutAnchorPoint = [self convertCoordinate:coordinate toPointToView:self]; + CGPoint calloutAnchorPoint = MGLPointRounded([self convertCoordinate:coordinate toPointToView:self]); CGRect frame = CGRectInset({ calloutAnchorPoint, CGSizeZero }, -image.size.width / 2, -image.size.height / 2); return UIEdgeInsetsInsetRect(frame, image.alignmentRectInsets); } @@ -4582,7 +4582,6 @@ public: if (_showsUserHeadingIndicator) { self.showsUserLocation = YES; - } [self validateUserHeadingUpdating]; } @@ -5288,7 +5287,7 @@ public: if (annotationView) { - annotationView.center = [self convertCoordinate:annotationContext.annotation.coordinate toPointToView:self]; + annotationView.center = MGLPointRounded([self convertCoordinate:annotationContext.annotation.coordinate toPointToView:self]); } } @@ -5406,7 +5405,7 @@ public: } else { - userPoint = [self convertCoordinate:self.userLocation.coordinate toPointToView:self]; + userPoint = MGLPointRounded([self convertCoordinate:self.userLocation.coordinate toPointToView:self]); } if ( ! annotationView.superview) -- cgit v1.2.1 From 817e5c109f5e48e0ce05b05058498cfa6be8e2da Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Tue, 17 Oct 2017 20:56:22 -0400 Subject: [ios] Fix scale bar label alignment --- platform/ios/src/MGLScaleBar.mm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/platform/ios/src/MGLScaleBar.mm b/platform/ios/src/MGLScaleBar.mm index 410aa7d57e..f63c39009f 100644 --- a/platform/ios/src/MGLScaleBar.mm +++ b/platform/ios/src/MGLScaleBar.mm @@ -334,7 +334,7 @@ static const CGFloat MGLFeetPerMeter = 3.28084; } - (void)layoutBars { - CGFloat barWidth = (CGRectGetWidth(self.bounds) - self.borderWidth * 2.0f) / self.bars.count; + CGFloat barWidth = round((CGRectGetWidth(self.bounds) - self.borderWidth * 2.0f) / self.bars.count); NSUInteger i = 0; for (UIView *bar in self.bars) { @@ -357,11 +357,11 @@ static const CGFloat MGLFeetPerMeter = 3.28084; } - (void)layoutLabels { - CGFloat barWidth = self.bounds.size.width / self.bars.count; + CGFloat barWidth = round(self.bounds.size.width / self.bars.count); BOOL RTL = [self usesRightToLeftLayout]; NSUInteger i = RTL ? self.bars.count : 0; for (MGLScaleBarLabel *label in self.labels) { - CGFloat xPosition = barWidth * i - CGRectGetMidX(label.bounds) + self.borderWidth; + CGFloat xPosition = round(barWidth * i - CGRectGetMidX(label.bounds) + self.borderWidth); label.frame = CGRectMake(xPosition, 0, CGRectGetWidth(label.bounds), CGRectGetHeight(label.bounds)); -- cgit v1.2.1 From 61ea2f43e4ea0233283f545982d5179b8397cde6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Fri, 13 Oct 2017 16:41:23 -0700 Subject: [core] limit annotation tile source to z16 --- src/mbgl/annotation/render_annotation_source.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mbgl/annotation/render_annotation_source.cpp b/src/mbgl/annotation/render_annotation_source.cpp index 34fb576727..ba80be0da0 100644 --- a/src/mbgl/annotation/render_annotation_source.cpp +++ b/src/mbgl/annotation/render_annotation_source.cpp @@ -38,7 +38,9 @@ void RenderAnnotationSource::update(Immutable baseImpl_, parameters, SourceType::Annotations, util::tileSize, - { 0, util::DEFAULT_MAX_ZOOM }, + // Zoom level 16 is typically sufficient for annotations. + // See https://github.com/mapbox/mapbox-gl-native/issues/10197 + { 0, 16 }, [&] (const OverscaledTileID& tileID) { return std::make_unique(tileID, parameters); }); -- cgit v1.2.1 From 2857090cf6c002e876be2b8c2826db99e7c40183 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Thu, 19 Oct 2017 11:40:30 -0400 Subject: [ios] Fix iOS 8 incompatibility in scale bar RTL check --- platform/ios/CHANGELOG.md | 1 + platform/ios/src/MGLScaleBar.mm | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index eca338db13..5ca8ed929e 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -39,6 +39,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT * Fixed an issue that could cause a crash when using `-[MGLMapView flyToCamera:completionHandler:]` and related methods with zoom levels at or near the maximum value. ([#9381](https://github.com/mapbox/mapbox-gl-native/pull/9381)) * Added `-[MGLMapView showAttribution:]` to allow custom attribution buttons to show the default attribution interface. ([#10085](https://github.com/mapbox/mapbox-gl-native/pull/10085)) * Fixed a conflict between multiple copies of SMCalloutView in a project. ([#10183](https://github.com/mapbox/mapbox-gl-native/pull/10183)) +* Fixed a crash when enabling the scale bar in iOS 8. ([#10241](https://github.com/mapbox/mapbox-gl-native/pull/10241)) ## 3.6.4 - September 25, 2017 diff --git a/platform/ios/src/MGLScaleBar.mm b/platform/ios/src/MGLScaleBar.mm index f63c39009f..1cf1caf0fe 100644 --- a/platform/ios/src/MGLScaleBar.mm +++ b/platform/ios/src/MGLScaleBar.mm @@ -175,10 +175,15 @@ static const CGFloat MGLFeetPerMeter = 3.28084; return [self usesMetricSystem] ? self.metersPerPoint : self.metersPerPoint * MGLFeetPerMeter; } -#pragma mark - Convenient methods +#pragma mark - Convenience methods - (BOOL)usesRightToLeftLayout { - return [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:self.superview.semanticContentAttribute] == UIUserInterfaceLayoutDirectionRightToLeft; + // semanticContentAttribute is iOS 9+ + if ([self.superview respondsToSelector:@selector(semanticContentAttribute)]) { + return [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:self.superview.semanticContentAttribute] == UIUserInterfaceLayoutDirectionRightToLeft; + } else { + return UIApplication.sharedApplication.userInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft; + } } - (BOOL)usesMetricSystem { @@ -244,7 +249,7 @@ static const CGFloat MGLFeetPerMeter = 3.28084; CGFloat alpha = maximumDistance > allowedDistance ? .0f : 1.0f; - if(self.alpha != alpha) { + if (self.alpha != alpha) { [UIView animateWithDuration:.2f delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ self.alpha = alpha; } completion:nil]; -- cgit v1.2.1 From 00e44e9cb09fbe1b8444a9f54592dee82b4b8469 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Thu, 19 Oct 2017 11:46:32 -0400 Subject: [ios] Fix iOS 8 incompatibility in iosapp hud label font --- platform/ios/app/MBXViewController.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m index 5bccb64608..2c3d26b489 100644 --- a/platform/ios/app/MBXViewController.m +++ b/platform/ios/app/MBXViewController.m @@ -165,7 +165,9 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { self.mapView.scaleBar.hidden = NO; self.mapView.showsUserHeadingIndicator = YES; self.hudLabel.hidden = YES; - self.hudLabel.titleLabel.font = [UIFont monospacedDigitSystemFontOfSize:10 weight:UIFontWeightRegular]; + if ([UIFont respondsToSelector:@selector(monospacedDigitSystemFontOfSize:weight:)]) { + self.hudLabel.titleLabel.font = [UIFont monospacedDigitSystemFontOfSize:10 weight:UIFontWeightRegular]; + } if ([MGLAccountManager accessToken].length) { -- cgit v1.2.1 From 6dfe4ffaf1916f59ac20be31cc0fb9dc3b03c62f Mon Sep 17 00:00:00 2001 From: Bruno de Oliveira Abinader Date: Thu, 19 Oct 2017 11:11:25 -0700 Subject: [build] Bump earcut to 0.12.4 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a86bbd192c..7f88252754 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,7 +51,7 @@ mason_use(boost VERSION 1.62.0 HEADER_ONLY) mason_use(geojsonvt VERSION 6.3.0 HEADER_ONLY) mason_use(supercluster VERSION 0.2.2 HEADER_ONLY) mason_use(kdbush VERSION 0.1.1-1 HEADER_ONLY) -mason_use(earcut VERSION 0.12.3 HEADER_ONLY) +mason_use(earcut VERSION 0.12.4 HEADER_ONLY) mason_use(protozero VERSION 1.5.2 HEADER_ONLY) mason_use(pixelmatch VERSION 0.10.0 HEADER_ONLY) mason_use(geojson VERSION 0.4.2 HEADER_ONLY) -- cgit v1.2.1 From af904467582e79d8dbf2a405776a81131ff1fa11 Mon Sep 17 00:00:00 2001 From: Tobrun Date: Mon, 23 Oct 2017 06:21:26 +0200 Subject: [android] - null check mapboxMap with onStop invocation --- .../src/main/java/com/mapbox/mapboxsdk/maps/MapView.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java index 309090ed3f..f4198cdde7 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 @@ -369,7 +369,10 @@ public class MapView extends FrameLayout { */ @UiThread public void onStop() { - mapboxMap.onStop(); + if (mapboxMap != null) { + // map was destroyed before it was started + mapboxMap.onStop(); + } ConnectivityReceiver.instance(getContext()).deactivate(); FileSource.getInstance(getContext()).deactivate(); } -- cgit v1.2.1 From ba8342a12e332a9caf2a21bb022bbc9f2b2214b3 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 24 Oct 2017 11:46:45 -0700 Subject: [core] Add layer before notifying observer --- src/mbgl/style/style_impl.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mbgl/style/style_impl.cpp b/src/mbgl/style/style_impl.cpp index 37907d3f60..3214c6316e 100644 --- a/src/mbgl/style/style_impl.cpp +++ b/src/mbgl/style/style_impl.cpp @@ -204,9 +204,10 @@ Layer* Style::Impl::addLayer(std::unique_ptr layer, optional } layer->setObserver(this); + Layer* result = layers.add(std::move(layer), before); observer->onUpdate(); - return layers.add(std::move(layer), before); + return result; } std::unique_ptr Style::Impl::removeLayer(const std::string& id) { -- cgit v1.2.1 From 8bc7e4957d85816f3e013e6e07a91a48929080f3 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Thu, 19 Oct 2017 18:42:20 -0400 Subject: [ios, build] Add CircleCI iOS builds --- Makefile | 8 +++++++ circle.yml | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7bcb281985..87bd899400 100644 --- a/Makefile +++ b/Makefile @@ -222,6 +222,14 @@ iproj: $(IOS_PROJ_PATH) ios-test: $(IOS_PROJ_PATH) set -o pipefail && $(IOS_XCODEBUILD_SIM) -scheme 'CI' 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: ipackage ipackage: $(IOS_PROJ_PATH) FORMAT=$(FORMAT) BUILD_DEVICE=$(BUILD_DEVICE) SYMBOLS=$(SYMBOLS) \ diff --git a/circle.yml b/circle.yml index 39b00e8ed0..785ffd10d8 100644 --- a/circle.yml +++ b/circle.yml @@ -30,6 +30,9 @@ workflows: - linux-gcc5-debug-coverage - linux-gcc5-release-qt4 - linux-gcc5-release-qt5 + - ios-debug + #- ios-sanitize-address + - ios-sanitize-thread step-library: - &generate-cache-key @@ -47,7 +50,7 @@ step-library: - &save-cache save_cache: key: 'v3/{{ .Environment.CIRCLE_JOB }}/{{ arch }}/{{ .Branch }}/{{ checksum ".circle-week" }}' - paths: [ "node_modules", "/root/.ccache", "mason_packages/.binaries" ] + paths: [ "node_modules", "/root/.ccache", "~/.ccache", "mason_packages/.binaries" ] - &reset-ccache-stats @@ -97,6 +100,24 @@ step-library: run: name: Build qt-test command: make qt-test + - &build-ios-test + run: + name: Build ios-test + command: make ios-test + + + - &check-public-symbols + run: + name: Check public symbols + command: make check-public-symbols + + + - &install-ios-dependencies + run: + name: Install dependencies + command: | + brew install cmake + brew install ccache - &run-node-tests @@ -589,3 +610,59 @@ jobs: command: | xvfb-run --server-args="-screen 0 1024x768x24" \ scripts/valgrind.sh build/qt-linux-x86_64/Release/mbgl-test --gtest_filter=-*.Load --gtest_filter=-Memory.Vector + +# ------------------------------------------------------------------------------ + ios-debug: + macos: + xcode: "9.0" + environment: + BUILDTYPE: Debug + HOMEBREW_NO_AUTO_UPDATE: 1 + steps: + - checkout + - *install-ios-dependencies + - *generate-cache-key + - *restore-cache + - *reset-ccache-stats + - *build-ios-test + - *check-public-symbols + - *show-ccache-stats + - *save-cache + +# ------------------------------------------------------------------------------ + ios-sanitize-address: + macos: + xcode: "9.0" + environment: + BUILDTYPE: Debug + HOMEBREW_NO_AUTO_UPDATE: 1 + steps: + - checkout + - *install-ios-dependencies + - *generate-cache-key + - *restore-cache + - *reset-ccache-stats + - run: + name: Build and run SDK unit tests with address sanitizer + command: make ios-sanitize-address + - *show-ccache-stats + - *save-cache + +# ------------------------------------------------------------------------------ + ios-sanitize-thread: + macos: + xcode: "9.0" + environment: + BUILDTYPE: Debug + HOMEBREW_NO_AUTO_UPDATE: 1 + steps: + - checkout + - *install-ios-dependencies + - *generate-cache-key + - *restore-cache + - *reset-ccache-stats + - run: + name: Build and run SDK unit tests with thread sanitizer + command: make ios-sanitize-thread + - *show-ccache-stats + - *save-cache -- cgit v1.2.1 From 018bc7c9f9d905c6ae3e8309286c481804b88687 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Tue, 24 Oct 2017 18:06:45 -0400 Subject: [macos, build] Add CircleCI macOS builds --- circle.yml | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/circle.yml b/circle.yml index 785ffd10d8..e651323428 100644 --- a/circle.yml +++ b/circle.yml @@ -33,6 +33,7 @@ workflows: - ios-debug #- ios-sanitize-address - ios-sanitize-thread + - macos-debug step-library: - &generate-cache-key @@ -104,6 +105,10 @@ step-library: run: name: Build ios-test command: make ios-test + - &build-macos-test + run: + name: Build and run macOS tests + command: make run-test - &check-public-symbols @@ -112,7 +117,7 @@ step-library: command: make check-public-symbols - - &install-ios-dependencies + - &install-macos-dependencies run: name: Install dependencies command: | @@ -620,7 +625,7 @@ jobs: HOMEBREW_NO_AUTO_UPDATE: 1 steps: - checkout - - *install-ios-dependencies + - *install-macos-dependencies - *generate-cache-key - *restore-cache - *reset-ccache-stats @@ -638,7 +643,7 @@ jobs: HOMEBREW_NO_AUTO_UPDATE: 1 steps: - checkout - - *install-ios-dependencies + - *install-macos-dependencies - *generate-cache-key - *restore-cache - *reset-ccache-stats @@ -657,7 +662,7 @@ jobs: HOMEBREW_NO_AUTO_UPDATE: 1 steps: - checkout - - *install-ios-dependencies + - *install-macos-dependencies - *generate-cache-key - *restore-cache - *reset-ccache-stats @@ -666,3 +671,24 @@ jobs: command: make ios-sanitize-thread - *show-ccache-stats - *save-cache + +# ------------------------------------------------------------------------------ + macos-debug: + macos: + xcode: "9.0" + environment: + BUILDTYPE: Debug + HOMEBREW_NO_AUTO_UPDATE: 1 + steps: + - checkout + - *install-macos-dependencies + - *generate-cache-key + - *restore-cache + - *reset-ccache-stats + - *build-macos-test + - *check-public-symbols + - *show-ccache-stats + - *save-cache + - store_artifacts: + path: test/fixtures + destination: test/fixtures -- cgit v1.2.1 From 1c78d14a2007720992305ac5170a0deced14e9d3 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Tue, 24 Oct 2017 19:03:10 -0400 Subject: [build] Disable iOS and macOS builds on Bitrise --- platform/ios/bitrise.yml | 43 ++----------------------------------------- platform/macos/bitrise.yml | 34 ++-------------------------------- 2 files changed, 4 insertions(+), 73 deletions(-) diff --git a/platform/ios/bitrise.yml b/platform/ios/bitrise.yml index 10f3e35ce8..24bd054dbc 100644 --- a/platform/ios/bitrise.yml +++ b/platform/ios/bitrise.yml @@ -13,48 +13,9 @@ workflows: primary: steps: - script: - title: Install Dependencies - inputs: - - content: |- - #!/bin/bash - set -eu -o pipefail - brew install cmake - - is_debug: 'yes' - - script: - title: Generate Workspace + title: Skip Workflow inputs: - - content: |- - #!/bin/bash - set -eu -o pipefail - export BUILDTYPE=Debug - make iproj - - is_debug: 'yes' - - xcode-test: - title: Run SDK Unit Tests - inputs: - - project_path: platform/ios/ios.xcworkspace - - scheme: CI - - deploy-to-bitrise-io: - title: Deploy to Bitrise.io - inputs: - - notify_user_groups: none - - slack: - title: Post to Slack - inputs: - - webhook_url: "$SLACK_HOOK_URL" - - channel: "#gl-bots" - - from_username: 'Bitrise iOS' - - from_username_on_error: 'Bitrise iOS' - - message: '<${BITRISE_BUILD_URL}|Build #${BITRISE_BUILD_NUMBER}> - for - by ${GIT_CLONE_COMMIT_COMMITER_NAME} - passed' - - message_on_error: '<${BITRISE_BUILD_URL}|Build #${BITRISE_BUILD_NUMBER}> - for - by ${GIT_CLONE_COMMIT_COMMITER_NAME} - failed' - - icon_url: https://bitrise-public-content-production.s3.amazonaws.com/slack/bitrise-slack-icon-128.png - - icon_url_on_error: https://bitrise-public-content-production.s3.amazonaws.com/slack/bitrise-slack-error-icon-128.png + - content: echo "This workflow is obsolete — see CircleCi." nightly-release: steps: - script: diff --git a/platform/macos/bitrise.yml b/platform/macos/bitrise.yml index 1f2495dab2..057193967e 100644 --- a/platform/macos/bitrise.yml +++ b/platform/macos/bitrise.yml @@ -12,39 +12,9 @@ workflows: primary: steps: - script: - title: Build - inputs: - - content: |- - #!/bin/bash - set -eu -o pipefail - brew install cmake - gem install xcpretty --no-rdoc --no-ri - export BUILDTYPE=Debug - export XCPRETTY="| tee ${BITRISE_DEPLOY_DIR}/raw-xcodebuild-output.txt | xcpretty --color --report html --output ${BITRISE_DEPLOY_DIR}/xcode-test-results.html" - make run-test - - deploy-to-bitrise-io: - title: Deploy to Bitrise.io - inputs: - - deploy_path: "test/fixtures" - - notify_user_groups: none - - is_compress: 'true' - - slack: - title: Post to Slack + title: Skip Workflow inputs: - - webhook_url: "$SLACK_HOOK_URL" - - channel: "#gl-bots" - - from_username: 'Bitrise macOS' - - from_username_on_error: 'Bitrise macOS' - - message: '<${BITRISE_BUILD_URL}|Build #${BITRISE_BUILD_NUMBER}> - for - by ${GIT_CLONE_COMMIT_COMMITER_NAME} - passed' - - message_on_error: '<${BITRISE_BUILD_URL}|Build #${BITRISE_BUILD_NUMBER}> - for - by ${GIT_CLONE_COMMIT_COMMITER_NAME} - failed' - - icon_url: https://bitrise-public-content-production.s3.amazonaws.com/slack/bitrise-slack-icon-128.png - - icon_url_on_error: https://bitrise-public-content-production.s3.amazonaws.com/slack/bitrise-slack-error-icon-128.png + - content: echo "This workflow is obsolete — see CircleCi." nightly-release: steps: - script: -- cgit v1.2.1 From c9d7951e0d0ab85614ab296573e583ed44dbe7c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Tue, 17 Oct 2017 09:10:50 -0700 Subject: [core] fix crash when querying GeoJSON tiles --- src/mbgl/tile/geojson_tile.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/mbgl/tile/geojson_tile.cpp b/src/mbgl/tile/geojson_tile.cpp index d648d2e5ff..bbec899950 100644 --- a/src/mbgl/tile/geojson_tile.cpp +++ b/src/mbgl/tile/geojson_tile.cpp @@ -23,19 +23,19 @@ void GeoJSONTile::querySourceFeatures( const SourceQueryOptions& options) { // Ignore the sourceLayer, there is only one - auto layer = getData()->getLayer({}); - - if (layer) { - auto featureCount = layer->featureCount(); - for (std::size_t i = 0; i < featureCount; i++) { - auto feature = layer->getFeature(i); - - // Apply filter, if any - if (options.filter && !(*options.filter)(*feature)) { - continue; + if (auto tileData = getData()) { + if (auto layer = tileData->getLayer({})) { + auto featureCount = layer->featureCount(); + for (std::size_t i = 0; i < featureCount; i++) { + auto feature = layer->getFeature(i); + + // Apply filter, if any + if (options.filter && !(*options.filter)(*feature)) { + continue; + } + + result.push_back(convertFeature(*feature, id.canonical)); } - - result.push_back(convertFeature(*feature, id.canonical)); } } } -- cgit v1.2.1 From 91eb595463cf42c85edc7fe5b47023f8d7b75828 Mon Sep 17 00:00:00 2001 From: Jesse Bounds Date: Tue, 24 Oct 2017 17:40:47 -0700 Subject: [ios] Move scale bar content size invalidation to map view --- platform/ios/src/MGLMapView.mm | 8 ++++++++ platform/ios/src/MGLScaleBar.mm | 3 --- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm index d0d34dbfa9..740555840f 100644 --- a/platform/ios/src/MGLMapView.mm +++ b/platform/ios/src/MGLMapView.mm @@ -1015,6 +1015,14 @@ public: // This gets called when the view dimension changes, e.g. because the device is being rotated. - (void)layoutSubviews { + // Calling this here instead of in the scale bar itself because if this is done in the + // scale bar instance, it triggers a call to this this `layoutSubviews` method that + // calls `_mbglMap->setSize()` just below that triggers rendering update which triggers + // another scale bar update which causes a rendering update loop and a major performace + // degradation. The only time the scale bar's intrinsic content size _must_ invalidated + // is here as a reaction to this object's view dimension changes. + [self.scaleBar invalidateIntrinsicContentSize]; + [super layoutSubviews]; [self adjustContentInset]; diff --git a/platform/ios/src/MGLScaleBar.mm b/platform/ios/src/MGLScaleBar.mm index 1cf1caf0fe..139dffdfab 100644 --- a/platform/ios/src/MGLScaleBar.mm +++ b/platform/ios/src/MGLScaleBar.mm @@ -230,9 +230,6 @@ static const CGFloat MGLFeetPerMeter = 3.28084; CGRectGetMinY(self.frame), size.width, size.height); - - [self invalidateIntrinsicContentSize]; - [self setNeedsLayout]; } - (void)updateVisibility { -- cgit v1.2.1 From bf9c1fb1b3f6121d4c939757ee043a70bef560e4 Mon Sep 17 00:00:00 2001 From: Ivo van Dongen Date: Fri, 20 Oct 2017 12:44:02 -0700 Subject: [core] blacklist VAO usage on adreno 3xx - Causes crashes on usage of glBuffer(Sub)Data --- src/mbgl/gl/context.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/mbgl/gl/context.cpp b/src/mbgl/gl/context.cpp index d0c538efb0..22b446e6d0 100644 --- a/src/mbgl/gl/context.cpp +++ b/src/mbgl/gl/context.cpp @@ -248,7 +248,14 @@ UniqueTexture Context::createTexture() { } bool Context::supportsVertexArrays() const { - return vertexArray && + static bool blacklisted = []() { + // Blacklist Adreno 3xx as it crashes on glBuffer(Sub)Data + const std::string renderer = reinterpret_cast(glGetString(GL_RENDERER)); + return renderer.find("Adreno (TM) 3") != std::string::npos; + }(); + + return !blacklisted && + vertexArray && vertexArray->genVertexArrays && vertexArray->bindVertexArray && vertexArray->deleteVertexArrays; -- cgit v1.2.1 From bd06e2f6fa3ca3367b831caeb52ecb6da3f199fd Mon Sep 17 00:00:00 2001 From: Antonio Zugaldia Date: Thu, 26 Oct 2017 12:57:46 -0400 Subject: [android] - lower amount of external dependencies (#10268) --- platform/android/MapboxGLAndroidSDK/build.gradle | 5 ++- .../mapboxsdk/maps/AttributionDialogManager.java | 6 +-- .../mapbox/mapboxsdk/maps/widgets/CompassView.java | 4 +- .../com/mapbox/mapboxsdk/utils/ColorUtils.java | 48 ++++++++++++++++------ .../src/main/res/values/styles.xml | 6 --- .../android/MapboxGLAndroidSDKTestApp/build.gradle | 1 + .../activity/camera/CameraPositionActivity.java | 3 +- platform/android/dependencies.gradle | 2 +- 8 files changed, 47 insertions(+), 28 deletions(-) delete mode 100644 platform/android/MapboxGLAndroidSDK/src/main/res/values/styles.xml diff --git a/platform/android/MapboxGLAndroidSDK/build.gradle b/platform/android/MapboxGLAndroidSDK/build.gradle index c96587fce6..19587d0078 100644 --- a/platform/android/MapboxGLAndroidSDK/build.gradle +++ b/platform/android/MapboxGLAndroidSDK/build.gradle @@ -2,12 +2,12 @@ apply plugin: 'com.android.library' dependencies { compile rootProject.ext.dep.supportAnnotations - compile rootProject.ext.dep.supportV4 - compile rootProject.ext.dep.supportDesign + compile rootProject.ext.dep.supportFragmentV4 compile rootProject.ext.dep.timber compile rootProject.ext.dep.okhttp3 compile(rootProject.ext.dep.lost) { exclude group: 'com.google.guava' + exclude group: 'com.android.support' } testCompile rootProject.ext.dep.junit testCompile rootProject.ext.dep.mockito @@ -20,6 +20,7 @@ dependencies { // Mapbox Android Services (Telemetry support) compile(rootProject.ext.dep.mapboxAndroidTelemetry) { transitive = true + exclude group: 'com.android.support' } } 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 5113a0cc73..9ccff387f5 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 @@ -1,12 +1,12 @@ package com.mapbox.mapboxsdk.maps; +import android.app.AlertDialog; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; import android.support.annotation.NonNull; -import android.support.v7.app.AlertDialog; import android.text.Html; import android.text.SpannableStringBuilder; import android.text.TextUtils; @@ -56,7 +56,7 @@ class AttributionDialogManager implements View.OnClickListener, DialogInterface. private void showAttributionDialog() { attributionKeys = attributionMap.keySet().toArray(new String[attributionMap.size()]); - AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.mapbox_AlertDialogStyle); + AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(R.string.mapbox_attributionsDialogTitle); builder.setAdapter(new ArrayAdapter<>(context, R.layout.mapbox_attribution_list_item, attributionKeys), this); builder.show(); @@ -77,7 +77,7 @@ class AttributionDialogManager implements View.OnClickListener, DialogInterface. } private void showTelemetryDialog() { - AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.mapbox_AlertDialogStyle); + AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(R.string.mapbox_attributionTelemetryTitle); builder.setMessage(R.string.mapbox_attributionTelemetryMessage); builder.setPositiveButton(R.string.mapbox_attributionTelemetryPositive, new DialogInterface.OnClickListener() { diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java index 45f72af1c5..1e604c9bef 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java @@ -6,10 +6,10 @@ import android.support.annotation.NonNull; import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewPropertyAnimatorCompat; import android.support.v4.view.ViewPropertyAnimatorListenerAdapter; -import android.support.v7.widget.AppCompatImageView; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; +import android.widget.ImageView; import com.mapbox.mapboxsdk.maps.MapboxMap; @@ -22,7 +22,7 @@ import com.mapbox.mapboxsdk.maps.MapboxMap; * use {@link com.mapbox.mapboxsdk.maps.UiSettings}. *

*/ -public final class CompassView extends AppCompatImageView implements Runnable { +public final class CompassView extends ImageView implements Runnable { public static final long TIME_WAIT_IDLE = 500; public static final long TIME_MAP_NORTH_ANIMATION = 150; diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/ColorUtils.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/ColorUtils.java index 14b18b00dc..1c0e439afc 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/ColorUtils.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/utils/ColorUtils.java @@ -5,6 +5,7 @@ import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.Color; import android.graphics.drawable.Drawable; +import android.os.Build; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; import android.support.v4.graphics.drawable.DrawableCompat; @@ -30,10 +31,15 @@ public class ColorUtils { */ @ColorInt public static int getPrimaryColor(@NonNull Context context) { - TypedValue typedValue = new TypedValue(); - Resources.Theme theme = context.getTheme(); - theme.resolveAttribute(R.attr.colorPrimary, typedValue, true); - return typedValue.data; + try { + TypedValue typedValue = new TypedValue(); + Resources.Theme theme = context.getTheme(); + int id = context.getResources().getIdentifier("colorPrimary", "attrs", context.getPackageName()); + theme.resolveAttribute(id, typedValue, true); + return typedValue.data; + } catch (Exception exception) { + return getColorCompat(context, R.color.mapbox_blue); + } } /** @@ -44,10 +50,15 @@ public class ColorUtils { */ @ColorInt public static int getPrimaryDarkColor(@NonNull Context context) { - TypedValue typedValue = new TypedValue(); - Resources.Theme theme = context.getTheme(); - theme.resolveAttribute(R.attr.colorPrimaryDark, typedValue, true); - return typedValue.data; + try { + TypedValue typedValue = new TypedValue(); + Resources.Theme theme = context.getTheme(); + int id = context.getResources().getIdentifier("colorPrimaryDark", "attrs", context.getPackageName()); + theme.resolveAttribute(id, typedValue, true); + return typedValue.data; + } catch (Exception exception) { + return getColorCompat(context, R.color.mapbox_blue); + } } /** @@ -58,10 +69,15 @@ public class ColorUtils { */ @ColorInt public static int getAccentColor(@NonNull Context context) { - TypedValue typedValue = new TypedValue(); - Resources.Theme theme = context.getTheme(); - theme.resolveAttribute(R.attr.colorAccent, typedValue, true); - return typedValue.data; + try { + TypedValue typedValue = new TypedValue(); + Resources.Theme theme = context.getTheme(); + int id = context.getResources().getIdentifier("colorAccent", "attrs", context.getPackageName()); + theme.resolveAttribute(id, typedValue, true); + return typedValue.data; + } catch (Exception exception) { + return getColorCompat(context, R.color.mapbox_gray); + } } /** @@ -122,4 +138,12 @@ public class ColorUtils { throw new ConversionException("Not a valid rgb/rgba value"); } } + + private static int getColorCompat(Context context, int id) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return context.getResources().getColor(id, context.getTheme()); + } else { + return context.getResources().getColor(id); + } + } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values/styles.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values/styles.xml deleted file mode 100644 index eba1fb3a7d..0000000000 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/values/styles.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - -