summaryrefslogtreecommitdiff
path: root/platform
diff options
context:
space:
mode:
Diffstat (limited to 'platform')
-rw-r--r--platform/android/CHANGELOG.md37
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/Mapbox.java12
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraPosition.java23
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java38
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMapOptions.java41
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMap.java6
-rwxr-xr-xplatform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java25
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/storage/FileSource.java9
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml1
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml1
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/camera/CameraPositionTest.java11
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapboxMapOptionsTest.java10
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapboxMapTest.kt12
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/maps/NativeMapViewTest.kt19
-rw-r--r--platform/android/core-files.json14
-rwxr-xr-xplatform/android/scripts/generate-style-code.js6
-rw-r--r--platform/android/src/async_task.cpp9
-rw-r--r--platform/android/src/file_source.cpp52
-rw-r--r--platform/android/src/file_source.hpp2
-rw-r--r--platform/android/src/geojson/feature.cpp25
-rw-r--r--platform/android/src/geojson/feature.hpp3
-rw-r--r--platform/android/src/i18n/collator.cpp (renamed from platform/android/src/text/collator.cpp)23
-rw-r--r--platform/android/src/i18n/collator_jni.hpp (renamed from platform/android/src/text/collator_jni.hpp)2
-rw-r--r--platform/android/src/i18n/number_format.cpp (renamed from platform/android/src/text/format_number.cpp)5
-rw-r--r--platform/android/src/i18n/number_format_jni.hpp (renamed from platform/android/src/text/format_number_jni.hpp)0
-rw-r--r--[-rwxr-xr-x]platform/android/src/jni_native.cpp12
-rw-r--r--platform/android/src/map_renderer.cpp2
-rw-r--r--platform/android/src/map_renderer.hpp4
-rw-r--r--platform/android/src/map_renderer_runnable.cpp7
-rw-r--r--platform/android/src/map_renderer_runnable.hpp4
-rw-r--r--platform/android/src/mapbox.cpp23
-rw-r--r--platform/android/src/mapbox.hpp19
-rw-r--r--[-rwxr-xr-x]platform/android/src/native_map_view.cpp201
-rwxr-xr-xplatform/android/src/native_map_view.hpp4
-rw-r--r--platform/android/src/style/sources/geojson_source.cpp82
-rw-r--r--platform/android/src/style/sources/geojson_source.hpp20
-rw-r--r--platform/android/src/test/render_test_collator.cpp40
-rw-r--r--platform/android/src/test/render_test_number_format.cpp15
-rw-r--r--platform/android/src/test/render_test_runner.cpp57
-rw-r--r--platform/android/src/timer.cpp12
-rw-r--r--platform/android/src/unaccent.cpp18
-rwxr-xr-xplatform/darwin/scripts/generate-style-code.js9
-rw-r--r--platform/darwin/src/MGLComputedShapeSource.mm4
-rw-r--r--platform/darwin/src/MGLFeature.mm18
-rw-r--r--platform/darwin/src/MGLFeature_Private.h15
-rw-r--r--platform/darwin/src/MGLImageSource.mm7
-rw-r--r--platform/darwin/src/MGLMapSnapshotter.h25
-rw-r--r--platform/darwin/src/MGLMapSnapshotter.mm66
-rw-r--r--platform/darwin/src/MGLMapSnapshotter_Private.h14
-rw-r--r--platform/darwin/src/MGLRasterTileSource.mm7
-rw-r--r--platform/darwin/src/MGLSDKMetricsManager.m28
-rw-r--r--platform/darwin/src/MGLShapeSource.h32
-rw-r--r--platform/darwin/src/MGLShapeSource.mm61
-rw-r--r--platform/darwin/src/MGLStyleValue.mm13
-rw-r--r--platform/darwin/src/MGLStyleValue_Private.h5
-rw-r--r--platform/darwin/src/MGLVectorTileSource.mm7
-rw-r--r--platform/darwin/src/NSExpression+MGLAdditions.h7
-rw-r--r--platform/darwin/src/NSExpression+MGLAdditions.mm43
-rw-r--r--platform/darwin/src/collator.mm9
-rw-r--r--platform/darwin/src/number_format.mm36
-rw-r--r--platform/darwin/src/string_nsstring.mm31
-rw-r--r--platform/darwin/test/MGLDocumentationExampleTests.swift34
-rw-r--r--platform/darwin/test/MGLExpressionTests.mm55
-rw-r--r--platform/darwin/test/MGLShapeSourceTests.mm5
-rw-r--r--platform/default/include/mbgl/gfx/headless_backend.hpp10
-rw-r--r--platform/default/include/mbgl/gfx/headless_frontend.hpp16
-rw-r--r--platform/default/include/mbgl/gl/headless_backend.hpp7
-rw-r--r--platform/default/include/mbgl/storage/offline_database.hpp1
-rw-r--r--platform/default/include/mbgl/storage/offline_schema.hpp2
-rwxr-xr-x[-rw-r--r--]platform/default/include/mbgl/storage/offline_schema.js8
-rw-r--r--platform/default/include/mbgl/storage/offline_schema.sql164
-rw-r--r--platform/default/src/mbgl/gfx/headless_frontend.cpp71
-rw-r--r--platform/default/src/mbgl/gl/headless_backend.cpp32
-rw-r--r--platform/default/src/mbgl/i18n/collator.cpp (renamed from platform/default/src/mbgl/text/collator.cpp)64
-rw-r--r--platform/default/src/mbgl/i18n/format_number.cpp (renamed from platform/default/src/mbgl/util/format_number.cpp)2
-rw-r--r--platform/default/src/mbgl/i18n/number_format.cpp41
-rw-r--r--platform/default/src/mbgl/map/map_snapshotter.cpp4
-rw-r--r--platform/default/src/mbgl/render-test/main.cpp5
-rw-r--r--platform/default/src/mbgl/storage/offline_database.cpp20
-rw-r--r--platform/default/src/mbgl/text/unaccent.cpp43
-rw-r--r--platform/default/src/mbgl/util/monotonic_timer.cpp24
-rw-r--r--platform/ios/CHANGELOG.md28
-rw-r--r--platform/ios/Integration Tests/Snapshotter Tests/MGLMapSnapshotterTest.m72
-rw-r--r--platform/ios/Mapbox-iOS-SDK-snapshot-dynamic.podspec2
-rw-r--r--platform/ios/Mapbox-iOS-SDK-stripped.podspec2
-rw-r--r--platform/ios/Mapbox-iOS-SDK.podspec2
-rw-r--r--platform/ios/core-files.json2
-rw-r--r--platform/ios/ios.xcodeproj/project.pbxproj18
-rw-r--r--platform/ios/sdk-files.json1
-rw-r--r--platform/ios/src/MGLMapView.h22
-rw-r--r--platform/ios/src/MGLMapView.mm169
-rw-r--r--platform/ios/src/MGLScaleBar.mm221
-rw-r--r--platform/ios/test/MGLMapViewContentInsetTests.m177
-rw-r--r--platform/ios/test/MGLMapViewGestureRecognizerTests.mm280
-rw-r--r--platform/ios/test/MGLMapViewLayoutTests.m85
-rw-r--r--platform/ios/test/MGLMapViewPitchTests.m4
-rw-r--r--platform/ios/test/MGLMapViewScaleBarTests.m15
-rw-r--r--platform/ios/test/MGLMockGestureRecognizers.h19
-rw-r--r--platform/ios/test/MGLMockGestureRecognizers.m44
-rw-r--r--platform/linux/config.cmake15
-rw-r--r--platform/macos/core-files.json2
-rw-r--r--platform/node/CHANGELOG.md4
-rw-r--r--platform/node/DEVELOPING.md21
-rw-r--r--platform/node/README.md6
-rw-r--r--platform/node/test/ignores.json8
-rw-r--r--platform/qt/src/number_format.cpp (renamed from platform/qt/src/format_number.cpp)0
-rw-r--r--platform/qt/src/qmapboxgl_scheduler.cpp10
-rw-r--r--platform/qt/src/qmapboxgl_scheduler.hpp7
-rw-r--r--platform/qt/src/qt_geojson.cpp2
-rw-r--r--platform/qt/src/qt_geojson.hpp2
110 files changed, 2566 insertions, 625 deletions
diff --git a/platform/android/CHANGELOG.md b/platform/android/CHANGELOG.md
index f0c5b2ff91..398844a64e 100644
--- a/platform/android/CHANGELOG.md
+++ b/platform/android/CHANGELOG.md
@@ -4,6 +4,34 @@ Mapbox welcomes participation and contributions from everyone. If you'd like to
## master
+### Bug fixes
+ - Fixed the rendering bug caused by redundant pending requests for already requested images [#15864](https://github.com/mapbox/mapbox-gl-native/pull/15864)
+
+### Performance improvements
+ - Enable incremental vacuum for the offline database in order to make data removal requests faster and to avoid the excessive disk space usage (creating a backup file on VACUUM call) [#15837](https://github.com/mapbox/mapbox-gl-native/pull/15837)
+ - Convert GeoJSON features to tiles in a background thread and thus unblock the UI thread on updating the GeoJsonSource [#15871](https://github.com/mapbox/mapbox-gl-native/pull/15871)
+
+## 8.5.0-alpha.2 - October 10, 2019
+[Changes](https://github.com/mapbox/mapbox-gl-native/compare/android-v8.5.0-alpha.1...android-v8.5.0-alpha.2) since [Mapbox Maps SDK for Android v8.5.0-alpha.1](https://github.com/mapbox/mapbox-gl-native/releases/tag/android-v8.5.0-alpha.1):
+
+### Features
+ - Expose pre-fetching zoom delta, allows to granular control the delta on which we prefetch tiles [#15769](https://github.com/mapbox/mapbox-gl-native/pull/15769)
+
+### Performance improvements
+ - Improved rendering performance for the styles with multiple sources [#15756](https://github.com/mapbox/mapbox-gl-native/pull/15756)
+
+### Bug fixes
+ - Fixed runtime exceptions that occurred when a manually built camera object without padding was serialized. [#15788](https://github.com/mapbox/mapbox-gl-native/pull/15788)
+ - Keep Mapbox when obfuscating code with proguard [#15762](https://github.com/mapbox/mapbox-gl-native/pull/15762)
+
+## 8.5.0-alpha.1 - October 3, 2019
+[Changes](https://github.com/mapbox/mapbox-gl-native/compare/android-v8.4.0...android-v8.5.0-alpha.1) since [Mapbox Maps SDK for Android v8.4.0](https://github.com/mapbox/mapbox-gl-native/releases/tag/android-v8.4.0):
+### Bug fixes
+ - Suppress network requests for expired tiles update, if these tiles are invisible. [#15741](https://github.com/mapbox/mapbox-gl-native/pull/15741)
+ - Fixed opacity interpolation for composition expressions [#15738](https://github.com/mapbox/mapbox-gl-native/pull/15738)
+ - Fixed an issue where `Projection#getMetersPerPixelAtLatitude` returned a value incorrectly divided by the pixel ratio. This also fixes an issue where `LocationComponent` accuracy circle's radius was artificially increased. [#15742](https://github.com/mapbox/mapbox-gl-native/pull/15742)
+ - Coalesce requests to the client for the same missing image [#15778](https://github.com/mapbox/mapbox-gl-native/pull/15778)
+
## 8.4.0 - September 25, 2019
[Changes](https://github.com/mapbox/mapbox-gl-native/compare/android-v8.4.0-beta.1...android-v8.4.0) since [Mapbox Maps SDK for Android v8.4.0-beta.1](https://github.com/mapbox/mapbox-gl-native/releases/tag/android-v8.4.0-beta.1):
@@ -25,11 +53,18 @@ Mapbox welcomes participation and contributions from everyone. If you'd like to
- Fixed MapSnapshotter so that `MapSnapshotter.withApiBaseUri` works again. [#15642](https://github.com/mapbox/mapbox-gl-native/pull/15642)
- Fixed an assertion hit caused by possibility of adding a layer to an incompatible source. [#15644](https://github.com/mapbox/mapbox-gl-native/pull/15644)
+## 8.3.2 - September 19, 2019
+[Changes](https://github.com/mapbox/mapbox-gl-native/compare/android-v8.3.1...android-v8.3.2) since [Mapbox Maps SDK for Android v8.3.1](https://github.com/mapbox/mapbox-gl-native/releases/tag/android-v8.3.1):
+
+### Bug fixes
+ - Fixed an issue of integer overflow when converting `tileCoordinates` to `LatLon`, which caused issues such as `queryRenderedFeatures` and `querySourceFeatures` returning incorrect coordinates at zoom levels 20 and higher. [#15560](https://github.com/mapbox/mapbox-gl-native/pull/15560)
+
## 8.3.1 - September 18, 2019
-[Changes](https://github.com/mapbox/mapbox-gl-native/compare/android-v8.3.0...android-v8.3.1) since [Mapbox Maps SDK for Android v8.3.0](https://github.com/mapbox/mapbox-gl-native/releases/tag/android-v8.3.0):
+[Changes](https://github.com/mapbox/mapbox-gl-native/compare/android-v8.3.0...android-v8.3.1) since [Mapbox Maps SDK for Android v8.3.0](https://github.com/mapbox/mapbox-gl-native/releases/tag/android-v8.3.0):
### Bug fixes
- Updated earcut.hpp submodule file [#15660](https://github.com/mapbox/mapbox-gl-native/pull/15660)
+ - Fixed a wrong variable assignment of `MapSnapshotter.Options.withApiBaseUri`. [#15642](https://github.com/mapbox/mapbox-gl-native/pull/15642)
## 8.4.0-alpha.2 - September 11, 2019
[Changes](https://github.com/mapbox/mapbox-gl-native/compare/android-v8.4.0-alpha.1...android-v8.4.0-alpha.2) since [Mapbox Maps SDK for Android v8.4.0-alpha.1](https://github.com/mapbox/mapbox-gl-native/releases/tag/android-v8.4.0-alpha.1):
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/Mapbox.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/Mapbox.java
index 01c82ddad4..35f7a5975d 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/Mapbox.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/Mapbox.java
@@ -2,6 +2,8 @@ package com.mapbox.mapboxsdk;
import android.annotation.SuppressLint;
import android.content.Context;
+import android.content.res.AssetManager;
+import android.support.annotation.Keep;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
@@ -24,6 +26,7 @@ import com.mapbox.mapboxsdk.utils.ThreadUtils;
*/
@UiThread
@SuppressLint("StaticFieldLeak")
+@Keep
public final class Mapbox {
private static final String TAG = "Mbgl-Mapbox";
@@ -222,4 +225,13 @@ public final class Mapbox {
public static boolean hasInstance() {
return INSTANCE != null;
}
+
+ /**
+ * Internal use. Returns AssetManager.
+ *
+ * @return the asset manager
+ */
+ private static AssetManager getAssetManager() {
+ return getApplicationContext().getResources().getAssets();
+ }
} \ No newline at end of file
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraPosition.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraPosition.java
index e2341029ff..fb89688e28 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraPosition.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraPosition.java
@@ -30,8 +30,16 @@ public final class CameraPosition implements Parcelable {
LatLng target = in.readParcelable(LatLng.class.getClassLoader());
double tilt = in.readDouble();
double zoom = in.readDouble();
- double[] padding = new double[4];
- in.readDoubleArray(padding);
+
+ double[] padding = null;
+ int paddingSize = in.readInt();
+ if (paddingSize > 0) {
+ padding = new double[paddingSize];
+ for (int i = 0; i < paddingSize; i++) {
+ padding[i] = in.readDouble();
+ }
+ }
+
return new CameraPosition(target, zoom, tilt, bearing, padding);
}
@@ -139,7 +147,16 @@ public final class CameraPosition implements Parcelable {
out.writeParcelable(target, flags);
out.writeDouble(tilt);
out.writeDouble(zoom);
- out.writeDoubleArray(padding);
+
+ if (padding != null) {
+ int length = padding.length;
+ out.writeInt(length);
+ for (double v : padding) {
+ out.writeDouble(v);
+ }
+ } else {
+ out.writeInt(-1);
+ }
}
/**
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 acd5093dad..8f4cb9438c 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
@@ -6,6 +6,7 @@ import android.graphics.PointF;
import android.graphics.RectF;
import android.os.Bundle;
import android.support.annotation.FloatRange;
+import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.Size;
@@ -246,12 +247,16 @@ public final class MapboxMap {
// Style
/**
- * Sets tile pre-fetching from MapboxOptions.
+ * Sets tile pre-fetching zoom delta from MapboxOptions.
*
* @param options the options object
*/
private void setPrefetchesTiles(@NonNull MapboxMapOptions options) {
- setPrefetchesTiles(options.getPrefetchesTiles());
+ if (!options.getPrefetchesTiles()) {
+ setPrefetchZoomDelta(0);
+ } else {
+ setPrefetchZoomDelta(options.getPrefetchZoomDelta());
+ }
}
/**
@@ -259,7 +264,9 @@ public final class MapboxMap {
* tile is rendered as soon as possible at the expense of a little bandwidth.
*
* @param enable true to enable
+ * @deprecated Use {@link #setPrefetchZoomDelta(int)} instead.
*/
+ @Deprecated
public void setPrefetchesTiles(boolean enable) {
nativeMapView.setPrefetchTiles(enable);
}
@@ -269,11 +276,38 @@ public final class MapboxMap {
*
* @return true if enabled
* @see MapboxMap#setPrefetchesTiles(boolean)
+ * @deprecated Use {@link #getPrefetchZoomDelta()} instead.
*/
+ @Deprecated
public boolean getPrefetchesTiles() {
return nativeMapView.getPrefetchTiles();
}
+ /**
+ * Set the tile pre-fetching zoom delta. Pre-fetching makes sure that a low-resolution
+ * tile at the (current_zoom_level - delta) is rendered as soon as possible at the
+ * expense of a little bandwidth.
+ * Note: This operation will override the MapboxMapOptions#setPrefetchesTiles(boolean)
+ * Setting zoom delta to 0 will disable pre-fetching.
+ * Default zoom delta is 4.
+ *
+ * @param delta zoom delta
+ */
+ public void setPrefetchZoomDelta(@IntRange(from = 0) int delta) {
+ nativeMapView.setPrefetchZoomDelta(delta);
+ }
+
+ /**
+ * Check current pre-fetching zoom delta.
+ *
+ * @return current zoom delta.
+ * @see MapboxMap#setPrefetchZoomDelta(int)
+ */
+ @IntRange(from = 0)
+ public int getPrefetchZoomDelta() {
+ return nativeMapView.getPrefetchZoomDelta();
+ }
+
//
// MinZoom
//
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMapOptions.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMapOptions.java
index 6cd3271d12..8277568707 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMapOptions.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMapOptions.java
@@ -8,6 +8,7 @@ import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.ColorInt;
+import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
@@ -70,6 +71,7 @@ public class MapboxMapOptions implements Parcelable {
private boolean quickZoomGesturesEnabled = true;
private boolean prefetchesTiles = true;
+ private int prefetchZoomDelta = 4;
private boolean zMediaOverlay = false;
private boolean localIdeographFontFamilyEnabled = true;
@@ -134,6 +136,7 @@ public class MapboxMapOptions implements Parcelable {
textureMode = in.readByte() != 0;
translucentTextureSurface = in.readByte() != 0;
prefetchesTiles = in.readByte() != 0;
+ prefetchZoomDelta = in.readInt();
zMediaOverlay = in.readByte() != 0;
localIdeographFontFamilyEnabled = in.readByte() != 0;
localIdeographFontFamily = in.readString();
@@ -257,6 +260,8 @@ public class MapboxMapOptions implements Parcelable {
typedArray.getBoolean(R.styleable.mapbox_MapView_mapbox_renderTextureTranslucentSurface, false));
mapboxMapOptions.setPrefetchesTiles(
typedArray.getBoolean(R.styleable.mapbox_MapView_mapbox_enableTilePrefetch, true));
+ mapboxMapOptions.setPrefetchZoomDelta(
+ typedArray.getInt(R.styleable.mapbox_MapView_mapbox_prefetchZoomDelta, 4));
mapboxMapOptions.renderSurfaceOnTop(
typedArray.getBoolean(R.styleable.mapbox_MapView_mapbox_enableZMediaOverlay, false));
@@ -633,7 +638,9 @@ public class MapboxMapOptions implements Parcelable {
*
* @param enable true to enable
* @return This
+ * @deprecated Use {@link #setPrefetchZoomDelta(int)} instead.
*/
+ @Deprecated
@NonNull
public MapboxMapOptions setPrefetchesTiles(boolean enable) {
this.prefetchesTiles = enable;
@@ -641,6 +648,23 @@ public class MapboxMapOptions implements Parcelable {
}
/**
+ * Set the tile pre-fetching zoom delta. Pre-fetching makes sure that a low-resolution
+ * tile at the (current_zoom_level - delta) is rendered as soon as possible at the
+ * expense of a little bandwidth.
+ * Note: This operation will override the MapboxMapOptions#setPrefetchesTiles(boolean)
+ * Setting zoom delta to 0 will disable pre-fetching.
+ * Default zoom delta is 4.
+ *
+ * @param delta zoom delta
+ * @return This
+ */
+ @NonNull
+ public MapboxMapOptions setPrefetchZoomDelta(@IntRange(from = 0) int delta) {
+ this.prefetchZoomDelta = delta;
+ return this;
+ }
+
+ /**
* Enable cross-source symbol collision detection, defaults to true.
* <p>
* If set to false, symbol layers will only run collision detection against
@@ -721,12 +745,24 @@ public class MapboxMapOptions implements Parcelable {
* Check whether tile pre-fetching is enabled.
*
* @return true if enabled
+ * @deprecated Use {@link #getPrefetchZoomDelta()} instead.
*/
+ @Deprecated
public boolean getPrefetchesTiles() {
return prefetchesTiles;
}
/**
+ * Check current pre-fetching zoom delta.
+ *
+ * @return current zoom delta.
+ */
+ @IntRange(from = 0)
+ public int getPrefetchZoomDelta() {
+ return prefetchZoomDelta;
+ }
+
+ /**
* Check whether cross-source symbol collision detection is enabled.
*
* @return true if enabled
@@ -1081,6 +1117,7 @@ public class MapboxMapOptions implements Parcelable {
dest.writeByte((byte) (textureMode ? 1 : 0));
dest.writeByte((byte) (translucentTextureSurface ? 1 : 0));
dest.writeByte((byte) (prefetchesTiles ? 1 : 0));
+ dest.writeInt(prefetchZoomDelta);
dest.writeByte((byte) (zMediaOverlay ? 1 : 0));
dest.writeByte((byte) (localIdeographFontFamilyEnabled ? 1 : 0));
dest.writeString(localIdeographFontFamily);
@@ -1175,6 +1212,9 @@ public class MapboxMapOptions implements Parcelable {
if (prefetchesTiles != options.prefetchesTiles) {
return false;
}
+ if (prefetchZoomDelta != options.prefetchZoomDelta) {
+ return false;
+ }
if (zMediaOverlay != options.zMediaOverlay) {
return false;
}
@@ -1231,6 +1271,7 @@ public class MapboxMapOptions implements Parcelable {
result = 31 * result + (textureMode ? 1 : 0);
result = 31 * result + (translucentTextureSurface ? 1 : 0);
result = 31 * result + (prefetchesTiles ? 1 : 0);
+ result = 31 * result + prefetchZoomDelta;
result = 31 * result + (zMediaOverlay ? 1 : 0);
result = 31 * result + (localIdeographFontFamilyEnabled ? 1 : 0);
result = 31 * result + (localIdeographFontFamily != null ? localIdeographFontFamily.hashCode() : 0);
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMap.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMap.java
index 7f3017c7ae..c805dcddb0 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMap.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMap.java
@@ -6,6 +6,7 @@ import android.graphics.RectF;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+
import com.mapbox.geojson.Feature;
import com.mapbox.geojson.Geometry;
import com.mapbox.mapboxsdk.annotations.Marker;
@@ -214,6 +215,11 @@ interface NativeMap {
boolean getPrefetchTiles();
+ void setPrefetchZoomDelta(@IntRange(from = 0) int delta);
+
+ @IntRange(from = 0)
+ int getPrefetchZoomDelta();
+
void setGestureInProgress(boolean inProgress);
float getPixelRatio();
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 8496160c7e..75ca5edfca 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
@@ -620,7 +620,7 @@ final class NativeMapView implements NativeMap {
if (checkState("getMetersPerPixelAtLatitude")) {
return 0;
}
- return nativeGetMetersPerPixelAtLatitude(lat, getZoom()) / pixelRatio;
+ return nativeGetMetersPerPixelAtLatitude(lat, getZoom());
}
@Override
@@ -724,6 +724,23 @@ final class NativeMapView implements NativeMap {
return nativeGetPrefetchTiles();
}
+ @Override
+ public void setPrefetchZoomDelta(@IntRange(from = 0) int delta) {
+ if (checkState("nativeSetPrefetchZoomDelta")) {
+ return;
+ }
+ nativeSetPrefetchZoomDelta(delta);
+ }
+
+ @Override
+ @IntRange(from = 0)
+ public int getPrefetchZoomDelta() {
+ if (checkState("nativeGetPrefetchZoomDelta")) {
+ return 0;
+ }
+ return nativeGetPrefetchZoomDelta();
+ }
+
// Runtime style Api
@Override
@@ -1383,6 +1400,12 @@ final class NativeMapView implements NativeMap {
@Keep
private native boolean nativeGetPrefetchTiles();
+ @Keep
+ private native void nativeSetPrefetchZoomDelta(int delta);
+
+ @Keep
+ private native int nativeGetPrefetchZoomDelta();
+
@Override
public long getNativePtr() {
return nativePtr;
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 763c97cca7..07e5b7e16d 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
@@ -4,7 +4,6 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.content.res.AssetManager;
import android.os.AsyncTask;
import android.os.Environment;
import android.support.annotation.Keep;
@@ -90,7 +89,7 @@ public class FileSource {
@UiThread
public static synchronized FileSource getInstance(@NonNull Context context) {
if (INSTANCE == null) {
- INSTANCE = new FileSource(getResourcesCachePath(context), context.getResources().getAssets());
+ INSTANCE = new FileSource(getResourcesCachePath(context));
}
return INSTANCE;
@@ -366,8 +365,8 @@ public class FileSource {
@Keep
private long nativePtr;
- private FileSource(String cachePath, AssetManager assetManager) {
- initialize(Mapbox.getAccessToken(), cachePath, assetManager);
+ private FileSource(String cachePath) {
+ initialize(Mapbox.getAccessToken(), cachePath);
}
@Keep
@@ -404,7 +403,7 @@ public class FileSource {
private native void setResourceCachePath(String path, ResourcesCachePathChangeCallback callback);
@Keep
- private native void initialize(String accessToken, String cachePath, AssetManager assetManager);
+ private native void initialize(String accessToken, String cachePath);
@Override
@Keep
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml b/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml
index 60a1efc771..36071cc2a7 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml
@@ -69,6 +69,7 @@
<public name="mapbox_foregroundLoadColor" type="attr" />
<public name="mapbox_enableTilePrefetch" type="attr" />
+ <public name="mapbox_prefetchZoomDelta" type="attr"/>
<public name="mapbox_enableZMediaOverlay" type="attr" />
<!-- Exposed content descriptions -->
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml
index ff8a32ac64..137f72f8d2 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml
+++ b/platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml
@@ -109,6 +109,7 @@
<attr name="mapbox_foregroundLoadColor" format="color"/>
<attr name="mapbox_enableTilePrefetch" format="boolean"/>
+ <attr name="mapbox_prefetchZoomDelta" format="integer"/>
<attr name="mapbox_enableZMediaOverlay" format="boolean"/>
<attr name="mapbox_pixelRatio" format="float"/>
diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/camera/CameraPositionTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/camera/CameraPositionTest.java
index 6974705fae..dcf84c87b3 100644
--- a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/camera/CameraPositionTest.java
+++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/camera/CameraPositionTest.java
@@ -117,4 +117,15 @@ public class CameraPositionTest {
CameraPosition cameraPosition2 = CameraPosition.CREATOR.createFromParcel(parcel);
assertEquals("Parcel should match original object", cameraPosition1, cameraPosition2);
}
+
+ @Test
+ public void testParcelableNulls() {
+ CameraPosition cameraPosition1 = new CameraPosition(null, 3, 4, 5, null);
+ Parcel parcel = Parcel.obtain();
+ cameraPosition1.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ CameraPosition cameraPosition2 = CameraPosition.CREATOR.createFromParcel(parcel);
+ assertEquals("Parcel should match original object", cameraPosition1, cameraPosition2);
+ }
}
diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapboxMapOptionsTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapboxMapOptionsTest.java
index 6978afcf1f..c46e6e3190 100644
--- a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapboxMapOptionsTest.java
+++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapboxMapOptionsTest.java
@@ -168,6 +168,16 @@ public class MapboxMapOptionsTest {
}
@Test
+ public void testPrefetchZoomDelta() {
+ // Default value
+ assertEquals(4, new MapboxMapOptions().getPrefetchZoomDelta());
+
+ // Check mutations
+ assertEquals(5, new MapboxMapOptions().setPrefetchZoomDelta(5).getPrefetchZoomDelta());
+ }
+
+
+ @Test
public void testCrossSourceCollisions() {
// Default value
assertTrue(new MapboxMapOptions().getCrossSourceCollisions());
diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapboxMapTest.kt b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapboxMapTest.kt
index deb5f603c8..6647fe5595 100644
--- a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapboxMapTest.kt
+++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/maps/MapboxMapTest.kt
@@ -127,6 +127,18 @@ class MapboxMapTest {
}
@Test
+ fun testGetPrefetchZoomDelta() {
+ every { nativeMapView.prefetchZoomDelta } answers { 3 }
+ assertEquals(3, mapboxMap.prefetchZoomDelta)
+ }
+
+ @Test
+ fun testSetPrefetchZoomDelta() {
+ mapboxMap.prefetchZoomDelta = 2
+ verify { nativeMapView.prefetchZoomDelta = 2 }
+ }
+
+ @Test
fun testCameraForLatLngBounds() {
val bounds = LatLngBounds.Builder().include(LatLng()).include(LatLng(1.0, 1.0)).build()
mapboxMap.setLatLngBoundsForCameraTarget(bounds)
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/maps/NativeMapViewTest.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/maps/NativeMapViewTest.kt
index b13bb6b796..dc313b5f64 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/maps/NativeMapViewTest.kt
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/maps/NativeMapViewTest.kt
@@ -223,6 +223,23 @@ class NativeMapViewTest : AppCenter() {
@Test
@UiThreadTest
+ fun testPrefetchZoomDelta() {
+ val expected = 2
+ nativeMapView.prefetchZoomDelta = 2
+ val actual = nativeMapView.prefetchZoomDelta
+ assertEquals("Prefetch zoom delta should match", expected, actual)
+ }
+
+ @Test
+ @UiThreadTest
+ fun testPrefetchZoomDeltaDefault() {
+ val expected = 4
+ val actual = nativeMapView.prefetchZoomDelta
+ assertEquals("Prefetch zoom delta should match", expected, actual)
+ }
+
+ @Test
+ @UiThreadTest
fun testSetContentPadding() {
val expected = doubleArrayOf(1.0, 2.0, 3.0, 4.0)
nativeMapView.contentPadding = expected
@@ -254,7 +271,7 @@ class NativeMapViewTest : AppCenter() {
@Test
@UiThreadTest
fun testGetProjectedMetersAtLatitude() {
- val expected = 38986.83510557766
+ val expected = 77973.67021115532
val actual = nativeMapView.getMetersPerPixelAtLatitude(5.0)
assertEquals("Get projected meters should match", expected, actual)
}
diff --git a/platform/android/core-files.json b/platform/android/core-files.json
index 362098dd9b..e21a586e29 100644
--- a/platform/android/core-files.json
+++ b/platform/android/core-files.json
@@ -34,6 +34,8 @@
"platform/android/src/gson/json_element.cpp",
"platform/android/src/gson/json_object.cpp",
"platform/android/src/gson/json_primitive.cpp",
+ "platform/android/src/i18n/collator.cpp",
+ "platform/android/src/i18n/number_format.cpp",
"platform/android/src/image.cpp",
"platform/android/src/java/util.cpp",
"platform/android/src/java_types.cpp",
@@ -41,6 +43,7 @@
"platform/android/src/jni_native.cpp",
"platform/android/src/logger.cpp",
"platform/android/src/logging_android.cpp",
+ "platform/android/src/mapbox.cpp",
"platform/android/src/map/camera_position.cpp",
"platform/android/src/map/image.cpp",
"platform/android/src/map_renderer.cpp",
@@ -80,19 +83,17 @@
"platform/android/src/style/sources/vector_source.cpp",
"platform/android/src/style/transition_options.cpp",
"platform/android/src/style/value.cpp",
- "platform/android/src/text/collator.cpp",
"platform/android/src/text/local_glyph_rasterizer.cpp",
- "platform/android/src/text/format_number.cpp",
"platform/android/src/gl_functions.cpp",
"platform/android/src/thread.cpp",
"platform/android/src/timer.cpp",
- "platform/android/src/unaccent.cpp",
"platform/default/src/mbgl/gfx/headless_backend.cpp",
"platform/default/src/mbgl/gfx/headless_frontend.cpp",
"platform/default/src/mbgl/gl/headless_backend.cpp",
"platform/default/src/mbgl/map/map_snapshotter.cpp",
"platform/default/src/mbgl/text/bidi.cpp",
"platform/default/src/mbgl/util/compression.cpp",
+ "platform/default/src/mbgl/util/monotonic_timer.cpp",
"platform/default/src/mbgl/util/png_writer.cpp",
"platform/default/src/mbgl/util/thread_local.cpp",
"platform/default/src/mbgl/util/utf.cpp",
@@ -103,8 +104,7 @@
"mbgl/gfx/headless_backend.hpp": "platform/default/include/mbgl/gfx/headless_backend.hpp",
"mbgl/gfx/headless_frontend.hpp": "platform/default/include/mbgl/gfx/headless_frontend.hpp",
"mbgl/gl/headless_backend.hpp": "platform/default/include/mbgl/gl/headless_backend.hpp",
- "mbgl/map/map_snapshotter.hpp": "platform/default/include/mbgl/map/map_snapshotter.hpp",
- "mbgl/text/unaccent.hpp": "platform/default/include/mbgl/text/unaccent.hpp"
+ "mbgl/map/map_snapshotter.hpp": "platform/default/include/mbgl/map/map_snapshotter.hpp"
},
"private_headers": {
"android_renderer_backend.hpp": "platform/android/src/android_renderer_backend.hpp",
@@ -145,7 +145,10 @@
"java_types.hpp": "platform/android/src/java_types.hpp",
"jni.hpp": "platform/android/src/jni.hpp",
"jni_native.hpp": "platform/android/src/jni_native.hpp",
+ "i18n/collator_jni.hpp": "platform/android/src/i18n/collator_jni.hpp",
+ "i18n/number_format_jni.hpp": "platform/android/src/i18n/number_format_jni.hpp",
"logger.hpp": "platform/android/src/logger.hpp",
+ "mapbox.hpp": "platform/android/src/mapbox.hpp",
"map/camera_position.hpp": "platform/android/src/map/camera_position.hpp",
"map/image.hpp": "platform/android/src/map/image.hpp",
"map_renderer.hpp": "platform/android/src/map_renderer.hpp",
@@ -187,7 +190,6 @@
"style/sources/vector_source.hpp": "platform/android/src/style/sources/vector_source.hpp",
"style/transition_options.hpp": "platform/android/src/style/transition_options.hpp",
"style/value.hpp": "platform/android/src/style/value.hpp",
- "text/collator_jni.hpp": "platform/android/src/text/collator_jni.hpp",
"text/local_glyph_rasterizer_jni.hpp": "platform/android/src/text/local_glyph_rasterizer_jni.hpp"
}
}
diff --git a/platform/android/scripts/generate-style-code.js b/platform/android/scripts/generate-style-code.js
index fa814f89af..83fb5c116e 100755
--- a/platform/android/scripts/generate-style-code.js
+++ b/platform/android/scripts/generate-style-code.js
@@ -72,6 +72,7 @@ global.propertyType = function propertyType(property) {
case 'formatted':
return 'Formatted';
case 'string':
+ case 'image': // TODO: replace once we implement image expressions
return 'String';
case 'enum':
return 'String';
@@ -93,6 +94,7 @@ global.propertyJavaType = function propertyType(property) {
case 'formatted':
return 'Formatted';
case 'string':
+ case 'image': // TODO: replace once we implement image expressions
return 'String';
case 'enum':
return 'String';
@@ -141,6 +143,7 @@ global.propertyNativeType = function (property) {
return 'float';
case 'formatted':
case 'string':
+ case 'image': // TODO: replace once we implement image expressions
return 'std::string';
case 'enum':
if(property['light-property']){
@@ -177,6 +180,7 @@ global.defaultExpressionJava = function(property) {
case 'formatted':
return 'format';
case 'string':
+ case 'image': // TODO: replace once we implement image expressions
return "string";
case 'enum':
return "string";
@@ -203,6 +207,7 @@ global.defaultValueJava = function(property) {
case 'formatted':
return 'new Formatted(new FormattedSection("default"))'
case 'string':
+ case 'image': // TODO: replace once we implement image expressions
return '"' + property['default'] + '"';
case 'enum':
return snakeCaseUpper(property.name) + "_" + snakeCaseUpper(Object.keys(property.values)[0]);
@@ -335,6 +340,7 @@ global.evaluatedType = function (property) {
return 'float';
case 'formatted':
case 'string':
+ case 'image': // TODO: replace once we implement image expressions
return 'std::string';
case 'enum':
return (isLightProperty(property) ? 'Light' : '') + `${camelize(property.name)}Type`;
diff --git a/platform/android/src/async_task.cpp b/platform/android/src/async_task.cpp
index 6c14e96fa6..0a4d90a275 100644
--- a/platform/android/src/async_task.cpp
+++ b/platform/android/src/async_task.cpp
@@ -16,6 +16,7 @@ public:
}
~Impl() {
+ queued = true;
loop->removeRunnable(this);
}
@@ -31,9 +32,11 @@ public:
}
void runTask() override {
- loop->removeRunnable(this);
- queued = true;
- task();
+ if (!queued) {
+ queued = true;
+ loop->removeRunnable(this);
+ task();
+ }
}
private:
diff --git a/platform/android/src/file_source.cpp b/platform/android/src/file_source.cpp
index 5f61aadba0..a002d6616f 100644
--- a/platform/android/src/file_source.cpp
+++ b/platform/android/src/file_source.cpp
@@ -1,5 +1,7 @@
#include "file_source.hpp"
+
#include "attach_env.hpp"
+#include "mapbox.hpp"
#include <mbgl/actor/actor.hpp>
#include <mbgl/actor/scheduler.hpp>
@@ -14,8 +16,15 @@
namespace mbgl {
std::shared_ptr<FileSource> FileSource::createPlatformFileSource(const ResourceOptions& options) {
- auto* assetFileSource = reinterpret_cast<AssetManagerFileSource*>(options.platformContext());
- auto fileSource = std::make_shared<DefaultFileSource>(options.cachePath(), std::unique_ptr<AssetManagerFileSource>(assetFileSource));
+ auto env{android::AttachEnv()};
+ std::shared_ptr<DefaultFileSource> fileSource;
+ if (android::Mapbox::hasInstance(*env)) {
+ auto assetManager = android::Mapbox::getAssetManager(*env);
+ fileSource = std::make_shared<DefaultFileSource>(options.cachePath(),
+ std::make_unique<AssetManagerFileSource>(*env, assetManager));
+ } else {
+ fileSource = std::make_shared<DefaultFileSource>(options.cachePath(), options.assetPath());
+ }
fileSource->setAccessToken(options.accessToken());
return fileSource;
}
@@ -24,17 +33,12 @@ namespace android {
// FileSource //
-FileSource::FileSource(jni::JNIEnv& _env,
- const jni::String& accessToken,
- const jni::String& _cachePath,
- const jni::Object<AssetManager>& assetManager) {
+FileSource::FileSource(jni::JNIEnv& _env, const jni::String& accessToken, const jni::String& _cachePath) {
std::string path = jni::Make<std::string>(_env, _cachePath);
mapbox::sqlite::setTempPath(path);
- resourceOptions
- .withAccessToken(accessToken ? jni::Make<std::string>(_env, accessToken) : "")
- .withCachePath(path + DATABASE_FILE)
- .withPlatformContext(reinterpret_cast<void*>(new AssetManagerFileSource(_env, assetManager)));
+ resourceOptions.withAccessToken(accessToken ? jni::Make<std::string>(_env, accessToken) : "")
+ .withCachePath(path + DATABASE_FILE);
// Create a core default file source
fileSource = std::static_pointer_cast<mbgl::DefaultFileSource>(mbgl::FileSource::getSharedFileSource(resourceOptions));
@@ -171,20 +175,20 @@ void FileSource::registerNative(jni::JNIEnv& env) {
#define METHOD(MethodPtr, name) jni::MakeNativePeerMethod<decltype(MethodPtr), (MethodPtr)>(name)
// Register the peer
- jni::RegisterNativePeer<FileSource>(
- env, javaClass, "nativePtr",
- jni::MakePeer<FileSource, const jni::String&, const jni::String&, const jni::Object<AssetManager>&>,
- "initialize",
- "finalize",
- METHOD(&FileSource::getAccessToken, "getAccessToken"),
- METHOD(&FileSource::setAccessToken, "setAccessToken"),
- METHOD(&FileSource::setAPIBaseUrl, "setApiBaseUrl"),
- METHOD(&FileSource::setResourceTransform, "setResourceTransform"),
- METHOD(&FileSource::setResourceCachePath, "setResourceCachePath"),
- METHOD(&FileSource::resume, "activate"),
- METHOD(&FileSource::pause, "deactivate"),
- METHOD(&FileSource::isResumed, "isActivated")
- );
+ jni::RegisterNativePeer<FileSource>(env,
+ javaClass,
+ "nativePtr",
+ jni::MakePeer<FileSource, const jni::String&, const jni::String&>,
+ "initialize",
+ "finalize",
+ METHOD(&FileSource::getAccessToken, "getAccessToken"),
+ METHOD(&FileSource::setAccessToken, "setAccessToken"),
+ METHOD(&FileSource::setAPIBaseUrl, "setApiBaseUrl"),
+ METHOD(&FileSource::setResourceTransform, "setResourceTransform"),
+ METHOD(&FileSource::setResourceCachePath, "setResourceCachePath"),
+ METHOD(&FileSource::resume, "activate"),
+ METHOD(&FileSource::pause, "deactivate"),
+ METHOD(&FileSource::isResumed, "isActivated"));
}
diff --git a/platform/android/src/file_source.hpp b/platform/android/src/file_source.hpp
index f3ad33eb31..6a9190fa06 100644
--- a/platform/android/src/file_source.hpp
+++ b/platform/android/src/file_source.hpp
@@ -41,7 +41,7 @@ public:
const jni::String&);
};
- FileSource(jni::JNIEnv&, const jni::String&, const jni::String&, const jni::Object<AssetManager>&);
+ FileSource(jni::JNIEnv&, const jni::String&, const jni::String&);
~FileSource();
diff --git a/platform/android/src/geojson/feature.cpp b/platform/android/src/geojson/feature.cpp
index 8d30404a50..afbf1ee11e 100644
--- a/platform/android/src/geojson/feature.cpp
+++ b/platform/android/src/geojson/feature.cpp
@@ -10,7 +10,7 @@ namespace geojson {
using namespace gson;
-mbgl::Feature Feature::convert(jni::JNIEnv& env, const jni::Object<Feature>& jFeature) {
+mbgl::GeoJSONFeature Feature::convert(jni::JNIEnv& env, const jni::Object<Feature>& jFeature) {
static auto& javaClass = jni::Class<Feature>::Singleton(env);
static auto id = javaClass.GetMethod<jni::String ()>(env, "id");
static auto geometry = javaClass.GetMethod<jni::Object<Geometry> ()>(env, "geometry");
@@ -20,11 +20,9 @@ mbgl::Feature Feature::convert(jni::JNIEnv& env, const jni::Object<Feature>& jFe
using mbid = mapbox::feature::identifier;
- return mbgl::Feature {
- Geometry::convert(env, jFeature.Call(env, geometry)),
- JsonObject::convert(env, jFeature.Call(env, properties)),
- jId ? mbid { jni::Make<std::string>(env, jId) } : mbid { mapbox::feature::null_value }
- };
+ return mbgl::GeoJSONFeature{Geometry::convert(env, jFeature.Call(env, geometry)),
+ JsonObject::convert(env, jFeature.Call(env, properties)),
+ jId ? mbid{jni::Make<std::string>(env, jId)} : mbid{mapbox::feature::null_value}};
}
/**
@@ -50,7 +48,7 @@ public:
}
};
-jni::Local<jni::Object<Feature>> convertFeature(jni::JNIEnv& env, const mbgl::Feature& value) {
+jni::Local<jni::Object<Feature>> convertFeature(jni::JNIEnv& env, const mbgl::GeoJSONFeature& value) {
static auto& javaClass = jni::Class<Feature>::Singleton(env);
static auto method = javaClass.GetStaticMethod<jni::Object<Feature> (jni::Object<Geometry>, jni::Object<JsonObject>, jni::String)>(env, "fromGeometry");
@@ -63,7 +61,18 @@ jni::Local<jni::Object<Feature>> convertFeature(jni::JNIEnv& env, const mbgl::Fe
jni::Local<jni::Array<jni::Object<Feature>>> Feature::convert(jni::JNIEnv& env, const std::vector<mbgl::Feature>& value) {
auto features = jni::Array<jni::Object<Feature>>::New(env, value.size());
- for (size_t i = 0; i < value.size(); i = i + 1) {
+ for (size_t i = 0; i < value.size(); ++i) {
+ features.Set(env, i, convertFeature(env, static_cast<mbgl::GeoJSONFeature>(value.at(i))));
+ }
+
+ return features;
+}
+
+jni::Local<jni::Array<jni::Object<Feature>>> Feature::convert(jni::JNIEnv& env,
+ const std::vector<mbgl::GeoJSONFeature>& value) {
+ auto features = jni::Array<jni::Object<Feature>>::New(env, value.size());
+
+ for (size_t i = 0; i < value.size(); ++i) {
features.Set(env, i, convertFeature(env, value.at(i)));
}
diff --git a/platform/android/src/geojson/feature.hpp b/platform/android/src/geojson/feature.hpp
index fdf5d977ba..aee45262e3 100644
--- a/platform/android/src/geojson/feature.hpp
+++ b/platform/android/src/geojson/feature.hpp
@@ -12,8 +12,9 @@ class Feature {
public:
static constexpr auto Name() { return "com/mapbox/geojson/Feature"; };
- static mbgl::Feature convert(jni::JNIEnv&, const jni::Object<Feature>&);
+ static mbgl::GeoJSONFeature convert(jni::JNIEnv&, const jni::Object<Feature>&);
static jni::Local<jni::Array<jni::Object<Feature>>> convert(jni::JNIEnv&, const std::vector<mbgl::Feature>&);
+ static jni::Local<jni::Array<jni::Object<Feature>>> convert(jni::JNIEnv&, const std::vector<mbgl::GeoJSONFeature>&);
static void registerNative(jni::JNIEnv&);
};
diff --git a/platform/android/src/text/collator.cpp b/platform/android/src/i18n/collator.cpp
index acb4f36ee1..b3dd8e21b3 100644
--- a/platform/android/src/text/collator.cpp
+++ b/platform/android/src/i18n/collator.cpp
@@ -1,8 +1,5 @@
-#include <mbgl/style/expression/collator.hpp>
+#include <mbgl/i18n/collator.hpp>
#include <mbgl/text/language_tag.hpp>
-#include <mbgl/util/platform.hpp>
-
-#include <mbgl/text/unaccent.hpp>
#include <jni/jni.hpp>
@@ -96,8 +93,7 @@ jni::Local<jni::Object<Locale>> Locale::New(jni::JNIEnv& env, const jni::String&
} // namespace android
-namespace style {
-namespace expression {
+namespace platform {
class Collator::Impl {
public:
@@ -144,12 +140,12 @@ public:
// Because of the difference in locale-awareness, this means turning on case-sensitivity
// can _potentially_ change compare results for strings that don't actually have any case
// differences.
- jni::Local<jni::String> jlhs = jni::Make<jni::String>(*env, useUnaccent ?
- platform::unaccent(lhs) :
- lhs);
- jni::Local<jni::String> jrhs = jni::Make<jni::String>(*env, useUnaccent ?
- platform::unaccent(rhs) :
- rhs);
+ jni::Local<jni::String> jlhs = useUnaccent
+ ? android::StringUtils::unaccent(*env, jni::Make<jni::String>(*env, lhs))
+ : jni::Make<jni::String>(*env, lhs);
+ jni::Local<jni::String> jrhs = useUnaccent
+ ? android::StringUtils::unaccent(*env, jni::Make<jni::String>(*env, rhs))
+ : jni::Make<jni::String>(*env, rhs);
jni::jint result = android::Collator::compare(*env, collator, jlhs, jrhs);
@@ -194,6 +190,5 @@ std::string Collator::resolvedLocale() const {
return impl->resolvedLocale();
}
-} // namespace expression
-} // namespace style
+} // namespace platform
} // namespace mbgl
diff --git a/platform/android/src/text/collator_jni.hpp b/platform/android/src/i18n/collator_jni.hpp
index dd3f845662..612f9b49f3 100644
--- a/platform/android/src/text/collator_jni.hpp
+++ b/platform/android/src/i18n/collator_jni.hpp
@@ -1,7 +1,5 @@
#pragma once
-#include <mbgl/util/image.hpp>
-
#include <jni/jni.hpp>
/*
diff --git a/platform/android/src/text/format_number.cpp b/platform/android/src/i18n/number_format.cpp
index 3a41175ecc..c8e0b70562 100644
--- a/platform/android/src/text/format_number.cpp
+++ b/platform/android/src/i18n/number_format.cpp
@@ -1,11 +1,10 @@
-#include <mbgl/style/expression/collator.hpp>
+#include <mbgl/i18n/number_format.hpp>
#include <mbgl/text/language_tag.hpp>
-#include <mbgl/util/platform.hpp>
#include <jni/jni.hpp>
#include "../attach_env.hpp"
-#include "format_number_jni.hpp"
+#include "number_format_jni.hpp"
namespace mbgl {
namespace android {
diff --git a/platform/android/src/text/format_number_jni.hpp b/platform/android/src/i18n/number_format_jni.hpp
index 1720038925..1720038925 100644
--- a/platform/android/src/text/format_number_jni.hpp
+++ b/platform/android/src/i18n/number_format_jni.hpp
diff --git a/platform/android/src/jni_native.cpp b/platform/android/src/jni_native.cpp
index df96ba9759..bcbdfcf484 100755..100644
--- a/platform/android/src/jni_native.cpp
+++ b/platform/android/src/jni_native.cpp
@@ -6,8 +6,8 @@
#include "bitmap.hpp"
#include "bitmap_factory.hpp"
#include "connectivity_listener.hpp"
-#include "conversion/conversion.hpp"
#include "conversion/collection.hpp"
+#include "conversion/conversion.hpp"
#include "file_source.hpp"
#include "geojson/feature.hpp"
#include "geojson/feature_collection.hpp"
@@ -32,6 +32,7 @@
#include "java_types.hpp"
#include "map_renderer.hpp"
#include "map_renderer_runnable.hpp"
+#include "mapbox.hpp"
#include "native_map_view.hpp"
#ifndef MBGL_MODULE_OFFLINE_DISABLE
#include "offline/offline_manager.hpp"
@@ -50,10 +51,10 @@
#include "snapshotter/map_snapshotter.hpp"
#include "snapshotter/map_snapshot.hpp"
#endif
-#include "text/collator_jni.hpp"
-#include "text/local_glyph_rasterizer_jni.hpp"
-#include "text/format_number_jni.hpp"
+#include "i18n/collator_jni.hpp"
+#include "i18n/number_format_jni.hpp"
#include "logger.hpp"
+#include "text/local_glyph_rasterizer_jni.hpp"
namespace mbgl {
namespace android {
@@ -158,6 +159,9 @@ void registerNatives(JavaVM *vm) {
// Logger
Logger::registerNative(env);
+
+ // AssetManager
+ Mapbox::registerNative(env);
}
} // namespace android
diff --git a/platform/android/src/map_renderer.cpp b/platform/android/src/map_renderer.cpp
index 6be708b994..0c0e907f14 100644
--- a/platform/android/src/map_renderer.cpp
+++ b/platform/android/src/map_renderer.cpp
@@ -43,7 +43,7 @@ ActorRef<Renderer> MapRenderer::actor() const {
return *rendererRef;
}
-void MapRenderer::schedule(std::weak_ptr<Mailbox> scheduled) {
+void MapRenderer::schedule(std::function<void()> scheduled) {
// Create a runnable
android::UniqueEnv _env = android::AttachEnv();
auto runnable = std::make_unique<MapRendererRunnable>(*_env, std::move(scheduled));
diff --git a/platform/android/src/map_renderer.hpp b/platform/android/src/map_renderer.hpp
index 5a8ddeeb91..047f1870c7 100644
--- a/platform/android/src/map_renderer.hpp
+++ b/platform/android/src/map_renderer.hpp
@@ -67,7 +67,8 @@ public:
// From Scheduler. Schedules by using callbacks to the
// JVM to process the mailbox on the right thread.
- void schedule(std::weak_ptr<Mailbox> scheduled) override;
+ void schedule(std::function<void()> scheduled) override;
+ mapbox::base::WeakPtr<Scheduler> makeWeakPtr() override { return weakFactory.makeWeakPtr(); }
void requestRender();
@@ -122,6 +123,7 @@ private:
std::atomic<bool> destroyed {false};
std::unique_ptr<SnapshotCallback> snapshotCallback;
+ mapbox::base::WeakPtrFactory<Scheduler> weakFactory{this};
};
} // namespace android
diff --git a/platform/android/src/map_renderer_runnable.cpp b/platform/android/src/map_renderer_runnable.cpp
index 77c3aa301d..227f49ee3f 100644
--- a/platform/android/src/map_renderer_runnable.cpp
+++ b/platform/android/src/map_renderer_runnable.cpp
@@ -5,9 +5,8 @@
namespace mbgl {
namespace android {
-MapRendererRunnable::MapRendererRunnable(jni::JNIEnv& env, std::weak_ptr<Mailbox> mailbox_)
- : mailbox(std::move(mailbox_)) {
-
+MapRendererRunnable::MapRendererRunnable(jni::JNIEnv& env, std::function<void()> function_)
+ : function(std::move(function_)) {
// Create the Java peer and hold on to a global reference
// Not using a weak reference here as this might oerflow
// the weak reference table on some devices
@@ -21,7 +20,7 @@ MapRendererRunnable::MapRendererRunnable(jni::JNIEnv& env, std::weak_ptr<Mailbox
MapRendererRunnable::~MapRendererRunnable() = default;
void MapRendererRunnable::run(jni::JNIEnv&) {
- Mailbox::maybeReceive(mailbox);
+ if (function) function();
}
jni::Global<jni::Object<MapRendererRunnable>> MapRendererRunnable::peer() {
diff --git a/platform/android/src/map_renderer_runnable.hpp b/platform/android/src/map_renderer_runnable.hpp
index 21c4369b69..24d0f2af49 100644
--- a/platform/android/src/map_renderer_runnable.hpp
+++ b/platform/android/src/map_renderer_runnable.hpp
@@ -24,7 +24,7 @@ public:
static void registerNative(jni::JNIEnv&);
- MapRendererRunnable(jni::JNIEnv&, std::weak_ptr<Mailbox>);
+ MapRendererRunnable(jni::JNIEnv&, std::function<void()>);
// Only for jni registration, unused
MapRendererRunnable(jni::JNIEnv&) {
@@ -40,7 +40,7 @@ public:
private:
jni::Global<jni::Object<MapRendererRunnable>> javaPeer;
- std::weak_ptr<Mailbox> mailbox;
+ std::function<void()> function;
};
} // namespace android
diff --git a/platform/android/src/mapbox.cpp b/platform/android/src/mapbox.cpp
new file mode 100644
index 0000000000..5246739cf6
--- /dev/null
+++ b/platform/android/src/mapbox.cpp
@@ -0,0 +1,23 @@
+#include "mapbox.hpp"
+
+namespace mbgl {
+namespace android {
+
+jni::Local<jni::Object<AssetManager>> Mapbox::getAssetManager(jni::JNIEnv& env) {
+ static auto& javaClass = jni::Class<Mapbox>::Singleton(env);
+ auto method = javaClass.GetStaticMethod<jni::Object<AssetManager>()>(env, "getAssetManager");
+ return javaClass.Call(env, method);
+}
+
+jboolean Mapbox::hasInstance(jni::JNIEnv& env) {
+ static auto& javaClass = jni::Class<Mapbox>::Singleton(env);
+ auto method = javaClass.GetStaticMethod<jboolean()>(env, "hasInstance");
+ return javaClass.Call(env, method);
+}
+
+void Mapbox::registerNative(jni::JNIEnv& env) {
+ jni::Class<Mapbox>::Singleton(env);
+}
+
+} // namespace android
+} // namespace mbgl
diff --git a/platform/android/src/mapbox.hpp b/platform/android/src/mapbox.hpp
new file mode 100644
index 0000000000..813f5bf174
--- /dev/null
+++ b/platform/android/src/mapbox.hpp
@@ -0,0 +1,19 @@
+#pragma once
+
+#include "asset_manager.hpp"
+
+#include <jni/jni.hpp>
+
+namespace mbgl {
+namespace android {
+
+class Mapbox {
+public:
+ static constexpr auto Name() { return "com/mapbox/mapboxsdk/Mapbox"; };
+ static jboolean hasInstance(jni::JNIEnv&);
+ static jni::Local<jni::Object<AssetManager>> getAssetManager(jni::JNIEnv&);
+ static void registerNative(jni::JNIEnv&);
+};
+
+} // namespace android
+} // namespace mbgl
diff --git a/platform/android/src/native_map_view.cpp b/platform/android/src/native_map_view.cpp
index 7b87693cf5..8cb637fa38 100755..100644
--- a/platform/android/src/native_map_view.cpp
+++ b/platform/android/src/native_map_view.cpp
@@ -40,19 +40,19 @@
#include "style/conversion/filter.hpp"
#include "geojson/feature.hpp"
-#include "jni.hpp"
-#include "attach_env.hpp"
-#include "map_renderer.hpp"
#include "android_renderer_frontend.hpp"
-#include "file_source.hpp"
+#include "attach_env.hpp"
#include "bitmap.hpp"
-#include "run_loop_impl.hpp"
-#include "java/util.hpp"
+#include "bitmap_factory.hpp"
+#include "file_source.hpp"
#include "geometry/lat_lng_bounds.hpp"
+#include "java/util.hpp"
+#include "jni.hpp"
#include "map/camera_position.hpp"
-#include "map/image.hpp"
+#include "map/image.hpp"
+#include "map_renderer.hpp"
+#include "run_loop_impl.hpp"
#include "style/light.hpp"
-#include "bitmap_factory.hpp"
namespace mbgl {
namespace android {
@@ -1050,6 +1050,14 @@ jni::jboolean NativeMapView::getPrefetchTiles(JNIEnv&) {
return jni::jboolean(map->getPrefetchZoomDelta() > 0);
}
+void NativeMapView::setPrefetchZoomDelta(JNIEnv&, jni::jint delta) {
+ map->setPrefetchZoomDelta(uint8_t(delta));
+}
+
+jni::jint NativeMapView::getPrefetchZoomDelta(JNIEnv&) {
+ return jni::jint(map->getPrefetchZoomDelta());
+}
+
mbgl::Map& NativeMapView::getMap() {
return *map;
}
@@ -1063,90 +1071,99 @@ void NativeMapView::registerNative(jni::JNIEnv& env) {
#define METHOD(MethodPtr, name) jni::MakeNativePeerMethod<decltype(MethodPtr), (MethodPtr)>(name)
// Register the peer
- jni::RegisterNativePeer<NativeMapView>(env, javaClass, "nativePtr",
- jni::MakePeer<NativeMapView, const jni::Object<NativeMapView>&, const jni::Object<FileSource>&, const jni::Object<MapRenderer>&, jni::jfloat, jni::jboolean>,
- "nativeInitialize",
- "nativeDestroy",
- METHOD(&NativeMapView::resizeView, "nativeResizeView"),
- METHOD(&NativeMapView::getStyleUrl, "nativeGetStyleUrl"),
- METHOD(&NativeMapView::setStyleUrl, "nativeSetStyleUrl"),
- METHOD(&NativeMapView::getStyleJson, "nativeGetStyleJson"),
- METHOD(&NativeMapView::setStyleJson, "nativeSetStyleJson"),
- METHOD(&NativeMapView::cancelTransitions, "nativeCancelTransitions"),
- METHOD(&NativeMapView::setGestureInProgress, "nativeSetGestureInProgress"),
- METHOD(&NativeMapView::moveBy, "nativeMoveBy"),
- METHOD(&NativeMapView::jumpTo, "nativeJumpTo"),
- METHOD(&NativeMapView::easeTo, "nativeEaseTo"),
- METHOD(&NativeMapView::flyTo, "nativeFlyTo"),
- 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"),
- METHOD(&NativeMapView::setPitch, "nativeSetPitch"),
- METHOD(&NativeMapView::getZoom, "nativeGetZoom"),
- METHOD(&NativeMapView::setZoom, "nativeSetZoom"),
- METHOD(&NativeMapView::resetZoom, "nativeResetZoom"),
- METHOD(&NativeMapView::setMinZoom, "nativeSetMinZoom"),
- METHOD(&NativeMapView::getMinZoom, "nativeGetMinZoom"),
- METHOD(&NativeMapView::setMaxZoom, "nativeSetMaxZoom"),
- METHOD(&NativeMapView::getMaxZoom, "nativeGetMaxZoom"),
- METHOD(&NativeMapView::rotateBy, "nativeRotateBy"),
- METHOD(&NativeMapView::setBearing, "nativeSetBearing"),
- METHOD(&NativeMapView::setBearingXY, "nativeSetBearingXY"),
- METHOD(&NativeMapView::getBearing, "nativeGetBearing"),
- METHOD(&NativeMapView::resetNorth, "nativeResetNorth"),
- METHOD(&NativeMapView::setVisibleCoordinateBounds, "nativeSetVisibleCoordinateBounds"),
- METHOD(&NativeMapView::scheduleSnapshot, "nativeTakeSnapshot"),
- METHOD(&NativeMapView::getCameraPosition, "nativeGetCameraPosition"),
- METHOD(&NativeMapView::updateMarker, "nativeUpdateMarker"),
- METHOD(&NativeMapView::addMarkers, "nativeAddMarkers"),
- METHOD(&NativeMapView::setDebug, "nativeSetDebug"),
- METHOD(&NativeMapView::cycleDebugOptions, "nativeCycleDebugOptions"),
- METHOD(&NativeMapView::getDebug, "nativeGetDebug"),
- METHOD(&NativeMapView::isFullyLoaded, "nativeIsFullyLoaded"),
- METHOD(&NativeMapView::onLowMemory, "nativeOnLowMemory"),
- METHOD(&NativeMapView::getMetersPerPixelAtLatitude, "nativeGetMetersPerPixelAtLatitude"),
- METHOD(&NativeMapView::projectedMetersForLatLng, "nativeProjectedMetersForLatLng"),
- METHOD(&NativeMapView::pixelForLatLng, "nativePixelForLatLng"),
- METHOD(&NativeMapView::latLngForProjectedMeters, "nativeLatLngForProjectedMeters"),
- METHOD(&NativeMapView::latLngForPixel, "nativeLatLngForPixel"),
- METHOD(&NativeMapView::addPolylines, "nativeAddPolylines"),
- METHOD(&NativeMapView::addPolygons, "nativeAddPolygons"),
- METHOD(&NativeMapView::updatePolyline, "nativeUpdatePolyline"),
- METHOD(&NativeMapView::updatePolygon, "nativeUpdatePolygon"),
- METHOD(&NativeMapView::removeAnnotations, "nativeRemoveAnnotations"),
- METHOD(&NativeMapView::addAnnotationIcon, "nativeAddAnnotationIcon"),
- METHOD(&NativeMapView::removeAnnotationIcon, "nativeRemoveAnnotationIcon"),
- METHOD(&NativeMapView::getTopOffsetPixelsForAnnotationSymbol, "nativeGetTopOffsetPixelsForAnnotationSymbol"),
- METHOD(&NativeMapView::getTransitionOptions, "nativeGetTransitionOptions"),
- METHOD(&NativeMapView::setTransitionOptions, "nativeSetTransitionOptions"),
- METHOD(&NativeMapView::queryPointAnnotations, "nativeQueryPointAnnotations"),
- METHOD(&NativeMapView::queryShapeAnnotations, "nativeQueryShapeAnnotations"),
- METHOD(&NativeMapView::queryRenderedFeaturesForPoint, "nativeQueryRenderedFeaturesForPoint"),
- METHOD(&NativeMapView::queryRenderedFeaturesForBox, "nativeQueryRenderedFeaturesForBox"),
- METHOD(&NativeMapView::getLight, "nativeGetLight"),
- METHOD(&NativeMapView::getLayers, "nativeGetLayers"),
- METHOD(&NativeMapView::getLayer, "nativeGetLayer"),
- METHOD(&NativeMapView::addLayer, "nativeAddLayer"),
- METHOD(&NativeMapView::addLayerAbove, "nativeAddLayerAbove"),
- METHOD(&NativeMapView::addLayerAt, "nativeAddLayerAt"),
- METHOD(&NativeMapView::removeLayerAt, "nativeRemoveLayerAt"),
- METHOD(&NativeMapView::removeLayer, "nativeRemoveLayer"),
- METHOD(&NativeMapView::getSources, "nativeGetSources"),
- METHOD(&NativeMapView::getSource, "nativeGetSource"),
- METHOD(&NativeMapView::addSource, "nativeAddSource"),
- METHOD(&NativeMapView::removeSource, "nativeRemoveSource"),
- METHOD(&NativeMapView::addImage, "nativeAddImage"),
- METHOD(&NativeMapView::addImages, "nativeAddImages"),
- METHOD(&NativeMapView::removeImage, "nativeRemoveImage"),
- METHOD(&NativeMapView::getImage, "nativeGetImage"),
- METHOD(&NativeMapView::setLatLngBounds, "nativeSetLatLngBounds"),
- METHOD(&NativeMapView::setPrefetchTiles, "nativeSetPrefetchTiles"),
- METHOD(&NativeMapView::getPrefetchTiles, "nativeGetPrefetchTiles")
- );
+ jni::RegisterNativePeer<NativeMapView>(
+ env,
+ javaClass,
+ "nativePtr",
+ jni::MakePeer<NativeMapView,
+ const jni::Object<NativeMapView>&,
+ const jni::Object<FileSource>&,
+ const jni::Object<MapRenderer>&,
+ jni::jfloat,
+ jni::jboolean>,
+ "nativeInitialize",
+ "nativeDestroy",
+ METHOD(&NativeMapView::resizeView, "nativeResizeView"),
+ METHOD(&NativeMapView::getStyleUrl, "nativeGetStyleUrl"),
+ METHOD(&NativeMapView::setStyleUrl, "nativeSetStyleUrl"),
+ METHOD(&NativeMapView::getStyleJson, "nativeGetStyleJson"),
+ METHOD(&NativeMapView::setStyleJson, "nativeSetStyleJson"),
+ METHOD(&NativeMapView::cancelTransitions, "nativeCancelTransitions"),
+ METHOD(&NativeMapView::setGestureInProgress, "nativeSetGestureInProgress"),
+ METHOD(&NativeMapView::moveBy, "nativeMoveBy"),
+ METHOD(&NativeMapView::jumpTo, "nativeJumpTo"),
+ METHOD(&NativeMapView::easeTo, "nativeEaseTo"),
+ METHOD(&NativeMapView::flyTo, "nativeFlyTo"),
+ 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"),
+ METHOD(&NativeMapView::setPitch, "nativeSetPitch"),
+ METHOD(&NativeMapView::getZoom, "nativeGetZoom"),
+ METHOD(&NativeMapView::setZoom, "nativeSetZoom"),
+ METHOD(&NativeMapView::resetZoom, "nativeResetZoom"),
+ METHOD(&NativeMapView::setMinZoom, "nativeSetMinZoom"),
+ METHOD(&NativeMapView::getMinZoom, "nativeGetMinZoom"),
+ METHOD(&NativeMapView::setMaxZoom, "nativeSetMaxZoom"),
+ METHOD(&NativeMapView::getMaxZoom, "nativeGetMaxZoom"),
+ METHOD(&NativeMapView::rotateBy, "nativeRotateBy"),
+ METHOD(&NativeMapView::setBearing, "nativeSetBearing"),
+ METHOD(&NativeMapView::setBearingXY, "nativeSetBearingXY"),
+ METHOD(&NativeMapView::getBearing, "nativeGetBearing"),
+ METHOD(&NativeMapView::resetNorth, "nativeResetNorth"),
+ METHOD(&NativeMapView::setVisibleCoordinateBounds, "nativeSetVisibleCoordinateBounds"),
+ METHOD(&NativeMapView::scheduleSnapshot, "nativeTakeSnapshot"),
+ METHOD(&NativeMapView::getCameraPosition, "nativeGetCameraPosition"),
+ METHOD(&NativeMapView::updateMarker, "nativeUpdateMarker"),
+ METHOD(&NativeMapView::addMarkers, "nativeAddMarkers"),
+ METHOD(&NativeMapView::setDebug, "nativeSetDebug"),
+ METHOD(&NativeMapView::cycleDebugOptions, "nativeCycleDebugOptions"),
+ METHOD(&NativeMapView::getDebug, "nativeGetDebug"),
+ METHOD(&NativeMapView::isFullyLoaded, "nativeIsFullyLoaded"),
+ METHOD(&NativeMapView::onLowMemory, "nativeOnLowMemory"),
+ METHOD(&NativeMapView::getMetersPerPixelAtLatitude, "nativeGetMetersPerPixelAtLatitude"),
+ METHOD(&NativeMapView::projectedMetersForLatLng, "nativeProjectedMetersForLatLng"),
+ METHOD(&NativeMapView::pixelForLatLng, "nativePixelForLatLng"),
+ METHOD(&NativeMapView::latLngForProjectedMeters, "nativeLatLngForProjectedMeters"),
+ METHOD(&NativeMapView::latLngForPixel, "nativeLatLngForPixel"),
+ METHOD(&NativeMapView::addPolylines, "nativeAddPolylines"),
+ METHOD(&NativeMapView::addPolygons, "nativeAddPolygons"),
+ METHOD(&NativeMapView::updatePolyline, "nativeUpdatePolyline"),
+ METHOD(&NativeMapView::updatePolygon, "nativeUpdatePolygon"),
+ METHOD(&NativeMapView::removeAnnotations, "nativeRemoveAnnotations"),
+ METHOD(&NativeMapView::addAnnotationIcon, "nativeAddAnnotationIcon"),
+ METHOD(&NativeMapView::removeAnnotationIcon, "nativeRemoveAnnotationIcon"),
+ METHOD(&NativeMapView::getTopOffsetPixelsForAnnotationSymbol, "nativeGetTopOffsetPixelsForAnnotationSymbol"),
+ METHOD(&NativeMapView::getTransitionOptions, "nativeGetTransitionOptions"),
+ METHOD(&NativeMapView::setTransitionOptions, "nativeSetTransitionOptions"),
+ METHOD(&NativeMapView::queryPointAnnotations, "nativeQueryPointAnnotations"),
+ METHOD(&NativeMapView::queryShapeAnnotations, "nativeQueryShapeAnnotations"),
+ METHOD(&NativeMapView::queryRenderedFeaturesForPoint, "nativeQueryRenderedFeaturesForPoint"),
+ METHOD(&NativeMapView::queryRenderedFeaturesForBox, "nativeQueryRenderedFeaturesForBox"),
+ METHOD(&NativeMapView::getLight, "nativeGetLight"),
+ METHOD(&NativeMapView::getLayers, "nativeGetLayers"),
+ METHOD(&NativeMapView::getLayer, "nativeGetLayer"),
+ METHOD(&NativeMapView::addLayer, "nativeAddLayer"),
+ METHOD(&NativeMapView::addLayerAbove, "nativeAddLayerAbove"),
+ METHOD(&NativeMapView::addLayerAt, "nativeAddLayerAt"),
+ METHOD(&NativeMapView::removeLayerAt, "nativeRemoveLayerAt"),
+ METHOD(&NativeMapView::removeLayer, "nativeRemoveLayer"),
+ METHOD(&NativeMapView::getSources, "nativeGetSources"),
+ METHOD(&NativeMapView::getSource, "nativeGetSource"),
+ METHOD(&NativeMapView::addSource, "nativeAddSource"),
+ METHOD(&NativeMapView::removeSource, "nativeRemoveSource"),
+ METHOD(&NativeMapView::addImage, "nativeAddImage"),
+ METHOD(&NativeMapView::addImages, "nativeAddImages"),
+ METHOD(&NativeMapView::removeImage, "nativeRemoveImage"),
+ METHOD(&NativeMapView::getImage, "nativeGetImage"),
+ METHOD(&NativeMapView::setLatLngBounds, "nativeSetLatLngBounds"),
+ METHOD(&NativeMapView::setPrefetchTiles, "nativeSetPrefetchTiles"),
+ METHOD(&NativeMapView::getPrefetchTiles, "nativeGetPrefetchTiles"),
+ METHOD(&NativeMapView::setPrefetchZoomDelta, "nativeSetPrefetchZoomDelta"),
+ METHOD(&NativeMapView::getPrefetchZoomDelta, "nativeGetPrefetchZoomDelta"));
}
} // namespace android
diff --git a/platform/android/src/native_map_view.hpp b/platform/android/src/native_map_view.hpp
index ba2178022e..5a340d1287 100755
--- a/platform/android/src/native_map_view.hpp
+++ b/platform/android/src/native_map_view.hpp
@@ -235,6 +235,10 @@ public:
jni::jboolean getPrefetchTiles(JNIEnv&);
+ void setPrefetchZoomDelta(JNIEnv&, jni::jint);
+
+ jni::jint getPrefetchZoomDelta(JNIEnv&);
+
mbgl::Map& getMap();
private:
diff --git a/platform/android/src/style/sources/geojson_source.cpp b/platform/android/src/style/sources/geojson_source.cpp
index 5ff4864275..0eece4b1ad 100644
--- a/platform/android/src/style/sources/geojson_source.cpp
+++ b/platform/android/src/style/sources/geojson_source.cpp
@@ -44,18 +44,16 @@ namespace android {
}
GeoJSONSource::GeoJSONSource(jni::JNIEnv& env, const jni::String& sourceId, const jni::Object<>& options)
- : Source(env, std::make_unique<mbgl::style::GeoJSONSource>(
- jni::Make<std::string>(env, sourceId),
- convertGeoJSONOptions(env, options)))
- , converter(std::make_unique<Actor<FeatureConverter>>(Scheduler::GetBackground())) {
- }
+ : Source(env,
+ std::make_unique<mbgl::style::GeoJSONSource>(jni::Make<std::string>(env, sourceId),
+ convertGeoJSONOptions(env, options))),
+ converter(std::make_unique<Actor<FeatureConverter>>(Scheduler::GetBackground(),
+ source.as<style::GeoJSONSource>()->getOptions())) {}
- GeoJSONSource::GeoJSONSource(jni::JNIEnv& env,
- mbgl::style::Source& coreSource,
- AndroidRendererFrontend& frontend)
- : Source(env, coreSource, createJavaPeer(env), frontend)
- , converter(std::make_unique<Actor<FeatureConverter>>(Scheduler::GetBackground())) {
- }
+ GeoJSONSource::GeoJSONSource(jni::JNIEnv& env, mbgl::style::Source& coreSource, AndroidRendererFrontend& frontend)
+ : Source(env, coreSource, createJavaPeer(env), frontend),
+ converter(std::make_unique<Actor<FeatureConverter>>(Scheduler::GetBackground(),
+ source.as<style::GeoJSONSource>()->getOptions())) {}
GeoJSONSource::~GeoJSONSource() = default;
@@ -63,7 +61,7 @@ namespace android {
std::shared_ptr<std::string> json = std::make_shared<std::string>(jni::Make<std::string>(env, jString));
- Update::Converter converterFn = [this, json](ActorRef<Callback> _callback) {
+ Update::Converter converterFn = [this, json](ActorRef<GeoJSONDataCallback> _callback) {
converter->self().invoke(&FeatureConverter::convertJson, json, _callback);
};
@@ -84,11 +82,11 @@ namespace android {
void GeoJSONSource::setURL(jni::JNIEnv& env, const jni::String& url) {
// Update the core source
- source.as<mbgl::style::GeoJSONSource>()->GeoJSONSource::setURL(jni::Make<std::string>(env, url));
+ source.as<style::GeoJSONSource>()->setURL(jni::Make<std::string>(env, url));
}
jni::Local<jni::String> GeoJSONSource::getURL(jni::JNIEnv& env) {
- optional<std::string> url = source.as<mbgl::style::GeoJSONSource>()->GeoJSONSource::getURL();
+ optional<std::string> url = source.as<style::GeoJSONSource>()->getURL();
return url ? jni::Make<jni::String>(env, *url) : jni::Local<jni::String>();
}
@@ -166,7 +164,7 @@ namespace android {
auto global = jni::NewGlobal<jni::EnvAttachingDeleter>(env, jObject);
auto object = std::make_shared<decltype(global)>(std::move(global));
- Update::Converter converterFn = [this, object](ActorRef<Callback> _callback) {
+ Update::Converter converterFn = [this, object](ActorRef<GeoJSONDataCallback> _callback) {
converter->self().invoke(&FeatureConverter::convertObject<JNIType>, object, _callback);
};
@@ -175,25 +173,23 @@ namespace android {
void GeoJSONSource::setAsync(Update::Converter converterFn) {
awaitingUpdate = std::make_unique<Update>(
- std::move(converterFn),
- std::make_unique<Actor<Callback>>(
- *Scheduler::GetCurrent(),
- [this](GeoJSON geoJSON) {
- // conversion from Java features to core ones finished
- android::UniqueEnv _env = android::AttachEnv();
-
- // Update the core source
- source.as<mbgl::style::GeoJSONSource>()->GeoJSONSource::setGeoJSON(geoJSON);
-
- // if there is an awaiting update, execute it, otherwise, release resources
- if (awaitingUpdate) {
- update = std::move(awaitingUpdate);
- update->converterFn(update->callback->self());
- } else {
- update.reset();
- }
- })
- );
+ std::move(converterFn),
+ std::make_unique<Actor<GeoJSONDataCallback>>(
+ *Scheduler::GetCurrent(), [this](std::shared_ptr<style::GeoJSONData> geoJSONData) {
+ // conversion from Java features to core ones finished
+ android::UniqueEnv _env = android::AttachEnv();
+
+ // Update the core source
+ source.as<mbgl::style::GeoJSONSource>()->setGeoJSONData(std::move(geoJSONData));
+
+ // if there is an awaiting update, execute it, otherwise, release resources
+ if (awaitingUpdate) {
+ update = std::move(awaitingUpdate);
+ update->converterFn(update->callback->self());
+ } else {
+ update.reset();
+ }
+ }));
// If another update is running, wait
if (update) {
@@ -230,12 +226,10 @@ namespace android {
);
}
- void FeatureConverter::convertJson(std::shared_ptr<std::string> json,
- ActorRef<Callback> callback) {
+ void FeatureConverter::convertJson(std::shared_ptr<std::string> json, ActorRef<GeoJSONDataCallback> callback) {
using namespace mbgl::style::conversion;
android::UniqueEnv _env = android::AttachEnv();
-
// Convert the jni object
Error error;
optional<GeoJSON> converted = parseGeoJSON(*json, error);
@@ -243,23 +237,23 @@ namespace android {
mbgl::Log::Error(mbgl::Event::JNI, "Error setting geo json: " + error.message);
return;
}
-
- callback.invoke(&Callback::operator(), *converted);
+ callback.invoke(&GeoJSONDataCallback::operator(), style::GeoJSONData::create(*converted, options));
}
template <class JNIType>
- void FeatureConverter::convertObject(std::shared_ptr<jni::Global<jni::Object<JNIType>, jni::EnvAttachingDeleter>> jObject, ActorRef<Callback> callback) {
+ void FeatureConverter::convertObject(
+ std::shared_ptr<jni::Global<jni::Object<JNIType>, jni::EnvAttachingDeleter>> jObject,
+ ActorRef<GeoJSONDataCallback> callback) {
using namespace mbgl::android::geojson;
android::UniqueEnv _env = android::AttachEnv();
// Convert the jni object
auto geometry = JNIType::convert(*_env, *jObject);
- callback.invoke(&Callback::operator(), GeoJSON(geometry));
+ callback.invoke(&GeoJSONDataCallback::operator(), style::GeoJSONData::create(geometry, options));
}
- Update::Update(Converter _converterFn, std::unique_ptr<Actor<Callback>> _callback)
- : converterFn(std::move(_converterFn))
- , callback(std::move(_callback)) {}
+ Update::Update(Converter _converterFn, std::unique_ptr<Actor<GeoJSONDataCallback>> _callback)
+ : converterFn(std::move(_converterFn)), callback(std::move(_callback)) {}
} // namespace android
} // namespace mbgl
diff --git a/platform/android/src/style/sources/geojson_source.hpp b/platform/android/src/style/sources/geojson_source.hpp
index e737e41924..e506191ceb 100644
--- a/platform/android/src/style/sources/geojson_source.hpp
+++ b/platform/android/src/style/sources/geojson_source.hpp
@@ -11,22 +11,28 @@
namespace mbgl {
namespace android {
-using Callback = std::function<void (GeoJSON)>;
+using GeoJSONDataCallback = std::function<void(std::shared_ptr<style::GeoJSONData>)>;
-struct FeatureConverter {
- void convertJson(std::shared_ptr<std::string>, ActorRef<Callback>);
+class FeatureConverter {
+public:
+ explicit FeatureConverter(style::GeoJSONOptions options_) : options(std::move(options_)) {}
+ void convertJson(std::shared_ptr<std::string>, ActorRef<GeoJSONDataCallback>);
template <class JNIType>
- void convertObject(std::shared_ptr<jni::Global<jni::Object<JNIType>, jni::EnvAttachingDeleter>>, ActorRef<Callback>);
+ void convertObject(std::shared_ptr<jni::Global<jni::Object<JNIType>, jni::EnvAttachingDeleter>>,
+ ActorRef<GeoJSONDataCallback>);
+
+private:
+ style::GeoJSONOptions options;
};
struct Update {
- using Converter = std::function<void (ActorRef<Callback>)>;
+ using Converter = std::function<void(ActorRef<GeoJSONDataCallback>)>;
Converter converterFn;
- std::unique_ptr<Actor<Callback>> callback;
+ std::unique_ptr<Actor<GeoJSONDataCallback>> callback;
- Update(Converter, std::unique_ptr<Actor<Callback>>);
+ Update(Converter, std::unique_ptr<Actor<GeoJSONDataCallback>>);
};
class GeoJSONSource : public Source {
diff --git a/platform/android/src/test/render_test_collator.cpp b/platform/android/src/test/render_test_collator.cpp
new file mode 100644
index 0000000000..e6e3b27b48
--- /dev/null
+++ b/platform/android/src/test/render_test_collator.cpp
@@ -0,0 +1,40 @@
+#include <mbgl/i18n/collator.hpp>
+
+#include <memory>
+
+namespace mbgl {
+namespace platform {
+
+class Collator::Impl {
+public:
+ Impl(bool caseSensitive_, bool diacriticSensitive_, optional<std::string>)
+ : caseSensitive(caseSensitive_), diacriticSensitive(diacriticSensitive_) {}
+
+ bool operator==(const Impl& other) const { return true; }
+
+ int compare(const std::string& lhs, const std::string& rhs) const { return 0; }
+
+ std::string resolvedLocale() const { return ""; }
+
+private:
+ bool caseSensitive;
+ bool diacriticSensitive;
+};
+
+Collator::Collator(bool caseSensitive, bool diacriticSensitive, optional<std::string> locale_)
+ : impl(std::make_shared<Impl>(caseSensitive, diacriticSensitive, std::move(locale_))) {}
+
+int Collator::compare(const std::string& lhs, const std::string& rhs) const {
+ return impl->compare(lhs, rhs);
+}
+
+bool Collator::operator==(const Collator& other) const {
+ return *impl == *(other.impl);
+}
+
+std::string Collator::resolvedLocale() const {
+ return impl->resolvedLocale();
+}
+
+} // namespace platform
+} // namespace mbgl
diff --git a/platform/android/src/test/render_test_number_format.cpp b/platform/android/src/test/render_test_number_format.cpp
new file mode 100644
index 0000000000..57710c8558
--- /dev/null
+++ b/platform/android/src/test/render_test_number_format.cpp
@@ -0,0 +1,15 @@
+#include <mbgl/i18n/number_format.hpp>
+
+namespace mbgl {
+namespace platform {
+
+std::string formatNumber(double /*number*/,
+ const std::string& /*localeId */,
+ const std::string& /*currency*/,
+ uint8_t /*minFractionDigits*/,
+ uint8_t /*maxFractionDigits*/) {
+ return "";
+}
+
+} // namespace platform
+} // namespace mbgl
diff --git a/platform/android/src/test/render_test_runner.cpp b/platform/android/src/test/render_test_runner.cpp
new file mode 100644
index 0000000000..d4554aa9de
--- /dev/null
+++ b/platform/android/src/test/render_test_runner.cpp
@@ -0,0 +1,57 @@
+#include <android_native_app_glue.h>
+#include <mbgl/render_test.hpp>
+#include "jni.hpp"
+#include "logger.hpp"
+
+#include <string>
+#include <vector>
+
+#include <mbgl/util/logging.hpp>
+
+#include <android/log.h>
+
+namespace mbgl {
+
+namespace {
+
+int severityToPriority(EventSeverity severity) {
+ switch (severity) {
+ case EventSeverity::Debug:
+ return ANDROID_LOG_DEBUG;
+
+ case EventSeverity::Info:
+ return ANDROID_LOG_INFO;
+
+ case EventSeverity::Warning:
+ return ANDROID_LOG_WARN;
+
+ case EventSeverity::Error:
+ return ANDROID_LOG_ERROR;
+
+ default:
+ return ANDROID_LOG_VERBOSE;
+ }
+}
+
+} // namespace
+
+void Log::platformRecord(EventSeverity severity, const std::string& msg) {
+ __android_log_print(severityToPriority(severity), "mbgl", "%s", msg.c_str());
+}
+
+} // namespace mbgl
+
+void android_main(struct android_app* app) {
+ mbgl::android::theJVM = app->activity->vm;
+ JNIEnv* env;
+ app->activity->vm->AttachCurrentThread(&env, NULL);
+
+ std::vector<std::string> arguments = {"mbgl-render-test-runner", "-p", "/sdcard/render-test/android-manifest.json"};
+ std::vector<char*> argv;
+ for (const auto& arg : arguments) {
+ argv.push_back((char*)arg.data());
+ }
+ argv.push_back(nullptr);
+ (void)mbgl::runRenderTests(argv.size() - 1, argv.data());
+ app->activity->vm->DetachCurrentThread();
+} \ No newline at end of file
diff --git a/platform/android/src/timer.cpp b/platform/android/src/timer.cpp
index a45c48702e..1d3d05c843 100644
--- a/platform/android/src/timer.cpp
+++ b/platform/android/src/timer.cpp
@@ -3,6 +3,7 @@
#include <mbgl/util/run_loop.hpp>
#include <mbgl/util/timer.hpp>
+#include <atomic>
#include <functional>
namespace mbgl {
@@ -10,7 +11,7 @@ namespace util {
class Timer::Impl : public RunLoop::Impl::Runnable {
public:
- Impl() = default;
+ Impl() : active(false) {}
~Impl() {
stop();
@@ -25,9 +26,11 @@ public:
due = (timeout == Duration::max()) ? std::chrono::time_point<Clock>::max() : Clock::now() +
timeout;
loop->addRunnable(this);
+ active = true;
}
void stop() {
+ active = false;
loop->removeRunnable(this);
}
@@ -45,8 +48,10 @@ public:
}
void runTask() override {
- reschedule();
- task();
+ if (active) {
+ reschedule();
+ task();
+ }
}
private:
@@ -56,6 +61,7 @@ private:
RunLoop::Impl* loop = reinterpret_cast<RunLoop::Impl*>(RunLoop::getLoopHandle());
std::function<void()> task;
+ std::atomic<bool> active;
};
Timer::Timer() : impl(std::make_unique<Impl>()) {
diff --git a/platform/android/src/unaccent.cpp b/platform/android/src/unaccent.cpp
deleted file mode 100644
index 8da0ce4931..0000000000
--- a/platform/android/src/unaccent.cpp
+++ /dev/null
@@ -1,18 +0,0 @@
-#include <mbgl/text/unaccent.hpp>
-#include <string>
-#include "attach_env.hpp"
-#include "text/collator_jni.hpp"
-#include <jni/jni.hpp>
-
-namespace mbgl {
-namespace platform {
-
-std::string unaccent(const std::string& str) {
- android::UniqueEnv env = android::AttachEnv();
- jni::Local<jni::String> input = jni::Make<jni::String>(*env, str);
- jni::Local<jni::String> unaccented = android::StringUtils::unaccent(*env, input);
- return jni::Make<std::string>(*env, unaccented);
-}
-
-} // namespace platform
-} // namespace mbgl
diff --git a/platform/darwin/scripts/generate-style-code.js b/platform/darwin/scripts/generate-style-code.js
index 38066c9f43..75dbdf367c 100755
--- a/platform/darwin/scripts/generate-style-code.js
+++ b/platform/darwin/scripts/generate-style-code.js
@@ -159,6 +159,7 @@ global.objCTestValue = function (property, layerType, arraysAsStructs, indent) {
`@"'${_.startCase(propertyName)}'"` :
`@"${_.startCase(propertyName)}"`;
case 'string':
+ case 'image': // TODO: replace once we implement image expressions
return `@"'${_.startCase(propertyName)}'"`;
case 'enum':
return `@"'${_.last(_.keys(property.values))}'"`;
@@ -208,6 +209,7 @@ global.mbglTestValue = function (property, layerType) {
return '1.0';
case 'formatted':
case 'string':
+ case 'image': // TODO: replace once we implement image expressions
return `"${_.startCase(propertyName)}"`;
case 'enum': {
let type = camelize(originalPropertyName(property));
@@ -294,6 +296,7 @@ global.testHelperMessage = function (property, layerType, isFunction) {
return 'testNumber' + fnSuffix;
case 'formatted':
case 'string':
+ case 'image': // TODO: replace once we implement image expressions
return 'testString' + fnSuffix;
case 'enum':
let objCType = global.objCType(layerType, property.name);
@@ -474,6 +477,7 @@ global.describeType = function (property) {
return 'numeric';
case 'formatted':
case 'string':
+ case 'image': // TODO: replace once we implement image expressions
return 'string';
case 'enum':
return '`MGL' + camelize(property.name) + '`';
@@ -522,6 +526,7 @@ global.describeValue = function (value, property, layerType) {
return 'the float ' + '`' + formatNumber(value) + '`';
case 'formatted':
case 'string':
+ case 'image': // TODO: replace once we implement image expressions
if (value === '') {
return 'the empty string';
}
@@ -608,6 +613,7 @@ global.propertyType = function (property) {
return 'NSNumber *';
case 'formatted':
case 'string':
+ case 'image': // TODO: replace once we implement image expressions
return 'NSString *';
case 'enum':
return 'NSValue *';
@@ -640,6 +646,7 @@ global.isInterpolatable = function (property) {
return type !== 'boolean' &&
type !== 'enum' &&
type !== 'string' &&
+ type !== 'image' &&
type !== 'formatted';
};
@@ -653,6 +660,7 @@ global.valueTransformerArguments = function (property) {
case 'formatted':
return ['mbgl::style::expression::Formatted', objCType];
case 'string':
+ case 'image': // TODO: replace once we implement image expressions
return ['std::string', objCType];
case 'enum':
return [mbglType(property), 'NSValue *', mbglType(property), `MGL${camelize(property.name)}`];
@@ -692,6 +700,7 @@ global.mbglType = function(property) {
case 'formatted':
return 'mbgl::style::expression::Formatted';
case 'string':
+ case 'image': // TODO: replace once we implement image expressions
return 'std::string';
case 'enum': {
let type = camelize(originalPropertyName(property));
diff --git a/platform/darwin/src/MGLComputedShapeSource.mm b/platform/darwin/src/MGLComputedShapeSource.mm
index a04181af2f..ceb83b3740 100644
--- a/platform/darwin/src/MGLComputedShapeSource.mm
+++ b/platform/darwin/src/MGLComputedShapeSource.mm
@@ -140,7 +140,7 @@ mbgl::style::CustomGeometrySource::Options MBGLCustomGeometrySourceOptionsFromDi
@"This will be logged only once.");
});
}
- mbgl::Feature geoJsonObject = [feature geoJSONObject].get<mbgl::Feature>();
+ mbgl::GeoJSONFeature geoJsonObject = [feature geoJSONObject].get<mbgl::GeoJSONFeature>();
featureCollection.push_back(geoJsonObject);
}
const auto geojson = mbgl::GeoJSON{featureCollection};
@@ -204,7 +204,7 @@ mbgl::style::CustomGeometrySource::Options MBGLCustomGeometrySourceOptionsFromDi
mbgl::FeatureCollection featureCollection;
featureCollection.reserve(features.count);
for (MGLShape <MGLFeature> * feature in features) {
- mbgl::Feature geoJsonObject = [feature geoJSONObject].get<mbgl::Feature>();
+ mbgl::GeoJSONFeature geoJsonObject = [feature geoJSONObject].get<mbgl::GeoJSONFeature>();
featureCollection.push_back(geoJsonObject);
if ([feature isMemberOfClass:[MGLShapeCollection class]]) {
static dispatch_once_t onceToken;
diff --git a/platform/darwin/src/MGLFeature.mm b/platform/darwin/src/MGLFeature.mm
index fbf262af29..df6b1bffea 100644
--- a/platform/darwin/src/MGLFeature.mm
+++ b/platform/darwin/src/MGLFeature.mm
@@ -336,8 +336,8 @@ MGL_DEFINE_FEATURE_ATTRIBUTES_GETTER();
featureCollection.reserve(self.shapes.count);
for (MGLShape <MGLFeature> *feature in self.shapes) {
auto geoJSONObject = feature.geoJSONObject;
- MGLAssert(geoJSONObject.is<mbgl::Feature>(), @"Feature collection must only contain features.");
- featureCollection.push_back(geoJSONObject.get<mbgl::Feature>());
+ MGLAssert(geoJSONObject.is<mbgl::GeoJSONFeature>(), @"Feature collection must only contain features.");
+ featureCollection.push_back(geoJSONObject.get<mbgl::GeoJSONFeature>());
}
return featureCollection;
}
@@ -470,7 +470,7 @@ public:
return shape;
}
- MGLShape <MGLFeature> * operator()(const mbgl::Feature &feature) const {
+ MGLShape <MGLFeature> * operator()(const mbgl::GeoJSONFeature &feature) const {
MGLShape <MGLFeature> *shape = (MGLShape <MGLFeature> *)MGLFeatureFromMBGLFeature(feature);
return shape;
}
@@ -487,12 +487,20 @@ public:
NSArray<MGLShape <MGLFeature> *> *MGLFeaturesFromMBGLFeatures(const std::vector<mbgl::Feature> &features) {
NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:features.size()];
for (const auto &feature : features) {
+ [shapes addObject:MGLFeatureFromMBGLFeature(static_cast<mbgl::GeoJSONFeature>(feature))];
+ }
+ return shapes;
+}
+
+NSArray<MGLShape <MGLFeature> *> *MGLFeaturesFromMBGLFeatures(const std::vector<mbgl::GeoJSONFeature> &features) {
+ NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:features.size()];
+ for (const auto &feature : features) {
[shapes addObject:MGLFeatureFromMBGLFeature(feature)];
}
return shapes;
}
-id <MGLFeature> MGLFeatureFromMBGLFeature(const mbgl::Feature &feature) {
+id <MGLFeature> MGLFeatureFromMBGLFeature(const mbgl::GeoJSONFeature &feature) {
NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithCapacity:feature.properties.size()];
for (auto &pair : feature.properties) {
auto &value = pair.second;
@@ -515,7 +523,7 @@ MGLShape* MGLShapeFromGeoJSON(const mapbox::geojson::geojson &geojson) {
return shape;
}
-mbgl::Feature mbglFeature(mbgl::Feature feature, id identifier, NSDictionary *attributes)
+mbgl::GeoJSONFeature mbglFeature(mbgl::GeoJSONFeature feature, id identifier, NSDictionary *attributes)
{
if (identifier) {
NSExpression *identifierExpression = [NSExpression expressionForConstantValue:identifier];
diff --git a/platform/darwin/src/MGLFeature_Private.h b/platform/darwin/src/MGLFeature_Private.h
index 9b0e16f4b9..599633dd31 100644
--- a/platform/darwin/src/MGLFeature_Private.h
+++ b/platform/darwin/src/MGLFeature_Private.h
@@ -16,10 +16,17 @@ MGL_EXPORT
NSArray<MGLShape <MGLFeature> *> *MGLFeaturesFromMBGLFeatures(const std::vector<mbgl::Feature> &features);
/**
- Returns an `MGLFeature` object converted from the given mbgl::Feature
+ Returns an array of `MGLFeature` objects converted from the given vector of
+ vector tile features.
+ */
+MGL_EXPORT
+NSArray<MGLShape <MGLFeature> *> *MGLFeaturesFromMBGLFeatures(const std::vector<mbgl::GeoJSONFeature> &features);
+
+/**
+ Returns an `MGLFeature` object converted from the given mbgl::GeoJSONFeature
*/
MGL_EXPORT
-id <MGLFeature> MGLFeatureFromMBGLFeature(const mbgl::Feature &feature);
+id <MGLFeature> MGLFeatureFromMBGLFeature(const mbgl::GeoJSONFeature &feature);
/**
Returns an `MGLShape` representing the given geojson. The shape can be
@@ -28,11 +35,11 @@ id <MGLFeature> MGLFeatureFromMBGLFeature(const mbgl::Feature &feature);
MGLShape* MGLShapeFromGeoJSON(const mapbox::geojson::geojson &geojson);
/**
- Takes an `mbgl::Feature` object, an identifer, and attributes dictionary and
+ Takes an `mbgl::GeoJSONFeature` object, an identifer, and attributes dictionary and
returns the feature object with converted `mbgl::FeatureIdentifier` and
`mbgl::PropertyMap` properties.
*/
-mbgl::Feature mbglFeature(mbgl::Feature feature, id identifier, NSDictionary * attributes);
+mbgl::GeoJSONFeature mbglFeature(mbgl::GeoJSONFeature feature, id identifier, NSDictionary * attributes);
/**
Returns an `NSDictionary` representation of an `MGLFeature`.
diff --git a/platform/darwin/src/MGLImageSource.mm b/platform/darwin/src/MGLImageSource.mm
index 421cc7a155..2d2f090079 100644
--- a/platform/darwin/src/MGLImageSource.mm
+++ b/platform/darwin/src/MGLImageSource.mm
@@ -1,6 +1,7 @@
#import "MGLImageSource.h"
#import "MGLGeometry_Private.h"
+#import "MGLLoggingConfiguration_Private.h"
#import "MGLSource_Private.h"
#import "MGLTileSource_Private.h"
#import "NSURL+MGLAdditions.h"
@@ -99,7 +100,11 @@
}
- (NSString *)attributionHTMLString {
- MGLAssertStyleSourceIsValid();
+ if (!self.rawSource) {
+ MGLAssert(0, @"Source with identifier `%@` was invalidated after a style change", self.identifier);
+ return nil;
+ }
+
auto attribution = self.rawSource->getAttribution();
return attribution ? @(attribution->c_str()) : nil;
}
diff --git a/platform/darwin/src/MGLMapSnapshotter.h b/platform/darwin/src/MGLMapSnapshotter.h
index 33febe0d0c..98c8e8a375 100644
--- a/platform/darwin/src/MGLMapSnapshotter.h
+++ b/platform/darwin/src/MGLMapSnapshotter.h
@@ -19,6 +19,31 @@ MGL_EXPORT
*/
@property (nonatomic, readonly) CGContextRef context;
+#if TARGET_OS_IPHONE
+/**
+ Converts the specified map coordinate to a point in the coordinate space of the
+ context.
+ */
+- (CGPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate;
+
+/**
+ Converts the specified context point to a map coordinate.
+ */
+- (CLLocationCoordinate2D)coordinateForPoint:(CGPoint)point;
+
+#else
+/**
+ Converts the specified map coordinate to a point in the coordinate space of the
+ context.
+ */
+- (NSPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate;
+
+/**
+ Converts the specified context point to a map coordinate.
+ */
+- (CLLocationCoordinate2D)coordinateForPoint:(NSPoint)point;
+#endif
+
@end
/**
diff --git a/platform/darwin/src/MGLMapSnapshotter.mm b/platform/darwin/src/MGLMapSnapshotter.mm
index 85619a780b..0dde94292c 100644
--- a/platform/darwin/src/MGLMapSnapshotter.mm
+++ b/platform/darwin/src/MGLMapSnapshotter.mm
@@ -17,6 +17,8 @@
#import "MGLAttributionInfo_Private.h"
#import "MGLLoggingConfiguration_Private.h"
#import "MGLRendererConfiguration.h"
+#import "MGLMapSnapshotter_Private.h"
+
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
#import "MGLMapboxEvents.h"
#endif
@@ -33,23 +35,61 @@ const CGPoint MGLLogoImagePosition = CGPointMake(8, 8);
const CGFloat MGLSnapshotterMinimumPixelSize = 64;
-@interface MGLMapSnapshotOverlay()
-
-- (instancetype)initWithContext:(CGContextRef)context;
+@interface MGLMapSnapshotOverlay() <MGLMapSnapshotProtocol>
+@property (nonatomic, assign) CGFloat scale;
+- (instancetype)initWithContext:(CGContextRef)context scale:(CGFloat)scale pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn latLngForFn:(mbgl::MapSnapshotter::LatLngForFn)latLngForFn;
@end
-@implementation MGLMapSnapshotOverlay
+@implementation MGLMapSnapshotOverlay {
+ mbgl::MapSnapshotter::PointForFn _pointForFn;
+ mbgl::MapSnapshotter::LatLngForFn _latLngForFn;
+}
-- (instancetype) initWithContext:(CGContextRef)context {
+- (instancetype) initWithContext:(CGContextRef)context scale:(CGFloat)scale pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn latLngForFn:(mbgl::MapSnapshotter::LatLngForFn)latLngForFn {
self = [super init];
if (self) {
_context = context;
+ _scale = scale;
+ _pointForFn = pointForFn;
+ _latLngForFn = latLngForFn;
}
return self;
}
+#if TARGET_OS_IPHONE
+
+- (CGPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate
+{
+ mbgl::ScreenCoordinate sc = _pointForFn(MGLLatLngFromLocationCoordinate2D(coordinate));
+ return CGPointMake(sc.x, sc.y);
+}
+
+- (CLLocationCoordinate2D)coordinateForPoint:(CGPoint)point
+{
+ mbgl::LatLng latLng = _latLngForFn(mbgl::ScreenCoordinate(point.x, point.y));
+ return MGLLocationCoordinate2DFromLatLng(latLng);
+}
+
+#else
+
+- (NSPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate
+{
+ mbgl::ScreenCoordinate sc = _pointForFn(MGLLatLngFromLocationCoordinate2D(coordinate));
+ CGFloat height = ((CGFloat)CGBitmapContextGetHeight(self.context))/self.scale;
+ return NSMakePoint(sc.x, height - sc.y);
+}
+
+- (CLLocationCoordinate2D)coordinateForPoint:(NSPoint)point
+{
+ CGFloat height = ((CGFloat)CGBitmapContextGetHeight(self.context))/self.scale;
+ auto screenCoord = mbgl::ScreenCoordinate(point.x, height - point.y);
+ mbgl::LatLng latLng = _latLngForFn(screenCoord);
+ return MGLLocationCoordinate2DFromLatLng(latLng);
+}
+
+#endif
@end
@implementation MGLMapSnapshotOptions
@@ -78,7 +118,7 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64;
@end
-@interface MGLMapSnapshot()
+@interface MGLMapSnapshot() <MGLMapSnapshotProtocol>
- (instancetype)initWithImage:(nullable MGLImage *)image scale:(CGFloat)scale pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn latLngForFn:(mbgl::MapSnapshotter::LatLngForFn)latLngForFn;
@property (nonatomic) CGFloat scale;
@@ -93,8 +133,8 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64;
{
self = [super init];
if (self) {
- _pointForFn = std::move(pointForFn);
- _latLngForFn = std::move(latLngForFn);
+ _pointForFn = pointForFn;
+ _latLngForFn = latLngForFn;
_scale = scale;
_image = image;
}
@@ -328,7 +368,10 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64;
CGContextRef currentContext = UIGraphicsGetCurrentContext();
if (currentContext && overlayHandler) {
- MGLMapSnapshotOverlay *snapshotOverlay = [[MGLMapSnapshotOverlay alloc] initWithContext:currentContext];
+ MGLMapSnapshotOverlay *snapshotOverlay = [[MGLMapSnapshotOverlay alloc] initWithContext:currentContext
+ scale:scale
+ pointForFn:pointForFn
+ latLngForFn:latLngForFn];
CGContextSaveGState(snapshotOverlay.context);
overlayHandler(snapshotOverlay);
CGContextRestoreGState(snapshotOverlay.context);
@@ -409,7 +452,10 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64;
NSGraphicsContext *currentContext = [NSGraphicsContext currentContext];
if (currentContext && overlayHandler) {
- MGLMapSnapshotOverlay *snapshotOverlay = [[MGLMapSnapshotOverlay alloc] initWithContext:currentContext.CGContext];
+ MGLMapSnapshotOverlay *snapshotOverlay = [[MGLMapSnapshotOverlay alloc] initWithContext:currentContext.CGContext
+ scale:scale
+ pointForFn:pointForFn
+ latLngForFn:latLngForFn];
[currentContext saveGraphicsState];
overlayHandler(snapshotOverlay);
[currentContext restoreGraphicsState];
diff --git a/platform/darwin/src/MGLMapSnapshotter_Private.h b/platform/darwin/src/MGLMapSnapshotter_Private.h
new file mode 100644
index 0000000000..205e51a6de
--- /dev/null
+++ b/platform/darwin/src/MGLMapSnapshotter_Private.h
@@ -0,0 +1,14 @@
+#import "MGLMapSnapshotter.h"
+
+@protocol MGLMapSnapshotProtocol <NSObject>
+
+#if TARGET_OS_IPHONE
+- (CGPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate;
+- (CLLocationCoordinate2D)coordinateForPoint:(CGPoint)point;
+
+#else
+- (NSPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate;
+- (CLLocationCoordinate2D)coordinateForPoint:(NSPoint)point;
+#endif
+
+@end
diff --git a/platform/darwin/src/MGLRasterTileSource.mm b/platform/darwin/src/MGLRasterTileSource.mm
index b31cee296f..80b21cc7dd 100644
--- a/platform/darwin/src/MGLRasterTileSource.mm
+++ b/platform/darwin/src/MGLRasterTileSource.mm
@@ -1,5 +1,6 @@
#import "MGLRasterTileSource_Private.h"
+#import "MGLLoggingConfiguration_Private.h"
#import "MGLMapView_Private.h"
#import "MGLSource_Private.h"
#import "MGLTileSource_Private.h"
@@ -72,7 +73,11 @@ static const CGFloat MGLRasterTileSourceRetinaTileSize = 512;
}
- (NSString *)attributionHTMLString {
- MGLAssertStyleSourceIsValid();
+ if (!self.rawSource) {
+ MGLAssert(0, @"Source with identifier `%@` was invalidated after a style change", self.identifier);
+ return nil;
+ }
+
auto attribution = self.rawSource->getAttribution();
return attribution ? @(attribution->c_str()) : nil;
}
diff --git a/platform/darwin/src/MGLSDKMetricsManager.m b/platform/darwin/src/MGLSDKMetricsManager.m
index 828fbcd505..0ef9ecda10 100644
--- a/platform/darwin/src/MGLSDKMetricsManager.m
+++ b/platform/darwin/src/MGLSDKMetricsManager.m
@@ -18,6 +18,23 @@ NSString* MGLStringFromMetricType(MGLMetricType metricType) {
return eventName;
}
+// Taken verbatim from NXFreeArchInfo header documentation
+#if TARGET_OS_IOS
+static void MGLFreeArchInfo(const NXArchInfo *x)
+{
+ const NXArchInfo *p;
+
+ p = NXGetAllArchInfos();
+ while(p->name != NULL){
+ if(x == p)
+ return;
+ p++;
+ }
+ free((char *)x->description);
+ free((NXArchInfo *)x);
+}
+#endif
+
@interface MGLMetricsManager() <MGLNetworkConfigurationMetricsDelegate>
@property (strong, nonatomic) NSDictionary *metadata;
@@ -54,7 +71,16 @@ NSString* MGLStringFromMetricType(MGLMetricType metricType) {
if (localArchInfo) {
abi = @(localArchInfo->description);
- NXFreeArchInfo(localArchInfo);
+
+ NSProcessInfo *processInfo = [NSProcessInfo processInfo];
+
+ // Although NXFreeArchInfo appears to be weakly linked, it does
+ // not have the weak_import attribute, so check the OS version.
+ if (&NXFreeArchInfo && [processInfo isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){10, 0, 0}]) {
+ NXFreeArchInfo(localArchInfo);
+ } else {
+ MGLFreeArchInfo(localArchInfo);
+ }
}
}
diff --git a/platform/darwin/src/MGLShapeSource.h b/platform/darwin/src/MGLShapeSource.h
index a57b963c63..675c219300 100644
--- a/platform/darwin/src/MGLShapeSource.h
+++ b/platform/darwin/src/MGLShapeSource.h
@@ -42,6 +42,38 @@ FOUNDATION_EXTERN MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionClus
FOUNDATION_EXTERN MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionClusterRadius;
/**
+ An `NSDictionary` object where the key is an `NSString`. The dictionary key will
+ be the feature attribute key. The resulting attribute value is
+ aggregated from the clustered points. The dictionary value is an `NSArray`
+ consisting of two `NSExpression` objects.
+
+ The first object determines how the attribute values are accumulated from the
+ cluster points. It is an `NSExpression` with an expression function that accepts
+ two or more arguments, such as `sum` or `max`. The arguments should be
+ `featureAccumulated` and the previously defined feature attribute key. The
+ resulting value is assigned to the specified attribute key.
+
+ The second `NSExpression` in the array determines which
+ attribute values are accessed from individual features within a cluster.
+
+ ```swift
+ let firstExpression = NSExpression(format: "sum:({$featureAccumulated, sumValue})")
+ let secondExpression = NSExpression(forKeyPath: "magnitude")
+ let clusterPropertiesDictionary = ["sumValue" : [firstExpression, secondExpression]]
+
+ let options : [MGLShapeSourceOption : Any] = [.clustered : true,
+ .clusterProperties: clusterPropertiesDictionary]
+ ```
+
+ This option corresponds to the
+ <a href="https://www.mapbox.com/mapbox-gl-style-spec/#sources-geojson-clusterProperties"><code>clusterProperties</code></a>
+ source property in the Mapbox Style Specification.
+
+ This option only affects point features within an `MGLShapeSource` object; it
+ is ignored when creating an `MGLComputedShapeSource` object.
+ */
+FOUNDATION_EXTERN MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionClusterProperties;
+/**
An `NSNumber` object containing an integer; specifies the maximum zoom level at
which to cluster points if clustering is enabled. Defaults to one zoom level
less than the value of `MGLShapeSourceOptionMaximumZoomLevel` so that, at the
diff --git a/platform/darwin/src/MGLShapeSource.mm b/platform/darwin/src/MGLShapeSource.mm
index 3628a0eb74..a4a100aaa2 100644
--- a/platform/darwin/src/MGLShapeSource.mm
+++ b/platform/darwin/src/MGLShapeSource.mm
@@ -3,6 +3,7 @@
#import "MGLLoggingConfiguration_Private.h"
#import "MGLStyle_Private.h"
+#import "MGLStyleValue_Private.h"
#import "MGLMapView_Private.h"
#import "MGLSource_Private.h"
#import "MGLFeature_Private.h"
@@ -19,6 +20,7 @@
const MGLShapeSourceOption MGLShapeSourceOptionBuffer = @"MGLShapeSourceOptionBuffer";
const MGLShapeSourceOption MGLShapeSourceOptionClusterRadius = @"MGLShapeSourceOptionClusterRadius";
const MGLShapeSourceOption MGLShapeSourceOptionClustered = @"MGLShapeSourceOptionClustered";
+const MGLShapeSourceOption MGLShapeSourceOptionClusterProperties = @"MGLShapeSourceOptionClusterProperties";
const MGLShapeSourceOption MGLShapeSourceOptionMaximumZoomLevel = @"MGLShapeSourceOptionMaximumZoomLevel";
const MGLShapeSourceOption MGLShapeSourceOptionMaximumZoomLevelForClustering = @"MGLShapeSourceOptionMaximumZoomLevelForClustering";
const MGLShapeSourceOption MGLShapeSourceOptionMinimumZoomLevel = @"MGLShapeSourceOptionMinimumZoomLevel";
@@ -84,6 +86,57 @@ mbgl::style::GeoJSONOptions MGLGeoJSONOptionsFromDictionary(NSDictionary<MGLShap
geoJSONOptions.cluster = value.boolValue;
}
+ if (NSDictionary *value = options[MGLShapeSourceOptionClusterProperties]) {
+ if (![value isKindOfClass:[NSDictionary<NSString *, NSArray *> class]]) {
+ [NSException raise:NSInvalidArgumentException
+ format:@"MGLShapeSourceOptionClusterProperties must be an NSDictionary with an NSString as a key and an array containing two NSExpression objects as a value."];
+ }
+
+ NSEnumerator *stringEnumerator = [value keyEnumerator];
+ NSString *key;
+
+ while (key = [stringEnumerator nextObject]) {
+ NSArray *expressionsArray = value[key];
+ if (![expressionsArray isKindOfClass:[NSArray class]]) {
+ [NSException raise:NSInvalidArgumentException
+ format:@"MGLShapeSourceOptionClusterProperties dictionary member value must be an array containing two objects."];
+ }
+ // Check that the array has 2 values. One should be a the reduce expression and one should be the map expression.
+ if ([expressionsArray count] != 2) {
+ [NSException raise:NSInvalidArgumentException
+ format:@"MGLShapeSourceOptionClusterProperties member value requires array of two objects."];
+ }
+
+ // reduceExpression should be a valid NSExpression
+ NSExpression *reduceExpression = expressionsArray[0];
+ if (![reduceExpression isKindOfClass:[NSExpression class]]) {
+ [NSException raise:NSInvalidArgumentException
+ format:@"MGLShapeSourceOptionClusterProperties array value requires two expression objects."];
+ }
+ auto reduce = MGLClusterPropertyFromNSExpression(reduceExpression);
+ if (!reduce) {
+ [NSException raise:NSInvalidArgumentException
+ format:@"Failed to convert MGLShapeSourceOptionClusterProperties reduce expression."];
+ }
+
+ // mapExpression should be a valid NSExpression
+ NSExpression *mapExpression = expressionsArray[1];
+ if (![mapExpression isKindOfClass:[NSExpression class]]) {
+ [NSException raise:NSInvalidArgumentException
+ format:@"MGLShapeSourceOptionClusterProperties member value must contain a valid NSExpression."];
+ }
+ auto map = MGLClusterPropertyFromNSExpression(mapExpression);
+ if (!map) {
+ [NSException raise:NSInvalidArgumentException
+ format:@"Failed to convert MGLShapeSourceOptionClusterProperties map expression."];
+ }
+
+ std::string keyString = std::string([key UTF8String]);
+
+ geoJSONOptions.clusterProperties.emplace(keyString, std::make_pair(std::move(map), std::move(reduce)));
+ }
+ }
+
if (NSNumber *value = options[MGLShapeSourceOptionLineDistanceMetrics]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
@@ -209,12 +262,12 @@ mbgl::style::GeoJSONOptions MGLGeoJSONOptionsFromDictionary(NSDictionary<MGLShap
auto geoJSON = [cluster geoJSONObject];
- if (!geoJSON.is<mbgl::Feature>()) {
+ if (!geoJSON.is<mbgl::GeoJSONFeature>()) {
MGLAssert(0, @"cluster geoJSON object is not a feature.");
return extensionValue;
}
- auto clusterFeature = geoJSON.get<mbgl::Feature>();
+ auto clusterFeature = geoJSON.get<mbgl::GeoJSONFeature>();
extensionValue = self.mapView.renderer->queryFeatureExtensions(self.rawSource->getID(),
clusterFeature,
@@ -240,7 +293,7 @@ mbgl::style::GeoJSONOptions MGLGeoJSONOptionsFromDictionary(NSDictionary<MGLShap
return @[];
}
- std::vector<mbgl::Feature> leaves = featureExtension->get<mbgl::FeatureCollection>();
+ std::vector<mbgl::GeoJSONFeature> leaves = featureExtension->get<mbgl::FeatureCollection>();
return MGLFeaturesFromMBGLFeatures(leaves);
}
@@ -255,7 +308,7 @@ mbgl::style::GeoJSONOptions MGLGeoJSONOptionsFromDictionary(NSDictionary<MGLShap
return @[];
}
- std::vector<mbgl::Feature> leaves = featureExtension->get<mbgl::FeatureCollection>();
+ std::vector<mbgl::GeoJSONFeature> leaves = featureExtension->get<mbgl::FeatureCollection>();
return MGLFeaturesFromMBGLFeatures(leaves);
}
diff --git a/platform/darwin/src/MGLStyleValue.mm b/platform/darwin/src/MGLStyleValue.mm
index 5103b5f5cf..01ad108d7f 100644
--- a/platform/darwin/src/MGLStyleValue.mm
+++ b/platform/darwin/src/MGLStyleValue.mm
@@ -44,3 +44,16 @@ id MGLJSONObjectFromMBGLValue(const mbgl::Value &value) {
id MGLJSONObjectFromMBGLExpression(const mbgl::style::expression::Expression &mbglExpression) {
return MGLJSONObjectFromMBGLValue(mbglExpression.serialize());
}
+
+
+std::unique_ptr<mbgl::style::expression::Expression> MGLClusterPropertyFromNSExpression(NSExpression *expression) {
+ if (!expression) {
+ return nullptr;
+ }
+
+ NSArray *jsonExpression = expression.mgl_jsonExpressionObject;
+
+ auto expr = mbgl::style::expression::dsl::createExpression(mbgl::style::conversion::makeConvertible(jsonExpression));
+
+ return expr;
+}
diff --git a/platform/darwin/src/MGLStyleValue_Private.h b/platform/darwin/src/MGLStyleValue_Private.h
index 376bf5e73b..82ce232c6b 100644
--- a/platform/darwin/src/MGLStyleValue_Private.h
+++ b/platform/darwin/src/MGLStyleValue_Private.h
@@ -12,12 +12,15 @@
#include <mbgl/style/conversion/color_ramp_property_value.hpp>
#include <mbgl/style/conversion/property_value.hpp>
#include <mbgl/style/conversion/position.hpp>
+#include <mbgl/style/expression/dsl.hpp>
#import <mbgl/style/transition_options.hpp>
#import <mbgl/style/types.hpp>
#import <mbgl/util/enum.hpp>
#include <mbgl/util/interpolate.hpp>
+#include <memory>
+
#if TARGET_OS_IPHONE
#import "UIColor+MGLAdditions.h"
#else
@@ -45,6 +48,8 @@ NS_INLINE mbgl::style::TransitionOptions MGLOptionsFromTransition(MGLTransition
return options;
}
+std::unique_ptr<mbgl::style::expression::Expression> MGLClusterPropertyFromNSExpression(NSExpression *expression);
+
id MGLJSONObjectFromMBGLExpression(const mbgl::style::expression::Expression &mbglExpression);
template <typename MBGLType, typename ObjCType, typename MBGLElement = MBGLType, typename ObjCEnum = ObjCType>
diff --git a/platform/darwin/src/MGLVectorTileSource.mm b/platform/darwin/src/MGLVectorTileSource.mm
index 85270c4a49..9ab11e2e56 100644
--- a/platform/darwin/src/MGLVectorTileSource.mm
+++ b/platform/darwin/src/MGLVectorTileSource.mm
@@ -1,6 +1,7 @@
#import "MGLVectorTileSource_Private.h"
#import "MGLFeature_Private.h"
+#import "MGLLoggingConfiguration_Private.h"
#import "MGLSource_Private.h"
#import "MGLTileSource_Private.h"
#import "MGLStyle_Private.h"
@@ -44,7 +45,11 @@
}
- (NSString *)attributionHTMLString {
- MGLAssertStyleSourceIsValid();
+ if (!self.rawSource) {
+ MGLAssert(0, @"Source with identifier `%@` was invalidated after a style change", self.identifier);
+ return nil;
+ }
+
auto attribution = self.rawSource->getAttribution();
return attribution ? @(attribution->c_str()) : nil;
}
diff --git a/platform/darwin/src/NSExpression+MGLAdditions.h b/platform/darwin/src/NSExpression+MGLAdditions.h
index 2a33367e9c..2109310e69 100644
--- a/platform/darwin/src/NSExpression+MGLAdditions.h
+++ b/platform/darwin/src/NSExpression+MGLAdditions.h
@@ -86,6 +86,13 @@ FOUNDATION_EXTERN MGL_EXPORT const MGLExpressionInterpolationMode MGLExpressionI
/**
`NSExpression` variable that corresponds to the
+ <a href="https://docs.mapbox.com/mapbox-gl-js/style-spec/#accumulated"><code>id</code></a>
+ expression operator in the Mapbox Style Specification.
+ */
+@property (class, nonatomic, readonly) NSExpression *featureAccumulatedVariableExpression;
+
+/**
+ `NSExpression` variable that corresponds to the
<a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-properties"><code>properties</code></a>
expression operator in the Mapbox Style Specification.
*/
diff --git a/platform/darwin/src/NSExpression+MGLAdditions.mm b/platform/darwin/src/NSExpression+MGLAdditions.mm
index 2ca4e0ed88..f139b86a88 100644
--- a/platform/darwin/src/NSExpression+MGLAdditions.mm
+++ b/platform/darwin/src/NSExpression+MGLAdditions.mm
@@ -553,6 +553,10 @@ const MGLExpressionInterpolationMode MGLExpressionInterpolationModeCubicBezier =
return [NSExpression expressionForVariable:@"lineProgress"];
}
++ (NSExpression *)featureAccumulatedVariableExpression {
+ return [NSExpression expressionForVariable:@"featureAccumulated"];
+}
+
+ (NSExpression *)geometryTypeVariableExpression {
return [NSExpression expressionForVariable:@"geometryType"];
}
@@ -648,7 +652,6 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) {
@"let": @"MGL_LET",
};
});
-
if (!object || object == [NSNull null]) {
return [NSExpression expressionForConstantValue:nil];
}
@@ -667,11 +670,10 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) {
}];
return [NSExpression expressionForConstantValue:dictionary];
}
-
if ([object isKindOfClass:[NSArray class]]) {
NSArray *array = (NSArray *)object;
NSString *op = array.firstObject;
-
+
if (![op isKindOfClass:[NSString class]]) {
NSArray *subexpressions = MGLSubexpressionsWithJSONObjects(array);
return [NSExpression expressionForFunction:@"MGL_FUNCTION" arguments:subexpressions];
@@ -839,6 +841,8 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) {
return NSExpression.heatmapDensityVariableExpression;
} else if ([op isEqualToString:@"line-progress"]) {
return NSExpression.lineProgressVariableExpression;
+ } else if ([op isEqualToString:@"accumulated"]) {
+ return NSExpression.featureAccumulatedVariableExpression;
} else if ([op isEqualToString:@"geometry-type"]) {
return NSExpression.geometryTypeVariableExpression;
} else if ([op isEqualToString:@"id"]) {
@@ -961,6 +965,9 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) {
if ([self.variable isEqualToString:@"zoomLevel"]) {
return @[@"zoom"];
}
+ if ([self.variable isEqualToString:@"featureAccumulated"]) {
+ return @[@"accumulated"];
+ }
if ([self.variable isEqualToString:@"geometryType"]) {
return @[@"geometry-type"];
}
@@ -1046,6 +1053,8 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) {
case NSFunctionExpressionType: {
NSString *function = self.function;
+
+ BOOL hasCollectionProperty = !( ! [self.arguments.firstObject isKindOfClass: [NSExpression class]] || self.arguments.firstObject.expressionType != NSAggregateExpressionType || self.arguments.firstObject.expressionType == NSSubqueryExpressionType);
NSString *op = MGLExpressionOperatorsByFunctionNames[function];
if (op) {
NSArray *arguments = self.arguments.mgl_jsonExpressionObject;
@@ -1057,16 +1066,31 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) {
NSExpression *count = [NSExpression expressionForFunction:@"count:" arguments:self.arguments];
return [NSExpression expressionForFunction:@"divide:by:" arguments:@[sum, count]].mgl_jsonExpressionObject;
} else if ([function isEqualToString:@"sum:"]) {
- NSArray *arguments = [self.arguments.firstObject.collection valueForKeyPath:@"mgl_jsonExpressionObject"];
+ NSArray *arguments;
+ if (hasCollectionProperty) {
+ arguments = [self.arguments.firstObject.collection valueForKeyPath:@"mgl_jsonExpressionObject"];
+ } else {
+ arguments = [self.arguments valueForKeyPath:@"mgl_jsonExpressionObject"];
+ }
return [@[@"+"] arrayByAddingObjectsFromArray:arguments];
} else if ([function isEqualToString:@"count:"]) {
NSArray *arguments = self.arguments.firstObject.mgl_jsonExpressionObject;
return @[@"length", arguments];
} else if ([function isEqualToString:@"min:"]) {
- NSArray *arguments = [self.arguments.firstObject.collection valueForKeyPath:@"mgl_jsonExpressionObject"];
+ NSArray *arguments;
+ if (!hasCollectionProperty) {
+ arguments = [self.arguments valueForKeyPath:@"mgl_jsonExpressionObject"];
+ } else {
+ arguments = [self.arguments.firstObject.collection valueForKeyPath:@"mgl_jsonExpressionObject"];
+ }
return [@[@"min"] arrayByAddingObjectsFromArray:arguments];
} else if ([function isEqualToString:@"max:"]) {
- NSArray *arguments = [self.arguments.firstObject.collection valueForKeyPath:@"mgl_jsonExpressionObject"];
+ NSArray *arguments;
+ if (!hasCollectionProperty) {
+ arguments = [self.arguments valueForKeyPath:@"mgl_jsonExpressionObject"];
+ } else {
+ arguments = [self.arguments.firstObject.collection valueForKeyPath:@"mgl_jsonExpressionObject"];
+ }
return [@[@"max"] arrayByAddingObjectsFromArray:arguments];
} else if ([function isEqualToString:@"exp:"]) {
return [NSExpression expressionForFunction:@"raise:toPower:" arguments:@[@(M_E), self.arguments.firstObject]].mgl_jsonExpressionObject;
@@ -1074,7 +1098,12 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) {
return [NSExpression expressionWithFormat:@"%@ - modulus:by:(%@, 1)",
self.arguments.firstObject, self.arguments.firstObject].mgl_jsonExpressionObject;
} else if ([function isEqualToString:@"mgl_join:"]) {
- NSArray *arguments = [self.arguments.firstObject.collection valueForKeyPath:@"mgl_jsonExpressionObject"];
+ NSArray *arguments;
+ if (!hasCollectionProperty) {
+ arguments = [self.arguments valueForKeyPath:@"mgl_jsonExpressionObject"];
+ } else {
+ arguments = [self.arguments.firstObject.collection valueForKeyPath:@"mgl_jsonExpressionObject"];
+ }
return [@[@"concat"] arrayByAddingObjectsFromArray:arguments];
} else if ([function isEqualToString:@"stringByAppendingString:"]) {
NSArray *arguments = self.arguments.mgl_jsonExpressionObject;
diff --git a/platform/darwin/src/collator.mm b/platform/darwin/src/collator.mm
index fe2b46ffa7..0f010c1df8 100644
--- a/platform/darwin/src/collator.mm
+++ b/platform/darwin/src/collator.mm
@@ -1,12 +1,11 @@
-#include <mbgl/style/expression/collator.hpp>
+#include <mbgl/i18n/collator.hpp>
#include <sstream>
#import <Foundation/Foundation.h>
namespace mbgl {
-namespace style {
-namespace expression {
+namespace platform {
class Collator::Impl {
public:
@@ -48,7 +47,6 @@ private:
NSLocale* locale;
};
-
Collator::Collator(bool caseSensitive, bool diacriticSensitive, optional<std::string> locale_)
: impl(std::make_shared<Impl>(caseSensitive, diacriticSensitive, std::move(locale_)))
{}
@@ -65,6 +63,5 @@ std::string Collator::resolvedLocale() const {
return impl->resolvedLocale();
}
-} // namespace expression
-} // namespace style
+} // namespace platform
} // namespace mbgl
diff --git a/platform/darwin/src/number_format.mm b/platform/darwin/src/number_format.mm
new file mode 100644
index 0000000000..86f16eb6dd
--- /dev/null
+++ b/platform/darwin/src/number_format.mm
@@ -0,0 +1,36 @@
+#import <Foundation/Foundation.h>
+
+#include <mbgl/i18n/number_format.hpp>
+
+namespace mbgl {
+namespace platform {
+
+std::string formatNumber(double number, const std::string& localeId, const std::string& currency,
+ uint8_t minFractionDigits, uint8_t maxFractionDigits) {
+
+ static NSNumberFormatter *numberFormatter;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ numberFormatter = [[NSNumberFormatter alloc] init];
+ });
+
+ numberFormatter.locale = !localeId.empty() ? [NSLocale localeWithLocaleIdentifier:@(localeId.c_str())] : nil;
+ numberFormatter.currencyCode = !currency.empty() ? @(currency.c_str()) : nil;
+ if (currency.empty()) {
+ numberFormatter.minimumFractionDigits = minFractionDigits;
+ numberFormatter.maximumFractionDigits = maxFractionDigits;
+ numberFormatter.numberStyle = NSNumberFormatterDecimalStyle;
+ } else {
+ // Reset fraction digits to NSNumberFormatter's default values, so that the
+ // system will choose formatting based on number formatter locale.
+ numberFormatter.minimumFractionDigits = 0;
+ numberFormatter.maximumFractionDigits = 0;
+ numberFormatter.numberStyle = NSNumberFormatterCurrencyStyle;
+ }
+ NSString *formatted = [numberFormatter stringFromNumber:@(number)];
+ std::string result = std::string([formatted UTF8String]);
+ return result;
+}
+
+} // namespace platform
+} // namespace mbgl
diff --git a/platform/darwin/src/string_nsstring.mm b/platform/darwin/src/string_nsstring.mm
index 382a2acf33..d7c81236ef 100644
--- a/platform/darwin/src/string_nsstring.mm
+++ b/platform/darwin/src/string_nsstring.mm
@@ -27,32 +27,5 @@ std::string lowercase(const std::string &string) {
return result;
}
-std::string formatNumber(double number, const std::string& localeId, const std::string& currency,
- uint8_t minFractionDigits, uint8_t maxFractionDigits) {
-
- static NSNumberFormatter *numberFormatter;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- numberFormatter = [[NSNumberFormatter alloc] init];
- });
-
- numberFormatter.locale = !localeId.empty() ? [NSLocale localeWithLocaleIdentifier:@(localeId.c_str())] : nil;
- numberFormatter.currencyCode = !currency.empty() ? @(currency.c_str()) : nil;
- if (currency.empty()) {
- numberFormatter.minimumFractionDigits = minFractionDigits;
- numberFormatter.maximumFractionDigits = maxFractionDigits;
- numberFormatter.numberStyle = NSNumberFormatterDecimalStyle;
- } else {
- // Reset fraction digits to NSNumberFormatter's default values, so that the
- // system will choose formatting based on number formatter locale.
- numberFormatter.minimumFractionDigits = 0;
- numberFormatter.maximumFractionDigits = 0;
- numberFormatter.numberStyle = NSNumberFormatterCurrencyStyle;
- }
- NSString *formatted = [numberFormatter stringFromNumber:@(number)];
- std::string result = std::string([formatted UTF8String]);
- return result;
-}
-
-}
-}
+} // namespace platform
+} // namespace mbgl
diff --git a/platform/darwin/test/MGLDocumentationExampleTests.swift b/platform/darwin/test/MGLDocumentationExampleTests.swift
index 9fbb0cc329..7d6bdbed54 100644
--- a/platform/darwin/test/MGLDocumentationExampleTests.swift
+++ b/platform/darwin/test/MGLDocumentationExampleTests.swift
@@ -554,7 +554,39 @@ class MGLDocumentationExampleTests: XCTestCase, MGLMapViewDelegate {
XCTAssertNotNil(attributedExpression)
}
-
+
+ func testMGLShapeSourceOptionClusterProperties() {
+ //#-example-code
+ let firstExpression = NSExpression(format: "sum:({$featureAccumulated, sumValue})")
+ let secondExpression = NSExpression(forKeyPath: "magnitude")
+ let clusterPropertiesDictionary = ["sumValue" : [firstExpression, secondExpression]]
+
+ let options : [MGLShapeSourceOption : Any] = [.clustered : true,
+ .clusterProperties: clusterPropertiesDictionary]
+ //#-end-example-code
+ let geoJSON: [String: Any] = [
+ "type" : "Feature",
+ "geometry" : [
+ "coordinates" : [
+ -77.00896639534831,
+ 38.87031006108791,
+ 0.0
+ ],
+ "type" : "Point"
+ ],
+ "properties" : [
+ "cluster" : true,
+ "cluster_id" : 123,
+ "point_count" : 4567,
+ ]
+ ]
+
+ let clusterShapeData = try! JSONSerialization.data(withJSONObject: geoJSON, options: [])
+ let shape = try! MGLShape(data: clusterShapeData, encoding: String.Encoding.utf8.rawValue)
+ let source = MGLShapeSource(identifier: "source", shape: shape, options: options)
+ mapView.style?.addSource(source)
+
+ }
// For testMGLMapView().
func myCustomFunction() {}
}
diff --git a/platform/darwin/test/MGLExpressionTests.mm b/platform/darwin/test/MGLExpressionTests.mm
index f1fe3ea878..4ccd7adb6e 100644
--- a/platform/darwin/test/MGLExpressionTests.mm
+++ b/platform/darwin/test/MGLExpressionTests.mm
@@ -180,6 +180,13 @@ using namespace std::string_literals;
XCTAssertEqualObjects([expression expressionValueWithObject:nil context:context], @1);
}
{
+ NSExpression *expression = [NSExpression expressionForVariable:@"featureAccumulated"];
+ XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @[@"accumulated"]);
+ XCTAssertEqualObjects([NSExpression expressionWithFormat:@"$featureAccumulated"].mgl_jsonExpressionObject, @[@"accumulated"]);
+ XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:@[@"accumulated"]], expression);
+ }
+
+ {
NSExpression *expression = [NSExpression expressionForVariable:@"geometryType"];
XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @[@"geometry-type"]);
XCTAssertEqualObjects([NSExpression expressionWithFormat:@"$geometryType"].mgl_jsonExpressionObject, @[@"geometry-type"]);
@@ -381,6 +388,26 @@ using namespace std::string_literals;
XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression);
}
{
+ NSExpression *testExpression = [NSExpression expressionWithFormat:@"sum:({1, 1, 2})"];
+ NSExpression *expression = [NSExpression expressionForFunction:@"sum:" arguments:@[[NSExpression expressionForAggregate:@[MGLConstantExpression(@1), MGLConstantExpression(@1), MGLConstantExpression(@2)]]]];
+
+ NSArray *jsonExpression = @[@"+", @1, @1, @2];
+
+ XCTAssertEqualObjects(testExpression.mgl_jsonExpressionObject, jsonExpression);
+ XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression);
+ XCTAssertEqualObjects(expression, testExpression);
+ }
+ {
+ NSExpression *expression = [NSExpression expressionForFunction:@"sum:" arguments:@[MGLConstantExpression(@1), MGLConstantExpression(@1), MGLConstantExpression(@2)]];
+ NSArray *jsonExpression = @[@"+", @1, @1, @2];
+ XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression);
+
+ // - [NSExpression expressionWithMGLJSONObject:] creates an expression with an aggregate expression as an argument. This is not equal to an expression with an array of expressions as an argument. For testing purposes, we will compare their operands and arrays of expressions.
+ NSExpression *aggregateExpression = [NSExpression expressionWithMGLJSONObject:jsonExpression];
+ XCTAssertEqualObjects(aggregateExpression.operand, expression.operand);
+ XCTAssertEqualObjects(aggregateExpression.arguments.firstObject.collection, expression.arguments);
+ }
+ {
NSArray *threeArguments = @[MGLConstantExpression(@1), MGLConstantExpression(@1), MGLConstantExpression(@1)];
NSExpression *expression = [NSExpression expressionForFunction:@"add:to:" arguments:threeArguments];
NSArray *jsonExpression = @[@"+", @1, @1, @1];
@@ -418,6 +445,24 @@ using namespace std::string_literals;
XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression);
}
{
+ NSExpression *expression = [NSExpression expressionForFunction:@"max:" arguments:arguments];
+ NSArray *jsonExpression = @[@"max", @1, @1];
+ XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression);
+
+ NSExpression *aggregateExpression = [NSExpression expressionWithMGLJSONObject:jsonExpression];
+ XCTAssertEqualObjects(aggregateExpression.operand, expression.operand);
+ XCTAssertEqualObjects(aggregateExpression.arguments.firstObject.collection, expression.arguments);
+ }
+ {
+ NSExpression *expression = [NSExpression expressionForFunction:@"min:" arguments:arguments];
+ NSArray *jsonExpression = @[@"min", @1, @1];
+ XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression);
+
+ NSExpression *aggregateExpression = [NSExpression expressionWithMGLJSONObject:jsonExpression];
+ XCTAssertEqualObjects(aggregateExpression.operand, expression.operand);
+ XCTAssertEqualObjects(aggregateExpression.arguments.firstObject.collection, expression.arguments);
+ }
+ {
NSExpression *expression = [NSExpression expressionForFunction:@"ceiling:" arguments:@[MGLConstantExpression(@1.5)]];
NSArray *jsonExpression = @[@"ceil", @1.5];
XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression);
@@ -622,6 +667,16 @@ using namespace std::string_literals;
XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], aftermarketExpression);
}
{
+ NSExpression *expression = [NSExpression expressionForFunction:@"mgl_join:" arguments:@[@"Old", @"MacDonald"]];
+ NSExpression *aftermarketExpression = [NSExpression expressionWithFormat:@"mgl_join({'Old', 'MacDonald'})"];
+ NSArray *jsonExpression = @[@"concat", @"Old", @"MacDonald"];
+ XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression);
+
+ XCTAssertEqualObjects(aftermarketExpression.mgl_jsonExpressionObject, expression.mgl_jsonExpressionObject);
+ NSExpression *aggregateExpression = [NSExpression expressionWithMGLJSONObject:jsonExpression];
+ XCTAssertEqualObjects(aggregateExpression.operand, expression.operand);
+ }
+ {
NSExpression *expression = [NSExpression expressionForFunction:@"uppercase:" arguments:arguments];
NSArray *jsonExpression = @[@"upcase", @"MacDonald"];
XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression);
diff --git a/platform/darwin/test/MGLShapeSourceTests.mm b/platform/darwin/test/MGLShapeSourceTests.mm
index 3459fb1733..3bf3ef04bd 100644
--- a/platform/darwin/test/MGLShapeSourceTests.mm
+++ b/platform/darwin/test/MGLShapeSourceTests.mm
@@ -13,8 +13,12 @@
@implementation MGLShapeSourceTests
- (void)testGeoJSONOptionsFromDictionary {
+ NSExpression *reduceExpression = [NSExpression expressionForFunction:@"sum:" arguments:@[[NSExpression expressionForKeyPath:@"featureAccumulated"], [NSExpression expressionForKeyPath:@"sumValue"]]];
+ NSExpression *mapExpression = [NSExpression expressionForKeyPath:@"mag"];
+ NSArray *clusterPropertyArray = @[reduceExpression, mapExpression];
NSDictionary *options = @{MGLShapeSourceOptionClustered: @YES,
MGLShapeSourceOptionClusterRadius: @42,
+ MGLShapeSourceOptionClusterProperties: @{@"sumValue": clusterPropertyArray},
MGLShapeSourceOptionMaximumZoomLevelForClustering: @98,
MGLShapeSourceOptionMaximumZoomLevel: @99,
MGLShapeSourceOptionBuffer: @1976,
@@ -29,6 +33,7 @@
XCTAssertEqual(mbglOptions.buffer, 1976);
XCTAssertEqual(mbglOptions.tolerance, 0.42);
XCTAssertTrue(mbglOptions.lineMetrics);
+ XCTAssertTrue(!mbglOptions.clusterProperties.empty());
options = @{MGLShapeSourceOptionClustered: @"number 1"};
XCTAssertThrows(MGLGeoJSONOptionsFromDictionary(options));
diff --git a/platform/default/include/mbgl/gfx/headless_backend.hpp b/platform/default/include/mbgl/gfx/headless_backend.hpp
index 325422323a..5167e6a465 100644
--- a/platform/default/include/mbgl/gfx/headless_backend.hpp
+++ b/platform/default/include/mbgl/gfx/headless_backend.hpp
@@ -15,11 +15,13 @@ namespace gfx {
// of readStillImage.
class HeadlessBackend : public gfx::Renderable {
public:
+ enum class SwapBehaviour { NoFlush, Flush };
+
// Factory.
- static std::unique_ptr<HeadlessBackend>
- Create(const Size size = { 256, 256 },
- const gfx::ContextMode contextMode = gfx::ContextMode::Unique) {
- return Backend::Create<HeadlessBackend, Size, gfx::ContextMode>(size, contextMode);
+ static std::unique_ptr<HeadlessBackend> Create(const Size size = {256, 256},
+ SwapBehaviour swapBehavior = SwapBehaviour::NoFlush,
+ const gfx::ContextMode contextMode = gfx::ContextMode::Unique) {
+ return Backend::Create<HeadlessBackend, Size, SwapBehaviour, gfx::ContextMode>(size, swapBehavior, contextMode);
}
virtual PremultipliedImage readStillImage() = 0;
diff --git a/platform/default/include/mbgl/gfx/headless_frontend.hpp b/platform/default/include/mbgl/gfx/headless_frontend.hpp
index 8f7a7bf202..8a98b4112d 100644
--- a/platform/default/include/mbgl/gfx/headless_frontend.hpp
+++ b/platform/default/include/mbgl/gfx/headless_frontend.hpp
@@ -1,11 +1,13 @@
#pragma once
+#include <mbgl/gfx/headless_backend.hpp>
+#include <mbgl/gfx/rendering_stats.hpp>
#include <mbgl/map/camera.hpp>
#include <mbgl/renderer/renderer_frontend.hpp>
-#include <mbgl/gfx/headless_backend.hpp>
#include <mbgl/util/async_task.hpp>
#include <mbgl/util/optional.hpp>
+#include <atomic>
#include <memory>
namespace mbgl {
@@ -16,11 +18,18 @@ class TransformState;
class HeadlessFrontend : public RendererFrontend {
public:
+ struct RenderResult {
+ PremultipliedImage image;
+ gfx::RenderingStats stats;
+ };
+
HeadlessFrontend(float pixelRatio_,
+ gfx::HeadlessBackend::SwapBehaviour swapBehviour = gfx::HeadlessBackend::SwapBehaviour::NoFlush,
gfx::ContextMode mode = gfx::ContextMode::Unique,
const optional<std::string> localFontFamily = {});
HeadlessFrontend(Size,
float pixelRatio_,
+ gfx::HeadlessBackend::SwapBehaviour swapBehviour = gfx::HeadlessBackend::SwapBehaviour::NoFlush,
gfx::ContextMode mode = gfx::ContextMode::Unique,
const optional<std::string> localFontFamily = {});
~HeadlessFrontend() override;
@@ -29,6 +38,7 @@ public:
void update(std::shared_ptr<UpdateParameters>) override;
void setObserver(RendererObserver&) override;
+ double getFrameTime() const;
Size getSize() const;
void setSize(Size);
@@ -44,7 +54,8 @@ public:
LatLng latLngForPixel(const ScreenCoordinate&);
PremultipliedImage readStillImage();
- PremultipliedImage render(Map&);
+ RenderResult render(Map&);
+ void renderOnce(Map&);
optional<TransformState> getTransformState() const;
@@ -52,6 +63,7 @@ private:
Size size;
float pixelRatio;
+ std::atomic<double> frameTime;
std::unique_ptr<gfx::HeadlessBackend> backend;
util::AsyncTask asyncInvalidate;
diff --git a/platform/default/include/mbgl/gl/headless_backend.hpp b/platform/default/include/mbgl/gl/headless_backend.hpp
index 8aefb5ff6c..b77f1b756f 100644
--- a/platform/default/include/mbgl/gl/headless_backend.hpp
+++ b/platform/default/include/mbgl/gl/headless_backend.hpp
@@ -10,13 +10,17 @@ namespace gl {
class HeadlessBackend final : public gl::RendererBackend, public gfx::HeadlessBackend {
public:
- HeadlessBackend(Size = { 256, 256 }, gfx::ContextMode = gfx::ContextMode::Unique);
+ HeadlessBackend(Size = {256, 256},
+ SwapBehaviour = SwapBehaviour::NoFlush,
+ gfx::ContextMode = gfx::ContextMode::Unique);
~HeadlessBackend() override;
void updateAssumedState() override;
gfx::Renderable& getDefaultRenderable() override;
PremultipliedImage readStillImage() override;
RendererBackend* getRendererBackend() override;
+ void swap();
+
class Impl {
public:
virtual ~Impl() = default;
@@ -37,6 +41,7 @@ private:
private:
std::unique_ptr<Impl> impl;
bool active = false;
+ SwapBehaviour swapBehaviour = SwapBehaviour::NoFlush;
};
} // namespace gl
diff --git a/platform/default/include/mbgl/storage/offline_database.hpp b/platform/default/include/mbgl/storage/offline_database.hpp
index 96b867eaa6..e599094a6d 100644
--- a/platform/default/include/mbgl/storage/offline_database.hpp
+++ b/platform/default/include/mbgl/storage/offline_database.hpp
@@ -108,6 +108,7 @@ private:
void migrateToVersion6();
void cleanup();
bool disabled();
+ void vacuum();
mapbox::sqlite::Statement& getStatement(const char *);
diff --git a/platform/default/include/mbgl/storage/offline_schema.hpp b/platform/default/include/mbgl/storage/offline_schema.hpp
index e177d0dbd3..77c66b7d15 100644
--- a/platform/default/include/mbgl/storage/offline_schema.hpp
+++ b/platform/default/include/mbgl/storage/offline_schema.hpp
@@ -1,7 +1,7 @@
#pragma once
// THIS IS A GENERATED FILE; EDIT offline_schema.sql INSTEAD
-// To regenerate, run `node platform/default/mbgl/storage/offline_schema.js`
+// To regenerate, run `node platform/default/include/mbgl/storage/offline_schema.js`
namespace mbgl {
diff --git a/platform/default/include/mbgl/storage/offline_schema.js b/platform/default/include/mbgl/storage/offline_schema.js
index fdb7dc6405..a58e216d4a 100644..100755
--- a/platform/default/include/mbgl/storage/offline_schema.js
+++ b/platform/default/include/mbgl/storage/offline_schema.js
@@ -1,13 +1,15 @@
+#!/usr/bin/env node
+
var fs = require('fs');
-fs.writeFileSync('platform/default/mbgl/storage/offline_schema.hpp', `#pragma once
+fs.writeFileSync('platform/default/include/mbgl/storage/offline_schema.hpp', `#pragma once
// THIS IS A GENERATED FILE; EDIT offline_schema.sql INSTEAD
-// To regenerate, run \`node platform/default/mbgl/storage/offline_schema.js\`
+// To regenerate, run \`node platform/default/include/mbgl/storage/offline_schema.js\`
namespace mbgl {
static constexpr const char* offlineDatabaseSchema =
-${fs.readFileSync('platform/default/mbgl/storage/offline_schema.sql', 'utf8')
+${fs.readFileSync('platform/default/include/mbgl/storage/offline_schema.sql', 'utf8')
.replace(/ *--.*/g, '')
.split('\n')
.filter(a => a)
diff --git a/platform/default/include/mbgl/storage/offline_schema.sql b/platform/default/include/mbgl/storage/offline_schema.sql
index 722b0e0451..93f6f2a5ce 100644
--- a/platform/default/include/mbgl/storage/offline_schema.sql
+++ b/platform/default/include/mbgl/storage/offline_schema.sql
@@ -1,55 +1,159 @@
-CREATE TABLE resources ( -- Generic table for style, source, sprite, and glyph resources.
- id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
- url TEXT NOT NULL,
- kind INTEGER NOT NULL,
- expires INTEGER,
- modified INTEGER,
- etag TEXT,
- data BLOB,
- compressed INTEGER NOT NULL DEFAULT 0,
- accessed INTEGER NOT NULL,
- must_revalidate INTEGER NOT NULL DEFAULT 0,
+--
+-- Table containing the style, source, sprite, and glyph
+-- resources. Essentially everything that is not a tile.
+--
+CREATE TABLE resources (
+ id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, -- Primary key.
+
+ url TEXT NOT NULL, -- The URL of the resource without the access token. If a Mapbox
+ -- resource, will be stored using the mapbox:// schema. Must be
+ -- unique and that is enforced by the database schema.
+
+ kind INTEGER NOT NULL, -- Type of the resource, taken from Resource::Kind enumeration:
+ -- style = 1
+ -- source = 2
+ -- tile = 3
+ -- glyphs = 4
+ -- sprite image = 5
+ -- sprite JSON = 6
+ -- image = 7
+
+ expires INTEGER, -- Expiration time. The resource will be refreshed after this
+ -- expiration is reached.
+
+ modified INTEGER, -- Last time the resource was modified.
+
+ etag TEXT, -- Checksum used for cache optimization. If, when refreshing the
+ -- resource, it matches the etag on the server, the resource will
+ -- not get re-downloaded. See:
+ -- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
+
+ data BLOB, -- Contents of the resource.
+
+ compressed INTEGER NOT NULL DEFAULT 0, -- If the resource is compressed with Deflate or not. Compression is
+ -- optional and should be used when the compression ratio is
+ -- significant. Using compression will make decoding time slower
+ -- because it will add an extra decompression step.
+
+ accessed INTEGER NOT NULL, -- Last time the resource was used by GL Native. Useful for when
+ -- evicting the least used resources from the cache.
+
+ must_revalidate INTEGER NOT NULL DEFAULT 0, -- When set to true, the resource will not be used unless it gets
+ -- first revalidated by the server.
UNIQUE (url)
);
+--
+-- Table containing all tiles, both vector and raster.
+--
CREATE TABLE tiles (
- id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
- url_template TEXT NOT NULL,
- pixel_ratio INTEGER NOT NULL,
- z INTEGER NOT NULL,
- x INTEGER NOT NULL,
- y INTEGER NOT NULL,
- expires INTEGER,
- modified INTEGER,
- etag TEXT,
- data BLOB,
- compressed INTEGER NOT NULL DEFAULT 0,
- accessed INTEGER NOT NULL,
- must_revalidate INTEGER NOT NULL DEFAULT 0,
+ id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, -- Primary key.
+
+ url_template TEXT NOT NULL, -- The URL of the resource without the access token and without
+ -- the tiles id substituted. Mapbox tiles will be stored using
+ -- the mapbox:// schema. Example:
+ -- mapbox://tiles/user.dataset/{z}/{x}/{y}.vector.pbf
+
+ pixel_ratio INTEGER NOT NULL, -- The tile pixel ratio, typically 1 for vector tiles.
+
+ z INTEGER NOT NULL, -- The zoom level of the tile.
+
+ x INTEGER NOT NULL, -- The x position of the tile on the tile grid.
+
+ y INTEGER NOT NULL, -- The y position of the tile on the tile grid.
+
+ expires INTEGER, -- Expiration time. The tile will be refreshed after this
+ -- expiration is reached. Expired tiles can still be rendered,
+ -- unless must_revalidate is set to true. If an expired tile
+ -- gets rendered, it will be replaced by a newer version as soon
+ -- as the network request with a new tile arrives.
+
+ modified INTEGER, -- Last time the tile was modified.
+
+ etag TEXT, -- Checksum used for cache optimization. If, when refreshing the
+ -- tile, it matches the etag on the server, the tile will not
+ -- get re-downloaded. See:
+ -- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
+
+ data BLOB, -- Contents of the tile.
+
+ compressed INTEGER NOT NULL DEFAULT 0, -- If the tile is compressed with Deflate or not. Compression is
+ -- optional and should be used when the compression ratio is
+ -- significant. Using compression will make decoding time slower
+ -- because it will add an extra decompression step.
+
+ accessed INTEGER NOT NULL, -- Last time the tile was used by GL Native. Useful for when
+ -- evicting the least used tiles from the cache.
+
+ must_revalidate INTEGER NOT NULL DEFAULT 0, -- When set to true, the tile will not be used unless it gets
+ -- first revalidated by the server.
UNIQUE (url_template, pixel_ratio, z, x, y)
);
+--
+-- Regions define the offline regions, which could be a GeoJSON geometry,
+-- or a bounding box like this example:
+--
+-- {
+-- "bounds": [
+-- 37.2,
+-- -122.8,
+-- 38.1,
+-- -121.7
+-- ],
+-- "include_ideographs": false,
+-- "max_zoom": 15.0,
+-- "min_zoom": 0.0,
+-- "pixel_ratio": 1.0,
+-- "style_url": "mapbox://styles/mapbox/streets-v11"
+-- }
+--
+-- The semantic definition of the region is up to the user and
+-- it could be a city, a country or an arbitrary bounding box.
+--
+-- Regions can overlap, which will cause them to share resources
+-- when it is the case.
+--
+-- "include_ideographs" is set to true when CJK characters are
+-- include on the offline package. By default, CJK is rendered
+-- by GL Native client side using local fonts. Downloading CJK
+-- will increase the size of the database considerably.
+--
CREATE TABLE regions (
- id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
- definition TEXT NOT NULL, -- JSON formatted definition of region. Regions may be of variant types:
- -- e.g. bbox and zoom range, route path, flyTo parameters, etc. Note that
- -- the set of tiles required for a region may span multiple sources.
- description BLOB -- User provided data in user-defined format
+ id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, -- Primary key.
+
+ definition TEXT NOT NULL, -- JSON formatted definition of a region, a bounding box
+ -- or a GeoJSON geometry. See https://geojson.org.
+
+ description BLOB -- User provided data in user-defined format.
);
+--
+-- Table mapping resources to regions. A resource
+-- might be part of many regions. Resources without
+-- regions are part of the ambient cache.
+--
CREATE TABLE region_resources (
region_id INTEGER NOT NULL REFERENCES regions(id) ON DELETE CASCADE,
resource_id INTEGER NOT NULL REFERENCES resources(id),
UNIQUE (region_id, resource_id)
);
+--
+-- Table mapping tiles to regions. A tile might
+-- be part of many regions, meaning that regions might
+-- overlap efficiently. Tiles without regions are part
+-- of the ambient cache.
+--
CREATE TABLE region_tiles (
region_id INTEGER NOT NULL REFERENCES regions(id) ON DELETE CASCADE,
tile_id INTEGER NOT NULL REFERENCES tiles(id),
UNIQUE (region_id, tile_id)
);
--- Indexes for efficient eviction queries
+--
+-- Indexes for efficient eviction queries.
+--
CREATE INDEX resources_accessed
ON resources (accessed);
diff --git a/platform/default/src/mbgl/gfx/headless_frontend.cpp b/platform/default/src/mbgl/gfx/headless_frontend.cpp
index 287567adbd..5235b2f408 100644
--- a/platform/default/src/mbgl/gfx/headless_frontend.cpp
+++ b/platform/default/src/mbgl/gfx/headless_frontend.cpp
@@ -1,43 +1,51 @@
-#include <mbgl/gfx/headless_frontend.hpp>
#include <mbgl/gfx/backend_scope.hpp>
+#include <mbgl/gfx/context.hpp>
+#include <mbgl/gfx/headless_frontend.hpp>
+#include <mbgl/map/map.hpp>
+#include <mbgl/map/transform_state.hpp>
#include <mbgl/renderer/renderer.hpp>
#include <mbgl/renderer/renderer_state.hpp>
#include <mbgl/renderer/update_parameters.hpp>
-#include <mbgl/map/map.hpp>
-#include <mbgl/map/transform_state.hpp>
+#include <mbgl/util/monotonic_timer.hpp>
#include <mbgl/util/run_loop.hpp>
namespace mbgl {
HeadlessFrontend::HeadlessFrontend(float pixelRatio_,
+ gfx::HeadlessBackend::SwapBehaviour swapBehavior,
const gfx::ContextMode contextMode,
const optional<std::string> localFontFamily)
- : HeadlessFrontend(
- { 256, 256 }, pixelRatio_, contextMode, localFontFamily) {
-}
+ : HeadlessFrontend({256, 256}, pixelRatio_, swapBehavior, contextMode, localFontFamily) {}
HeadlessFrontend::HeadlessFrontend(Size size_,
float pixelRatio_,
+ gfx::HeadlessBackend::SwapBehaviour swapBehavior,
const gfx::ContextMode contextMode,
const optional<std::string> localFontFamily)
: size(size_),
pixelRatio(pixelRatio_),
- backend(gfx::HeadlessBackend::Create({ static_cast<uint32_t>(size.width * pixelRatio),
- static_cast<uint32_t>(size.height * pixelRatio) }, contextMode)),
- asyncInvalidate([this] {
- if (renderer && updateParameters) {
- gfx::BackendScope guard { *getBackend() };
-
- // onStyleImageMissing might be called during a render. The user implemented method
- // could trigger a call to MGLRenderFrontend#update which overwrites `updateParameters`.
- // Copy the shared pointer here so that the parameters aren't destroyed while `render(...)` is
- // still using them.
- auto updateParameters_ = updateParameters;
- renderer->render(*updateParameters_);
- }
- }),
- renderer(std::make_unique<Renderer>(*getBackend(), pixelRatio, localFontFamily)) {
-}
+ frameTime(0),
+ backend(gfx::HeadlessBackend::Create(
+ {static_cast<uint32_t>(size.width * pixelRatio), static_cast<uint32_t>(size.height * pixelRatio)},
+ swapBehavior,
+ contextMode)),
+ asyncInvalidate([this] {
+ if (renderer && updateParameters) {
+ auto startTime = mbgl::util::MonotonicTimer::now();
+ gfx::BackendScope guard{*getBackend()};
+
+ // onStyleImageMissing might be called during a render. The user implemented method
+ // could trigger a call to MGLRenderFrontend#update which overwrites `updateParameters`.
+ // Copy the shared pointer here so that the parameters aren't destroyed while `render(...)` is
+ // still using them.
+ auto updateParameters_ = updateParameters;
+ renderer->render(*updateParameters_);
+
+ auto endTime = mbgl::util::MonotonicTimer::now();
+ frameTime = (endTime - startTime).count();
+ }
+ }),
+ renderer(std::make_unique<Renderer>(*getBackend(), pixelRatio, localFontFamily)) {}
HeadlessFrontend::~HeadlessFrontend() = default;
@@ -56,6 +64,10 @@ void HeadlessFrontend::setObserver(RendererObserver& observer_) {
renderer->setObserver(&observer_);
}
+double HeadlessFrontend::getFrameTime() const {
+ return frameTime;
+}
+
Size HeadlessFrontend::getSize() const {
return size;
}
@@ -128,29 +140,34 @@ PremultipliedImage HeadlessFrontend::readStillImage() {
return backend->readStillImage();
}
-PremultipliedImage HeadlessFrontend::render(Map& map) {
- PremultipliedImage result;
+HeadlessFrontend::RenderResult HeadlessFrontend::render(Map& map) {
+ HeadlessFrontend::RenderResult result;
std::exception_ptr error;
map.renderStill([&](std::exception_ptr e) {
if (e) {
error = e;
} else {
- result = backend->readStillImage();
+ result.image = backend->readStillImage();
+ result.stats = getBackend()->getContext().renderingStats();
}
});
- while (!result.valid() && !error) {
+ while (!result.image.valid() && !error) {
util::RunLoop::Get()->runOnce();
}
if (error) {
std::rethrow_exception(error);
}
-
+
return result;
}
+void HeadlessFrontend::renderOnce(Map&) {
+ util::RunLoop::Get()->runOnce();
+}
+
optional<TransformState> HeadlessFrontend::getTransformState() const {
if (updateParameters) {
return updateParameters->transformState;
diff --git a/platform/default/src/mbgl/gl/headless_backend.cpp b/platform/default/src/mbgl/gl/headless_backend.cpp
index 732e4babae..697c560f76 100644
--- a/platform/default/src/mbgl/gl/headless_backend.cpp
+++ b/platform/default/src/mbgl/gl/headless_backend.cpp
@@ -12,12 +12,12 @@ namespace gl {
class HeadlessRenderableResource final : public gl::RenderableResource {
public:
- HeadlessRenderableResource(gl::Context& context_, Size size_)
- : context(context_),
+ HeadlessRenderableResource(HeadlessBackend& backend_, gl::Context& context_, Size size_)
+ : backend(backend_),
+ context(context_),
color(context.createRenderbuffer<gfx::RenderbufferPixelType::RGBA>(size_)),
depthStencil(context.createRenderbuffer<gfx::RenderbufferPixelType::DepthStencil>(size_)),
- framebuffer(context.createFramebuffer(color, depthStencil)) {
- }
+ framebuffer(context.createFramebuffer(color, depthStencil)) {}
void bind() override {
context.bindFramebuffer = framebuffer.framebuffer;
@@ -25,18 +25,22 @@ public:
context.viewport = { 0, 0, framebuffer.size };
}
+ void swap() override { backend.swap(); }
+
+ HeadlessBackend& backend;
gl::Context& context;
gfx::Renderbuffer<gfx::RenderbufferPixelType::RGBA> color;
gfx::Renderbuffer<gfx::RenderbufferPixelType::DepthStencil> depthStencil;
gl::Framebuffer framebuffer;
};
-HeadlessBackend::HeadlessBackend(const Size size_, const gfx::ContextMode contextMode_)
- : mbgl::gl::RendererBackend(contextMode_), mbgl::gfx::HeadlessBackend(size_) {
-}
+HeadlessBackend::HeadlessBackend(const Size size_,
+ gfx::HeadlessBackend::SwapBehaviour swapBehaviour_,
+ const gfx::ContextMode contextMode_)
+ : mbgl::gl::RendererBackend(contextMode_), mbgl::gfx::HeadlessBackend(size_), swapBehaviour(swapBehaviour_) {}
HeadlessBackend::~HeadlessBackend() {
- gfx::BackendScope guard { *this };
+ gfx::BackendScope guard{*this};
resource.reset();
// Explicitly reset the context so that it is destructed and cleaned up before we destruct
// the impl object.
@@ -67,11 +71,15 @@ void HeadlessBackend::deactivate() {
gfx::Renderable& HeadlessBackend::getDefaultRenderable() {
if (!resource) {
- resource = std::make_unique<HeadlessRenderableResource>(static_cast<gl::Context&>(getContext()), size);
+ resource = std::make_unique<HeadlessRenderableResource>(*this, static_cast<gl::Context&>(getContext()), size);
}
return *this;
}
+void HeadlessBackend::swap() {
+ if (swapBehaviour == SwapBehaviour::Flush) static_cast<gl::Context&>(getContext()).finish();
+}
+
void HeadlessBackend::updateAssumedState() {
// no-op
}
@@ -89,9 +97,9 @@ RendererBackend* HeadlessBackend::getRendererBackend() {
namespace gfx {
template <>
-std::unique_ptr<gfx::HeadlessBackend>
-Backend::Create<gfx::Backend::Type::OpenGL>(const Size size, const gfx::ContextMode contextMode) {
- return std::make_unique<gl::HeadlessBackend>(size, contextMode);
+std::unique_ptr<gfx::HeadlessBackend> Backend::Create<gfx::Backend::Type::OpenGL>(
+ const Size size, gfx::HeadlessBackend::SwapBehaviour swapBehavior, const gfx::ContextMode contextMode) {
+ return std::make_unique<gl::HeadlessBackend>(size, swapBehavior, contextMode);
}
} // namespace gfx
diff --git a/platform/default/src/mbgl/text/collator.cpp b/platform/default/src/mbgl/i18n/collator.cpp
index 400fa4d94d..f46accff8a 100644
--- a/platform/default/src/mbgl/text/collator.cpp
+++ b/platform/default/src/mbgl/i18n/collator.cpp
@@ -1,7 +1,9 @@
-#include <mbgl/style/expression/collator.hpp>
-#include <mbgl/util/platform.hpp>
#include <libnu/strcoll.h>
-#include <mbgl/text/unaccent.hpp>
+#include <libnu/unaccent.h>
+#include <mbgl/i18n/collator.hpp>
+
+#include <cstring>
+#include <sstream>
/*
The default implementation of Collator ignores locale.
@@ -16,9 +18,35 @@
but would require bundling locale data.
*/
+namespace {
+std::string unaccent(const std::string& str) {
+ std::stringstream output;
+ char const *itr = str.c_str(), *nitr;
+ char const* end = itr + str.length();
+ char lo[5] = {0};
+
+ for (; itr < end; itr = nitr) {
+ uint32_t code_point = 0;
+ char const* buf = nullptr;
+
+ nitr = _nu_tounaccent(itr, end, nu_utf8_read, &code_point, &buf, nullptr);
+ if (buf != nullptr) {
+ do {
+ buf = NU_CASEMAP_DECODING_FUNCTION(buf, &code_point);
+ if (code_point == 0) break;
+ output.write(lo, nu_utf8_write(code_point, lo) - lo);
+ } while (code_point != 0);
+ } else {
+ output.write(itr, nitr - itr);
+ }
+ }
+
+ return output.str();
+}
+} // namespace
+
namespace mbgl {
-namespace style {
-namespace expression {
+namespace platform {
class Collator::Impl {
public:
@@ -28,8 +56,7 @@ public:
{}
bool operator==(const Impl& other) const {
- return caseSensitive == other.caseSensitive &&
- diacriticSensitive == other.diacriticSensitive;
+ return caseSensitive == other.caseSensitive && diacriticSensitive == other.diacriticSensitive;
}
int compare(const std::string& lhs, const std::string& rhs) const {
@@ -40,40 +67,35 @@ public:
return nu_strcasecoll(lhs.c_str(), rhs.c_str(),
nu_utf8_read, nu_utf8_read);
} else if (caseSensitive && !diacriticSensitive) {
- return nu_strcoll(platform::unaccent(lhs).c_str(), platform::unaccent(rhs).c_str(),
- nu_utf8_read, nu_utf8_read);
+ return nu_strcoll(unaccent(lhs).c_str(), unaccent(rhs).c_str(), nu_utf8_read, nu_utf8_read);
} else {
- return nu_strcasecoll(platform::unaccent(lhs).c_str(), platform::unaccent(rhs).c_str(),
- nu_utf8_read, nu_utf8_read);
+ return nu_strcasecoll(unaccent(lhs).c_str(), unaccent(rhs).c_str(), nu_utf8_read, nu_utf8_read);
}
}
std::string resolvedLocale() const {
return "";
}
+
private:
bool caseSensitive;
bool diacriticSensitive;
};
-
-Collator::Collator(bool caseSensitive, bool diacriticSensitive, optional<std::string> locale_)
- : impl(std::make_shared<Impl>(caseSensitive, diacriticSensitive, std::move(locale_)))
-{}
+int Collator::compare(const std::string& lhs, const std::string& rhs) const {
+ return impl->compare(lhs, rhs);
+}
bool Collator::operator==(const Collator& other) const {
return *impl == *(other.impl);
}
-int Collator::compare(const std::string& lhs, const std::string& rhs) const {
- return impl->compare(lhs, rhs);
-}
-
std::string Collator::resolvedLocale() const {
return impl->resolvedLocale();
}
+Collator::Collator(bool caseSensitive, bool diacriticSensitive, optional<std::string> locale)
+ : impl(std::make_shared<Impl>(caseSensitive, diacriticSensitive, std::move(locale))) {}
-} // namespace expression
-} // namespace style
+} // namespace platform
} // namespace mbgl
diff --git a/platform/default/src/mbgl/util/format_number.cpp b/platform/default/src/mbgl/i18n/format_number.cpp
index d1b51e11a1..7b6f24221d 100644
--- a/platform/default/src/mbgl/util/format_number.cpp
+++ b/platform/default/src/mbgl/i18n/format_number.cpp
@@ -1,4 +1,4 @@
-#include <mbgl/util/platform.hpp>
+#include <mbgl/i18n/number_format.hpp>
#include <unicode/numberformatter.h>
diff --git a/platform/default/src/mbgl/i18n/number_format.cpp b/platform/default/src/mbgl/i18n/number_format.cpp
new file mode 100644
index 0000000000..7f2bc5a5ef
--- /dev/null
+++ b/platform/default/src/mbgl/i18n/number_format.cpp
@@ -0,0 +1,41 @@
+#include <mbgl/i18n/number_format.hpp>
+
+#include <unicode/numberformatter.h>
+
+namespace mbgl {
+namespace platform {
+
+std::string formatNumber(double number,
+ const std::string& localeId,
+ const std::string& currency,
+ uint8_t minFractionDigits,
+ uint8_t maxFractionDigits) {
+ UErrorCode status = U_ZERO_ERROR;
+ icu::UnicodeString ustr;
+ std::string formatted;
+
+ icu::Locale locale = icu::Locale(localeId.c_str());
+ // Print the value as currency
+ if (!currency.empty()) {
+ icu::UnicodeString ucurrency = icu::UnicodeString::fromUTF8(currency);
+ ustr = icu::number::NumberFormatter::with()
+ .unit(icu::CurrencyUnit(ucurrency.getBuffer(), status))
+ .locale(locale)
+ .formatDouble(number, status)
+ .toString();
+ } else {
+ ustr = icu::number::NumberFormatter::with()
+#if U_ICU_VERSION_MAJOR_NUM >= 62
+ .precision(icu::number::Precision::minMaxFraction(minFractionDigits, maxFractionDigits))
+#else
+ .rounding(icu::number::Rounder::minMaxFraction(minFractionDigits, maxFractionDigits))
+#endif
+ .locale(locale)
+ .formatDouble(number, status)
+ .toString();
+ }
+ return ustr.toUTF8String(formatted);
+}
+
+} // namespace platform
+} // namespace mbgl
diff --git a/platform/default/src/mbgl/map/map_snapshotter.cpp b/platform/default/src/mbgl/map/map_snapshotter.cpp
index 5f4060e3f0..705a791af9 100644
--- a/platform/default/src/mbgl/map/map_snapshotter.cpp
+++ b/platform/default/src/mbgl/map/map_snapshotter.cpp
@@ -51,8 +51,8 @@ MapSnapshotter::Impl::Impl(const std::pair<bool, std::string> style,
const optional<LatLngBounds> region,
const optional<std::string> localFontFamily,
const ResourceOptions& resourceOptions)
- : frontend(
- size, pixelRatio, gfx::ContextMode::Unique, localFontFamily),
+ : frontend(
+ size, pixelRatio, gfx::HeadlessBackend::SwapBehaviour::NoFlush, gfx::ContextMode::Unique, localFontFamily),
map(frontend,
MapObserver::nullObserver(),
MapOptions().withMapMode(MapMode::Static).withSize(size).withPixelRatio(pixelRatio),
diff --git a/platform/default/src/mbgl/render-test/main.cpp b/platform/default/src/mbgl/render-test/main.cpp
new file mode 100644
index 0000000000..9b22b20e00
--- /dev/null
+++ b/platform/default/src/mbgl/render-test/main.cpp
@@ -0,0 +1,5 @@
+#include <mbgl/render_test.hpp>
+
+int main(int argc, char *argv[]) {
+ return mbgl::runRenderTests(argc, argv);
+}
diff --git a/platform/default/src/mbgl/storage/offline_database.cpp b/platform/default/src/mbgl/storage/offline_database.cpp
index 133e1f7992..5aa5738f41 100644
--- a/platform/default/src/mbgl/storage/offline_database.cpp
+++ b/platform/default/src/mbgl/storage/offline_database.cpp
@@ -140,11 +140,12 @@ void OfflineDatabase::removeExisting() {
void OfflineDatabase::removeOldCacheTable() {
assert(db);
db->exec("DROP TABLE IF EXISTS http_cache");
- db->exec("VACUUM");
+ vacuum();
}
void OfflineDatabase::createSchema() {
assert(db);
+ vacuum();
db->exec("PRAGMA journal_mode = DELETE");
db->exec("PRAGMA synchronous = FULL");
mapbox::sqlite::Transaction transaction(*db);
@@ -155,7 +156,7 @@ void OfflineDatabase::createSchema() {
void OfflineDatabase::migrateToVersion3() {
assert(db);
- db->exec("VACUUM");
+ vacuum();
db->exec("PRAGMA user_version = 3");
}
@@ -181,6 +182,15 @@ void OfflineDatabase::migrateToVersion6() {
transaction.commit();
}
+void OfflineDatabase::vacuum() {
+ if (getPragma<int64_t>("PRAGMA auto_vacuum") != 2 /*INCREMENTAL*/) {
+ db->exec("PRAGMA auto_vacuum = INCREMENTAL");
+ db->exec("VACUUM");
+ } else {
+ db->exec("PRAGMA incremental_vacuum");
+ }
+}
+
mapbox::sqlite::Statement& OfflineDatabase::getStatement(const char* sql) {
if (!db) {
initialize();
@@ -683,7 +693,7 @@ std::exception_ptr OfflineDatabase::clearAmbientCache() try {
resourceQuery.run();
- db->exec("VACUUM");
+ vacuum();
return nullptr;
} catch (const mapbox::sqlite::Exception& ex) {
@@ -871,7 +881,7 @@ std::exception_ptr OfflineDatabase::deleteRegion(OfflineRegion&& region) try {
evict(0);
assert(db);
- db->exec("VACUUM");
+ vacuum();
// Ensure that the cached offlineTileCount value is recalculated.
offlineMapboxTileCount = {};
@@ -1218,7 +1228,7 @@ std::exception_ptr OfflineDatabase::setMaximumAmbientCacheSize(uint64_t size) {
if (databaseSize > maximumAmbientCacheSize) {
evict(0);
- db->exec("VACUUM");
+ vacuum();
}
return nullptr;
diff --git a/platform/default/src/mbgl/text/unaccent.cpp b/platform/default/src/mbgl/text/unaccent.cpp
deleted file mode 100644
index 37b9a0d9ca..0000000000
--- a/platform/default/src/mbgl/text/unaccent.cpp
+++ /dev/null
@@ -1,43 +0,0 @@
-#include <mbgl/util/platform.hpp>
-#include <libnu/unaccent.h>
-#include <mbgl/text/unaccent.hpp>
-
-#include <cstring>
-#include <sstream>
-
-namespace mbgl { namespace platform {
-
-std::string unaccent(const std::string& str)
-{
- std::stringstream output;
- char const *itr = str.c_str(), *nitr;
- char const *end = itr + str.length();
- char lo[5] = { 0 };
-
- for (; itr < end; itr = nitr)
- {
- uint32_t code_point = 0;
- char const* buf = nullptr;
-
- nitr = _nu_tounaccent(itr, end, nu_utf8_read, &code_point, &buf, nullptr);
- if (buf != nullptr)
- {
- do
- {
- buf = NU_CASEMAP_DECODING_FUNCTION(buf, &code_point);
- if (code_point == 0) break;
- output.write(lo, nu_utf8_write(code_point, lo) - lo);
- }
- while (code_point != 0);
- }
- else
- {
- output.write(itr, nitr - itr);
- }
- }
-
- return output.str();
-}
-
-} // namespace platform
-} // namespace mbgl
diff --git a/platform/default/src/mbgl/util/monotonic_timer.cpp b/platform/default/src/mbgl/util/monotonic_timer.cpp
new file mode 100644
index 0000000000..43c2ce6717
--- /dev/null
+++ b/platform/default/src/mbgl/util/monotonic_timer.cpp
@@ -0,0 +1,24 @@
+#include <assert.h>
+#include <chrono>
+#include <mbgl/util/monotonic_timer.hpp>
+
+namespace mbgl {
+namespace util {
+
+// Prefer high resolution timer if it is monotonic
+template <typename T, std::enable_if_t<std::chrono::high_resolution_clock::is_steady, T>* = nullptr>
+static T sample() {
+ return std::chrono::duration_cast<T>(std::chrono::high_resolution_clock::now().time_since_epoch());
+}
+
+template <typename T, std::enable_if_t<!std::chrono::high_resolution_clock::is_steady, T>* = nullptr>
+static T sample() {
+ return std::chrono::duration_cast<T>(std::chrono::steady_clock::now().time_since_epoch());
+}
+
+std::chrono::duration<double> MonotonicTimer::now() {
+ return sample<std::chrono::duration<double>>();
+}
+
+} // namespace util
+} // namespace mbgl \ No newline at end of file
diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md
index 90c05d2288..cb877e6123 100644
--- a/platform/ios/CHANGELOG.md
+++ b/platform/ios/CHANGELOG.md
@@ -5,14 +5,34 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT
## master
### Styles and rendering
-* Added an `-[MGLMapSnapshotter startWithOverlayHandler:completionHandler:]` method to provide the snapshot's current `CGContext` in order to perform custom drawing on `MGLMapSnapShot` objects. ([#15530](https://github.com/mapbox/mapbox-gl-native/pull/15530))
-* Fixed an issue that `maxzoom` in style `Sources` option was ignored when URL resource is provided. It may cause problems such as extra tiles downloading at higher zoom level than `maxzoom`, or problems that wrong setting of `overscaledZ` in `OverscaledTileID` that will be passed to `SymbolLayout`, leading wrong rendering appearance. ([#15581](https://github.com/mapbox/mapbox-gl-native/pull/15581))
-* Fixed an assertion hit caused by possibility of adding a layer to an incompatible source. ([#15644](https://github.com/mapbox/mapbox-gl-native/pull/15644))
* Improve camera accuracy when user tilt the map as zoom in. ([#15674](https://github.com/mapbox/mapbox-gl-native/pull/15674))
+### Other changes
+
+### Bug fixes
+* Fixed the rendering bug caused by redundant pending requests for already requested images [#15864](https://github.com/mapbox/mapbox-gl-native/pull/15864)
+* Enable incremental vacuum for the offline database in order to make data removal requests faster and to avoid the excessive disk space usage (creating a backup file on VACUUM call). ([#15837](https://github.com/mapbox/mapbox-gl-native/pull/15837))
+
+## 5.5.0
+
### Performance improvements
-* Newly loaded labels appear faster on the screen. ([#15308](https://github.com/mapbox/mapbox-gl-native/pull/15308))
+* Improved rendering performance for the styles with multiple sources ([#15756](https://github.com/mapbox/mapbox-gl-native/pull/15756))
+
+### Styles and rendering
+
+* Added an `MGLShapeSourceOptionClusterProperties` option that allows styling individual clusters based on aggregated feature data. ([#15515](https://github.com/mapbox/mapbox-gl-native/pull/15515))
+
+### Other changes
+
+* Added `-[MGLMapSnapshotOverlay coordinateForPoint:]` and `-[MGLMapSnapshotOverlay pointForCoordinate:]` to convert between context and map coordinates, mirroring those of `MGLMapSnapshot`. ([#15746](https://github.com/mapbox/mapbox-gl-native/pull/15746))
+* Suppress network requests for expired tiles update, if these tiles are invisible. ([#15741](https://github.com/mapbox/mapbox-gl-native/pull/15741))
+* Fixed an issue that cause the ornaments to ignore `MGLMapView.contentInset` property. ([#15584](https://github.com/mapbox/mapbox-gl-native/pull/15584))
+* Fixed an issue that cause `-[MGLMapView setCamere:withDuration:animationTimingFunction:edgePadding:completionHandler:]` persist the value of `edgePadding`. ([#15584](https://github.com/mapbox/mapbox-gl-native/pull/15584))
+* Added `MGLMapView.automaticallyAdjustsContentInset` property that indicates if wether the map view should automatically adjust its content insets. ([#15584](https://github.com/mapbox/mapbox-gl-native/pull/15584))
+* Fixed an issue that caused `MGLScaleBar` to have an incorrect size when resizing or rotating. ([#15703](https://github.com/mapbox/mapbox-gl-native/pull/15703))
+* Fixed crash at launch seen on iOS 9 simulator. ([#15771](https://github.com/mapbox/mapbox-gl-native/pull/15771))
+
## 5.4.0 - September 25, 2019
diff --git a/platform/ios/Integration Tests/Snapshotter Tests/MGLMapSnapshotterTest.m b/platform/ios/Integration Tests/Snapshotter Tests/MGLMapSnapshotterTest.m
index 19718165b3..9ef2054dff 100644
--- a/platform/ios/Integration Tests/Snapshotter Tests/MGLMapSnapshotterTest.m
+++ b/platform/ios/Integration Tests/Snapshotter Tests/MGLMapSnapshotterTest.m
@@ -1,4 +1,5 @@
#import "MGLMapViewIntegrationTest.h"
+#import "MGLMapSnapshotter_Private.h"
@interface MGLMapSnapshotter ()
@property (nonatomic) BOOL cancelled;
@@ -24,6 +25,21 @@ MGLMapSnapshotter* snapshotterWithCoordinates(CLLocationCoordinate2D coordinates
return snapshotter;
}
+MGLMapSnapshotter* snapshotterWithBounds(MGLCoordinateBounds bounds, CGSize size) {
+
+ MGLMapCamera* mapCamera = [[MGLMapCamera alloc] init];
+ MGLMapSnapshotOptions* options = [[MGLMapSnapshotOptions alloc] initWithStyleURL:[MGLStyle satelliteStreetsStyleURL]
+ camera:mapCamera
+ size:size];
+ options.coordinateBounds = bounds;
+
+ // Create and start the snapshotter
+ MGLMapSnapshotter* snapshotter = [[MGLMapSnapshotter alloc] initWithOptions:options];
+ return snapshotter;
+}
+
+
+
@implementation MGLMapSnapshotterTest
- (void)testMultipleSnapshotsWithASingleSnapshotter🔒 {
@@ -462,4 +478,60 @@ MGLMapSnapshotter* snapshotterWithCoordinates(CLLocationCoordinate2D coordinates
[self waitForExpectations:@[expectation] timeout:10.0];
}
+- (void)testSnapshotCoordinatesWithOverlayHandler🔒 {
+ CGSize size = self.mapView.bounds.size;
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"snapshot with overlay succeeds"];
+ expectation.expectedFulfillmentCount = 2;
+
+ CLLocationCoordinate2D london = { .latitude = 51.5074, .longitude = -0.1278 };
+ CLLocationCoordinate2D paris = { .latitude = 48.8566, .longitude = 2.3522 };
+
+ MGLCoordinateBounds bounds = {
+ .ne = london,
+ .sw = paris
+ };
+
+ MGLMapSnapshotter *snapshotter = snapshotterWithBounds(bounds, size);
+ XCTAssertNotNil(snapshotter);
+
+ void (^testCoordinates)(id<MGLMapSnapshotProtocol>) = ^(id<MGLMapSnapshotProtocol> snapshot){
+ XCTAssertNotNil(snapshot);
+
+ CGPoint londonPoint = [snapshot pointForCoordinate:london];
+ CGPoint parisPoint = [snapshot pointForCoordinate:paris];
+
+ XCTAssertEqualWithAccuracy(londonPoint.x, 0, 0.1);
+ XCTAssertEqualWithAccuracy(parisPoint.x, size.width, 0.1);
+
+ // Vertically, London and Paris are inset (due to the size vs coordinate bounds)
+ XCTAssert(parisPoint.y > londonPoint.y);
+ XCTAssert(londonPoint.y > 0.0);
+ XCTAssert(parisPoint.y < size.height);
+
+ CLLocationCoordinate2D london2 = [snapshot coordinateForPoint:londonPoint];
+ CLLocationCoordinate2D paris2 = [snapshot coordinateForPoint:parisPoint];
+
+ XCTAssertEqualWithAccuracy(london.latitude, london2.latitude, 0.0000001);
+ XCTAssertEqualWithAccuracy(london.longitude, london2.longitude, 0.0000001);
+ XCTAssertEqualWithAccuracy(paris.latitude, paris2.latitude, 0.0000001);
+ XCTAssertEqualWithAccuracy(paris.longitude, paris2.longitude, 0.0000001);
+ };
+
+ [snapshotter startWithOverlayHandler:^(MGLMapSnapshotOverlay *snapshotOverlay) {
+ XCTAssert([snapshotOverlay conformsToProtocol:@protocol(MGLMapSnapshotProtocol)]);
+ testCoordinates((id<MGLMapSnapshotProtocol>)snapshotOverlay);
+
+ [expectation fulfill];
+ } completionHandler:^(MGLMapSnapshot * _Nullable snapshot, NSError * _Nullable error) {
+ XCTAssert([snapshot conformsToProtocol:@protocol(MGLMapSnapshotProtocol)]);
+ testCoordinates((id<MGLMapSnapshotProtocol>)snapshot);
+
+ [expectation fulfill];
+ }];
+
+ [self waitForExpectations:@[expectation] timeout:10.0];
+}
+
+
@end
diff --git a/platform/ios/Mapbox-iOS-SDK-snapshot-dynamic.podspec b/platform/ios/Mapbox-iOS-SDK-snapshot-dynamic.podspec
index 68c45dd399..84ce50bb4f 100644
--- a/platform/ios/Mapbox-iOS-SDK-snapshot-dynamic.podspec
+++ b/platform/ios/Mapbox-iOS-SDK-snapshot-dynamic.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |m|
- version = '5.4.0'
+ version = '5.5.0-alpha.2'
m.name = 'Mapbox-iOS-SDK-snapshot-dynamic'
m.version = "#{version}-snapshot"
diff --git a/platform/ios/Mapbox-iOS-SDK-stripped.podspec b/platform/ios/Mapbox-iOS-SDK-stripped.podspec
index 225ef2729b..51d7087c63 100644
--- a/platform/ios/Mapbox-iOS-SDK-stripped.podspec
+++ b/platform/ios/Mapbox-iOS-SDK-stripped.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |m|
- version = '5.4.0'
+ version = '5.5.0-alpha.2'
m.name = 'Mapbox-iOS-SDK-stripped'
m.version = "#{version}-stripped"
diff --git a/platform/ios/Mapbox-iOS-SDK.podspec b/platform/ios/Mapbox-iOS-SDK.podspec
index f5d8a45d4e..c4f686f060 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 = '5.4.0'
+ version = '5.5.0-alpha.2'
m.name = 'Mapbox-iOS-SDK'
m.version = version
diff --git a/platform/ios/core-files.json b/platform/ios/core-files.json
index c4930b1667..08cf1b5946 100644
--- a/platform/ios/core-files.json
+++ b/platform/ios/core-files.json
@@ -7,6 +7,7 @@
"platform/darwin/src/image.mm",
"platform/darwin/src/local_glyph_rasterizer.mm",
"platform/darwin/src/logging_nslog.mm",
+ "platform/darwin/src/number_format.mm",
"platform/darwin/src/nsthread.mm",
"platform/darwin/src/reachability.m",
"platform/darwin/src/string_nsstring.mm",
@@ -16,6 +17,7 @@
"platform/default/src/mbgl/map/map_snapshotter.cpp",
"platform/default/src/mbgl/text/bidi.cpp",
"platform/default/src/mbgl/util/compression.cpp",
+ "platform/default/src/mbgl/util/monotonic_timer.cpp",
"platform/default/src/mbgl/util/png_writer.cpp",
"platform/default/src/mbgl/util/thread_local.cpp",
"platform/default/src/mbgl/util/utf.cpp"
diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj
index 20001c26a8..350a8014fd 100644
--- a/platform/ios/ios.xcodeproj/project.pbxproj
+++ b/platform/ios/ios.xcodeproj/project.pbxproj
@@ -33,6 +33,7 @@
170C437D2029D97900863DF0 /* MGLHeatmapStyleLayerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 170C43792028D49800863DF0 /* MGLHeatmapStyleLayerTests.mm */; };
1753ED421E53CE6F00A9FD90 /* MGLConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 1753ED411E53CE6F00A9FD90 /* MGLConversion.h */; };
1753ED431E53CE6F00A9FD90 /* MGLConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 1753ED411E53CE6F00A9FD90 /* MGLConversion.h */; };
+ 1F0196AA23174B0700F5C819 /* MGLMapViewContentInsetTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1F0196A923174B0700F5C819 /* MGLMapViewContentInsetTests.m */; };
1F06668A1EC64F8E001C16D7 /* MGLLight.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F0666881EC64F8E001C16D7 /* MGLLight.h */; settings = {ATTRIBUTES = (Public, ); }; };
1F06668D1EC64F8E001C16D7 /* MGLLight.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1F0666891EC64F8E001C16D7 /* MGLLight.mm */; };
1F26B6C120E189C9007BCC21 /* MBXCustomLocationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1F26B6C020E189C9007BCC21 /* MBXCustomLocationViewController.m */; };
@@ -50,6 +51,7 @@
1F7454A91ED08AB400021D39 /* MGLLightTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1F7454A61ED08AB400021D39 /* MGLLightTest.mm */; };
1F8A59F72165326D004DFE75 /* sideload_sat.db in Resources */ = {isa = PBXBuildFile; fileRef = 1F8A59F62165326C004DFE75 /* sideload_sat.db */; };
1F8A59F821653275004DFE75 /* sideload_sat.db in Resources */ = {isa = PBXBuildFile; fileRef = 1F8A59F62165326C004DFE75 /* sideload_sat.db */; };
+ 1F8E8A81233A9FD9009B51ED /* MGLMapViewGestureRecognizerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1F8E8A80233A9FD9009B51ED /* MGLMapViewGestureRecognizerTests.mm */; };
1F95931D1E6DE2E900D5B294 /* MGLNSDateAdditionsTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1F95931C1E6DE2E900D5B294 /* MGLNSDateAdditionsTests.mm */; };
1FC4817D2098CBC0000D09B4 /* NSPredicate+MGLPrivateAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FC4817B2098CBC0000D09B4 /* NSPredicate+MGLPrivateAdditions.h */; };
1FC4817F2098CD80000D09B4 /* NSPredicate+MGLPrivateAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FC4817B2098CBC0000D09B4 /* NSPredicate+MGLPrivateAdditions.h */; };
@@ -528,6 +530,8 @@
CABE5DAD2072FAB40003AF3C /* Mapbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA8847D21CBAF91600AB86E3 /* Mapbox.framework */; };
CAD9D0AA22A86D6F001B25EE /* MGLResourceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CAD9D0A922A86D6F001B25EE /* MGLResourceTests.mm */; };
CAE7AD5520F46EF5003B6782 /* MGLMapSnapshotterSwiftTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAE7AD5420F46EF5003B6782 /* MGLMapSnapshotterSwiftTests.swift */; };
+ CAFB3C14234505D500399265 /* MGLMapSnapshotter_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = CAFB3C13234505D500399265 /* MGLMapSnapshotter_Private.h */; };
+ CAFB3C15234505D500399265 /* MGLMapSnapshotter_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = CAFB3C13234505D500399265 /* MGLMapSnapshotter_Private.h */; };
CF75A91522D85E860058A5C4 /* MGLLoggingConfiguration.mm in Sources */ = {isa = PBXBuildFile; fileRef = CF75A91422D85E860058A5C4 /* MGLLoggingConfiguration.mm */; };
CF75A91622D85E860058A5C4 /* MGLLoggingConfiguration.mm in Sources */ = {isa = PBXBuildFile; fileRef = CF75A91422D85E860058A5C4 /* MGLLoggingConfiguration.mm */; };
DA00FC8E1D5EEB0D009AABC8 /* MGLAttributionInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA00FC8C1D5EEB0D009AABC8 /* MGLAttributionInfo.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -897,6 +901,7 @@
170C43782028D49800863DF0 /* MGLHeatmapColorTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLHeatmapColorTests.mm; path = ../../darwin/test/MGLHeatmapColorTests.mm; sourceTree = "<group>"; };
170C43792028D49800863DF0 /* MGLHeatmapStyleLayerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLHeatmapStyleLayerTests.mm; path = ../../darwin/test/MGLHeatmapStyleLayerTests.mm; sourceTree = "<group>"; };
1753ED411E53CE6F00A9FD90 /* MGLConversion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLConversion.h; sourceTree = "<group>"; };
+ 1F0196A923174B0700F5C819 /* MGLMapViewContentInsetTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLMapViewContentInsetTests.m; sourceTree = "<group>"; };
1F0666881EC64F8E001C16D7 /* MGLLight.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLLight.h; sourceTree = "<group>"; };
1F0666891EC64F8E001C16D7 /* MGLLight.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLLight.mm; sourceTree = "<group>"; };
1F26B6BF20E189C9007BCC21 /* MBXCustomLocationViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MBXCustomLocationViewController.h; sourceTree = "<group>"; };
@@ -908,6 +913,7 @@
1F7454941ECD450D00021D39 /* MGLLight_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLLight_Private.h; sourceTree = "<group>"; };
1F7454A61ED08AB400021D39 /* MGLLightTest.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLLightTest.mm; path = ../../darwin/test/MGLLightTest.mm; sourceTree = "<group>"; };
1F8A59F62165326C004DFE75 /* sideload_sat.db */ = {isa = PBXFileReference; lastKnownFileType = file; name = sideload_sat.db; path = ../../../test/fixtures/offline_database/sideload_sat.db; sourceTree = "<group>"; };
+ 1F8E8A80233A9FD9009B51ED /* MGLMapViewGestureRecognizerTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLMapViewGestureRecognizerTests.mm; sourceTree = "<group>"; };
1F95931C1E6DE2E900D5B294 /* MGLNSDateAdditionsTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLNSDateAdditionsTests.mm; path = ../../darwin/test/MGLNSDateAdditionsTests.mm; sourceTree = "<group>"; };
1FC4817B2098CBC0000D09B4 /* NSPredicate+MGLPrivateAdditions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSPredicate+MGLPrivateAdditions.h"; sourceTree = "<group>"; };
1FCAE2A020B872A400C577DD /* MGLLocationManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLLocationManager.h; sourceTree = "<group>"; };
@@ -1072,8 +1078,8 @@
5580B45A229570A10091291B /* MGLMapView+OpenGL.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "MGLMapView+OpenGL.mm"; sourceTree = "<group>"; };
558DE79E1E5615E400C7916D /* MGLFoundation_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLFoundation_Private.h; sourceTree = "<group>"; };
558DE79F1E5615E400C7916D /* MGLFoundation.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLFoundation.mm; sourceTree = "<group>"; };
- 55CF752E213ED92000ED86C4 /* libmbgl-vendor-icu.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libmbgl-vendor-icu.a; sourceTree = BUILT_PRODUCTS_DIR; };
- 55CF7530213ED92A00ED86C4 /* libmbgl-vendor-icu.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libmbgl-vendor-icu.a; sourceTree = BUILT_PRODUCTS_DIR; };
+ 55CF752E213ED92000ED86C4 /* libmbgl-vendor-icu.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = "libmbgl-vendor-icu.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+ 55CF7530213ED92A00ED86C4 /* libmbgl-vendor-icu.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = "libmbgl-vendor-icu.a"; sourceTree = BUILT_PRODUCTS_DIR; };
55D120A71F791007004B6D81 /* libmbgl-loop-darwin.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = "libmbgl-loop-darwin.a"; sourceTree = BUILT_PRODUCTS_DIR; };
55D120A91F79100C004B6D81 /* libmbgl-filesource.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = "libmbgl-filesource.a"; sourceTree = BUILT_PRODUCTS_DIR; };
55D8C9941D0F133500F42F10 /* config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = config.xcconfig; path = ../../build/ios/config.xcconfig; sourceTree = "<group>"; };
@@ -1218,6 +1224,7 @@
CAD9D0A922A86D6F001B25EE /* MGLResourceTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLResourceTests.mm; path = ../../darwin/test/MGLResourceTests.mm; sourceTree = "<group>"; };
CAE7AD5320F46EF5003B6782 /* integration-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "integration-Bridging-Header.h"; sourceTree = "<group>"; };
CAE7AD5420F46EF5003B6782 /* MGLMapSnapshotterSwiftTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MGLMapSnapshotterSwiftTests.swift; sourceTree = "<group>"; };
+ CAFB3C13234505D500399265 /* MGLMapSnapshotter_Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLMapSnapshotter_Private.h; sourceTree = "<group>"; };
CF75A91422D85E860058A5C4 /* MGLLoggingConfiguration.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLLoggingConfiguration.mm; sourceTree = "<group>"; };
DA00FC8C1D5EEB0D009AABC8 /* MGLAttributionInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAttributionInfo.h; sourceTree = "<group>"; };
DA00FC8D1D5EEB0D009AABC8 /* MGLAttributionInfo.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLAttributionInfo.mm; sourceTree = "<group>"; };
@@ -2081,7 +2088,9 @@
DA5DB1291FABF1EE001C2326 /* MGLMapAccessibilityElementTests.m */,
DA695425215B1E75002041A4 /* MGLMapCameraTests.m */,
96E6145522CC135200109F14 /* MGLMapViewCompassViewTests.mm */,
+ 1F0196A923174B0700F5C819 /* MGLMapViewContentInsetTests.m */,
96ED34DD22374C0900E9FCA9 /* MGLMapViewDirectionTests.mm */,
+ 1F8E8A80233A9FD9009B51ED /* MGLMapViewGestureRecognizerTests.mm */,
16376B481FFEED010000563E /* MGLMapViewLayoutTests.m */,
96381C0122C6F3950053497D /* MGLMapViewPitchTests.m */,
9658C154204761FC00D8A674 /* MGLMapViewScaleBarTests.m */,
@@ -2151,6 +2160,7 @@
DA8847E21CBAFA5100AB86E3 /* MGLMapCamera.h */,
DA8848031CBAFA6200AB86E3 /* MGLMapCamera.mm */,
927FBCFD1F4DB05500F8BF1F /* MGLMapSnapshotter.h */,
+ CAFB3C13234505D500399265 /* MGLMapSnapshotter_Private.h */,
927FBCFE1F4DB05500F8BF1F /* MGLMapSnapshotter.mm */,
DD0902A41DB18F1B00C5BDCE /* MGLNetworkConfiguration.h */,
1F2B94BF221636D800210640 /* MGLNetworkConfiguration_Private.h */,
@@ -2529,6 +2539,7 @@
1F6A82A221360F9D00BA5B41 /* MGLLoggingConfiguration.h in Headers */,
DA8848311CBAFA6200AB86E3 /* NSString+MGLAdditions.h in Headers */,
967C864B210A9D3C004DF794 /* UIDevice+MGLAdditions.h in Headers */,
+ CAFB3C14234505D500399265 /* MGLMapSnapshotter_Private.h in Headers */,
1FCAE2A220B872A400C577DD /* MGLLocationManager.h in Headers */,
DACA86262019218600E9693A /* MGLRasterDEMSource.h in Headers */,
353933F81D3FB79F003F57D7 /* MGLLineStyleLayer.h in Headers */,
@@ -2722,6 +2733,7 @@
35E1A4D91D74336F007AA97F /* MGLValueEvaluator.h in Headers */,
DABFB8701CBE9A0F00D62B32 /* MGLMapView+IBAdditions.h in Headers */,
9C6E283822A982670056B7BE /* MMEEventLogger.h in Headers */,
+ CAFB3C15234505D500399265 /* MGLMapSnapshotter_Private.h in Headers */,
6F018BAF220031BF003E7269 /* UIView+MGLAdditions.h in Headers */,
96E516EA2000560B00A02306 /* MGLAnnotationView_Private.h in Headers */,
96E516FB20005A4000A02306 /* MGLUserLocationHeadingBeamLayer.h in Headers */,
@@ -3316,11 +3328,13 @@
920A3E5D1E6F995200C16EFC /* MGLSourceQueryTests.m in Sources */,
DA5DB12A1FABF1EE001C2326 /* MGLMapAccessibilityElementTests.m in Sources */,
96ED34DE22374C0900E9FCA9 /* MGLMapViewDirectionTests.mm in Sources */,
+ 1F0196AA23174B0700F5C819 /* MGLMapViewContentInsetTests.m in Sources */,
FAE1CDCB1E9D79CB00C40B5B /* MGLFillExtrusionStyleLayerTests.mm in Sources */,
DA35A2AA1CCA058D00E826B2 /* MGLCoordinateFormatterTests.m in Sources */,
357579831D502AE6000B822E /* MGLRasterStyleLayerTests.mm in Sources */,
3502D6CC22AE88D5006BDFCE /* MGLAccountManagerTests.m in Sources */,
DAF25720201902BC00367EF5 /* MGLHillshadeStyleLayerTests.mm in Sources */,
+ 1F8E8A81233A9FD9009B51ED /* MGLMapViewGestureRecognizerTests.mm in Sources */,
353D23961D0B0DFE002BE09D /* MGLAnnotationViewTests.m in Sources */,
DA0CD5901CF56F6A00A5F5A5 /* MGLFeatureTests.mm in Sources */,
556660D81E1D085500E2C41B /* MGLVersionNumber.m in Sources */,
diff --git a/platform/ios/sdk-files.json b/platform/ios/sdk-files.json
index 0df4b381ba..dc59e179c8 100644
--- a/platform/ios/sdk-files.json
+++ b/platform/ios/sdk-files.json
@@ -243,6 +243,7 @@
"MMEDate.h": "platform/ios/vendor/mapbox-events-ios/MapboxMobileEvents/MMEDate.h",
"NSString+MGLAdditions.h": "platform/darwin/src/NSString+MGLAdditions.h",
"UIDevice+MGLAdditions.h": "platform/ios/src/UIDevice+MGLAdditions.h",
+ "MGLMapSnapshotter_Private.h": "platform/darwin/src/MGLMapSnapshotter_Private.h",
"MGLRendererFrontend.h": "platform/darwin/src/MGLRendererFrontend.h",
"MGLStyleValue_Private.h": "platform/darwin/src/MGLStyleValue_Private.h",
"MGLFillExtrusionStyleLayer_Private.h": "platform/darwin/src/MGLFillExtrusionStyleLayer_Private.h",
diff --git a/platform/ios/src/MGLMapView.h b/platform/ios/src/MGLMapView.h
index 017ba525c4..8f27adee9e 100644
--- a/platform/ios/src/MGLMapView.h
+++ b/platform/ios/src/MGLMapView.h
@@ -287,6 +287,17 @@ MGL_EXPORT
- (IBAction)reloadStyle:(nullable id)sender;
/**
+ A boolean value that indicates if whether the map view should automatically
+ adjust its content insets.
+
+ When this property is set to `YES` the map automatically updates its
+ `contentInset` property to account for any area not covered by navigation bars,
+ tab bars, toolbars, and other ancestors that obscure the map view.
+
+ */
+@property (assign) BOOL automaticallyAdjustsContentInset;
+
+/**
A Boolean value indicating whether the map may display scale information.
The scale bar may not be shown at all zoom levels. The scale bar becomes visible
@@ -1308,10 +1319,13 @@ MGL_EXPORT
view’s frame. Otherwise, those properties are inset, excluding part of the
frame from the viewport. For instance, if the only the top edge is inset, the
map center is effectively shifted downward.
-
+
When the map view’s superview is an instance of `UIViewController` whose
`automaticallyAdjustsScrollViewInsets` property is `YES`, the value of this
property may be overridden at any time.
+
+ The usage of `automaticallyAdjustsScrollViewInsets` has been deprecated
+ use the map view’s property `MGLMapView.automaticallyAdjustsContentInset`instead.
Changing the value of this property updates the map view immediately. If you
want to animate the change, use the `-setContentInset:animated:completionHandler:`
@@ -1333,6 +1347,9 @@ MGL_EXPORT
`automaticallyAdjustsScrollViewInsets` property is `YES`, the value of this
property may be overridden at any time.
+ The usage of `automaticallyAdjustsScrollViewInsets` has been deprecated
+ use the map view’s property `MGLMapView.automaticallyAdjustsContentInset`instead.
+
To specify a completion handler to execute after the animation finishes, use
the `-setContentInset:animated:completionHandler:` method.
@@ -1357,6 +1374,9 @@ MGL_EXPORT
When the map view’s superview is an instance of `UIViewController` whose
`automaticallyAdjustsScrollViewInsets` property is `YES`, the value of this
property may be overridden at any time.
+
+ The usage of `automaticallyAdjustsScrollViewInsets` has been deprecated
+ use the map view’s property `MGLMapView.automaticallyAdjustsContentInset`instead.
@param contentInset The new values to inset the content by.
@param animated Specify `YES` if you want the map view to animate the change to
diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm
index 62b943fd3d..9b4ef8ff2d 100644
--- a/platform/ios/src/MGLMapView.mm
+++ b/platform/ios/src/MGLMapView.mm
@@ -274,6 +274,11 @@ public:
/// Tilt gesture recognizer helper
@property (nonatomic, assign) CGPoint dragGestureMiddlePoint;
+/// This property is used to keep track of the view's safe edge insets
+/// and calculate the ornament's position
+@property (nonatomic, assign) UIEdgeInsets safeMapViewContentInsets;
+@property (nonatomic, strong) NSNumber *automaticallyAdjustContentInsetHolder;
+
- (mbgl::Map &)mbglMap;
@end
@@ -518,6 +523,14 @@ public:
_annotationViewReuseQueueByIdentifier = [NSMutableDictionary dictionary];
_selectedAnnotationTag = MGLAnnotationTagNotFound;
_annotationsNearbyLastTap = {};
+
+ // TODO: This warning should be removed when automaticallyAdjustsScrollViewInsets is removed from
+ // the UIViewController api.
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ NSLog(@"%@ WARNING UIViewController.automaticallyAdjustsScrollViewInsets is deprecated use MGLMapView.automaticallyAdjustContentInset instead.",
+ NSStringFromClass(self.class));
+ });
// setup logo
//
@@ -832,29 +845,54 @@ public:
size:(CGSize)size
margins:(CGPoint)margins {
NSMutableArray *updatedConstraints = [NSMutableArray array];
+ UIEdgeInsets inset = UIEdgeInsetsZero;
+
+ BOOL automaticallyAdjustContentInset;
+ if (_automaticallyAdjustContentInsetHolder) {
+ automaticallyAdjustContentInset = _automaticallyAdjustContentInsetHolder.boolValue;
+ } else {
+ UIViewController *viewController = [self rootViewController];
+ automaticallyAdjustContentInset = viewController.automaticallyAdjustsScrollViewInsets;
+ }
+
+ if (! automaticallyAdjustContentInset) {
+ inset = UIEdgeInsetsMake(self.contentInset.top - self.safeMapViewContentInsets.top,
+ self.contentInset.left - self.safeMapViewContentInsets.left,
+ self.contentInset.bottom - self.safeMapViewContentInsets.bottom,
+ self.contentInset.right - self.safeMapViewContentInsets.right);
+
+ // makes sure the insets don't have negative values that could hide the ornaments
+ // thus violating our ToS
+ inset = UIEdgeInsetsMake(fmaxf(inset.top, 0),
+ fmaxf(inset.left, 0),
+ fmaxf(inset.bottom, 0),
+ fmaxf(inset.right, 0));
+ }
switch (position) {
case MGLOrnamentPositionTopLeft:
- [updatedConstraints addObject:[view.topAnchor constraintEqualToAnchor:self.mgl_safeTopAnchor constant:margins.y]];
- [updatedConstraints addObject:[view.leadingAnchor constraintEqualToAnchor:self.mgl_safeLeadingAnchor constant:margins.x]];
+ [updatedConstraints addObject:[view.topAnchor constraintEqualToAnchor:self.mgl_safeTopAnchor constant:margins.y + inset.top]];
+ [updatedConstraints addObject:[view.leadingAnchor constraintEqualToAnchor:self.mgl_safeLeadingAnchor constant:margins.x + inset.left]];
break;
case MGLOrnamentPositionTopRight:
- [updatedConstraints addObject:[view.topAnchor constraintEqualToAnchor:self.mgl_safeTopAnchor constant:margins.y]];
- [updatedConstraints addObject:[self.mgl_safeTrailingAnchor constraintEqualToAnchor:view.trailingAnchor constant:margins.x]];
+ [updatedConstraints addObject:[view.topAnchor constraintEqualToAnchor:self.mgl_safeTopAnchor constant:margins.y + inset.top]];
+ [updatedConstraints addObject:[self.mgl_safeTrailingAnchor constraintEqualToAnchor:view.trailingAnchor constant:margins.x + inset.right]];
break;
case MGLOrnamentPositionBottomLeft:
- [updatedConstraints addObject:[self.mgl_safeBottomAnchor constraintEqualToAnchor:view.bottomAnchor constant:margins.y]];
- [updatedConstraints addObject:[view.leadingAnchor constraintEqualToAnchor:self.mgl_safeLeadingAnchor constant:margins.x]];
+ [updatedConstraints addObject:[self.mgl_safeBottomAnchor constraintEqualToAnchor:view.bottomAnchor constant:margins.y + inset.bottom]];
+ [updatedConstraints addObject:[view.leadingAnchor constraintEqualToAnchor:self.mgl_safeLeadingAnchor constant:margins.x + inset.left]];
break;
case MGLOrnamentPositionBottomRight:
- [updatedConstraints addObject:[self.mgl_safeBottomAnchor constraintEqualToAnchor:view.bottomAnchor constant:margins.y]];
- [updatedConstraints addObject: [self.mgl_safeTrailingAnchor constraintEqualToAnchor:view.trailingAnchor constant:margins.x]];
+ [updatedConstraints addObject:[self.mgl_safeBottomAnchor constraintEqualToAnchor:view.bottomAnchor constant:margins.y + inset.bottom]];
+ [updatedConstraints addObject: [self.mgl_safeTrailingAnchor constraintEqualToAnchor:view.trailingAnchor constant:margins.x + inset.right]];
break;
}
- [updatedConstraints addObject:[view.widthAnchor constraintEqualToConstant:size.width]];
- [updatedConstraints addObject:[view.heightAnchor constraintEqualToConstant:size.height]];
-
+ if (!CGSizeEqualToSize(size, CGSizeZero)) {
+ [updatedConstraints addObject:[view.widthAnchor constraintEqualToConstant:size.width]];
+ [updatedConstraints addObject:[view.heightAnchor constraintEqualToConstant:size.height]];
+ }
+
[NSLayoutConstraint deactivateConstraints:constraints];
[constraints removeAllObjects];
[NSLayoutConstraint activateConstraints:updatedConstraints];
@@ -883,7 +921,7 @@ public:
[self updateConstraintsForOrnament:self.scaleBar
constraints:self.scaleBarConstraints
position:self.scaleBarPosition
- size:self.scaleBar.intrinsicContentSize
+ size:CGSizeZero
margins:self.scaleBarMargins];
}
@@ -929,15 +967,14 @@ public:
// This gets called when the view dimension changes, e.g. because the device is being rotated.
- (void)layoutSubviews
{
+ [super 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 `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.
+ // degradation.
[self.scaleBar invalidateIntrinsicContentSize];
-
- [super layoutSubviews];
[self adjustContentInset];
@@ -967,6 +1004,38 @@ public:
/// Updates `contentInset` to reflect the current window geometry.
- (void)adjustContentInset
{
+ UIEdgeInsets adjustedContentInsets = UIEdgeInsetsZero;
+ UIViewController *viewController = [self rootViewController];
+ BOOL automaticallyAdjustContentInset;
+ if (@available(iOS 11.0, *))
+ {
+ adjustedContentInsets = self.safeAreaInsets;
+
+ } else {
+ adjustedContentInsets.top = viewController.topLayoutGuide.length;
+ CGFloat bottomPoint = CGRectGetMaxY(viewController.view.bounds) -
+ (CGRectGetMaxY(viewController.view.bounds)
+ - viewController.bottomLayoutGuide.length);
+ adjustedContentInsets.bottom = bottomPoint;
+
+ }
+
+ if (_automaticallyAdjustContentInsetHolder) {
+ automaticallyAdjustContentInset = _automaticallyAdjustContentInsetHolder.boolValue;
+ } else {
+ automaticallyAdjustContentInset = viewController.automaticallyAdjustsScrollViewInsets;
+ }
+
+ self.safeMapViewContentInsets = adjustedContentInsets;
+ if ( ! automaticallyAdjustContentInset)
+ {
+ return;
+ }
+
+ self.contentInset = adjustedContentInsets;
+}
+
+- (UIViewController *)rootViewController {
// We could crawl all the way up the responder chain using
// -viewControllerForLayoutGuides, but an intervening view means that any
// manual contentInset should not be overridden; something other than the
@@ -982,25 +1051,16 @@ public:
// This map view is an immediate child of a view controller’s content view.
viewController = (UIViewController *)self.superview.nextResponder;
}
+ return viewController;
+}
- if ( ! viewController.automaticallyAdjustsScrollViewInsets)
- {
- return;
- }
-
- UIEdgeInsets contentInset = UIEdgeInsetsZero;
- CGPoint topPoint = CGPointMake(0, viewController.topLayoutGuide.length);
- contentInset.top = [self convertPoint:topPoint fromView:viewController.view].y;
- CGPoint bottomPoint = CGPointMake(0, CGRectGetMaxY(viewController.view.bounds)
- - viewController.bottomLayoutGuide.length);
- contentInset.bottom = (CGRectGetMaxY(self.bounds)
- - [self convertPoint:bottomPoint fromView:viewController.view].y);
-
- // Negative insets are invalid, replace with 0.
- contentInset.top = fmaxf(contentInset.top, 0);
- contentInset.bottom = fmaxf(contentInset.bottom, 0);
+- (void)setAutomaticallyAdjustsContentInset:(BOOL)automaticallyAdjustsContentInset {
+ MGLLogDebug(@"Setting automaticallyAdjustsContentInset: %@", MGLStringFromBOOL(automaticallyAdjustsContentInset));
+ _automaticallyAdjustContentInsetHolder = [NSNumber numberWithBool:automaticallyAdjustsContentInset];
+}
- self.contentInset = contentInset;
+- (BOOL)automaticallyAdjustsContentInset {
+ return _automaticallyAdjustContentInsetHolder.boolValue;
}
- (void)setContentInset:(UIEdgeInsets)contentInset
@@ -1700,7 +1760,10 @@ public:
if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
{
- self.mbglMap.jumpTo(mbgl::CameraOptions().withZoom(newZoom).withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }));
+ self.mbglMap.jumpTo(mbgl::CameraOptions()
+ .withZoom(newZoom)
+ .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y })
+ .withPadding(MGLEdgeInsetsFromNSEdgeInsets(self.contentInset)));
// The gesture recognizer only reports the gesture’s current center
// point, so use the previous center point to anchor the transition.
@@ -1758,7 +1821,10 @@ public:
{
if (drift)
{
- self.mbglMap.easeTo(mbgl::CameraOptions().withZoom(zoom).withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }), MGLDurationFromTimeInterval(duration));
+ self.mbglMap.easeTo(mbgl::CameraOptions()
+ .withZoom(zoom)
+ .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y })
+ .withPadding(MGLEdgeInsetsFromNSEdgeInsets(self.contentInset)), MGLDurationFromTimeInterval(duration));
}
}
@@ -1823,7 +1889,8 @@ public:
{
self.mbglMap.jumpTo(mbgl::CameraOptions()
.withBearing(newDegrees)
- .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y}));
+ .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y})
+ .withPadding(MGLEdgeInsetsFromNSEdgeInsets(self.contentInset)));
}
[self cameraIsChanging];
@@ -1864,7 +1931,8 @@ public:
{
self.mbglMap.easeTo(mbgl::CameraOptions()
.withBearing(newDegrees)
- .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }),
+ .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y })
+ .withPadding(MGLEdgeInsetsFromNSEdgeInsets(self.contentInset)),
MGLDurationFromTimeInterval(decelerationRate));
[self notifyGestureDidEndWithDrift:YES];
@@ -1992,7 +2060,10 @@ public:
if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
{
mbgl::ScreenCoordinate center(gesturePoint.x, gesturePoint.y);
- self.mbglMap.easeTo(mbgl::CameraOptions().withZoom(newZoom).withAnchor(center), MGLDurationFromTimeInterval(MGLAnimationDuration));
+ self.mbglMap.easeTo(mbgl::CameraOptions()
+ .withZoom(newZoom)
+ .withAnchor(center)
+ .withPadding(MGLEdgeInsetsFromNSEdgeInsets(self.contentInset)), MGLDurationFromTimeInterval(MGLAnimationDuration));
__weak MGLMapView *weakSelf = self;
@@ -2030,7 +2101,10 @@ public:
if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
{
mbgl::ScreenCoordinate center(gesturePoint.x, gesturePoint.y);
- self.mbglMap.easeTo(mbgl::CameraOptions().withZoom(newZoom).withAnchor(center), MGLDurationFromTimeInterval(MGLAnimationDuration));
+ self.mbglMap.easeTo(mbgl::CameraOptions()
+ .withZoom(newZoom)
+ .withAnchor(center)
+ .withPadding(MGLEdgeInsetsFromNSEdgeInsets(self.contentInset)), MGLDurationFromTimeInterval(MGLAnimationDuration));
__weak MGLMapView *weakSelf = self;
@@ -2072,7 +2146,10 @@ public:
if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
{
- self.mbglMap.jumpTo(mbgl::CameraOptions().withZoom(newZoom).withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }));
+ self.mbglMap.jumpTo(mbgl::CameraOptions()
+ .withZoom(newZoom)
+ .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y })
+ .withPadding(MGLEdgeInsetsFromNSEdgeInsets(self.contentInset)));
}
[self cameraIsChanging];
@@ -2139,7 +2216,8 @@ public:
{
self.mbglMap.jumpTo(mbgl::CameraOptions()
.withPitch(pitchNew)
- .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }));
+ .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y })
+ .withPadding(MGLEdgeInsetsFromNSEdgeInsets(self.contentInset)));
}
[self cameraIsChanging];
@@ -3199,7 +3277,10 @@ public:
centerPoint = self.userLocationAnnotationViewCenter;
}
double newZoom = round(self.zoomLevel) + log2(scaleFactor);
- self.mbglMap.jumpTo(mbgl::CameraOptions().withZoom(newZoom).withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }));
+ self.mbglMap.jumpTo(mbgl::CameraOptions()
+ .withZoom(newZoom)
+ .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y })
+ .withPadding(MGLEdgeInsetsFromNSEdgeInsets(self.contentInset)));
[self unrotateIfNeededForGesture];
_accessibilityValueAnnouncementIsPending = YES;
@@ -6642,11 +6723,7 @@ public:
// setting this property.
if ( ! self.scaleBar.hidden)
{
- CGSize originalSize = self.scaleBar.intrinsicContentSize;
[(MGLScaleBar *)self.scaleBar setMetersPerPoint:[self metersPerPointAtLatitude:self.centerCoordinate.latitude]];
- if ( ! CGSizeEqualToSize(originalSize, self.scaleBar.intrinsicContentSize)) {
- [self installScaleBarConstraints];
- }
}
}
diff --git a/platform/ios/src/MGLScaleBar.mm b/platform/ios/src/MGLScaleBar.mm
index 8525881da7..3efa80013f 100644
--- a/platform/ios/src/MGLScaleBar.mm
+++ b/platform/ios/src/MGLScaleBar.mm
@@ -82,16 +82,20 @@ static const MGLRow MGLImperialTable[] ={
@property (nonatomic, assign) MGLRow row;
@property (nonatomic) UIColor *primaryColor;
@property (nonatomic) UIColor *secondaryColor;
-@property (nonatomic) CALayer *borderLayer;
@property (nonatomic, assign) CGFloat borderWidth;
@property (nonatomic) NSMutableDictionary* labelImageCache;
@property (nonatomic) MGLScaleBarLabel* prototypeLabel;
@property (nonatomic) CGFloat lastLabelWidth;
-
+@property (nonatomic) CGSize size;
+@property (nonatomic) BOOL recalculateSize;
+@property (nonatomic) BOOL shouldLayoutBars;
+@property (nonatomic) NSNumber *testingRightToLeftOverride;
@end
static const CGFloat MGLBarHeight = 4;
static const CGFloat MGLFeetPerMeter = 3.28084;
+static const CGFloat MGLScaleBarLabelWidthHint = 30.0;
+static const CGFloat MGLScaleBarMinimumBarWidth = 30.0; // Arbitrary
@interface MGLScaleBarLabel : UILabel
@@ -137,6 +141,8 @@ static const CGFloat MGLFeetPerMeter = 3.28084;
}
- (void)commonInit {
+ _size = CGSizeZero;
+
_primaryColor = [UIColor colorWithRed:18.0/255.0 green:45.0/255.0 blue:17.0/255.0 alpha:1];
_secondaryColor = [UIColor colorWithRed:247.0/255.0 green:247.0/255.0 blue:247.0/255.0 alpha:1];
_borderWidth = 1.0f;
@@ -144,17 +150,17 @@ static const CGFloat MGLFeetPerMeter = 3.28084;
self.clipsToBounds = NO;
self.hidden = YES;
- _containerView = [[UIView alloc] init];
- _containerView.clipsToBounds = YES;
- _containerView.backgroundColor = self.secondaryColor;
+ _containerView = [[UIView alloc] init];
+ _containerView.clipsToBounds = YES;
+ _containerView.backgroundColor = _secondaryColor;
+ _containerView.layer.borderColor = _primaryColor.CGColor;
+ _containerView.layer.borderWidth = _borderWidth / [[UIScreen mainScreen] scale];
+
+ _containerView.layer.cornerRadius = MGLBarHeight / 2.0;
+ _containerView.layer.masksToBounds = YES;
+
[self addSubview:_containerView];
- _borderLayer = [CAShapeLayer layer];
- _borderLayer.borderColor = [self.primaryColor CGColor];
- _borderLayer.borderWidth = 1.0f / [[UIScreen mainScreen] scale];
-
- [_containerView.layer addSublayer:_borderLayer];
-
_formatter = [[MGLDistanceFormatter alloc] init];
// Image labels are now images
@@ -176,6 +182,7 @@ static const CGFloat MGLFeetPerMeter = 3.28084;
[self addSubview:view];
}
_labelViews = [labelViews copy];
+ _lastLabelWidth = MGLScaleBarLabelWidthHint;
// Zero is a special case (no formatting)
[self addZeroLabel];
@@ -194,16 +201,33 @@ static const CGFloat MGLFeetPerMeter = 3.28084;
#pragma mark - Dimensions
-- (CGSize)intrinsicContentSize {
- return self.actualWidth > 0 ? CGSizeMake(ceil(self.actualWidth + self.lastLabelWidth/2), 16) : CGSizeZero;
+- (void)setBorderWidth:(CGFloat)borderWidth {
+ _borderWidth = borderWidth;
+ _containerView.layer.borderWidth = borderWidth / [[UIScreen mainScreen] scale];
}
+// Determines the width of the bars NOT the size of the entire scale bar,
+// which includes space for (half) a label.
+// Uses the current set `row`
- (CGFloat)actualWidth {
- CGFloat width = self.row.distance / [self unitsPerPoint];
- return !isnan(width) ? width : 0;
+ CGFloat unitsPerPoint = [self unitsPerPoint];
+
+ if (unitsPerPoint == 0.0) {
+ return 0.0;
+ }
+
+ CGFloat width = self.row.distance / unitsPerPoint;
+
+ if (width <= MGLScaleBarMinimumBarWidth) {
+ return 0.0;
+ }
+
+ // Round, so that each bar section has an integer width
+ return self.row.numberOfBars * floor(width/self.row.numberOfBars);
}
- (CGFloat)maximumWidth {
+ // TODO: Consider taking Scale Bar margins into account here.
CGFloat fullWidth = CGRectGetWidth(self.superview.bounds);
return floorf(fullWidth / 2);
}
@@ -215,6 +239,10 @@ static const CGFloat MGLFeetPerMeter = 3.28084;
#pragma mark - Convenience methods
- (BOOL)usesRightToLeftLayout {
+ if (self.testingRightToLeftOverride) {
+ return [self.testingRightToLeftOverride boolValue];
+ }
+
return [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:self.superview.semanticContentAttribute] == UIUserInterfaceLayoutDirectionRightToLeft;
}
@@ -265,10 +293,61 @@ static const CGFloat MGLFeetPerMeter = 3.28084;
[self updateVisibility];
+ self.recalculateSize = YES;
+ [self invalidateIntrinsicContentSize];
+}
+
+- (CGSize)intrinsicContentSize {
+ // Size is calculated elsewhere - since intrinsicContentSize is part of the
+ // constraint system, this should be done in updateConstraints
+ if (self.size.width < 0.0) {
+ return CGSizeZero;
+ }
+ return self.size;
+}
+
+/// updateConstraints
+///
+/// The primary job of updateConstraints here is to recalculate the
+/// intrinsicContentSize: _metersPerPoint and the maximum width determine the
+/// current "row", which in turn determines the "actualWidth". To obtain the full
+/// width of the scale bar, we also need to include some space for the "last"
+/// label
+
+- (void)updateConstraints {
+ if (self.isHidden || !self.recalculateSize) {
+ [super updateConstraints];
+ return;
+ }
+
+ // TODO: Improve this (and the side-effects)
self.row = [self preferredRow];
- [self invalidateIntrinsicContentSize];
+ NSAssert(self.row.numberOfBars > 0, @"");
+
+ CGFloat totalBarWidth = self.actualWidth;
+
+ if (totalBarWidth <= 0.0) {
+ [super updateConstraints];
+ return;
+ }
+
+ // Determine the "lastLabelWidth". This has changed to take a maximum of each
+ // label, to ensure that the size does not change in LTR & RTL layouts, and
+ // also to stop jiggling when the scale bar is on the right hand of the screen
+ // This will most likely be a constant, as we take a max using a "hint" for
+ // the initial value
+
+ if (self.shouldLayoutBars) {
+ [self updateLabels];
+ }
+
+ CGFloat halfLabelWidth = ceil(self.lastLabelWidth/2);
+
+ self.size = CGSizeMake(totalBarWidth + halfLabelWidth, 16);
+
[self setNeedsLayout];
+ [super updateConstraints]; // This calls intrinsicContentSize
}
- (void)updateVisibility {
@@ -297,11 +376,9 @@ static const CGFloat MGLFeetPerMeter = 3.28084;
return;
}
+ self.shouldLayoutBars = YES;
+
_row = row;
- [_bars makeObjectsPerformSelector:@selector(removeFromSuperview)];
- _bars = nil;
-
- [self updateLabels];
}
#pragma mark - Views
@@ -378,9 +455,9 @@ static const CGFloat MGLFeetPerMeter = 3.28084;
CLLocationDistance barDistance = multiplier * i;
UIImage *image = [self cachedLabelImageForDistance:barDistance];
- if (i == self.row.numberOfBars) {
- self.lastLabelWidth = image.size.width;
- }
+
+ self.lastLabelWidth = MAX(self.lastLabelWidth, image.size.width);
+
labelView.layer.contents = (id)image.CGImage;
labelView.layer.contentsScale = image.scale;
}
@@ -397,53 +474,83 @@ static const CGFloat MGLFeetPerMeter = 3.28084;
- (void)layoutSubviews {
[super layoutSubviews];
- if (!self.row.numberOfBars) {
- // Current distance is not within allowed range
+ if (!self.recalculateSize) {
return;
}
- [self layoutBars];
- [self layoutLabels];
-}
+ self.recalculateSize = NO;
+
+ // If size is 0, then we keep the existing layout (which will fade out)
+ if (self.size.width <= 0.0) {
+ return;
+ }
+
+ CGFloat totalBarWidth = self.actualWidth;
+
+ if (totalBarWidth <= 0.0) {
+ return;
+ }
+
+ if (self.shouldLayoutBars) {
+ self.shouldLayoutBars = NO;
+ [_bars makeObjectsPerformSelector:@selector(removeFromSuperview)];
+ _bars = nil;
+ }
+
+ // Re-layout the component bars and labels of the scale bar
+ CGFloat intrinsicContentHeight = self.intrinsicContentSize.height;
+ CGFloat barWidth = totalBarWidth/self.bars.count;
-- (void)layoutBars {
- CGFloat barWidth = round((self.intrinsicContentSize.width - self.borderWidth * 2.0f) / self.bars.count);
+ BOOL RTL = [self usesRightToLeftLayout];
+ CGFloat halfLabelWidth = ceil(self.lastLabelWidth/2);
+ CGFloat barOffset = RTL ? halfLabelWidth : 0.0;
+ self.containerView.frame = CGRectMake(barOffset,
+ intrinsicContentHeight - MGLBarHeight,
+ totalBarWidth,
+ MGLBarHeight);
+
+ [self layoutBarsWithWidth:barWidth];
+
+ CGFloat yPosition = round(0.5 * ( intrinsicContentHeight - MGLBarHeight));
+ CGFloat barDelta = RTL ? -barWidth : barWidth;
+ [self layoutLabelsWithOffset:barOffset delta:barDelta yPosition:yPosition];
+}
+
+- (void)layoutBarsWithWidth:(CGFloat)barWidth {
NSUInteger i = 0;
for (UIView *bar in self.bars) {
- CGFloat xPosition = barWidth * i + self.borderWidth;
+ CGFloat xPosition = barWidth * i;
bar.backgroundColor = (i % 2 == 0) ? self.primaryColor : self.secondaryColor;
- bar.frame = CGRectMake(xPosition, self.borderWidth, barWidth, MGLBarHeight);
+ bar.frame = CGRectMake(xPosition, 0, barWidth, MGLBarHeight);
i++;
}
-
- self.containerView.frame = CGRectMake(CGRectGetMinX(self.bars.firstObject.frame),
- self.intrinsicContentSize.height-MGLBarHeight,
- self.actualWidth,
- MGLBarHeight+self.borderWidth*2);
-
- [CATransaction begin];
- [CATransaction setDisableActions:YES];
- self.borderLayer.frame = CGRectInset(self.containerView.bounds, self.borderWidth, self.borderWidth);
- self.borderLayer.zPosition = FLT_MAX;
- [CATransaction commit];
}
-- (void)layoutLabels {
- CGFloat barWidth = round(self.actualWidth / self.bars.count);
- BOOL RTL = [self usesRightToLeftLayout];
- NSUInteger i = RTL ? self.bars.count : 0;
+- (void)layoutLabelsWithOffset:(CGFloat)barOffset delta:(CGFloat)barDelta yPosition:(CGFloat)yPosition {
+#if !defined(NS_BLOCK_ASSERTIONS)
+ NSUInteger countOfVisibleLabels = 0;
+ for (UIView *view in self.labelViews) {
+ if (!view.isHidden) {
+ countOfVisibleLabels++;
+ }
+ }
+ NSAssert(self.bars.count == countOfVisibleLabels - 1, @"");
+#endif
+
+ CGFloat xPosition = barOffset;
+
+ if (barDelta < 0) {
+ xPosition -= (barDelta*self.bars.count);
+ }
+
for (UIView *label in self.labelViews) {
- CGFloat xPosition = round(barWidth * i - CGRectGetMidX(label.bounds) + self.borderWidth);
- CGFloat yPosition = round(0.5 * (self.intrinsicContentSize.height - MGLBarHeight));
-
- CGRect frame = label.frame;
- frame.origin.x = xPosition;
- frame.origin.y = yPosition;
- label.frame = frame;
-
- i = RTL ? i-1 : i+1;
+ // Label frames have 0 size - though the layer contents use "center" and do
+ // not clip to bounds. This way we don't need to worry about positioning the
+ // label. (Though you won't see the label in the view debugger)
+ label.frame = CGRectMake(xPosition, yPosition, 0.0, 0.0);
+
+ xPosition += barDelta;
}
}
-
@end
diff --git a/platform/ios/test/MGLMapViewContentInsetTests.m b/platform/ios/test/MGLMapViewContentInsetTests.m
new file mode 100644
index 0000000000..185baf4a05
--- /dev/null
+++ b/platform/ios/test/MGLMapViewContentInsetTests.m
@@ -0,0 +1,177 @@
+#import <Mapbox/Mapbox.h>
+#import <XCTest/XCTest.h>
+
+@interface MGLMapViewContentInsetTests : XCTestCase <MGLMapViewDelegate>
+
+@property (nonatomic) MGLMapView *mapView;
+@property (nonatomic) UIWindow *window;
+@property (nonatomic) UIViewController *viewController;
+@property (nonatomic) XCTestExpectation *styleLoadingExpectation;
+@property (assign) CGRect screenBounds;
+
+@end
+
+@implementation MGLMapViewContentInsetTests
+
+- (void)setUp {
+ [super setUp];
+
+ [MGLAccountManager setAccessToken:@"pk.feedcafedeadbeefbadebede"];
+ NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"];
+ self.screenBounds = UIScreen.mainScreen.bounds;
+ self.mapView = [[MGLMapView alloc] initWithFrame:self.screenBounds styleURL:styleURL];
+ self.mapView.zoomLevel = 16;
+ self.mapView.delegate = self;
+
+ self.viewController = [[UIViewController alloc] init];
+ self.viewController.view = [[UIView alloc] initWithFrame:self.screenBounds];
+ [self.viewController.view addSubview:self.mapView];
+ self.window = [[UIWindow alloc] initWithFrame:self.screenBounds];
+ [self.window addSubview:self.viewController.view];
+ [self.window makeKeyAndVisible];
+
+ if (!self.mapView.style) {
+ _styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."];
+ [self waitForExpectationsWithTimeout:10 handler:nil];
+ }
+}
+
+- (void)mapView:(MGLMapView *)mapView didFinishLoadingStyle:(MGLStyle *)style {
+ XCTAssertNotNil(mapView.style);
+ XCTAssertEqual(mapView.style, style);
+
+ [_styleLoadingExpectation fulfill];
+}
+
+- (void)tearDown {
+ self.mapView = nil;
+ [MGLAccountManager setAccessToken:nil];
+ [super tearDown];
+}
+
+- (void)testContentInsetCenter {
+ CLLocationCoordinate2D center = CLLocationCoordinate2DMake(1.0, 5.0);
+ self.mapView.centerCoordinate = center;
+ XCTAssertEqualWithAccuracy(self.mapView.centerCoordinate.latitude, center.latitude, 0.01);
+ XCTAssertEqualWithAccuracy(self.mapView.centerCoordinate.longitude, center.longitude, 0.01);
+
+ CGPoint centerPoint = [self.mapView convertCoordinate:center toPointToView:self.mapView];
+
+ XCTAssertEqualWithAccuracy(centerPoint.x, self.screenBounds.size.width/2, 0.01);
+ XCTAssertEqualWithAccuracy(centerPoint.y, self.screenBounds.size.height/2, 0.01);
+
+ // shifting contentInset should keep the same centerCoordinate but shift the screen
+ // center point accordingly
+ UIEdgeInsets contentInset = UIEdgeInsetsMake(50.0, 10.0, 10.0, 30.0);
+ self.mapView.contentInset = contentInset;
+ XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset));
+ XCTAssertEqualWithAccuracy(self.mapView.centerCoordinate.latitude, center.latitude, 0.01);
+ XCTAssertEqualWithAccuracy(self.mapView.centerCoordinate.longitude, center.longitude, 0.01);
+ CGPoint shiftedPoint = [self.mapView convertCoordinate:center toPointToView:self.mapView];
+ CGPoint expectedShiftedPoint = CGPointMake((self.screenBounds.size.width/2) + ((contentInset.left - contentInset.right) / 2 ),
+ (self.screenBounds.size.height/2) + ((contentInset.top - contentInset.bottom) / 2));
+ XCTAssertEqualWithAccuracy(shiftedPoint.x, expectedShiftedPoint.x, 0.01);
+ XCTAssertEqualWithAccuracy(shiftedPoint.y, expectedShiftedPoint.y, 0.01);
+
+
+}
+
+- (void)testContentInsetOrnaments {
+ CGFloat margin = 8;
+ self.mapView.contentInset = UIEdgeInsetsZero;
+ UIView *scaleBar = self.mapView.scaleBar;
+ CGPoint expectedScaleBarOrigin = CGPointMake(margin, margin);
+ XCTAssertTrue(CGPointEqualToPoint(scaleBar.frame.origin, expectedScaleBarOrigin));
+
+ UIView *compassView = self.mapView.compassView;
+ CGFloat x = self.screenBounds.size.width - compassView.bounds.size.width - margin;
+ CGPoint expectedCompassOrigin = CGPointMake(x, margin);
+ XCTAssertTrue(CGPointEqualToPoint(compassView.frame.origin, expectedCompassOrigin));
+
+ UIView *logoView = self.mapView.logoView;
+ CGFloat y = self.screenBounds.size.height - logoView.bounds.size.height - margin;
+ CGPoint expectedLogoOrigin = CGPointMake(margin, y);
+ XCTAssertTrue(CGPointEqualToPoint(logoView.frame.origin, expectedLogoOrigin));
+
+ UIView *attributionView = self.mapView.attributionButton;
+ x = self.screenBounds.size.width - attributionView.bounds.size.width - margin;
+ y = self.screenBounds.size.height - attributionView.bounds.size.height - margin;
+ CGPoint expectedAttributionOrigin = CGPointMake(x, y);
+ XCTAssertTrue(CGPointEqualToPoint(attributionView.frame.origin, expectedAttributionOrigin));
+
+ UIEdgeInsets insets = UIEdgeInsetsMake(15, 10, 20, 5);
+ self.viewController.automaticallyAdjustsScrollViewInsets = NO;
+ self.mapView.contentInset = insets;
+
+ [self.mapView setNeedsLayout];
+ [self.mapView layoutIfNeeded];
+
+ expectedScaleBarOrigin = CGPointMake(insets.left + self.mapView.scaleBarMargins.x, insets.top + self.mapView.scaleBarMargins.y);
+ XCTAssertTrue(CGPointEqualToPoint(scaleBar.frame.origin, expectedScaleBarOrigin));
+
+ x = self.screenBounds.size.width - compassView.bounds.size.width - insets.right - self.mapView.compassViewMargins.x;
+ expectedCompassOrigin = CGPointMake(x, insets.top + self.mapView.compassViewMargins.y);
+ XCTAssertTrue(CGPointEqualToPoint(compassView.frame.origin, expectedCompassOrigin));
+
+ y = self.screenBounds.size.height - logoView.bounds.size.height - insets.bottom - self.mapView.logoViewMargins.y;
+ expectedLogoOrigin = CGPointMake(insets.left + self.mapView.logoViewMargins.x, y);
+ XCTAssertTrue(CGPointEqualToPoint(logoView.frame.origin, expectedLogoOrigin));
+
+ x = self.screenBounds.size.width - attributionView.bounds.size.width - insets.right - self.mapView.attributionButtonMargins.x;
+ y = self.screenBounds.size.height - attributionView.bounds.size.height - insets.bottom - self.mapView.attributionButtonMargins.y;
+ expectedAttributionOrigin = CGPointMake(x, y);
+ XCTAssertTrue(CGPointEqualToPoint(attributionView.frame.origin, expectedAttributionOrigin));
+
+ // tests that passing negative values result in a 0 inset value
+ insets = UIEdgeInsetsMake(-100, -100, -100, -100);
+ self.mapView.contentInset = insets;
+
+ [self.mapView setNeedsLayout];
+ [self.mapView layoutIfNeeded];
+
+ expectedScaleBarOrigin = CGPointMake(margin, margin);
+ XCTAssertTrue(CGPointEqualToPoint(scaleBar.frame.origin, expectedScaleBarOrigin));
+
+ x = self.screenBounds.size.width - compassView.bounds.size.width - margin;
+ expectedCompassOrigin = CGPointMake(x, margin);
+ XCTAssertTrue(CGPointEqualToPoint(compassView.frame.origin, expectedCompassOrigin));
+
+ y = self.screenBounds.size.height - logoView.bounds.size.height - margin;
+ expectedLogoOrigin = CGPointMake(margin, y);
+ XCTAssertTrue(CGPointEqualToPoint(logoView.frame.origin, expectedLogoOrigin));
+
+ x = self.screenBounds.size.width - attributionView.bounds.size.width - margin;
+ y = self.screenBounds.size.height - attributionView.bounds.size.height - margin;
+ expectedAttributionOrigin = CGPointMake(x, y);
+ XCTAssertTrue(CGPointEqualToPoint(attributionView.frame.origin, expectedAttributionOrigin));
+
+ self.mapView.automaticallyAdjustsContentInset = YES;
+ insets = UIEdgeInsetsMake(100, 100, 100, 100);
+ self.mapView.contentInset = insets;
+ XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, insets));
+
+ [self.mapView setNeedsLayout];
+ [self.mapView layoutIfNeeded];
+
+ // when automaticallyAdjustsContentInset = YES the content insets should be overwriten
+ XCTAssertFalse(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, insets));
+
+ expectedScaleBarOrigin = CGPointMake(margin, margin);
+ XCTAssertTrue(CGPointEqualToPoint(scaleBar.frame.origin, expectedScaleBarOrigin));
+
+ x = self.screenBounds.size.width - compassView.bounds.size.width - margin;
+ expectedCompassOrigin = CGPointMake(x, margin);
+ XCTAssertTrue(CGPointEqualToPoint(compassView.frame.origin, expectedCompassOrigin));
+
+ y = self.screenBounds.size.height - logoView.bounds.size.height - margin;
+ expectedLogoOrigin = CGPointMake(margin, y);
+ XCTAssertTrue(CGPointEqualToPoint(logoView.frame.origin, expectedLogoOrigin));
+
+ x = self.screenBounds.size.width - attributionView.bounds.size.width - margin;
+ y = self.screenBounds.size.height - attributionView.bounds.size.height - margin;
+ expectedAttributionOrigin = CGPointMake(x, y);
+ XCTAssertTrue(CGPointEqualToPoint(attributionView.frame.origin, expectedAttributionOrigin));
+
+}
+
+@end
diff --git a/platform/ios/test/MGLMapViewGestureRecognizerTests.mm b/platform/ios/test/MGLMapViewGestureRecognizerTests.mm
new file mode 100644
index 0000000000..58fbf2d03b
--- /dev/null
+++ b/platform/ios/test/MGLMapViewGestureRecognizerTests.mm
@@ -0,0 +1,280 @@
+#import <Mapbox/Mapbox.h>
+#import <XCTest/XCTest.h>
+
+#import "../../darwin/src/MGLGeometry_Private.h"
+#import "MGLMockGestureRecognizers.h"
+
+#include <mbgl/map/map.hpp>
+#include <mbgl/map/camera.hpp>
+
+@interface MGLMapView (MGLMapViewGestureRecognizerTests)
+
+- (mbgl::Map &)mbglMap;
+
+- (void)handlePinchGesture:(UIPinchGestureRecognizer *)pinch;
+- (void)handleRotateGesture:(UIRotationGestureRecognizer *)rotate;
+- (void)handleDoubleTapGesture:(UITapGestureRecognizer *)doubleTap;
+- (void)handleTwoFingerTapGesture:(UITapGestureRecognizer *)twoFingerTap;
+- (void)handleQuickZoomGesture:(UILongPressGestureRecognizer *)quickZoom;
+- (void)handleTwoFingerDragGesture:(UIPanGestureRecognizer *)twoFingerDrag;
+
+@end
+
+@interface MGLMapViewGestureRecognizerTests : XCTestCase <MGLMapViewDelegate>
+
+@property (nonatomic) MGLMapView *mapView;
+@property (nonatomic) UIWindow *window;
+@property (nonatomic) UIViewController *viewController;
+@property (nonatomic) XCTestExpectation *styleLoadingExpectation;
+@property (nonatomic) XCTestExpectation *twoFingerExpectation;
+@property (nonatomic) XCTestExpectation *quickZoomExpectation;
+@property (nonatomic) XCTestExpectation *doubleTapExpectation;
+@property (nonatomic) XCTestExpectation *twoFingerDragExpectation;
+@property (assign) CGRect screenBounds;
+
+@end
+
+@implementation MGLMapViewGestureRecognizerTests
+
+- (void)setUp {
+ [super setUp];
+
+ [MGLAccountManager setAccessToken:@"pk.feedcafedeadbeefbadebede"];
+ NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"];
+ self.screenBounds = UIScreen.mainScreen.bounds;
+ self.mapView = [[MGLMapView alloc] initWithFrame:self.screenBounds styleURL:styleURL];
+ self.mapView.zoomLevel = 16;
+ self.mapView.delegate = self;
+
+ self.viewController = [[UIViewController alloc] init];
+ self.viewController.view = [[UIView alloc] initWithFrame:self.screenBounds];
+ [self.viewController.view addSubview:self.mapView];
+ self.window = [[UIWindow alloc] initWithFrame:self.screenBounds];
+ [self.window addSubview:self.viewController.view];
+ [self.window makeKeyAndVisible];
+
+ if (!self.mapView.style) {
+ _styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."];
+ [self waitForExpectationsWithTimeout:10 handler:nil];
+ }
+}
+
+- (void)mapView:(MGLMapView *)mapView didFinishLoadingStyle:(MGLStyle *)style {
+ XCTAssertNotNil(mapView.style);
+ XCTAssertEqual(mapView.style, style);
+
+ [_styleLoadingExpectation fulfill];
+}
+
+- (void)testHandlePinchGestureContentInset {
+ UIEdgeInsets contentInset = UIEdgeInsetsZero;
+ self.mapView.contentInset = contentInset;
+ mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(self.mapView.contentInset);
+ auto cameraPadding = self.mapView.mbglMap.getCameraOptions().padding;
+ XCTAssertEqual(padding, cameraPadding, @"MGLMapView's contentInset property should match camera's padding.");
+ XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset));
+
+ contentInset = UIEdgeInsetsMake(20, 20, 20, 20);
+ [self.mapView setCamera:self.mapView.camera withDuration:0.1 animationTimingFunction:nil edgePadding:contentInset completionHandler:nil];
+ XCTAssertFalse(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset));
+
+ cameraPadding = self.mapView.mbglMap.getCameraOptions().padding;
+ XCTAssertNotEqual(padding, cameraPadding);
+
+ UIPinchGestureRecognizerMock *pinchGesture = [[UIPinchGestureRecognizerMock alloc] initWithTarget:nil action:nil];
+ pinchGesture.state = UIGestureRecognizerStateBegan;
+ pinchGesture.scale = 1.0;
+ [self.mapView handlePinchGesture:pinchGesture];
+ XCTAssertNotEqual(padding, cameraPadding);
+
+ pinchGesture.state = UIGestureRecognizerStateChanged;
+ [self.mapView handlePinchGesture:pinchGesture];
+ cameraPadding = self.mapView.mbglMap.getCameraOptions().padding;
+ XCTAssertEqual(padding, cameraPadding, @"When a gesture recognizer is performed contentInsets and camera padding should match.");
+
+ pinchGesture.state = UIGestureRecognizerStateEnded;
+ [self.mapView handlePinchGesture:pinchGesture];
+ cameraPadding = self.mapView.mbglMap.getCameraOptions().padding;
+ XCTAssertEqual(padding, cameraPadding, @"When a gesture recognizer is performed contentInsets and camera padding should match.");
+
+}
+
+- (void)testHandleRotateGestureContentInset {
+ UIEdgeInsets contentInset = UIEdgeInsetsZero;
+ self.mapView.contentInset = contentInset;
+ mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(self.mapView.contentInset);
+ auto cameraPadding = self.mapView.mbglMap.getCameraOptions().padding;
+ XCTAssertEqual(padding, cameraPadding, @"MGLMapView's contentInset property should match camera's padding.");
+ XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset));
+
+ contentInset = UIEdgeInsetsMake(20, 20, 20, 20);
+ [self.mapView setCamera:self.mapView.camera withDuration:0.1 animationTimingFunction:nil edgePadding:contentInset completionHandler:nil];
+ XCTAssertFalse(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset));
+
+ cameraPadding = self.mapView.mbglMap.getCameraOptions().padding;
+ XCTAssertNotEqual(padding, cameraPadding);
+
+ UIRotationGestureRecognizerMock *rotateGesture = [[UIRotationGestureRecognizerMock alloc] initWithTarget:nil action:nil];
+ rotateGesture.state = UIGestureRecognizerStateBegan;
+ rotateGesture.rotation = 1;
+ [self.mapView handleRotateGesture:rotateGesture];
+ XCTAssertNotEqual(padding, cameraPadding);
+
+ rotateGesture.state = UIGestureRecognizerStateChanged;
+ [self.mapView handleRotateGesture:rotateGesture];
+ cameraPadding = self.mapView.mbglMap.getCameraOptions().padding;
+ XCTAssertEqual(padding, cameraPadding, @"When a gesture recognizer is performed contentInsets and camera padding should match.");
+
+ rotateGesture.state = UIGestureRecognizerStateEnded;
+ [self.mapView handleRotateGesture:rotateGesture];
+ cameraPadding = self.mapView.mbglMap.getCameraOptions().padding;
+ XCTAssertEqual(padding, cameraPadding, @"When a gesture recognizer is performed contentInsets and camera padding should match.");
+
+}
+
+- (void)testHandleDoubleTapGestureContentInset {
+ UIEdgeInsets contentInset = UIEdgeInsetsMake(1, 1, 1, 1);
+ self.mapView.contentInset = contentInset;
+ mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(self.mapView.contentInset);
+ auto cameraPadding = self.mapView.mbglMap.getCameraOptions().padding;
+ XCTAssertEqual(padding, cameraPadding, @"MGLMapView's contentInset property should match camera's padding.");
+ XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset));
+
+ contentInset = UIEdgeInsetsMake(20, 20, 20, 20);
+ [self.mapView setCamera:self.mapView.camera withDuration:0.1 animationTimingFunction:nil edgePadding:contentInset completionHandler:nil];
+ XCTAssertFalse(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset));
+
+ cameraPadding = self.mapView.mbglMap.getCameraOptions().padding;
+ XCTAssertNotEqual(padding, cameraPadding);
+
+ UITapGestureRecognizerMock *doubleTapGesture = [[UITapGestureRecognizerMock alloc] initWithTarget:nil action:nil];
+ doubleTapGesture.mockTappedView = self.mapView;
+ doubleTapGesture.mockTappedPoint = CGPointMake(1.0, 1.0);
+
+ [self.mapView handleDoubleTapGesture:doubleTapGesture];
+ _doubleTapExpectation = [self expectationWithDescription:@"Double tap gesture animation."];
+
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+ [self->_doubleTapExpectation fulfill];
+ });
+ [self waitForExpectationsWithTimeout:10 handler:nil];
+
+ cameraPadding = self.mapView.mbglMap.getCameraOptions().padding;
+ XCTAssertEqual(padding, cameraPadding, @"When a gesture recognizer is performed contentInsets and camera padding should match.");
+
+}
+
+- (void)testHandleTwoFingerTapGesture {
+ UIEdgeInsets contentInset = UIEdgeInsetsZero;
+ self.mapView.contentInset = contentInset;
+ mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(self.mapView.contentInset);
+ auto cameraPadding = self.mapView.mbglMap.getCameraOptions().padding;
+ XCTAssertEqual(padding, cameraPadding, @"MGLMapView's contentInset property should match camera's padding.");
+ XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset));
+
+ contentInset = UIEdgeInsetsMake(20, 20, 20, 20);
+ [self.mapView setCamera:self.mapView.camera withDuration:0.1 animationTimingFunction:nil edgePadding:contentInset completionHandler:nil];
+ XCTAssertFalse(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset));
+
+ cameraPadding = self.mapView.mbglMap.getCameraOptions().padding;
+ XCTAssertNotEqual(padding, cameraPadding);
+
+ UITapGestureRecognizerMock *twoFingerTap = [[UITapGestureRecognizerMock alloc] initWithTarget:nil action:nil];
+ twoFingerTap.mockTappedView = self.mapView;
+ twoFingerTap.mockTappedPoint = CGPointMake(1.0, 1.0);
+
+ [self.mapView handleTwoFingerTapGesture:twoFingerTap];
+ _twoFingerExpectation = [self expectationWithDescription:@"Two Finger tap gesture animation."];
+
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+ [self->_twoFingerExpectation fulfill];
+ });
+ [self waitForExpectationsWithTimeout:10 handler:nil];
+
+ cameraPadding = self.mapView.mbglMap.getCameraOptions().padding;
+ XCTAssertEqual(padding, cameraPadding, @"When a gesture recognizer is performed contentInsets and camera padding should match.");
+}
+
+- (void)testHandleQuickZoomGesture {
+ UIEdgeInsets contentInset = UIEdgeInsetsZero;
+ self.mapView.contentInset = contentInset;
+ mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(self.mapView.contentInset);
+ auto cameraPadding = self.mapView.mbglMap.getCameraOptions().padding;
+ XCTAssertEqual(padding, cameraPadding, @"MGLMapView's contentInset property should match camera's padding.");
+ XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset));
+
+ contentInset = UIEdgeInsetsMake(20, 20, 20, 20);
+ [self.mapView setCamera:self.mapView.camera withDuration:0.1 animationTimingFunction:nil edgePadding:contentInset completionHandler:nil];
+ XCTAssertFalse(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset));
+
+ cameraPadding = self.mapView.mbglMap.getCameraOptions().padding;
+ XCTAssertNotEqual(padding, cameraPadding);
+
+ UILongPressGestureRecognizerMock *quickZoom = [[UILongPressGestureRecognizerMock alloc] initWithTarget:nil action:nil];
+ quickZoom.state = UIGestureRecognizerStateBegan;
+ [self.mapView handleQuickZoomGesture:quickZoom];
+ XCTAssertNotEqual(padding, cameraPadding);
+
+ quickZoom.state = UIGestureRecognizerStateChanged;
+ quickZoom.mockTappedPoint = CGPointMake(self.mapView.frame.size.width / 2, self.mapView.frame.size.height / 2);
+ [self.mapView handleQuickZoomGesture:quickZoom];
+ _quickZoomExpectation = [self expectationWithDescription:@"Quick zoom gesture animation."];
+
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+ [self->_quickZoomExpectation fulfill];
+ });
+ [self waitForExpectationsWithTimeout:10 handler:nil];
+
+ cameraPadding = self.mapView.mbglMap.getCameraOptions().padding;
+ XCTAssertEqual(padding, cameraPadding, @"When a gesture recognizer is performed contentInsets and camera padding should match.");
+
+ quickZoom.state = UIGestureRecognizerStateEnded;
+ [self.mapView handleQuickZoomGesture:quickZoom];
+ cameraPadding = self.mapView.mbglMap.getCameraOptions().padding;
+ XCTAssertEqual(padding, cameraPadding, @"When a gesture recognizer is performed contentInsets and camera padding should match.");
+}
+
+- (void)testHandleTwoFingerDragGesture {
+ UIEdgeInsets contentInset = UIEdgeInsetsZero;
+ self.mapView.contentInset = contentInset;
+ mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(self.mapView.contentInset);
+ auto cameraPadding = self.mapView.mbglMap.getCameraOptions().padding;
+ XCTAssertEqual(padding, cameraPadding, @"MGLMapView's contentInset property should match camera's padding.");
+ XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset));
+
+ contentInset = UIEdgeInsetsMake(20, 20, 20, 20);
+ [self.mapView setCamera:self.mapView.camera withDuration:0.1 animationTimingFunction:nil edgePadding:contentInset completionHandler:nil];
+ XCTAssertFalse(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset));
+
+ cameraPadding = self.mapView.mbglMap.getCameraOptions().padding;
+ XCTAssertNotEqual(padding, cameraPadding);
+
+ UIPanGestureRecognizerMock *twoFingerDrag = [[UIPanGestureRecognizerMock alloc] initWithTarget:nil action:nil];
+ twoFingerDrag.state = UIGestureRecognizerStateBegan;
+ twoFingerDrag.firstFingerPoint = CGPointMake(self.mapView.frame.size.width / 3, self.mapView.frame.size.height/2);
+ twoFingerDrag.secondFingerPoint = CGPointMake((self.mapView.frame.size.width / 2), self.mapView.frame.size.height/2);
+ twoFingerDrag.numberOfTouches = 2;
+ [self.mapView handleTwoFingerDragGesture:twoFingerDrag];
+ XCTAssertNotEqual(padding, cameraPadding);
+
+ twoFingerDrag.state = UIGestureRecognizerStateChanged;
+ twoFingerDrag.firstFingerPoint = CGPointMake(self.mapView.frame.size.width / 3, (self.mapView.frame.size.height/2)-10);
+ twoFingerDrag.secondFingerPoint = CGPointMake((self.mapView.frame.size.width / 2), (self.mapView.frame.size.height/2)-10);
+ [self.mapView handleTwoFingerDragGesture:twoFingerDrag];
+ _twoFingerDragExpectation = [self expectationWithDescription:@"Quick zoom gesture animation."];
+
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+ [self->_twoFingerDragExpectation fulfill];
+ });
+ [self waitForExpectationsWithTimeout:10 handler:nil];
+
+ cameraPadding = self.mapView.mbglMap.getCameraOptions().padding;
+ XCTAssertEqual(padding, cameraPadding, @"When a gesture recognizer is performed contentInsets and camera padding should match.");
+
+ twoFingerDrag.state = UIGestureRecognizerStateEnded;
+ [self.mapView handleTwoFingerDragGesture:twoFingerDrag];
+ cameraPadding = self.mapView.mbglMap.getCameraOptions().padding;
+ XCTAssertEqual(padding, cameraPadding, @"When a gesture recognizer is performed contentInsets and camera padding should match.");
+}
+
+@end
diff --git a/platform/ios/test/MGLMapViewLayoutTests.m b/platform/ios/test/MGLMapViewLayoutTests.m
index 5d9c0339b5..2a9579818a 100644
--- a/platform/ios/test/MGLMapViewLayoutTests.m
+++ b/platform/ios/test/MGLMapViewLayoutTests.m
@@ -3,6 +3,7 @@
#import "MGLMapViewDelegate.h"
#import "MGLAccountManager.h"
+#import "MGLScaleBar.h"
@interface MGLOrnamentTestData : NSObject
@@ -24,6 +25,14 @@
@end
+@interface MGLScaleBar (Tests)
+@property (nonatomic, readonly) NSArray<UIView *> *labelViews;
+@property (nonatomic, readonly) NSArray<UIView *> *bars;
+@property (nonatomic, readonly) UIView *containerView;
+@property (nonatomic, readonly) CGSize size;
+@property (nonatomic) NSNumber *testingRightToLeftOverride;
+@end
+
@interface MGLMapViewLayoutTests : XCTestCase<MGLMapViewDelegate>
@@ -140,7 +149,7 @@
expectedOrigin:CGPointMake(margin, margin)],
[MGLOrnamentTestData createWithPosition:MGLOrnamentPositionTopRight
offset:CGPointMake(margin, margin)
- expectedOrigin:CGPointMake(CGRectGetMaxX(self.mapView.bounds) - margin - CGRectGetWidth(view.frame), 4)],
+ expectedOrigin:CGPointMake(CGRectGetMaxX(self.mapView.bounds) - margin - CGRectGetWidth(view.frame), margin)],
[MGLOrnamentTestData createWithPosition:MGLOrnamentPositionBottomLeft
offset:CGPointMake(margin, margin)
expectedOrigin:CGPointMake(margin, CGRectGetMaxY(self.mapView.bounds) - margin - bottomSafeAreaInset - CGRectGetHeight(view.frame))],
@@ -176,6 +185,8 @@
CGFloat margin = 4.0;
UIView *scaleBar = self.mapView.scaleBar;
+ XCTAssertFalse(CGSizeEqualToSize(scaleBar.bounds.size, CGSizeZero));
+
NSArray *testDataList = [self makeTestDataListWithView:scaleBar margin:margin];
for (MGLOrnamentTestData *testData in testDataList) {
@@ -191,6 +202,78 @@
}
}
+// This test checks the frames of the scalebar's subviews, based on the positions
+// as above, but also with forced Right-to-Left reading, and modifying zoom levels.
+- (void)testScalebarSubviewPlacement {
+ double accuracy = 0.01;
+ CGFloat margin = 20.0;
+
+ MGLScaleBar *scaleBar = (MGLScaleBar*)self.mapView.scaleBar;
+ XCTAssertFalse(CGSizeEqualToSize(scaleBar.bounds.size, CGSizeZero));
+
+ for (NSInteger rtl = 0; rtl <= 1; rtl++) {
+ scaleBar.testingRightToLeftOverride = @((BOOL)rtl);
+
+ NSString *positions[] = {
+ @"MGLOrnamentPositionTopLeft",
+ @"MGLOrnamentPositionTopRight",
+ @"MGLOrnamentPositionBottomLeft",
+ @"MGLOrnamentPositionBottomRight"
+ };
+
+ for (CGFloat zoomLevel = 0; zoomLevel < 20; zoomLevel++)
+ {
+ self.mapView.zoomLevel = zoomLevel;
+ [self.superView setNeedsLayout];
+ [self.superView layoutIfNeeded];
+
+ // Following method assumes scaleBar has an up-to-date frame, based
+ // on the current zoom level. Modifying the position and margins
+ // should not affect the overall size of the scalebar.
+
+ NSArray *testDataList = [self makeTestDataListWithView:scaleBar margin:margin];
+
+ CGSize initialSize = scaleBar.intrinsicContentSize;
+ XCTAssert(CGSizeEqualToSize(initialSize, scaleBar.bounds.size));
+
+ for (MGLOrnamentTestData *testData in testDataList) {
+ self.mapView.scaleBarPosition = testData.position;
+ self.mapView.scaleBarMargins = testData.offset;
+
+ [self.superView setNeedsLayout];
+ [self.superView layoutIfNeeded];
+
+ XCTAssert(CGSizeEqualToSize(initialSize, scaleBar.bounds.size));
+
+ NSString *activityName = [NSString stringWithFormat:
+ @"Scalebar subview tests: RTL=%@, Zoom=%ld, POS=%@, Visible=%@",
+ (rtl == 0 ? @"NO" : @"YES"),
+ (long)zoomLevel,
+ positions[testData.position],
+ scaleBar.alpha > 0.0 ? @"YES" : @"NO"];
+
+ [XCTContext runActivityNamed:activityName
+ block:^(id<XCTActivity> activity) {
+
+ // Check the subviews
+ XCTAssertEqualWithAccuracy(CGRectGetMinX(scaleBar.frame), testData.expectedOrigin.x, accuracy);
+ XCTAssertEqualWithAccuracy(CGRectGetMinY(scaleBar.frame), testData.expectedOrigin.y, accuracy);
+
+ XCTAssertTrue(CGRectContainsRect(scaleBar.bounds, scaleBar.containerView.frame));
+ for (UIView *bar in scaleBar.bars) {
+ XCTAssertTrue(CGRectContainsRect(scaleBar.containerView.bounds, bar.frame));
+ }
+ for (UIView *label in scaleBar.labelViews) {
+ if (!label.isHidden) {
+ XCTAssertTrue(CGRectContainsRect(scaleBar.bounds, label.frame));
+ }
+ }
+ }];
+ }
+ }
+ }
+}
+
- (void)testAttributionButtonPlacement {
double accuracy = 0.01;
CGFloat margin = 4.0;
diff --git a/platform/ios/test/MGLMapViewPitchTests.m b/platform/ios/test/MGLMapViewPitchTests.m
index 3e9311dbd4..fa657eb994 100644
--- a/platform/ios/test/MGLMapViewPitchTests.m
+++ b/platform/ios/test/MGLMapViewPitchTests.m
@@ -2,6 +2,7 @@
#import <XCTest/XCTest.h>
@interface MockUIPanGestureRecognizer : UIPanGestureRecognizer
+@property(nonatomic, readwrite) UIGestureRecognizerState state;
@property NSUInteger mbx_numberOfFingersForGesture;
@property CGPoint mbx_middlePoint;
@property CGPoint mbx_westPoint;
@@ -9,6 +10,9 @@
@end
@implementation MockUIPanGestureRecognizer
+
+@synthesize state;
+
- (instancetype)initWithTarget:(id)target action:(SEL)action {
if (self = [super initWithTarget:target action:action]) {
self.mbx_numberOfFingersForGesture = 2;
diff --git a/platform/ios/test/MGLMapViewScaleBarTests.m b/platform/ios/test/MGLMapViewScaleBarTests.m
index 29acc75d7f..b4f81ef62b 100644
--- a/platform/ios/test/MGLMapViewScaleBarTests.m
+++ b/platform/ios/test/MGLMapViewScaleBarTests.m
@@ -36,11 +36,14 @@
XCTAssertFalse(scaleBar.hidden);
// Scale bar should not be visible at default zoom (~z0), but it should be ready.
- XCTAssertFalse(CGSizeEqualToSize(scaleBar.intrinsicContentSize, CGSizeZero));
+ // Size is not a measure of readiness here though.
+ XCTAssertTrue(CGSizeEqualToSize(scaleBar.intrinsicContentSize, CGSizeZero));
XCTAssertEqual(scaleBar.alpha, 0);
self.mapView.zoomLevel = 15;
+ [self.mapView layoutIfNeeded];
XCTAssertGreaterThan(scaleBar.alpha, 0);
+ XCTAssertFalse(CGSizeEqualToSize(scaleBar.intrinsicContentSize, CGSizeZero));
}
- (void)testDirectlySettingScaleBarViewHiddenProperty {
@@ -54,10 +57,14 @@
// ... but triggering any camera event will update it.
self.mapView.zoomLevel = 1;
- XCTAssertFalse(CGSizeEqualToSize(scaleBar.intrinsicContentSize, CGSizeZero));
+ [self.mapView layoutIfNeeded];
+
+ XCTAssertTrue(CGSizeEqualToSize(scaleBar.intrinsicContentSize, CGSizeZero));
XCTAssertEqual(scaleBar.alpha, 0);
self.mapView.zoomLevel = 15;
+ [self.mapView layoutIfNeeded];
+
XCTAssertGreaterThan(scaleBar.alpha, 0);
-}
-@end
+ XCTAssertFalse(CGSizeEqualToSize(scaleBar.intrinsicContentSize, CGSizeZero));
+}@end
diff --git a/platform/ios/test/MGLMockGestureRecognizers.h b/platform/ios/test/MGLMockGestureRecognizers.h
index aa5fbec494..29889e39f4 100644
--- a/platform/ios/test/MGLMockGestureRecognizers.h
+++ b/platform/ios/test/MGLMockGestureRecognizers.h
@@ -4,7 +4,26 @@
@interface UIPinchGestureRecognizerMock : UIPinchGestureRecognizer
@property (nonatomic, readwrite) CGFloat velocity;
@property (nonatomic) CGPoint locationInViewOverride;
+@property(nonatomic, readwrite) UIGestureRecognizerState state;
@end
@interface UIRotationGestureRecognizerMock : UIRotationGestureRecognizer
+@property(nonatomic, readwrite) UIGestureRecognizerState state;
+@end
+
+@interface UITapGestureRecognizerMock : UITapGestureRecognizer
+@property (strong, nonatomic) UIView *mockTappedView;
+@property (assign) CGPoint mockTappedPoint;
+@end
+
+@interface UILongPressGestureRecognizerMock : UILongPressGestureRecognizer
+@property(nonatomic, readwrite) UIGestureRecognizerState state;
+@property (assign) CGPoint mockTappedPoint;
+@end
+
+@interface UIPanGestureRecognizerMock : UIPanGestureRecognizer
+@property(nonatomic, readwrite) UIGestureRecognizerState state;
+@property (assign) CGPoint firstFingerPoint;
+@property (assign) CGPoint secondFingerPoint;
+@property(nonatomic, readwrite) NSUInteger numberOfTouches;
@end
diff --git a/platform/ios/test/MGLMockGestureRecognizers.m b/platform/ios/test/MGLMockGestureRecognizers.m
index 89df6750a9..c818805174 100644
--- a/platform/ios/test/MGLMockGestureRecognizers.m
+++ b/platform/ios/test/MGLMockGestureRecognizers.m
@@ -1,11 +1,55 @@
#import "MGLMockGestureRecognizers.h"
+#import "objc/runtime.h"
@implementation UIPinchGestureRecognizerMock
@synthesize velocity;
+@synthesize state;
- (CGPoint)locationInView:(nullable UIView *)view { return self.locationInViewOverride; }
@end
@implementation UIRotationGestureRecognizerMock
- (CGPoint)locationInView:(nullable UIView*)view { return view.center; }
+@synthesize state;
+@end
+
+@implementation UITapGestureRecognizerMock
+
++ (void)load {
+ method_exchangeImplementations(class_getInstanceMethod(self, @selector(state)),
+ class_getInstanceMethod(self, @selector(mockState)));
+}
+
+- (UIGestureRecognizerState)mockState {
+ return UIGestureRecognizerStateRecognized;
+}
+
+- (UIView *)view {
+ return self.mockTappedView;
+}
+
+- (CGPoint)locationInView:(UIView *)view {
+ return self.mockTappedPoint;
+}
+
+@end
+
+@implementation UILongPressGestureRecognizerMock
+@synthesize state;
+
+- (CGPoint)locationInView:(UIView *)view {
+ return self.mockTappedPoint;
+}
+@end
+
+@implementation UIPanGestureRecognizerMock
+@synthesize state;
+@synthesize numberOfTouches;
+
+- (CGPoint)locationOfTouch:(NSUInteger)touchIndex inView:(UIView *)view {
+ if (touchIndex) {
+ return self.secondFingerPoint;
+ }
+ return self.firstFingerPoint;
+}
@end
diff --git a/platform/linux/config.cmake b/platform/linux/config.cmake
index 55e6e3c192..39ae7c6d52 100644
--- a/platform/linux/config.cmake
+++ b/platform/linux/config.cmake
@@ -45,19 +45,18 @@ macro(mbgl_platform_core)
PRIVATE platform/linux/src/gl_functions.cpp
# Misc
+ PRIVATE platform/default/src/mbgl/i18n/collator.cpp
+ PRIVATE platform/default/src/mbgl/i18n/number_format.cpp
+ PRIVATE platform/default/src/mbgl/text/bidi.cpp
+ PRIVATE platform/default/src/mbgl/text/local_glyph_rasterizer.cpp
+ PRIVATE platform/default/src/mbgl/layermanager/layer_manager.cpp
PRIVATE platform/default/src/mbgl/util/compression.cpp
PRIVATE platform/default/src/mbgl/util/logging_stderr.cpp
+ PRIVATE platform/default/src/mbgl/util/monotonic_timer.cpp
PRIVATE platform/default/src/mbgl/util/string_stdlib.cpp
PRIVATE platform/default/src/mbgl/util/thread.cpp
- PRIVATE platform/default/src/mbgl/text/bidi.cpp
- PRIVATE platform/default/src/mbgl/text/collator.cpp
- PRIVATE platform/default/src/mbgl/layermanager/layer_manager.cpp
- PRIVATE platform/default/src/mbgl/text/local_glyph_rasterizer.cpp
PRIVATE platform/default/src/mbgl/util/thread_local.cpp
- PRIVATE platform/default/src/mbgl/text/unaccent.cpp
- PRIVATE platform/default/include/mbgl/text/unaccent.hpp
PRIVATE platform/default/src/mbgl/util/utf.cpp
- PRIVATE platform/default/src/mbgl/util/format_number.cpp
# Image handling
PRIVATE platform/default/src/mbgl/util/image.cpp
@@ -88,7 +87,7 @@ macro(mbgl_platform_core)
target_add_mason_package(mbgl-core PRIVATE icu)
# Ignore warning caused by ICU header unistr.h in some CI environments
- set_source_files_properties(platform/default/src/mbgl/util/format_number.cpp PROPERTIES COMPILE_FLAGS -Wno-error=shadow)
+ set_source_files_properties(platform/default/src/mbgl/i18n/number_format.cpp PROPERTIES COMPILE_FLAGS -Wno-error=shadow)
# Link all ICU libraries (by default only libicuuc is linked)
find_library(LIBICUI18N NAMES icui18n HINTS ${MASON_PACKAGE_icu_INCLUDE_DIRS}/../lib)
diff --git a/platform/macos/core-files.json b/platform/macos/core-files.json
index 21b0e698f0..5fde52876a 100644
--- a/platform/macos/core-files.json
+++ b/platform/macos/core-files.json
@@ -7,6 +7,7 @@
"platform/darwin/src/local_glyph_rasterizer.mm",
"platform/darwin/src/logging_nslog.mm",
"platform/darwin/src/nsthread.mm",
+ "platform/darwin/src/number_format.mm",
"platform/darwin/src/reachability.m",
"platform/darwin/src/string_nsstring.mm",
"platform/default/src/mbgl/gfx/headless_backend.cpp",
@@ -15,6 +16,7 @@
"platform/default/src/mbgl/map/map_snapshotter.cpp",
"platform/default/src/mbgl/text/bidi.cpp",
"platform/default/src/mbgl/util/compression.cpp",
+ "platform/default/src/mbgl/util/monotonic_timer.cpp",
"platform/default/src/mbgl/util/png_writer.cpp",
"platform/default/src/mbgl/util/thread_local.cpp",
"platform/default/src/mbgl/util/utf.cpp"
diff --git a/platform/node/CHANGELOG.md b/platform/node/CHANGELOG.md
index 84fbff741c..6af55f5d50 100644
--- a/platform/node/CHANGELOG.md
+++ b/platform/node/CHANGELOG.md
@@ -1,4 +1,6 @@
-# master
+
+# 5.0.0
+* No longer supporting source-compile fallback ([#15748](https://github.com/mapbox/mapbox-gl-native/pull/15748))
* Add support for feature state APIs. ([#15480](https://github.com/mapbox/mapbox-gl-native/pull/15480))
# 4.3.0
diff --git a/platform/node/DEVELOPING.md b/platform/node/DEVELOPING.md
index 3d07253ee3..1466052155 100644
--- a/platform/node/DEVELOPING.md
+++ b/platform/node/DEVELOPING.md
@@ -8,27 +8,28 @@ To develop these bindings, you’ll need to build them from source. Building req
the [macOS](../macos/INSTALL.md#requirements) or [Linux](../linux/README.md#prerequisites) install documentation, depending
on the target platform.
-To compile the Node.js bindings and install module dependencies, from the repository root directory, run:
+To compile the Node.js bindings and install module dependencies, from the repository root directory, first run:
- npm install --build-from-source
+```
+make distclean
+```
-To recompile just the C++ code while developing, run `make node`.
+If you are rebuilding after time has passed.
-To create an Xcode project and use a GUI debugger in the case of a crash, run `make xnode`.
+Then do:
+
+```bash
+make node
+```
## Testing
To test the Node.js bindings:
-```
+```bash
npm test
```
-To run the visual render test suite:
-
-```
-npm run test-suite
-```
## Merging your pull request
diff --git a/platform/node/README.md b/platform/node/README.md
index 7273c0813a..859172108b 100644
--- a/platform/node/README.md
+++ b/platform/node/README.md
@@ -9,7 +9,7 @@ Requires a modern C++ runtime that supports C++14.
By default, installs binaries. On these platforms no additional dependencies are needed.
- 64 bit macOS or 64 bit Linux
-- Node.js v4.x _(note: v5+ is known to have issues)_
+- Node.js v10.x
Run:
@@ -168,9 +168,9 @@ var map = new mbgl.Map({
if (res.headers.modified) { response.modified = new Date(res.headers.modified); }
if (res.headers.expires) { response.expires = new Date(res.headers.expires); }
if (res.headers.etag) { response.etag = res.headers.etag; }
-
+
response.data = body;
-
+
callback(null, response);
} else {
callback(new Error(JSON.parse(body).message));
diff --git a/platform/node/test/ignores.json b/platform/node/test/ignores.json
index f053b2add1..51decb351c 100644
--- a/platform/node/test/ignores.json
+++ b/platform/node/test/ignores.json
@@ -16,6 +16,10 @@
"expression-tests/legacy/interval/composite": "https://github.com/mapbox/mapbox-gl-native/issues/12747",
"expression-tests/legacy/interval/composite-default": "https://github.com/mapbox/mapbox-gl-native/issues/12747",
"expression-tests/legacy/interval/tokens-zoom": "https://github.com/mapbox/mapbox-gl-native/issues/12747",
+ "expression-tests/image/basic": "https://github.com/mapbox/mapbox-gl-native/issues/15800",
+ "expression-tests/image/compound": "https://github.com/mapbox/mapbox-gl-native/issues/15800",
+ "expression-tests/image/coalesce": "https://github.com/mapbox/mapbox-gl-native/issues/15800",
+ "expression-tests/image/implicit-assert": "https://github.com/mapbox/mapbox-gl-native/issues/15800",
"query-tests/geometry/multilinestring": "needs investigation",
"query-tests/geometry/multipolygon": "needs investigation",
"query-tests/geometry/polygon": "needs investigation",
@@ -71,6 +75,10 @@
"render-tests/circle-sort-key/literal": "https://github.com/mapbox/mapbox-gl-native/issues/15008",
"render-tests/fill-sort-key/literal": "https://github.com/mapbox/mapbox-gl-native/issues/15008",
"render-tests/line-sort-key/literal": "https://github.com/mapbox/mapbox-gl-native/issues/15008",
+ "render-tests/regressions/mapbox-gl-js#8817": "skip - https://github.com/mapbox/mapbox-gl-native/issues/15737",
+ "render-tests/text-max-width/zero-width-point-placement": "https://github.com/mapbox/mapbox-gl-native/issues/15648",
+ "render-tests/icon-image/image-expression": "https://github.com/mapbox/mapbox-gl-native/issues/15800",
+ "render-tests/icon-text-fit/text-variable-anchor-overlap": "https://github.com/mapbox/mapbox-gl-native/issues/15809",
"query-tests/fill-extrusion/base-in": "https://github.com/mapbox/mapbox-gl-native/issues/13139",
"query-tests/fill-extrusion/box-in": "https://github.com/mapbox/mapbox-gl-native/issues/13139",
"query-tests/fill-extrusion/side-in": "https://github.com/mapbox/mapbox-gl-native/issues/13139",
diff --git a/platform/qt/src/format_number.cpp b/platform/qt/src/number_format.cpp
index b6fe3558e6..b6fe3558e6 100644
--- a/platform/qt/src/format_number.cpp
+++ b/platform/qt/src/number_format.cpp
diff --git a/platform/qt/src/qmapboxgl_scheduler.cpp b/platform/qt/src/qmapboxgl_scheduler.cpp
index e2d39703ee..5fc3ab13de 100644
--- a/platform/qt/src/qmapboxgl_scheduler.cpp
+++ b/platform/qt/src/qmapboxgl_scheduler.cpp
@@ -13,10 +13,9 @@ QMapboxGLScheduler::~QMapboxGLScheduler()
MBGL_VERIFY_THREAD(tid);
}
-void QMapboxGLScheduler::schedule(std::weak_ptr<mbgl::Mailbox> mailbox)
-{
+void QMapboxGLScheduler::schedule(std::function<void()> function) {
std::lock_guard<std::mutex> lock(m_taskQueueMutex);
- m_taskQueue.push(mailbox);
+ m_taskQueue.push(std::move(function));
// Need to force the main thread to wake
// up this thread and process the events.
@@ -25,14 +24,15 @@ void QMapboxGLScheduler::schedule(std::weak_ptr<mbgl::Mailbox> mailbox)
void QMapboxGLScheduler::processEvents()
{
- std::queue<std::weak_ptr<mbgl::Mailbox>> taskQueue;
+ std::queue<std::function<void()>> taskQueue;
{
std::unique_lock<std::mutex> lock(m_taskQueueMutex);
std::swap(taskQueue, m_taskQueue);
}
while (!taskQueue.empty()) {
- mbgl::Mailbox::maybeReceive(taskQueue.front());
+ auto& function = taskQueue.front();
+ if (function) function();
taskQueue.pop();
}
}
diff --git a/platform/qt/src/qmapboxgl_scheduler.hpp b/platform/qt/src/qmapboxgl_scheduler.hpp
index 68636d0d11..0b000b9fcc 100644
--- a/platform/qt/src/qmapboxgl_scheduler.hpp
+++ b/platform/qt/src/qmapboxgl_scheduler.hpp
@@ -1,6 +1,5 @@
#pragma once
-#include <mbgl/actor/mailbox.hpp>
#include <mbgl/actor/scheduler.hpp>
#include <mbgl/util/util.hpp>
@@ -19,7 +18,8 @@ public:
virtual ~QMapboxGLScheduler();
// mbgl::Scheduler implementation.
- void schedule(std::weak_ptr<mbgl::Mailbox> scheduled) final;
+ void schedule(std::function<void()> scheduled) final;
+ mapbox::base::WeakPtr<Scheduler> makeWeakPtr() override { return weakFactory.makeWeakPtr(); }
void processEvents();
@@ -30,5 +30,6 @@ private:
MBGL_STORE_THREAD(tid);
std::mutex m_taskQueueMutex;
- std::queue<std::weak_ptr<mbgl::Mailbox>> m_taskQueue;
+ std::queue<std::function<void()>> m_taskQueue;
+ mapbox::base::WeakPtrFactory<Scheduler> weakFactory{this};
};
diff --git a/platform/qt/src/qt_geojson.cpp b/platform/qt/src/qt_geojson.cpp
index 48d78abfe0..3b775685b1 100644
--- a/platform/qt/src/qt_geojson.cpp
+++ b/platform/qt/src/qt_geojson.cpp
@@ -121,7 +121,7 @@ mbgl::FeatureIdentifier asMapboxGLFeatureIdentifier(const QVariant &id) {
}
}
-mbgl::Feature asMapboxGLFeature(const QMapbox::Feature &feature) {
+mbgl::GeoJSONFeature asMapboxGLFeature(const QMapbox::Feature &feature) {
mbgl::PropertyMap properties;
properties.reserve(feature.properties.size());
for (auto it = feature.properties.constBegin(); it != feature.properties.constEnd(); ++it) {
diff --git a/platform/qt/src/qt_geojson.hpp b/platform/qt/src/qt_geojson.hpp
index a9c10272ab..a7db3ba644 100644
--- a/platform/qt/src/qt_geojson.hpp
+++ b/platform/qt/src/qt_geojson.hpp
@@ -22,6 +22,6 @@ mbgl::Polygon<double> asMapboxGLPolygon(const QMapbox::CoordinatesCollection &po
mbgl::MultiPolygon<double> asMapboxGLMultiPolygon(const QMapbox::CoordinatesCollections &multiPolygon);
mbgl::Value asMapboxGLPropertyValue(const QVariant &value);
mbgl::FeatureIdentifier asMapboxGLFeatureIdentifier(const QVariant &id);
-mbgl::Feature asMapboxGLFeature(const QMapbox::Feature &feature);
+mbgl::GeoJSONFeature asMapboxGLFeature(const QMapbox::Feature &feature);
} // namespace QMapbox