summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorm-stephen <truestyle2005@163.com>2019-11-01 10:27:03 +0800
committerGitHub <noreply@github.com>2019-11-01 10:27:03 +0800
commit9427c04bc709c39f7e083b6d1597aaf33af8c302 (patch)
tree224fa2bffbc6a81b447c76b98e4c13a51baadc29
parentfc2c02bbc6abaef52077fe5e9e78f772e6009967 (diff)
parent5b38cfee18800cbb3c6a3186882744592662c3d6 (diff)
downloadqtlocation-mapboxgl-upstream/stephen-improve-accuracy-for-camera.tar.gz
Merge branch 'master' into stephen-improve-accuracy-for-cameraupstream/stephen-improve-accuracy-for-camera
-rw-r--r--.gitignore5
-rw-r--r--.npmignore32
-rw-r--r--LICENSE.md14
-rw-r--r--README.md2
-rw-r--r--benchmark/api/render.benchmark.cpp44
-rw-r--r--benchmark/fixtures/api/cache.dbbin1298432 -> 1298432 bytes
-rw-r--r--bin/render.cpp2
-rw-r--r--circle.yml254
-rw-r--r--cmake/expression-test.cmake2
-rw-r--r--cmake/node.cmake8
-rw-r--r--cmake/render-test.cmake58
-rw-r--r--expression-test/expression_test_parser.cpp31
-rw-r--r--expression-test/expression_test_runner.cpp114
-rw-r--r--expression-test/test_runner_common.cpp131
-rw-r--r--expression-test/test_runner_common.hpp13
-rw-r--r--include/mbgl/actor/mailbox.hpp2
-rw-r--r--include/mbgl/actor/scheduler.hpp24
-rw-r--r--include/mbgl/gfx/rendering_stats.hpp22
-rw-r--r--include/mbgl/i18n/collator.hpp24
-rw-r--r--include/mbgl/i18n/number_format.hpp15
-rw-r--r--include/mbgl/style/conversion.hpp5
-rw-r--r--include/mbgl/style/conversion_impl.hpp87
-rw-r--r--include/mbgl/style/expression/collator.hpp6
-rw-r--r--include/mbgl/style/expression/formatted.hpp16
-rw-r--r--include/mbgl/style/layer.hpp10
-rw-r--r--include/mbgl/style/layers/background_layer.hpp2
-rw-r--r--include/mbgl/style/layers/circle_layer.hpp2
-rw-r--r--include/mbgl/style/layers/custom_layer.hpp2
-rw-r--r--include/mbgl/style/layers/fill_extrusion_layer.hpp2
-rw-r--r--include/mbgl/style/layers/fill_layer.hpp2
-rw-r--r--include/mbgl/style/layers/heatmap_layer.hpp2
-rw-r--r--include/mbgl/style/layers/hillshade_layer.hpp2
-rw-r--r--include/mbgl/style/layers/layer.hpp.ejs2
-rw-r--r--include/mbgl/style/layers/line_layer.hpp2
-rw-r--r--include/mbgl/style/layers/raster_layer.hpp2
-rw-r--r--include/mbgl/style/layers/symbol_layer.hpp2
-rw-r--r--include/mbgl/style/light.hpp2
-rw-r--r--include/mbgl/style/light.hpp.ejs2
-rw-r--r--include/mbgl/style/sources/geojson_source.hpp17
-rw-r--r--include/mbgl/style/style_property.hpp26
-rw-r--r--include/mbgl/util/feature.hpp26
-rw-r--r--include/mbgl/util/monotonic_timer.hpp24
-rw-r--r--include/mbgl/util/platform.hpp3
-rw-r--r--include/mbgl/util/run_loop.hpp10
-rw-r--r--include/mbgl/util/traits.hpp10
m---------mapbox-gl-js0
-rw-r--r--next/CMakeLists.txt17
-rw-r--r--next/benchmark/CMakeLists.txt6
-rw-r--r--next/expression-test/CMakeLists.txt2
-rw-r--r--next/platform/android/android.cmake73
-rw-r--r--next/platform/ios/ios.cmake20
-rw-r--r--next/platform/linux/linux.cmake36
-rw-r--r--next/platform/macos/macos.cmake31
-rw-r--r--next/platform/node/CMakeLists.txt1
-rw-r--r--next/platform/qt/qt.cmake7
-rw-r--r--next/render-test/CMakeLists.txt39
-rw-r--r--next/test/CMakeLists.txt1
-rw-r--r--package.json4
-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
-rw-r--r--render-test/android-manifest.json7
-rw-r--r--render-test/android/README.md9
-rw-r--r--render-test/android/app/build.gradle29
-rw-r--r--render-test/android/app/src/main/AndroidManifest.xml26
-rw-r--r--render-test/android/app/src/main/res/mipmap-hdpi/ic_launcher.pngbin0 -> 3418 bytes
-rw-r--r--render-test/android/app/src/main/res/mipmap-mdpi/ic_launcher.pngbin0 -> 2206 bytes
-rw-r--r--render-test/android/app/src/main/res/mipmap-xhdpi/ic_launcher.pngbin0 -> 4842 bytes
-rw-r--r--render-test/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.pngbin0 -> 7718 bytes
-rw-r--r--render-test/android/app/src/main/res/values/strings.xml4
-rw-r--r--render-test/android/build.gradle16
-rw-r--r--render-test/android/gradle.properties1
-rw-r--r--render-test/android/gradle/wrapper/gradle-wrapper.jarbin0 -> 49896 bytes
-rw-r--r--render-test/android/gradle/wrapper/gradle-wrapper.properties6
-rwxr-xr-xrender-test/android/gradlew164
-rw-r--r--render-test/android/gradlew.bat90
-rwxr-xr-xrender-test/android/render_test_setup.sh42
-rw-r--r--render-test/android/settings.gradle2
-rw-r--r--render-test/expected/render-tests/debug/collision-lines-overscaled/expected.png (renamed from render-test/expected/debug/collision-lines-overscaled/expected.png)bin7774 -> 7774 bytes
-rw-r--r--render-test/expected/render-tests/debug/collision-lines-pitched/expected.png (renamed from render-test/expected/debug/collision-lines-pitched/expected.png)bin165500 -> 165500 bytes
-rw-r--r--render-test/expected/render-tests/debug/collision-lines/expected.png (renamed from render-test/expected/debug/collision-lines/expected.png)bin196454 -> 196454 bytes
-rw-r--r--render-test/expected/render-tests/debug/collision-pitched-wrapped/expected.png (renamed from render-test/expected/debug/collision-pitched-wrapped/expected.png)bin69725 -> 69725 bytes
-rw-r--r--render-test/expected/render-tests/symbol-visibility/visible/expected.png (renamed from render-test/expected/symbol-visibility/visible/expected.png)bin14729 -> 14729 bytes
-rw-r--r--render-test/expected/render-tests/text-pitch-alignment/auto-text-rotation-alignment-map/expected.png (renamed from render-test/expected/text-pitch-alignment/auto-text-rotation-alignment-map/expected.png)bin26157 -> 26157 bytes
-rw-r--r--render-test/expected/render-tests/text-pitch-alignment/map-text-rotation-alignment-map/expected.png (renamed from render-test/expected/text-pitch-alignment/map-text-rotation-alignment-map/expected.png)bin26157 -> 26157 bytes
-rw-r--r--render-test/expected/render-tests/text-pitch-alignment/viewport-text-rotation-alignment-map/expected.png (renamed from render-test/expected/text-pitch-alignment/viewport-text-rotation-alignment-map/expected.png)bin26866 -> 26866 bytes
-rw-r--r--render-test/expected/render-tests/text-variable-anchor/pitched-rotated-debug/expected.png (renamed from render-test/expected/text-variable-anchor/pitched-rotated-debug/expected.png)bin43135 -> 43135 bytes
-rw-r--r--render-test/expected/render-tests/text-variable-anchor/rotated-offset/expected.png (renamed from render-test/expected/text-variable-anchor/rotated-offset/expected.png)bin35553 -> 35553 bytes
-rw-r--r--render-test/file_source.cpp65
-rw-r--r--render-test/file_source.hpp40
-rw-r--r--render-test/include/mbgl/render_test.hpp6
-rw-r--r--render-test/linux-manifest.json7
-rw-r--r--render-test/linux-probe-manifest.json7
-rw-r--r--render-test/mac-manifest.json7
-rw-r--r--render-test/mac-probe-manifest.json7
-rw-r--r--render-test/manifest_parser.cpp400
-rw-r--r--render-test/manifest_parser.hpp55
-rw-r--r--render-test/metadata.hpp89
-rw-r--r--render-test/parser.cpp673
-rw-r--r--render-test/parser.hpp20
-rw-r--r--render-test/render_test.cpp (renamed from render-test/main.cpp)126
-rw-r--r--render-test/runner.cpp762
-rw-r--r--render-test/runner.hpp27
-rw-r--r--render-test/tests/file-size/fail-file-doesnt-match/expected.pngbin0 -> 169 bytes
-rw-r--r--render-test/tests/file-size/fail-file-doesnt-match/metrics.json14
-rw-r--r--render-test/tests/file-size/fail-file-doesnt-match/style.json32
-rw-r--r--render-test/tests/file-size/fail-file-not-found/expected.pngbin0 -> 169 bytes
-rw-r--r--render-test/tests/file-size/fail-file-not-found/style.json32
-rw-r--r--render-test/tests/file-size/fail-size-is-over/expected.pngbin0 -> 169 bytes
-rw-r--r--render-test/tests/file-size/fail-size-is-over/metrics.json14
-rw-r--r--render-test/tests/file-size/fail-size-is-over/style.json32
-rw-r--r--render-test/tests/file-size/fail-size-is-under/expected.pngbin0 -> 169 bytes
-rw-r--r--render-test/tests/file-size/fail-size-is-under/metrics.json14
-rw-r--r--render-test/tests/file-size/fail-size-is-under/style.json32
-rw-r--r--render-test/tests/file-size/pass-size-is-in-tolerance-higher/expected.pngbin0 -> 169 bytes
-rw-r--r--render-test/tests/file-size/pass-size-is-in-tolerance-higher/metrics.json14
-rw-r--r--render-test/tests/file-size/pass-size-is-in-tolerance-higher/style.json32
-rw-r--r--render-test/tests/file-size/pass-size-is-in-tolerance-lower/expected.pngbin0 -> 169 bytes
-rw-r--r--render-test/tests/file-size/pass-size-is-in-tolerance-lower/metrics.json14
-rw-r--r--render-test/tests/file-size/pass-size-is-in-tolerance-lower/style.json32
-rw-r--r--render-test/tests/file-size/pass-size-is-same/expected.pngbin0 -> 169 bytes
-rw-r--r--render-test/tests/file-size/pass-size-is-same/metrics.json14
-rw-r--r--render-test/tests/file-size/pass-size-is-same/style.json32
-rw-r--r--render-test/tests/gfx/fail-ib-mem-mismatch/expected.pngbin0 -> 46431 bytes
-rw-r--r--render-test/tests/gfx/fail-ib-mem-mismatch/metrics.json5
-rw-r--r--render-test/tests/gfx/fail-ib-mem-mismatch/style.json20
-rw-r--r--render-test/tests/gfx/fail-negative-framebuffer-count/expected.pngbin0 -> 46431 bytes
-rw-r--r--render-test/tests/gfx/fail-negative-framebuffer-count/metrics.json5
-rw-r--r--render-test/tests/gfx/fail-negative-framebuffer-count/style.json20
-rw-r--r--render-test/tests/gfx/fail-texture-mem-mismatch/expected.pngbin0 -> 46431 bytes
-rw-r--r--render-test/tests/gfx/fail-texture-mem-mismatch/metrics.json5
-rw-r--r--render-test/tests/gfx/fail-texture-mem-mismatch/style.json20
-rw-r--r--render-test/tests/gfx/fail-too-few-buffers/expected.pngbin0 -> 46431 bytes
-rw-r--r--render-test/tests/gfx/fail-too-few-buffers/metrics.json5
-rw-r--r--render-test/tests/gfx/fail-too-few-buffers/style.json20
-rw-r--r--render-test/tests/gfx/fail-too-few-textures/expected.pngbin0 -> 46431 bytes
-rw-r--r--render-test/tests/gfx/fail-too-few-textures/metrics.json5
-rw-r--r--render-test/tests/gfx/fail-too-few-textures/style.json20
-rw-r--r--render-test/tests/gfx/fail-too-many-drawcalls/expected.pngbin0 -> 46431 bytes
-rw-r--r--render-test/tests/gfx/fail-too-many-drawcalls/metrics.json5
-rw-r--r--render-test/tests/gfx/fail-too-many-drawcalls/style.json20
-rw-r--r--render-test/tests/gfx/fail-vb-mem-mismatch/expected.pngbin0 -> 46431 bytes
-rw-r--r--render-test/tests/gfx/fail-vb-mem-mismatch/metrics.json5
-rw-r--r--render-test/tests/gfx/fail-vb-mem-mismatch/style.json20
-rw-r--r--render-test/tests/gfx/pass-double-probe/expected.pngbin0 -> 42373 bytes
-rw-r--r--render-test/tests/gfx/pass-double-probe/metrics.json6
-rw-r--r--render-test/tests/gfx/pass-double-probe/style.json22
-rw-r--r--render-test/tests/gfx/pass-probe-reset/expected.pngbin0 -> 42373 bytes
-rw-r--r--render-test/tests/gfx/pass-probe-reset/metrics.json6
-rw-r--r--render-test/tests/gfx/pass-probe-reset/style.json24
-rw-r--r--render-test/tests/gfx/pass/expected.pngbin0 -> 46431 bytes
-rw-r--r--render-test/tests/gfx/pass/metrics.json5
-rw-r--r--render-test/tests/gfx/pass/style.json20
-rw-r--r--render-test/tests/mac/memory/pass-memory-size-is-same/expected.pngbin0 -> 96 bytes
-rw-r--r--render-test/tests/mac/memory/pass-memory-size-is-same/metrics.json19
-rw-r--r--render-test/tests/memory/fail-memory-size-is-too-big/expected.pngbin0 -> 96 bytes
-rw-r--r--render-test/tests/memory/fail-memory-size-is-too-big/metrics.json19
-rw-r--r--render-test/tests/memory/fail-memory-size-is-too-big/style.json52
-rw-r--r--render-test/tests/memory/fail-memory-size-is-too-small/expected.pngbin0 -> 96 bytes
-rw-r--r--render-test/tests/memory/fail-memory-size-is-too-small/metrics.json19
-rw-r--r--render-test/tests/memory/fail-memory-size-is-too-small/style.json52
-rw-r--r--render-test/tests/memory/pass-memory-size-is-same/expected.pngbin0 -> 96 bytes
-rw-r--r--render-test/tests/memory/pass-memory-size-is-same/metrics.json19
-rw-r--r--render-test/tests/memory/pass-memory-size-is-same/style.json52
-rw-r--r--render-test/tests/network/fail-requests-transferred/expected.pngbin0 -> 17383 bytes
-rw-r--r--render-test/tests/network/fail-requests-transferred/metrics.json14
-rw-r--r--render-test/tests/network/fail-requests-transferred/style.json67
-rw-r--r--render-test/tests/network/fail-requests/expected.pngbin0 -> 17383 bytes
-rw-r--r--render-test/tests/network/fail-requests/metrics.json14
-rw-r--r--render-test/tests/network/fail-requests/style.json67
-rw-r--r--render-test/tests/network/fail-transferred/expected.pngbin0 -> 17383 bytes
-rw-r--r--render-test/tests/network/fail-transferred/metrics.json14
-rw-r--r--render-test/tests/network/fail-transferred/style.json67
-rw-r--r--render-test/tests/network/pass/expected.pngbin0 -> 17383 bytes
-rw-r--r--render-test/tests/network/pass/metrics.json14
-rw-r--r--render-test/tests/network/pass/style.json67
-rw-r--r--render-test/tests/should-fail.json18
-rw-r--r--scripts/ci/Dockerfile8
-rwxr-xr-xscripts/generate-file-lists.js3
-rwxr-xr-xscripts/generate-style-code.js3
-rw-r--r--src/core-files.json10
-rw-r--r--src/mbgl/actor/mailbox.cpp12
-rw-r--r--src/mbgl/actor/scheduler.cpp11
-rw-r--r--src/mbgl/geometry/line_atlas.cpp128
-rw-r--r--src/mbgl/geometry/line_atlas.hpp50
-rw-r--r--src/mbgl/gfx/context.hpp7
-rw-r--r--src/mbgl/gfx/rendering_stats.cpp12
-rw-r--r--src/mbgl/gl/context.cpp34
-rw-r--r--src/mbgl/gl/context.hpp6
-rw-r--r--src/mbgl/gl/index_buffer_resource.cpp14
-rw-r--r--src/mbgl/gl/index_buffer_resource.hpp5
-rw-r--r--src/mbgl/gl/object.cpp2
-rw-r--r--src/mbgl/gl/texture_resource.cpp45
-rw-r--r--src/mbgl/gl/texture_resource.hpp7
-rw-r--r--src/mbgl/gl/upload_pass.cpp12
-rw-r--r--src/mbgl/gl/vertex_buffer_resource.cpp14
-rw-r--r--src/mbgl/gl/vertex_buffer_resource.hpp5
-rw-r--r--src/mbgl/layout/symbol_layout.cpp19
-rw-r--r--src/mbgl/renderer/image_manager.cpp73
-rw-r--r--src/mbgl/renderer/image_manager.hpp17
-rw-r--r--src/mbgl/renderer/layers/render_line_layer.cpp41
-rw-r--r--src/mbgl/renderer/paint_property_binder.hpp9
-rw-r--r--src/mbgl/renderer/render_orchestrator.cpp85
-rw-r--r--src/mbgl/renderer/render_orchestrator.hpp4
-rw-r--r--src/mbgl/renderer/tile_pyramid.cpp4
-rw-r--r--src/mbgl/style/expression/assertion.cpp6
-rw-r--r--src/mbgl/style/expression/collator.cpp24
-rw-r--r--src/mbgl/style/expression/formatted.cpp29
-rw-r--r--src/mbgl/style/expression/number_format.cpp4
-rw-r--r--src/mbgl/style/expression/parsing_context.cpp66
-rw-r--r--src/mbgl/style/layers/background_layer.cpp87
-rw-r--r--src/mbgl/style/layers/circle_layer.cpp199
-rw-r--r--src/mbgl/style/layers/custom_layer.cpp4
-rw-r--r--src/mbgl/style/layers/fill_extrusion_layer.cpp157
-rw-r--r--src/mbgl/style/layers/fill_layer.cpp143
-rw-r--r--src/mbgl/style/layers/heatmap_layer.cpp115
-rw-r--r--src/mbgl/style/layers/hillshade_layer.cpp129
-rw-r--r--src/mbgl/style/layers/layer.cpp.ejs87
-rw-r--r--src/mbgl/style/layers/line_layer.cpp232
-rw-r--r--src/mbgl/style/layers/raster_layer.cpp157
-rw-r--r--src/mbgl/style/layers/symbol_layer.cpp496
-rw-r--r--src/mbgl/style/light.cpp83
-rw-r--r--src/mbgl/style/light.cpp.ejs49
-rw-r--r--src/mbgl/style/sources/geojson_source.cpp24
-rw-r--r--src/mbgl/style/sources/geojson_source_impl.cpp38
-rw-r--r--src/mbgl/style/sources/geojson_source_impl.hpp16
-rw-r--r--src/mbgl/text/placement.cpp67
-rw-r--r--src/mbgl/text/quads.cpp28
-rw-r--r--src/mbgl/text/shaping.cpp64
-rw-r--r--src/mbgl/text/shaping.hpp8
-rw-r--r--src/mbgl/util/color.cpp5
-rw-r--r--src/mbgl/util/thread_pool.cpp63
-rw-r--r--src/mbgl/util/thread_pool.hpp58
-rw-r--r--test/actor/actor.test.cpp4
-rw-r--r--test/api/annotations.test.cpp3
-rw-r--r--test/api/custom_geometry_source.test.cpp2
-rw-r--r--test/api/custom_layer.test.cpp2
-rw-r--r--test/api/recycle_map.cpp4
-rw-r--r--test/fixtures/offline_database/no_auto_vacuum.dbbin0 -> 61440 bytes
-rw-r--r--test/geometry/line_atlas.test.cpp4
-rw-r--r--test/gl/context.test.cpp7
-rw-r--r--test/map/map.test.cpp153
-rw-r--r--test/renderer/image_manager.test.cpp18
-rw-r--r--test/storage/offline_database.test.cpp52
-rw-r--r--test/style/source.test.cpp76
-rw-r--r--test/text/local_glyph_rasterizer.test.cpp8
-rw-r--r--test/text/quads.test.cpp329
m---------vendor/mapbox-base0
-rw-r--r--vendor/mapbox-base-files.json1
356 files changed, 9195 insertions, 2864 deletions
diff --git a/.gitignore b/.gitignore
index bf6b13e007..538870edf7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,9 @@
*.gcno
*.gcda
*~
+**/actual.png
+**/diff.png
+render-test/index.html
offline.db
new_offline.db
xcuserdata
@@ -19,8 +22,6 @@ xcuserdata
/test/fixtures/api/2.png
/test/fixtures/offline_database/offline.db
/test/fixtures/offline_database/offline.db-*
-/test/fixtures/**/actual.png
-/test/fixtures/**/diff.png
/test/output
/node_modules
/platform/ios/benchmark/assets/glyphs/DIN*
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000000..d2490da83f
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,32 @@
+Makefile
+CmakeLists.txt
+*yml
+build
+cmake
+misc
+.*
+*tgz
+benchmark
+expression-test
+cloudformation
+bin
+vendor
+include
+mason_packages
+mapbox-gl-js
+test
+lib
+next
+src
+scripts
+render-test
+platform/android
+platform/darwin
+platform/default
+platform/glfw
+platform/ios
+platform/linux
+platform/macos
+platform/qt
+platform/node/symbol-list
+platform/node/version-script \ No newline at end of file
diff --git a/LICENSE.md b/LICENSE.md
index f4abdd104f..b544bf3ad3 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -863,4 +863,16 @@ limitations under the License.
===========================================================================
-Mapbox GL uses portions of software mentioned in [platform/android/LICENSE.md](https://github.com/mapbox/mapbox-gl-native/blob/master/platform/android/LICENSE.md).
+Mapbox GL uses portions of SMCalloutView.
+
+Copyright 2016 Nick Farina.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License:
+https://github.com/nfarina/calloutview/blob/master/LICENSE
+
+===========================================================================
+
+Mapbox GL uses portions of software listed in [platform/android/LICENSE.md](https://github.com/mapbox/mapbox-gl-native/blob/master/platform/android/LICENSE.md).
+
+Mapbox GL uses portions of software listed in [platform/ios/vendor/mapbox-events-ios/LICENSE.md](https://github.com/mapbox/mobile-events-ios/blob/master/LICENSE.md).
diff --git a/README.md b/README.md
index 392e41ac51..d125fc53c8 100644
--- a/README.md
+++ b/README.md
@@ -19,6 +19,6 @@ This repository hosts the cross-platform Mapbox GL Native library, plus convenie
## License
-Mapbox GL Native is licensed under the [3-Clause BSD license](LICENSE.md). The licenses of its dependencies are tracked via [FOSSA](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fmapbox%2Fmapbox-gl-native):
+Mapbox GL Native is licensed under the [2-Clause BSD license](LICENSE.md). The licenses of its dependencies are tracked via [FOSSA](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fmapbox%2Fmapbox-gl-native):
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fmapbox%2Fmapbox-gl-native.svg?type=large)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fmapbox%2Fmapbox-gl-native)
diff --git a/benchmark/api/render.benchmark.cpp b/benchmark/api/render.benchmark.cpp
index 305ae8ce79..f192e7d3a3 100644
--- a/benchmark/api/render.benchmark.cpp
+++ b/benchmark/api/render.benchmark.cpp
@@ -1,18 +1,22 @@
#include <benchmark/benchmark.h>
+#include <mbgl/gfx/headless_frontend.hpp>
#include <mbgl/map/map.hpp>
#include <mbgl/map/map_observer.hpp>
#include <mbgl/map/map_options.hpp>
-#include <mbgl/gfx/headless_frontend.hpp>
#include <mbgl/renderer/renderer.hpp>
-#include <mbgl/style/style.hpp>
-#include <mbgl/style/image.hpp>
-#include <mbgl/storage/resource_options.hpp>
#include <mbgl/storage/network_status.hpp>
+#include <mbgl/storage/resource_options.hpp>
+#include <mbgl/style/image.hpp>
+#include <mbgl/style/layers/symbol_layer.hpp>
+#include <mbgl/style/sources/geojson_source.hpp>
+#include <mbgl/style/style.hpp>
#include <mbgl/util/image.hpp>
#include <mbgl/util/io.hpp>
#include <mbgl/util/run_loop.hpp>
+#include <sstream>
+
using namespace mbgl;
namespace {
@@ -94,7 +98,39 @@ static void API_renderStill_recreate_map(::benchmark::State& state) {
}
}
+static void API_renderStill_multiple_sources(::benchmark::State& state) {
+ using namespace mbgl::style;
+ RenderBenchmark bench;
+ HeadlessFrontend frontend{size, pixelRatio};
+ Map map{frontend,
+ MapObserver::nullObserver(),
+ MapOptions().withMapMode(MapMode::Static).withSize(size).withPixelRatio(pixelRatio),
+ ResourceOptions().withCachePath(cachePath).withAccessToken("foobar")};
+ prepare(map);
+ auto& style = map.getStyle();
+ const int kSourcesCount = 50;
+ const int kLayersCount = 50;
+ for (int i = 0; i < kSourcesCount; ++i) {
+ std::ostringstream sourceOss;
+ sourceOss << "GeoJSONSource" << i;
+ std::string sourceId{sourceOss.str()};
+ auto source = std::make_unique<GeoJSONSource>(sourceId);
+ style.addSource(std::move(source));
+ for (int j = 0; j < kLayersCount; ++j) {
+ std::ostringstream layerOss;
+ layerOss << sourceId << '#' << j;
+ auto layer = std::make_unique<SymbolLayer>(layerOss.str(), sourceId);
+ style.addLayer(std::move(layer));
+ }
+ }
+
+ while (state.KeepRunning()) {
+ frontend.render(map);
+ }
+}
+
BENCHMARK(API_renderStill_reuse_map);
BENCHMARK(API_renderStill_reuse_map_formatted_labels);
BENCHMARK(API_renderStill_reuse_map_switch_styles);
BENCHMARK(API_renderStill_recreate_map);
+BENCHMARK(API_renderStill_multiple_sources);
diff --git a/benchmark/fixtures/api/cache.db b/benchmark/fixtures/api/cache.db
index fc51eca3b9..64cd376892 100644
--- a/benchmark/fixtures/api/cache.db
+++ b/benchmark/fixtures/api/cache.db
Binary files differ
diff --git a/bin/render.cpp b/bin/render.cpp
index 85231c8cba..409dfa7922 100644
--- a/bin/render.cpp
+++ b/bin/render.cpp
@@ -99,7 +99,7 @@ int main(int argc, char *argv[]) {
try {
std::ofstream out(output, std::ios::binary);
- out << encodePNG(frontend.render(map));
+ out << encodePNG(frontend.render(map).image);
out.close();
} catch(std::exception& e) {
std::cout << "Error: " << e.what() << std::endl;
diff --git a/circle.yml b/circle.yml
index 66ebaf0226..ad4dc8bfd0 100644
--- a/circle.yml
+++ b/circle.yml
@@ -123,13 +123,12 @@ workflows:
jobs:
#
# Naming convention: {platform}-{additional description}-{build type}
- # - {platform} is the client platform/framework, which may differ from
+ # - {platform} is the client platform/framework, which may differ from
# the build platform. Specify both if applicable, e.g., "qt5-macos".
# - {additional description} optionally describes the compiler or other
# unique aspect of the build environment.
# - {build type} is typically "debug" or "release".
#
- - nitpick
- android-debug-arm-v7-buck
- android-arm-template:
name: android-debug-arm-v8
@@ -144,11 +143,7 @@ workflows:
filters:
tags:
only: /android-v.*/
- - node-clang39-release:
- filters:
- tags:
- only: /node-.*/
- - node-gcc8-debug:
+ - node-linux-release:
filters:
tags:
only: /node-.*/
@@ -156,13 +151,10 @@ workflows:
filters:
tags:
only: /node-.*/
- - linux-clang-38-libcxx-debug:
- name: linux-clang-3.8-libcxx-debug
- linux-clang-7-sanitize-address-undefined
- linux-clang-7-sanitize-thread
- linux-gcc5-debug-coverage
- linux-doxygen
- - linux-render-tests
- ios-debug
- ios-debug-xcode10
- ios-release-template:
@@ -174,7 +166,6 @@ workflows:
branches:
ignore: /.*/
- macos-debug
- - macos-render-tests
nightly:
triggers:
- schedule:
@@ -196,7 +187,7 @@ executors:
ubuntu-disco:
docker:
# FIXME: Move the image to mbgl/
- - image: tmpsantos/mbgl_ci:1.5
+ - image: tmpsantos/mbgl_ci:1.6
resource_class: xlarge
working_directory: /src
environment:
@@ -207,7 +198,7 @@ executors:
QT_VERSION: 5
macos-11_0_0:
macos:
- xcode: '11.0.0'
+ xcode: '11.1.0'
environment:
HOMEBREW_NO_AUTO_UPDATE: 1
HOMEBREW_NO_INSTALL_CLEANUP: 1
@@ -234,6 +225,12 @@ commands:
name: Prepare macOS
command: |
brew install cmake ccache glfw ninja pkgconfig qt
+ - run:
+ name: Code Generators macOS
+ command: |
+ platform/darwin/scripts/generate-style-code.js
+ platform/darwin/scripts/update-examples.js
+ git add -A && git diff --staged --exit-code
next-config:
parameters:
config_params:
@@ -270,7 +267,9 @@ commands:
when: on_fail
command: |
mkdir -p /tmp/tests/render
- if [ -f mapbox-gl-js/test/integration/render-tests/index.html ]; then cp mapbox-gl-js/test/integration/render-tests/index.html /tmp/tests/render; fi
+ if [ -f render-test/tests_index.html ]; then cp render-test/tests_index.html /tmp/tests/render; fi
+ if [ -f render-test/render-tests_index.html ]; then cp render-test/render-tests_index.html /tmp/tests/render; fi
+ if [ -f render-test/query-tests_index.html ]; then cp render-test/query-tests_index.html /tmp/tests/render; fi
mkdir -p /tmp/tests/coredumps
if ls core* 1> /dev/null 2>&1; then cp core* /tmp/tests/coredumps; fi
- store_artifacts:
@@ -287,7 +286,7 @@ commands:
name: Test
command: |
cd build
- << parameters.test_wrapper >> ctest -j 8 --output-on-failure << parameters.test_params >>
+ << parameters.test_wrapper >> ctest -V << parameters.test_params >>
npm-install:
steps:
- run:
@@ -471,16 +470,6 @@ commands:
- run:
name: Build node
command: make node-all
- build-mbgl-render-test:
- steps:
- - run:
- name: Build mbgl-render-test
- command: cmake --build build --config ${BUILDTYPE} --target mbgl-render-test -- -j${JOBS}
- build-mbgl-expression-test:
- steps:
- - run:
- name: Build mbgl-expression-test
- command: cmake --build build --config ${BUILDTYPE} --target mbgl-expression-test -- -j${JOBS}
build-linux:
steps:
- run:
@@ -586,26 +575,6 @@ commands:
xvfb-run --server-args="-screen 0 1024x768x24" \
logbt -- apitrace trace --api=egl -v make test-node
- run-macos-render-tests:
- steps:
- - run:
- name: Run render tests (mbgl-render-test)
- command: |
- build/mbgl-render-test --recycle-map --shuffle
- no_output_timeout: 2m
-
- run-linux-render-tests:
- parameters:
- node_version:
- type: string
- default: v8
- steps:
- - run:
- name: Run render tests (mbgl-render-test)
- command: |
- xvfb-run --server-args="-screen 0 1024x768x24" \
- logbt -- apitrace trace --api=egl -v build/mbgl-render-test --recycle-map --shuffle
-
run-unit-tests:
steps:
- run:
@@ -619,24 +588,11 @@ commands:
- run:
name: Run tests
command: |
- # Source.RenderTileSetSourceUpdate is filtered out due to #15294
- xvfb-run --server-args="-screen 0 1024x768x24" make run-test--Source.RenderTileSetSourceUpdate 2> >(tee sanitizer 1>&2)
+ # Source.RenderTileSetSourceUpdate and Source.InvisibleSourcesTileNecessity are filtered out due to #15294
+ xvfb-run --server-args="-screen 0 1024x768x24" make run-test--Source.RenderTileSetSourceUpdate:Source.InvisibleSourcesTileNecessity 2> >(tee sanitizer 1>&2)
# Unfortunately, Google Test eats the status code, so we'll have to check the output.
[ -z "$(sed -n '/^SUMMARY: .*Sanitizer:/p' sanitizer)" ]
- run-expression-tests:
- steps:
- - run:
- name: Run expression tests
- command: |
- build/mbgl-expression-test -s --seed=$RANDOM
-
- upload-expression-tests:
- steps:
- - store_artifacts:
- path: mapbox-gl-js/test/integration/expression-tests/index.html
- destination: expression-tests
-
publish-node-package:
steps:
- run:
@@ -644,12 +600,6 @@ commands:
when: on_success
command: platform/node/scripts/publish.sh
- upload-render-tests:
- steps:
- - store_artifacts:
- path: mapbox-gl-js/test/integration/render-tests/index.html
- destination: render-tests
-
collect-xcode-build-logs:
steps:
- run:
@@ -711,6 +661,7 @@ jobs:
name: Code Generators
command: |
platform/android/scripts/generate-style-code.js
+ platform/default/include/mbgl/storage/offline_schema.js
scripts/generate-file-lists.js
scripts/generate-shaders.js
scripts/generate-style-code.js
@@ -789,42 +740,6 @@ jobs:
test_params: << parameters.test_params >>
- next-save
- nitpick:
- docker:
- - image: mbgl/linux-clang-7:a5a3c52107
- working_directory: /src
- environment:
- LIBSYSCONFCPUS: 2
- JOBS: 2
- BUILDTYPE: Debug
- steps:
- - install-dependencies: { mason: false, ccache: false }
- - run:
- name: Initialize submodules
- command: |
- git submodule update --init
- git submodule foreach git submodule update --init
- - run:
- name: Verify submodule pin
- command: scripts/nitpick/submodule-pin.js
- when: always
- - run:
- name: Source file list generation
- command: scripts/nitpick/generated-code.js sources
- when: always
- - run:
- name: Shader code generation
- command: scripts/nitpick/generated-code.js shader
- when: always
- - run:
- name: Style code generation
- command: scripts/nitpick/generated-code.js style
- when: always
- - run:
- name: Android code generation
- command: scripts/nitpick/generated-code.js android
- when: always
-
# ------------------------------------------------------------------------------
android-arm-template:
parameters:
@@ -944,7 +859,7 @@ jobs:
command: |
if [ -n "${MOBILE_METRICS_TOKEN}" ]; then
if [[ $CIRCLE_BRANCH == master ]]; then
- curl -u $MOBILE_METRICS_TOKEN -d build_parameters[CIRCLE_JOB]=android-core-benchmark https://circleci.com/api/v1.1/project/github/mapbox/mobile-metrics/tree/master
+ curl -u ${MOBILE_METRICS_TOKEN}: -d build_parameters[CIRCLE_JOB]=android-core-benchmark https://circleci.com/api/v1.1/project/github/mapbox/mobile-metrics/tree/master
fi
fi
- run:
@@ -952,7 +867,7 @@ jobs:
command: |
if [ -n "${MOBILE_METRICS_TOKEN}" ]; then
if [[ $CIRCLE_BRANCH == master ]]; then
- curl -u $MOBILE_METRICS_TOKEN -d build_parameters[CIRCLE_JOB]=android-benchmark https://circleci.com/api/v1.1/project/github/mapbox/mobile-metrics/tree/master
+ curl -u ${MOBILE_METRICS_TOKEN}: -d build_parameters[CIRCLE_JOB]=android-benchmark https://circleci.com/api/v1.1/project/github/mapbox/mobile-metrics/tree/master
fi
fi
- run:
@@ -997,17 +912,6 @@ jobs:
path: platform/android/MapboxGLAndroidSDK/build/intermediates/cmake/release/obj/x86/libmapbox-gl.so.gz
- store_artifacts:
path: platform/android/MapboxGLAndroidSDK/build/intermediates/cmake/release/obj/x86_64/libmapbox-gl.so.gz
- - deploy:
- name: Publish to Bintray
- command: |
- if [[ $CIRCLE_BRANCH == master ]] || [[ $CIRCLE_BRANCH == release-* ]] || [[ $CIRCLE_TAG == android-v* ]]; then
- version=$(cat platform/android/MapboxGLAndroidSDK/gradle.properties | grep "VERSION_NAME")
- if [[ $version != *"SNAPSHOT"* ]]; then
- make run-android-upload-to-bintray
- else
- make run-android-upload-to-artifactory
- fi
- fi
- run:
name: Trigger external deploy steps
command: |
@@ -1016,9 +920,9 @@ jobs:
export DOCS_REPO="android-docs"
scripts/trigger-maps-documentation-deploy-steps.sh
background: true
- - run:
- name: Record size
- command: platform/android/scripts/metrics.sh
+# - run:
+# name: Record size
+# command: platform/android/scripts/metrics.sh
# ------------------------------------------------------------------------------
android-debug-arm-v7-buck:
docker:
@@ -1046,7 +950,7 @@ jobs:
buck build mapbox-gl-native:android-core
# ------------------------------------------------------------------------------
- node-clang39-release:
+ node-linux-release:
docker:
- image: mbgl/linux-clang-3.9:2077f965ed
resource_class: large
@@ -1064,27 +968,9 @@ jobs:
- publish-node-package
# ------------------------------------------------------------------------------
- node-gcc8-debug:
- docker:
- - image: mbgl/linux-gcc-8:d2b1553d2f
- resource_class: large
- working_directory: /src
- environment:
- LIBSYSCONFCPUS: 4
- JOBS: 4
- BUILDTYPE: Debug
- WITH_EGL: 1
- steps:
- - install-dependencies
- - check-if-this-job-can-be-skipped
- - build-node
- - save-dependencies
- - publish-node-package
-
-# ------------------------------------------------------------------------------
node-macos-release:
macos:
- xcode: "11.0.0"
+ xcode: "11.1.0"
environment:
BUILDTYPE: RelWithDebInfo
HOMEBREW_NO_AUTO_UPDATE: 1
@@ -1102,23 +988,6 @@ jobs:
- upload-xcode-build-logs
# ------------------------------------------------------------------------------
- linux-clang-38-libcxx-debug:
- docker:
- - image: mbgl/linux-clang-3.8-libcxx:d6800bdbb4
- resource_class: large
- working_directory: /src
- environment:
- LIBSYSCONFCPUS: 4
- JOBS: 4
- BUILDTYPE: Debug
- WITH_EGL: 1
- steps:
- - install-dependencies
- - check-if-this-job-can-be-skipped
- - build-linux
- - save-dependencies
-
-# ------------------------------------------------------------------------------
linux-clang-7-sanitize-address-undefined:
docker:
- image: mbgl/linux-clang-7:a5a3c52107
@@ -1223,32 +1092,9 @@ jobs:
scripts/publish_doxygen_coverage.js "build/linux-$(uname -m)/Debug/doxygen-coverage.json"
# ------------------------------------------------------------------------------
- linux-render-tests:
- docker:
- - image: mbgl/linux-clang-7:a5a3c52107
- resource_class: large
- working_directory: /src
- environment:
- LIBSYSCONFCPUS: 4
- JOBS: 4
- BUILDTYPE: RelWithDebInfo
- WITH_EGL: 1
- steps:
- - install-dependencies
- - check-if-this-job-can-be-skipped
- - configure-cmake
- - build-mbgl-expression-test
- - build-mbgl-render-test
- - run-expression-tests
- - run-linux-render-tests
- - save-dependencies
- - upload-expression-tests
- - upload-render-tests
-
-# ------------------------------------------------------------------------------
ios-debug:
macos:
- xcode: "11.0.0"
+ xcode: "11.1.0"
environment:
BUILDTYPE: Debug
HOMEBREW_NO_AUTO_UPDATE: 1
@@ -1265,9 +1111,6 @@ jobs:
- run:
name: Check symbol namespacing for mapbox-events-ios
command: make ios-check-events-symbols
- - run:
- name: Nitpick Darwin code generation
- command: scripts/nitpick/generated-code.js darwin
- save-dependencies
- collect-xcode-build-logs
- upload-xcode-build-logs
@@ -1292,9 +1135,6 @@ jobs:
- run:
name: Check symbol namespacing for mapbox-events-ios
command: make ios-check-events-symbols
- - run:
- name: Nitpick Darwin code generation
- command: scripts/nitpick/generated-code.js darwin
- save-dependencies
- collect-xcode-build-logs
- upload-xcode-build-logs
@@ -1302,7 +1142,7 @@ jobs:
# ------------------------------------------------------------------------------
ios-sanitize-nightly:
macos:
- xcode: "11.0.0"
+ xcode: "11.1.0"
environment:
BUILDTYPE: Debug
HOMEBREW_NO_AUTO_UPDATE: 1
@@ -1317,7 +1157,10 @@ jobs:
- run:
name: Get iOS code coverage
command: |
- platform/ios/scripts/ios-code-coverage.sh CI
+ echo "Skipping iOS code coverage"
+ # Currently failing, need to update for Xcode 11
+ # platform/ios/scripts/ios-code-coverage.sh CI
+ #
- save-dependencies
- collect-xcode-build-logs
- upload-xcode-build-logs
@@ -1326,7 +1169,7 @@ jobs:
# ------------------------------------------------------------------------------
ios-sanitize-address-nightly:
macos:
- xcode: "11.0.0"
+ xcode: "11.1.0"
environment:
BUILDTYPE: Debug
HOMEBREW_NO_AUTO_UPDATE: 1
@@ -1346,7 +1189,7 @@ jobs:
# ------------------------------------------------------------------------------
ios-static-analyzer-nightly:
macos:
- xcode: "11.0.0"
+ xcode: "11.1.0"
environment:
BUILDTYPE: Debug
HOMEBREW_NO_AUTO_UPDATE: 1
@@ -1386,7 +1229,7 @@ jobs:
# ------------------------------------------------------------------------------
ios-release-template:
macos:
- xcode: "11.0.0"
+ xcode: "11.1.0"
shell: /bin/bash --login -eo pipefail
environment:
BUILDTYPE: Release
@@ -1415,14 +1258,17 @@ jobs:
platform/ios/scripts/deploy-to-cocoapods.sh
fi
- run:
- name: Record size
- command: platform/ios/scripts/metrics.sh
+ name: Record size
+ command: |
+ echo "Skipping Record size step"
+ # Skipping due to https://github.com/mapbox/mapbox-gl-native/issues/15751
+ #platform/ios/scripts/metrics.sh
- run:
name: Trigger metrics
command: |
if [[ $CIRCLE_BRANCH == master ]]; then
if [ -n "${MOBILE_METRICS_TOKEN}" ]; then
- curl -u $MOBILE_METRICS_TOKEN -d build_parameters[CIRCLE_JOB]=ios-maps-benchmark https://circleci.com/api/v1.1/project/github/mapbox/mobile-metrics/tree/master
+ curl -u ${MOBILE_METRICS_TOKEN}: -d build_parameters[CIRCLE_JOB]=ios-maps-benchmark https://circleci.com/api/v1.1/project/github/mapbox/mobile-metrics/tree/master
else
echo "MOBILE_METRICS_TOKEN not provided"
fi
@@ -1514,28 +1360,6 @@ jobs:
- upload-xcode-build-logs
# ------------------------------------------------------------------------------
- macos-render-tests:
- macos:
- xcode: "11.0.0"
- environment:
- BUILDTYPE: RelWithDebInfo
- HOMEBREW_NO_AUTO_UPDATE: 1
- HOMEBREW_NO_INSTALL_CLEANUP: 1
- JOBS: 2
- steps:
- - install-macos-dependencies
- - install-dependencies
- - check-if-this-job-can-be-skipped
- - configure-cmake
- - build-mbgl-expression-test
- - build-mbgl-render-test
- - save-dependencies
- - run-expression-tests
- - run-macos-render-tests
- - upload-expression-tests
- - upload-render-tests
-
-# ------------------------------------------------------------------------------
metrics-nightly:
docker:
- image: mbgl/linux-gcc-5:54f59e3ac5
diff --git a/cmake/expression-test.cmake b/cmake/expression-test.cmake
index 6c5b71a4f6..da4f90d751 100644
--- a/cmake/expression-test.cmake
+++ b/cmake/expression-test.cmake
@@ -3,6 +3,8 @@ add_executable(mbgl-expression-test
expression-test/expression_test_parser.cpp
expression-test/expression_test_runner.cpp
expression-test/expression_test_logger.cpp
+ expression-test/test_runner_common.cpp
+ expression-test/test_runner_common.hpp
)
if(APPLE)
diff --git a/cmake/node.cmake b/cmake/node.cmake
index a791d99b42..1e01c8edba 100644
--- a/cmake/node.cmake
+++ b/cmake/node.cmake
@@ -20,13 +20,13 @@ create_source_groups(mbgl-loop-node)
add_node_module(mbgl-node
INSTALL_PATH "lib/{node_abi}/mbgl.node"
- NAN_VERSION "2.10.0"
-
- # Don't build for Node 4.x, 5.x, 7.x, 9.x, 11.x and 12.x
+ NAN_VERSION "2.14.0"
+
+ # Don't build for Node 4.x, 5.x, 7.x, 9.x, 11.x, 12.x and 13.x
# See https://nodejs.org/en/download/releases/ for mapping of Node version to ABI number.
# Changes to this property should happen in tandem with updates to the version targets
# in platform/node/scripts/publish.sh and the "node" engines property in package.json.
- EXCLUDE_NODE_ABIS 46 47 51 59 67 72
+ EXCLUDE_NODE_ABIS 46 47 51 59 67 72 79
)
target_sources(mbgl-node INTERFACE
diff --git a/cmake/render-test.cmake b/cmake/render-test.cmake
index 7369655630..ac17be559c 100644
--- a/cmake/render-test.cmake
+++ b/cmake/render-test.cmake
@@ -1,30 +1,62 @@
-add_executable(mbgl-render-test
+add_executable(
+ mbgl-render-test
+ expression-test/test_runner_common.cpp
+ expression-test/test_runner_common.hpp
+ platform/default/src/mbgl/render-test/main.cpp
render-test/allocation_index.cpp
- render-test/main.cpp
+ render-test/allocation_index.hpp
+ render-test/filesystem.hpp
+ render-test/filesystem.hpp
+ render-test/include/mbgl/render_test.hpp
+ render-test/manifest_parser.cpp
+ render-test/manifest_parser.hpp
+ render-test/metadata.hpp
render-test/parser.cpp
+ render-test/parser.hpp
+ render-test/render_test.cpp
render-test/runner.cpp
+ render-test/runner.hpp
)
if(APPLE)
- target_link_libraries(mbgl-render-test PRIVATE mbgl-loop-darwin)
+ target_link_libraries(
+ mbgl-render-test
+ PRIVATE mbgl-loop-darwin
+ )
else()
- target_link_libraries(mbgl-render-test PRIVATE mbgl-loop-uv)
+ target_link_libraries(
+ mbgl-render-test
+ PRIVATE mbgl-loop-uv
+ )
endif()
-target_include_directories(mbgl-render-test
+target_include_directories(
+ mbgl-render-test
PRIVATE src
PRIVATE platform/default/include
PRIVATE render-test
)
-target_link_libraries(mbgl-render-test PRIVATE
- mbgl-core
- mbgl-filesource
- Mapbox::Base::Extras::args
- mbgl-vendor-expected
- Mapbox::Base::Extras::filesystem
- Mapbox::Base::pixelmatch-cpp
- Mapbox::Base::Extras::rapidjson
+target_include_directories(
+ mbgl-render-test
+ PUBLIC render-test/include
+ PUBLIC include
)
+target_link_libraries(
+ mbgl-render-test
+ PRIVATE
+ mbgl-core
+ mbgl-filesource
+ Mapbox::Base::Extras::args
+ mbgl-vendor-expected
+ Mapbox::Base::Extras::filesystem
+ Mapbox::Base::pixelmatch-cpp
+ Mapbox::Base::Extras::rapidjson
+)
+
+create_source_groups(mbgl-render-test)
+
+set_target_properties(mbgl-render-test PROPERTIES FOLDER "Executables")
+
add_definitions(-DTEST_RUNNER_ROOT_PATH="${CMAKE_SOURCE_DIR}")
diff --git a/expression-test/expression_test_parser.cpp b/expression-test/expression_test_parser.cpp
index 3c194ffee0..3b40eeb3b0 100644
--- a/expression-test/expression_test_parser.cpp
+++ b/expression-test/expression_test_parser.cpp
@@ -104,7 +104,8 @@ optional<Value> toValue(const JSValue& jsvalue) {
style::expression::type::Type stringToType(const std::string& type) {
using namespace style::expression;
- if (type == "string"s || type == "number-format"s) {
+ if (type == "string"s || type == "number-format"s ||
+ type == "image"s) { // TODO: replace once we implement image expressions
return type::String;
} else if (type == "number"s) {
return type::Number;
@@ -439,33 +440,7 @@ optional<Value> toValue(const expression::Value& exprValue) {
std::vector<Value> color { double(c.r), double(c.g), double(c.b), double(c.a) };
return {Value{std::move(color)}};
},
- [](const expression::Formatted& formatted) -> optional<Value> {
- std::unordered_map<std::string, Value> serialized;
- std::vector<Value> sections;
- for (const auto& section : formatted.sections) {
- std::unordered_map<std::string, Value> serializedSection;
- serializedSection.emplace("text", section.text);
- if (section.fontScale) {
- serializedSection.emplace("scale", *section.fontScale);
- } else {
- serializedSection.emplace("scale", NullValue());
- }
- if (section.fontStack) {
- std::string fontStackString;
- serializedSection.emplace("fontStack", fontStackToString(*section.fontStack));
- } else {
- serializedSection.emplace("fontStack", NullValue());
- }
- if (section.textColor) {
- serializedSection.emplace("textColor", section.textColor->toObject());
- } else {
- serializedSection.emplace("textColor", NullValue());
- }
- sections.emplace_back(serializedSection);
- }
- serialized.emplace("sections", sections);
- return {Value{std::move(serialized)}};
- },
+ [](const expression::Formatted& formatted) -> optional<Value> { return {formatted.toObject()}; },
[](const std::vector<expression::Value>& values) -> optional<Value> {
std::vector<Value> mbglValues;
for (const auto& value : values) {
diff --git a/expression-test/expression_test_runner.cpp b/expression-test/expression_test_runner.cpp
index 695d129049..c8a39f07ce 100644
--- a/expression-test/expression_test_runner.cpp
+++ b/expression-test/expression_test_runner.cpp
@@ -1,7 +1,8 @@
#include "expression_test_runner.hpp"
-#include "expression_test_parser.hpp"
#include "expression_test_logger.hpp"
+#include "expression_test_parser.hpp"
#include "filesystem.hpp"
+#include "test_runner_common.hpp"
#include <mbgl/util/io.hpp>
@@ -16,113 +17,6 @@ using namespace std::literals;
namespace {
-// Strip precision for numbers, so that we can compare evaluated results with fixtures.
-// Copied from JS expression harness.
-Value stripPrecision(const Value& value) {
- const double decimalSigFigs = 6;
- if (auto num = numericValue<double>(value)) {
- if (*num == 0) {
- return *num;
- }
-
- const double multiplier = std::pow(10,
- std::max(0.0, decimalSigFigs - std::ceil(std::log10(std::fabs(*num)))));
-
- // We strip precision twice in a row here to avoid cases where
- // stripping an already stripped number will modify its value
- // due to bad floating point precision luck
- // eg `Math.floor(8.16598 * 100000) / 100000` -> 8.16597
- const double firstStrip = std::floor(*num * multiplier) / multiplier;
- return std::floor(firstStrip * multiplier) / multiplier;
- }
-
- if (value.is<std::vector<Value>>()) {
- std::vector<Value> stripped;
- const auto& vec = value.get<std::vector<Value>>();
- stripped.reserve(vec.size());
- for (const auto& val : vec) {
- stripped.emplace_back(stripPrecision(val));
- }
- return stripped;
- } else if (value.is<std::unordered_map<std::string, Value>>()) {
- std::unordered_map<std::string, Value> stripped;
- const auto& map = value.get<std::unordered_map<std::string, Value>>();
- for (const auto& pair : map) {
- stripped.emplace(pair.first, stripPrecision(pair.second));
- }
- return stripped;
- }
-
- return value;
-}
-
-bool deepEqual(const Value& a, const Value& b) {
- const auto& anum = numericValue<double>(a);
- const auto& bnum = numericValue<double>(b);
- if (anum && bnum) {
- return stripPrecision(*anum) == stripPrecision(*bnum);
- }
-
- if (a.which() != b.which()) {
- return false;
- }
-
- if (a.is<std::vector<Value>>()) {
- const auto& avec = a.get<std::vector<Value>>();
- const auto& bvec = b.get<std::vector<Value>>();
- if (avec.size() != bvec.size()) {
- return false;
- }
- for (std::size_t i = 0; i < avec.size(); ++i) {
- if (!deepEqual(avec[i], bvec[i])) {
- return false;
- }
- }
- return true;
- }
-
- if (a.is<std::unordered_map<std::string, Value>>()) {
- const auto& amap = a.get<std::unordered_map<std::string, Value>>();
- const auto& bmap = b.get<std::unordered_map<std::string, Value>>();
- if (amap.size() != bmap.size()) {
- return false;
- }
- for (const auto& pair : amap) {
- auto it = bmap.find(pair.first);
- if (it == bmap.end()) {
- return false;
- }
- if (!deepEqual(pair.second, it->second)) {
- return false;
- }
- }
- return true;
- }
-
- return a == b;
-}
-
-bool deepEqual(const optional<Value>& a, const optional<Value>& b) {
- if ((a && !b) || (!a && b)) {
- return false;
- }
-
- if (a && b) {
- return deepEqual(*a, *b);
- }
-
- return true;
-}
-
-std::vector<std::string> tokenize(std::string str) {
- std::vector<std::string> tokens;
- std::regex re("\n");
- std::copy(std::regex_token_iterator<std::string::iterator>(str.begin(), str.end(), re, -1),
- std::regex_token_iterator<std::string::iterator>(),
- std::back_inserter(tokens));
- return tokens;
-}
-
std::string simpleDiff(const Value& result, const Value& expected) {
std::vector<std::string> resultTokens {tokenize(toJSON(result, 2, true))};
std::vector<std::string> expectedTokens {tokenize(toJSON(expected, 2, true))};
@@ -136,12 +30,12 @@ std::string simpleDiff(const Value& result, const Value& expected) {
for (std::size_t i = 0; i < maxLength; ++i) {
if (resultTokens.size() <= i) {
- flush(expectedTokens, i, diff, "+"s);
+ flush(expectedTokens, i, diff, "-"s);
break;
}
if (expectedTokens.size() <= i) {
- flush(expectedTokens, i, diff, "-"s);
+ flush(resultTokens, i, diff, "+"s);
break;
}
diff --git a/expression-test/test_runner_common.cpp b/expression-test/test_runner_common.cpp
new file mode 100644
index 0000000000..745ef9fced
--- /dev/null
+++ b/expression-test/test_runner_common.cpp
@@ -0,0 +1,131 @@
+#include "test_runner_common.hpp"
+
+#include <cmath>
+#include <regex>
+
+using namespace mbgl;
+
+// Strip precision for numbers, so that we can compare evaluated results with fixtures.
+// Copied from JS expression harness.
+Value stripPrecision(const Value& value) {
+ const double decimalSigFigs = 6;
+ if (auto num = numericValue<double>(value)) {
+ if (*num == 0) {
+ return *num;
+ }
+
+ const double multiplier = std::pow(10, std::max(0.0, decimalSigFigs - std::ceil(std::log10(std::fabs(*num)))));
+
+ // We strip precision twice in a row here to avoid cases where
+ // stripping an already stripped number will modify its value
+ // due to bad floating point precision luck
+ // eg `Math.floor(8.16598 * 100000) / 100000` -> 8.16597
+ const double firstStrip = std::floor(*num * multiplier) / multiplier;
+ return std::floor(firstStrip * multiplier) / multiplier;
+ }
+
+ if (value.is<std::vector<Value>>()) {
+ std::vector<Value> stripped;
+ const auto& vec = value.get<std::vector<Value>>();
+ stripped.reserve(vec.size());
+ for (const auto& val : vec) {
+ stripped.emplace_back(stripPrecision(val));
+ }
+ return stripped;
+ } else if (value.is<std::unordered_map<std::string, Value>>()) {
+ std::unordered_map<std::string, Value> stripped;
+ const auto& map = value.get<std::unordered_map<std::string, Value>>();
+ for (const auto& pair : map) {
+ stripped.emplace(pair.first, stripPrecision(pair.second));
+ }
+ return stripped;
+ }
+
+ return value;
+}
+
+std::vector<std::string> tokenize(std::string str) {
+ std::vector<std::string> tokens;
+ std::regex re("\n");
+ std::copy(std::regex_token_iterator<std::string::iterator>(str.begin(), str.end(), re, -1),
+ std::regex_token_iterator<std::string::iterator>(),
+ std::back_inserter(tokens));
+ return tokens;
+}
+
+bool deepEqual(const Value& a, const Value& b) {
+ const auto& anum = numericValue<double>(a);
+ const auto& bnum = numericValue<double>(b);
+ if (anum && bnum) {
+ return stripPrecision(*anum) == stripPrecision(*bnum);
+ }
+
+ if (a.which() != b.which()) {
+ return false;
+ }
+
+ if (a.getArray() && b.getArray()) {
+ const auto& avec = *a.getArray();
+ const auto& bvec = *b.getArray();
+ if (avec.size() != bvec.size()) {
+ return false;
+ }
+ for (std::size_t i = 0; i < avec.size(); ++i) {
+ if (!deepEqual(avec[i], bvec[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ if (a.getObject() && b.getObject()) {
+ const auto& amap = *a.getObject();
+ const auto& bmap = *b.getObject();
+ if (amap.size() != bmap.size()) {
+ return false;
+ }
+ for (const auto& pair : amap) {
+ auto it = bmap.find(pair.first);
+ if (it == bmap.end()) {
+ return false;
+ }
+ if (!deepEqual(pair.second, it->second)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ if (a == b) {
+ return true;
+ }
+
+ if (a.getString() && b.getString()) {
+ const auto& strA = *a.getString();
+ const auto& strB = *b.getString();
+ if (strA == strB) {
+ return true;
+ }
+
+ try {
+ double numA = std::stod(strA);
+ double numB = std::stod(strB);
+ return stripPrecision(numA) == stripPrecision(numB);
+ } catch (...) {
+ }
+ }
+
+ return false;
+}
+
+bool deepEqual(const optional<Value>& a, const optional<Value>& b) {
+ if ((a && !b) || (!a && b)) {
+ return false;
+ }
+
+ if (a && b) {
+ return deepEqual(*a, *b);
+ }
+
+ return true;
+}
diff --git a/expression-test/test_runner_common.hpp b/expression-test/test_runner_common.hpp
new file mode 100644
index 0000000000..b30d1a145c
--- /dev/null
+++ b/expression-test/test_runner_common.hpp
@@ -0,0 +1,13 @@
+#pragma once
+
+#include <mbgl/util/feature.hpp>
+
+#include <string>
+#include <vector>
+
+using namespace mbgl;
+
+Value stripPrecision(const Value& value);
+std::vector<std::string> tokenize(std::string str);
+bool deepEqual(const Value& a, const Value& b);
+bool deepEqual(const optional<Value>& a, const optional<Value>& b);
diff --git a/include/mbgl/actor/mailbox.hpp b/include/mbgl/actor/mailbox.hpp
index 23c579917a..2b9838ef29 100644
--- a/include/mbgl/actor/mailbox.hpp
+++ b/include/mbgl/actor/mailbox.hpp
@@ -2,6 +2,7 @@
#include <mbgl/util/optional.hpp>
+#include <functional>
#include <memory>
#include <mutex>
#include <queue>
@@ -34,6 +35,7 @@ public:
void receive();
static void maybeReceive(std::weak_ptr<Mailbox>);
+ static std::function<void()> makeClosure(std::weak_ptr<Mailbox>);
private:
optional<Scheduler*> scheduler;
diff --git a/include/mbgl/actor/scheduler.hpp b/include/mbgl/actor/scheduler.hpp
index 6470ab1245..bb2cf124b8 100644
--- a/include/mbgl/actor/scheduler.hpp
+++ b/include/mbgl/actor/scheduler.hpp
@@ -1,5 +1,8 @@
#pragma once
+#include <mapbox/weak.hpp>
+
+#include <functional>
#include <memory>
namespace mbgl {
@@ -31,12 +34,21 @@ class Mailbox;
class Scheduler {
public:
virtual ~Scheduler() = default;
-
- // Used by a Mailbox when it has a message in its queue to request that it
- // be scheduled. Specifically, the scheduler is expected to asynchronously
- // call `receive() on the given mailbox, provided it still exists at that
- // time.
- virtual void schedule(std::weak_ptr<Mailbox>) = 0;
+
+ // Enqueues a function for execution.
+ virtual void schedule(std::function<void()>) = 0;
+ // Makes a weak pointer to this Scheduler.
+ virtual mapbox::base::WeakPtr<Scheduler> makeWeakPtr() = 0;
+
+ // Returns a closure wrapping the given one.
+ //
+ // When the returned closure is invoked for the first time, it schedules
+ // the given closure to this scheduler, the consequent calls of the
+ // returned closure are ignored.
+ //
+ // If this scheduler is already deleted by the time the returnded closure is
+ // first invoked, the call is ignored.
+ std::function<void()> bindOnce(std::function<void()>);
// Set/Get the current Scheduler for this thread
static Scheduler* GetCurrent();
diff --git a/include/mbgl/gfx/rendering_stats.hpp b/include/mbgl/gfx/rendering_stats.hpp
new file mode 100644
index 0000000000..8fb01a5825
--- /dev/null
+++ b/include/mbgl/gfx/rendering_stats.hpp
@@ -0,0 +1,22 @@
+#pragma once
+
+namespace mbgl {
+namespace gfx {
+
+struct RenderingStats {
+ RenderingStats() = default;
+ bool isZero() const;
+
+ int numDrawCalls;
+ int numActiveTextures;
+ int numCreatedTextures;
+ int numBuffers;
+ int numFrameBuffers;
+
+ int memTextures;
+ int memIndexBuffers;
+ int memVertexBuffers;
+};
+
+} // namespace gfx
+} // namespace mbgl \ No newline at end of file
diff --git a/include/mbgl/i18n/collator.hpp b/include/mbgl/i18n/collator.hpp
new file mode 100644
index 0000000000..9db1f804aa
--- /dev/null
+++ b/include/mbgl/i18n/collator.hpp
@@ -0,0 +1,24 @@
+#pragma once
+
+#include <mbgl/util/optional.hpp>
+
+#include <memory>
+#include <string>
+
+namespace mbgl {
+namespace platform {
+
+class Collator {
+public:
+ explicit Collator(bool caseSensitive, bool diacriticSensitive, optional<std::string> locale = nullopt);
+ int compare(const std::string& lhs, const std::string& rhs) const;
+ std::string resolvedLocale() const;
+ bool operator==(const Collator& other) const;
+
+private:
+ class Impl;
+ std::shared_ptr<Impl> impl;
+};
+
+} // namespace platform
+} // namespace mbgl
diff --git a/include/mbgl/i18n/number_format.hpp b/include/mbgl/i18n/number_format.hpp
new file mode 100644
index 0000000000..cb1e94c7bd
--- /dev/null
+++ b/include/mbgl/i18n/number_format.hpp
@@ -0,0 +1,15 @@
+#pragma once
+
+#include <string>
+
+namespace mbgl {
+namespace platform {
+
+std::string formatNumber(double number,
+ const std::string& localeId,
+ const std::string& currency,
+ uint8_t minFractionDigits,
+ uint8_t maxFractionDigits);
+
+} // namespace platform
+} // namespace mbgl
diff --git a/include/mbgl/style/conversion.hpp b/include/mbgl/style/conversion.hpp
index 2c83d1561b..29af9fac91 100644
--- a/include/mbgl/style/conversion.hpp
+++ b/include/mbgl/style/conversion.hpp
@@ -17,9 +17,12 @@ class ConversionTraits;
class Convertible;
-template <class T, class Enable = void>
+template <typename T, typename Enable = void>
struct Converter;
+template <typename T, typename Enable = void>
+struct ValueFactory;
+
} // namespace conversion
} // namespace style
} // namespace mbgl
diff --git a/include/mbgl/style/conversion_impl.hpp b/include/mbgl/style/conversion_impl.hpp
index f049ba4ffb..3e1b8455e5 100644
--- a/include/mbgl/style/conversion_impl.hpp
+++ b/include/mbgl/style/conversion_impl.hpp
@@ -1,11 +1,21 @@
#pragma once
+#include <mbgl/style/color_ramp_property_value.hpp>
#include <mbgl/style/conversion.hpp>
-#include <mbgl/util/optional.hpp>
+#include <mbgl/style/layer.hpp>
+#include <mbgl/style/property_value.hpp>
+#include <mbgl/style/transition_options.hpp>
#include <mbgl/util/feature.hpp>
#include <mbgl/util/geojson.hpp>
+#include <mbgl/util/optional.hpp>
+#include <mbgl/util/traits.hpp>
+
+#include <mapbox/value.hpp>
+#include <array>
+#include <chrono>
#include <string>
+#include <type_traits>
namespace mbgl {
namespace style {
@@ -288,6 +298,81 @@ optional<T> convert(const Convertible& value, Error& error, Args&&...args) {
return Converter<T>()(value, error, std::forward<Args>(args)...);
}
+template <>
+struct ValueFactory<ColorRampPropertyValue> {
+ static Value make(const ColorRampPropertyValue& value) { return value.getExpression().serialize(); }
+};
+
+template <>
+struct ValueFactory<TransitionOptions> {
+ static Value make(const TransitionOptions& value) {
+ return mapbox::base::ValueArray{
+ {std::chrono::duration_cast<std::chrono::milliseconds>(value.duration.value_or(mbgl::Duration::zero()))
+ .count(),
+ std::chrono::duration_cast<std::chrono::milliseconds>(value.delay.value_or(mbgl::Duration::zero()))
+ .count(),
+ value.enablePlacementTransitions}};
+ }
+};
+
+template <>
+struct ValueFactory<Color> {
+ static Value make(const Color& color) { return color.toObject(); }
+};
+
+template <typename T>
+struct ValueFactory<T, typename std::enable_if<(!std::is_enum<T>::value && !is_linear_container<T>::value)>::type> {
+ static Value make(const T& arg) { return {arg}; }
+};
+
+template <typename T>
+struct ValueFactory<T, typename std::enable_if<std::is_enum<T>::value>::type> {
+ static Value make(T arg) { return {Enum<T>::toString(arg)}; }
+};
+
+template <typename T>
+struct ValueFactory<T, typename std::enable_if<is_linear_container<T>::value>::type> {
+ static Value make(const T& arg) {
+ mapbox::base::ValueArray result;
+ result.reserve(arg.size());
+ for (const auto& item : arg) {
+ result.emplace_back(ValueFactory<std::decay_t<decltype(item)>>::make(item));
+ }
+ return result;
+ }
+};
+
+template <>
+struct ValueFactory<Position> {
+ static Value make(const Position& position) {
+ return ValueFactory<std::array<float, 3>>::make(position.getSpherical());
+ }
+};
+
+template <typename T>
+Value makeValue(T&& arg) {
+ return ValueFactory<std::decay_t<T>>::make(std::forward<T>(arg));
+}
+
+template <typename T>
+StyleProperty makeStyleProperty(const PropertyValue<T>& value) {
+ return value.match([](const Undefined&) -> StyleProperty { return {}; },
+ [](const T& t) -> StyleProperty {
+ return {makeValue(t), StyleProperty::Kind::Constant};
+ },
+ [](const PropertyExpression<T>& fn) -> StyleProperty {
+ return {fn.getExpression().serialize(), StyleProperty::Kind::Expression};
+ });
+}
+
+inline StyleProperty makeStyleProperty(const TransitionOptions& value) {
+ return {makeValue(value), StyleProperty::Kind::Transition};
+}
+
+inline StyleProperty makeStyleProperty(const ColorRampPropertyValue& value) {
+ return {makeValue(value), StyleProperty::Kind::Expression};
+}
+
} // namespace conversion
} // namespace style
} // namespace mbgl
diff --git a/include/mbgl/style/expression/collator.hpp b/include/mbgl/style/expression/collator.hpp
index 2a79e55556..51ba426fad 100644
--- a/include/mbgl/style/expression/collator.hpp
+++ b/include/mbgl/style/expression/collator.hpp
@@ -1,10 +1,9 @@
#pragma once
-#include <mbgl/util/feature.hpp>
+#include <mbgl/i18n/collator.hpp>
#include <mbgl/util/optional.hpp>
#include <string>
-#include <memory>
namespace mbgl {
namespace style {
@@ -20,8 +19,7 @@ public:
std::string resolvedLocale() const;
private:
- class Impl;
- std::shared_ptr<Impl> impl;
+ platform::Collator collator;
};
} // namespace expression
diff --git a/include/mbgl/style/expression/formatted.hpp b/include/mbgl/style/expression/formatted.hpp
index f4f08e9197..bb3d609c91 100644
--- a/include/mbgl/style/expression/formatted.hpp
+++ b/include/mbgl/style/expression/formatted.hpp
@@ -48,7 +48,8 @@ public:
bool operator==(const Formatted& ) const;
std::string toString() const;
-
+ mbgl::Value toObject() const;
+
bool empty() const {
return sections.empty() || sections.at(0).text.empty();
}
@@ -59,13 +60,18 @@ public:
} // namespace expression
namespace conversion {
-
+
template <>
-struct Converter<mbgl::style::expression::Formatted> {
+struct Converter<expression::Formatted> {
public:
- optional<mbgl::style::expression::Formatted> operator()(const Convertible& value, Error& error) const;
+ optional<expression::Formatted> operator()(const Convertible& value, Error& error) const;
};
-
+
+template <>
+struct ValueFactory<expression::Formatted> {
+ static Value make(const expression::Formatted& formatted) { return formatted.toObject(); }
+};
+
} // namespace conversion
} // namespace style
diff --git a/include/mbgl/style/layer.hpp b/include/mbgl/style/layer.hpp
index 35577411eb..b50ca75067 100644
--- a/include/mbgl/style/layer.hpp
+++ b/include/mbgl/style/layer.hpp
@@ -1,9 +1,10 @@
#pragma once
+#include <mbgl/style/conversion.hpp>
+#include <mbgl/style/style_property.hpp>
+#include <mbgl/style/types.hpp>
#include <mbgl/util/immutable.hpp>
#include <mbgl/util/optional.hpp>
-#include <mbgl/style/types.hpp>
-#include <mbgl/style/conversion.hpp>
#include <mapbox/weak.hpp>
#include <mapbox/type_wrapper.hpp>
@@ -110,9 +111,12 @@ public:
// Dynamic properties
virtual optional<conversion::Error> setLayoutProperty(const std::string& name, const conversion::Convertible& value) = 0;
- virtual optional<conversion::Error> setPaintProperty(const std::string& name, const conversion::Convertible& value) = 0;
+ virtual optional<conversion::Error> setPaintProperty(const std::string& name,
+ const conversion::Convertible& value) = 0;
optional<conversion::Error> setVisibility(const conversion::Convertible& value);
+ virtual StyleProperty getProperty(const std::string&) const = 0;
+
// Private implementation
// TODO : We should not have public mutable data members.
class Impl;
diff --git a/include/mbgl/style/layers/background_layer.hpp b/include/mbgl/style/layers/background_layer.hpp
index 4a73ae4a0b..ebdce35d0f 100644
--- a/include/mbgl/style/layers/background_layer.hpp
+++ b/include/mbgl/style/layers/background_layer.hpp
@@ -24,6 +24,8 @@ public:
optional<conversion::Error> setLayoutProperty(const std::string& name, const conversion::Convertible& value) final;
optional<conversion::Error> setPaintProperty(const std::string& name, const conversion::Convertible& value) final;
+ StyleProperty getProperty(const std::string& name) const final;
+
// Paint properties
static PropertyValue<Color> getDefaultBackgroundColor();
diff --git a/include/mbgl/style/layers/circle_layer.hpp b/include/mbgl/style/layers/circle_layer.hpp
index f171805806..842094f944 100644
--- a/include/mbgl/style/layers/circle_layer.hpp
+++ b/include/mbgl/style/layers/circle_layer.hpp
@@ -24,6 +24,8 @@ public:
optional<conversion::Error> setLayoutProperty(const std::string& name, const conversion::Convertible& value) final;
optional<conversion::Error> setPaintProperty(const std::string& name, const conversion::Convertible& value) final;
+ StyleProperty getProperty(const std::string& name) const final;
+
// Paint properties
static PropertyValue<float> getDefaultCircleBlur();
diff --git a/include/mbgl/style/layers/custom_layer.hpp b/include/mbgl/style/layers/custom_layer.hpp
index 4ae59dfae3..c193cfb8d9 100644
--- a/include/mbgl/style/layers/custom_layer.hpp
+++ b/include/mbgl/style/layers/custom_layer.hpp
@@ -71,7 +71,7 @@ public:
// Dynamic properties
optional<conversion::Error> setLayoutProperty(const std::string& name, const conversion::Convertible& value) final;
optional<conversion::Error> setPaintProperty(const std::string& name, const conversion::Convertible& value) final;
-
+ StyleProperty getProperty(const std::string&) const final;
// Private implementation
class Impl;
diff --git a/include/mbgl/style/layers/fill_extrusion_layer.hpp b/include/mbgl/style/layers/fill_extrusion_layer.hpp
index 2e89032ae7..76c2359617 100644
--- a/include/mbgl/style/layers/fill_extrusion_layer.hpp
+++ b/include/mbgl/style/layers/fill_extrusion_layer.hpp
@@ -24,6 +24,8 @@ public:
optional<conversion::Error> setLayoutProperty(const std::string& name, const conversion::Convertible& value) final;
optional<conversion::Error> setPaintProperty(const std::string& name, const conversion::Convertible& value) final;
+ StyleProperty getProperty(const std::string& name) const final;
+
// Paint properties
static PropertyValue<float> getDefaultFillExtrusionBase();
diff --git a/include/mbgl/style/layers/fill_layer.hpp b/include/mbgl/style/layers/fill_layer.hpp
index 0c4369de4c..9ec33d7e96 100644
--- a/include/mbgl/style/layers/fill_layer.hpp
+++ b/include/mbgl/style/layers/fill_layer.hpp
@@ -24,6 +24,8 @@ public:
optional<conversion::Error> setLayoutProperty(const std::string& name, const conversion::Convertible& value) final;
optional<conversion::Error> setPaintProperty(const std::string& name, const conversion::Convertible& value) final;
+ StyleProperty getProperty(const std::string& name) const final;
+
// Paint properties
static PropertyValue<bool> getDefaultFillAntialias();
diff --git a/include/mbgl/style/layers/heatmap_layer.hpp b/include/mbgl/style/layers/heatmap_layer.hpp
index 2023d8c21e..2dec03b927 100644
--- a/include/mbgl/style/layers/heatmap_layer.hpp
+++ b/include/mbgl/style/layers/heatmap_layer.hpp
@@ -25,6 +25,8 @@ public:
optional<conversion::Error> setLayoutProperty(const std::string& name, const conversion::Convertible& value) final;
optional<conversion::Error> setPaintProperty(const std::string& name, const conversion::Convertible& value) final;
+ StyleProperty getProperty(const std::string& name) const final;
+
// Paint properties
static ColorRampPropertyValue getDefaultHeatmapColor();
diff --git a/include/mbgl/style/layers/hillshade_layer.hpp b/include/mbgl/style/layers/hillshade_layer.hpp
index f6b04a0062..824606c29b 100644
--- a/include/mbgl/style/layers/hillshade_layer.hpp
+++ b/include/mbgl/style/layers/hillshade_layer.hpp
@@ -24,6 +24,8 @@ public:
optional<conversion::Error> setLayoutProperty(const std::string& name, const conversion::Convertible& value) final;
optional<conversion::Error> setPaintProperty(const std::string& name, const conversion::Convertible& value) final;
+ StyleProperty getProperty(const std::string& name) const final;
+
// Paint properties
static PropertyValue<Color> getDefaultHillshadeAccentColor();
diff --git a/include/mbgl/style/layers/layer.hpp.ejs b/include/mbgl/style/layers/layer.hpp.ejs
index 638db5fe4b..7b60fcf6ea 100644
--- a/include/mbgl/style/layers/layer.hpp.ejs
+++ b/include/mbgl/style/layers/layer.hpp.ejs
@@ -40,6 +40,8 @@ public:
optional<conversion::Error> setLayoutProperty(const std::string& name, const conversion::Convertible& value) final;
optional<conversion::Error> setPaintProperty(const std::string& name, const conversion::Convertible& value) final;
+ StyleProperty getProperty(const std::string& name) const final;
+
<% if (layoutProperties.length) { -%>
// Layout properties
diff --git a/include/mbgl/style/layers/line_layer.hpp b/include/mbgl/style/layers/line_layer.hpp
index 8f1d51295c..4f2cf53708 100644
--- a/include/mbgl/style/layers/line_layer.hpp
+++ b/include/mbgl/style/layers/line_layer.hpp
@@ -27,6 +27,8 @@ public:
optional<conversion::Error> setLayoutProperty(const std::string& name, const conversion::Convertible& value) final;
optional<conversion::Error> setPaintProperty(const std::string& name, const conversion::Convertible& value) final;
+ StyleProperty getProperty(const std::string& name) const final;
+
// Layout properties
static PropertyValue<LineCapType> getDefaultLineCap();
diff --git a/include/mbgl/style/layers/raster_layer.hpp b/include/mbgl/style/layers/raster_layer.hpp
index ba2ea45428..bff38d42ac 100644
--- a/include/mbgl/style/layers/raster_layer.hpp
+++ b/include/mbgl/style/layers/raster_layer.hpp
@@ -24,6 +24,8 @@ public:
optional<conversion::Error> setLayoutProperty(const std::string& name, const conversion::Convertible& value) final;
optional<conversion::Error> setPaintProperty(const std::string& name, const conversion::Convertible& value) final;
+ StyleProperty getProperty(const std::string& name) const final;
+
// Paint properties
static PropertyValue<float> getDefaultRasterBrightnessMax();
diff --git a/include/mbgl/style/layers/symbol_layer.hpp b/include/mbgl/style/layers/symbol_layer.hpp
index b60e991f49..92e214919a 100644
--- a/include/mbgl/style/layers/symbol_layer.hpp
+++ b/include/mbgl/style/layers/symbol_layer.hpp
@@ -26,6 +26,8 @@ public:
optional<conversion::Error> setLayoutProperty(const std::string& name, const conversion::Convertible& value) final;
optional<conversion::Error> setPaintProperty(const std::string& name, const conversion::Convertible& value) final;
+ StyleProperty getProperty(const std::string& name) const final;
+
// Layout properties
static PropertyValue<bool> getDefaultIconAllowOverlap();
diff --git a/include/mbgl/style/light.hpp b/include/mbgl/style/light.hpp
index 9a82eb14b5..7434fb2882 100644
--- a/include/mbgl/style/light.hpp
+++ b/include/mbgl/style/light.hpp
@@ -6,6 +6,7 @@
#include <mbgl/style/conversion.hpp>
#include <mbgl/style/property_value.hpp>
+#include <mbgl/style/style_property.hpp>
#include <mbgl/style/transition_options.hpp>
#include <mbgl/style/types.hpp>
#include <mbgl/util/immutable.hpp>
@@ -22,6 +23,7 @@ public:
// Dynamic properties
optional<conversion::Error> setProperty(const std::string& name, const conversion::Convertible& value);
+ StyleProperty getProperty(const std::string&) const;
static LightAnchorType getDefaultAnchor();
PropertyValue<LightAnchorType> getAnchor() const;
diff --git a/include/mbgl/style/light.hpp.ejs b/include/mbgl/style/light.hpp.ejs
index a5b907b4dc..e4661dc59b 100644
--- a/include/mbgl/style/light.hpp.ejs
+++ b/include/mbgl/style/light.hpp.ejs
@@ -9,6 +9,7 @@
#include <mbgl/style/conversion.hpp>
#include <mbgl/style/property_value.hpp>
+#include <mbgl/style/style_property.hpp>
#include <mbgl/style/transition_options.hpp>
#include <mbgl/style/types.hpp>
#include <mbgl/util/immutable.hpp>
@@ -25,6 +26,7 @@ public:
// Dynamic properties
optional<conversion::Error> setProperty(const std::string& name, const conversion::Convertible& value);
+ StyleProperty getProperty(const std::string&) const;
<% for (const property of properties) { -%>
static <%- evaluatedType(property) %> getDefault<%- camelize(property.name) %>();
diff --git a/include/mbgl/style/sources/geojson_source.hpp b/include/mbgl/style/sources/geojson_source.hpp
index a256ad6f15..549393e9b2 100644
--- a/include/mbgl/style/sources/geojson_source.hpp
+++ b/include/mbgl/style/sources/geojson_source.hpp
@@ -2,6 +2,7 @@
#include <mbgl/style/expression/expression.hpp>
#include <mbgl/style/source.hpp>
+#include <mbgl/tile/tile_id.hpp>
#include <mbgl/util/constants.hpp>
#include <mbgl/util/geojson.hpp>
#include <mbgl/util/optional.hpp>
@@ -34,6 +35,20 @@ struct GeoJSONOptions {
using ClusterProperties = std::unordered_map<std::string, ClusterExpression>;
ClusterProperties clusterProperties;
};
+class GeoJSONData {
+public:
+ static std::shared_ptr<GeoJSONData> create(const GeoJSON&, const GeoJSONOptions&);
+
+ virtual ~GeoJSONData() = default;
+ virtual mapbox::feature::feature_collection<int16_t> getTile(const CanonicalTileID&) = 0;
+
+ // SuperclusterData
+ virtual mapbox::feature::feature_collection<double> getChildren(const std::uint32_t) = 0;
+ virtual mapbox::feature::feature_collection<double> getLeaves(const std::uint32_t,
+ const std::uint32_t limit = 10u,
+ const std::uint32_t offset = 0u) = 0;
+ virtual std::uint8_t getClusterExpansionZoom(std::uint32_t) = 0;
+};
class GeoJSONSource final : public Source {
public:
@@ -42,8 +57,10 @@ public:
void setURL(const std::string& url);
void setGeoJSON(const GeoJSON&);
+ void setGeoJSONData(std::shared_ptr<GeoJSONData>);
optional<std::string> getURL() const;
+ const GeoJSONOptions& getOptions() const;
class Impl;
const Impl& impl() const;
diff --git a/include/mbgl/style/style_property.hpp b/include/mbgl/style/style_property.hpp
new file mode 100644
index 0000000000..fc34078dec
--- /dev/null
+++ b/include/mbgl/style/style_property.hpp
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <mbgl/util/feature.hpp>
+
+namespace mbgl {
+namespace style {
+
+/**
+ * @brief Generic representation of a style property.
+ */
+class StyleProperty {
+public:
+ enum class Kind : uint8_t { Undefined, Constant, Expression, Transition };
+ StyleProperty(Value value_, Kind kind_) : value(std::move(value_)), kind(kind_) {}
+ StyleProperty() = default;
+ const Value& getValue() const { return value; }
+ Value& getValue() { return value; }
+ Kind getKind() const { return kind; }
+
+private:
+ Value value;
+ Kind kind = Kind::Undefined;
+};
+
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/util/feature.hpp b/include/mbgl/util/feature.hpp
index 390cc65720..5d8fe89e36 100644
--- a/include/mbgl/util/feature.hpp
+++ b/include/mbgl/util/feature.hpp
@@ -3,19 +3,33 @@
#include <mbgl/util/optional.hpp>
#include <mbgl/util/string.hpp>
-#include <mapbox/feature.hpp>
+#include <mapbox/value.hpp>
namespace mbgl {
-using Value = mapbox::feature::value;
-using NullValue = mapbox::feature::null_value_t;
-using PropertyMap = mapbox::feature::property_map;
+using Value = mapbox::base::Value;
+using NullValue = mapbox::base::NullValue;
+using PropertyMap = mapbox::base::ValueObject;
using FeatureIdentifier = mapbox::feature::identifier;
-using Feature = mapbox::feature::feature<double>;
-using FeatureState = PropertyMap;
+using GeoJSONFeature = mapbox::feature::feature<double>;
+using FeatureState = mapbox::base::ValueObject;
using FeatureStates = std::unordered_map<std::string, FeatureState>; // <featureID, FeatureState>
using LayerFeatureStates = std::unordered_map<std::string, FeatureStates>; // <sourceLayer, FeatureStates>
+class Feature : public GeoJSONFeature {
+public:
+ std::string source;
+ std::string sourceLayer;
+ PropertyMap state;
+
+ using GeometryType = mapbox::geometry::geometry<double>;
+
+ Feature() = default;
+ Feature(const GeoJSONFeature& f) : GeoJSONFeature(f) {}
+ Feature(const GeometryType& geom_) : GeoJSONFeature(geom_) {}
+ Feature(GeometryType&& geom_) : GeoJSONFeature(std::move(geom_)) {}
+};
+
template <class T>
optional<T> numericValue(const Value& value) {
return value.match(
diff --git a/include/mbgl/util/monotonic_timer.hpp b/include/mbgl/util/monotonic_timer.hpp
new file mode 100644
index 0000000000..bdb167214b
--- /dev/null
+++ b/include/mbgl/util/monotonic_timer.hpp
@@ -0,0 +1,24 @@
+#pragma once
+
+#include <chrono>
+#include <functional>
+#include <mbgl/util/noncopyable.hpp>
+#include <memory>
+
+namespace mbgl {
+namespace util {
+
+class MonotonicTimer {
+public:
+ static std::chrono::duration<double> now();
+
+ template <typename F, typename... Args>
+ inline static std::chrono::duration<double> duration(F&& func, Args&&... args) {
+ auto start = now();
+ func(std::forward<Args>(args)...);
+ return now() - start;
+ }
+};
+
+} // namespace util
+} // namespace mbgl \ No newline at end of file
diff --git a/include/mbgl/util/platform.hpp b/include/mbgl/util/platform.hpp
index 2e11e5f186..3544659740 100644
--- a/include/mbgl/util/platform.hpp
+++ b/include/mbgl/util/platform.hpp
@@ -16,9 +16,6 @@ std::string lowercase(const std::string &string);
// Gets the name of the current thread.
std::string getCurrentThreadName();
-std::string formatNumber(double number, const std::string& localeId, const std::string& currency,
- uint8_t minFractionDigits, uint8_t maxFractionDigits);
-
// Set the name of the current thread, truncated at 15.
void setCurrentThreadName(const std::string& name);
diff --git a/include/mbgl/util/run_loop.hpp b/include/mbgl/util/run_loop.hpp
index 381e3ae213..5cde95a531 100644
--- a/include/mbgl/util/run_loop.hpp
+++ b/include/mbgl/util/run_loop.hpp
@@ -72,12 +72,9 @@ public:
push(Priority::Default, task);
return std::make_unique<WorkRequest>(task);
}
-
- void schedule(std::weak_ptr<Mailbox> mailbox) override {
- invoke([mailbox] () {
- Mailbox::maybeReceive(mailbox);
- });
- }
+
+ void schedule(std::function<void()> fn) override { invoke(std::move(fn)); }
+ ::mapbox::base::WeakPtr<Scheduler> makeWeakPtr() override { return weakFactory.makeWeakPtr(); }
class Impl;
@@ -125,6 +122,7 @@ private:
std::mutex mutex;
std::unique_ptr<Impl> impl;
+ ::mapbox::base::WeakPtrFactory<Scheduler> weakFactory{this};
};
} // namespace util
diff --git a/include/mbgl/util/traits.hpp b/include/mbgl/util/traits.hpp
index 5b9401aad7..e37144e60e 100644
--- a/include/mbgl/util/traits.hpp
+++ b/include/mbgl/util/traits.hpp
@@ -1,7 +1,9 @@
#pragma once
+#include <array>
#include <cstdint>
#include <type_traits>
+#include <vector>
namespace mbgl {
@@ -25,4 +27,12 @@ typename std::enable_if<is_utf16char_like<InChar>::value && is_utf16char_like_po
return reinterpret_cast<OutPointer>(in);
}
+template <typename T>
+struct is_linear_container : std::false_type {};
+
+template <typename T, std::size_t N>
+struct is_linear_container<std::array<T, N>> : std::true_type {};
+template <typename... Ts>
+struct is_linear_container<std::vector<Ts...>> : std::true_type {};
+
} // namespace mbgl
diff --git a/mapbox-gl-js b/mapbox-gl-js
-Subproject 7ea73ed381a81c3ff7e48b523b25d50793baf1f
+Subproject 6453c38a5118e52e7cc7fb9b6e23d28e5bf5268
diff --git a/next/CMakeLists.txt b/next/CMakeLists.txt
index dae8ce09a1..924761ad85 100644
--- a/next/CMakeLists.txt
+++ b/next/CMakeLists.txt
@@ -31,7 +31,7 @@ set(CMAKE_CXX_FLAGS_DEBUGCOVERAGE "${CMAKE_CXX_FLAGS_DEBUG} --coverage")
set(CMAKE_C_FLAGS_DEBUGCOVERAGE "${CMAKE_CXX_FLAGS_DEBUG} --coverage")
set(CMAKE_CXX_FLAGS_SANITIZE "-DSANITIZE -g -O1 -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=${MBGL_WITH_SANITIZER}")
-set(CMAKE_C_FLAGS_SANITIZE "-DSANITEIZE -g -O1 -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=${MBGL_WITH_SANITIZER}")
+set(CMAKE_C_FLAGS_SANITIZE "-DSANITIZE -g -O1 -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=${MBGL_WITH_SANITIZER}")
if(MBGL_WITH_QT)
find_package(Qt5Core REQUIRED)
@@ -70,6 +70,7 @@ add_library(
${MBGL_ROOT}/include/mbgl/gfx/backend_scope.hpp
${MBGL_ROOT}/include/mbgl/gfx/renderable.hpp
${MBGL_ROOT}/include/mbgl/gfx/renderer_backend.hpp
+ ${MBGL_ROOT}/include/mbgl/gfx/rendering_stats.hpp
${MBGL_ROOT}/include/mbgl/gl/renderable_resource.hpp
${MBGL_ROOT}/include/mbgl/gl/renderer_backend.hpp
${MBGL_ROOT}/include/mbgl/layermanager/background_layer_factory.hpp
@@ -189,6 +190,7 @@ add_library(
${MBGL_ROOT}/include/mbgl/style/sources/raster_source.hpp
${MBGL_ROOT}/include/mbgl/style/sources/vector_source.hpp
${MBGL_ROOT}/include/mbgl/style/style.hpp
+ ${MBGL_ROOT}/include/mbgl/style/style_property.hpp
${MBGL_ROOT}/include/mbgl/style/transition_options.hpp
${MBGL_ROOT}/include/mbgl/style/types.hpp
${MBGL_ROOT}/include/mbgl/style/undefined.hpp
@@ -284,6 +286,7 @@ add_library(
${MBGL_ROOT}/src/mbgl/gfx/render_pass.hpp
${MBGL_ROOT}/src/mbgl/gfx/renderbuffer.hpp
${MBGL_ROOT}/src/mbgl/gfx/renderer_backend.cpp
+ ${MBGL_ROOT}/src/mbgl/gfx/rendering_stats.cpp
${MBGL_ROOT}/src/mbgl/gfx/stencil_mode.hpp
${MBGL_ROOT}/src/mbgl/gfx/texture.hpp
${MBGL_ROOT}/src/mbgl/gfx/types.hpp
@@ -305,6 +308,7 @@ add_library(
${MBGL_ROOT}/src/mbgl/gl/enum.hpp
${MBGL_ROOT}/src/mbgl/gl/extension.hpp
${MBGL_ROOT}/src/mbgl/gl/framebuffer.hpp
+ ${MBGL_ROOT}/src/mbgl/gl/index_buffer_resource.cpp
${MBGL_ROOT}/src/mbgl/gl/index_buffer_resource.hpp
${MBGL_ROOT}/src/mbgl/gl/object.cpp
${MBGL_ROOT}/src/mbgl/gl/object.hpp
@@ -318,6 +322,7 @@ add_library(
${MBGL_ROOT}/src/mbgl/gl/state.hpp
${MBGL_ROOT}/src/mbgl/gl/texture.cpp
${MBGL_ROOT}/src/mbgl/gl/texture.hpp
+ ${MBGL_ROOT}/src/mbgl/gl/texture_resource.cpp
${MBGL_ROOT}/src/mbgl/gl/texture_resource.hpp
${MBGL_ROOT}/src/mbgl/gl/types.hpp
${MBGL_ROOT}/src/mbgl/gl/uniform.cpp
@@ -329,6 +334,7 @@ add_library(
${MBGL_ROOT}/src/mbgl/gl/vertex_array.cpp
${MBGL_ROOT}/src/mbgl/gl/vertex_array.hpp
${MBGL_ROOT}/src/mbgl/gl/vertex_array_extension.hpp
+ ${MBGL_ROOT}/src/mbgl/gl/vertex_buffer_resource.cpp
${MBGL_ROOT}/src/mbgl/gl/vertex_buffer_resource.hpp
${MBGL_ROOT}/src/mbgl/layermanager/background_layer_factory.cpp
${MBGL_ROOT}/src/mbgl/layermanager/circle_layer_factory.cpp
@@ -596,6 +602,7 @@ add_library(
${MBGL_ROOT}/src/mbgl/style/expression/check_subtype.cpp
${MBGL_ROOT}/src/mbgl/style/expression/coalesce.cpp
${MBGL_ROOT}/src/mbgl/style/expression/coercion.cpp
+ ${MBGL_ROOT}/src/mbgl/style/expression/collator.cpp
${MBGL_ROOT}/src/mbgl/style/expression/collator_expression.cpp
${MBGL_ROOT}/src/mbgl/style/expression/comparison.cpp
${MBGL_ROOT}/src/mbgl/style/expression/compound_expression.cpp
@@ -875,8 +882,10 @@ target_include_directories(
PUBLIC ${MBGL_ROOT}/include
)
-add_subdirectory(${PROJECT_SOURCE_DIR}/vendor/mapbox-base/mapbox)
-add_subdirectory(${PROJECT_SOURCE_DIR}/vendor/mapbox-base/extras)
+if(NOT TARGET mapbox-base)
+ add_subdirectory(${PROJECT_SOURCE_DIR}/vendor/mapbox-base/mapbox)
+ add_subdirectory(${PROJECT_SOURCE_DIR}/vendor/mapbox-base/extras)
+endif(NOT TARGET mapbox-base)
include(${PROJECT_SOURCE_DIR}/vendor/boost.cmake)
include(${PROJECT_SOURCE_DIR}/vendor/earcut.hpp.cmake)
@@ -911,6 +920,7 @@ target_link_libraries(
Mapbox::Base::geometry.hpp
Mapbox::Base::optional
Mapbox::Base::typewrapper
+ Mapbox::Base::value
Mapbox::Base::variant
Mapbox::Base::weak
mbgl-vendor-expected
@@ -940,3 +950,4 @@ endif()
add_subdirectory(${PROJECT_SOURCE_DIR}/test)
add_subdirectory(${PROJECT_SOURCE_DIR}/benchmark)
+add_subdirectory(${PROJECT_SOURCE_DIR}/render-test)
diff --git a/next/benchmark/CMakeLists.txt b/next/benchmark/CMakeLists.txt
index 2ae1227d91..117d556041 100644
--- a/next/benchmark/CMakeLists.txt
+++ b/next/benchmark/CMakeLists.txt
@@ -31,7 +31,11 @@ get_target_property(MBGL_CORE_PRIVATE_LIBRARIES mbgl-core LINK_LIBRARIES)
target_link_libraries(
mbgl-benchmark
- PRIVATE ${MBGL_CORE_PRIVATE_LIBRARIES} mbgl-core mbgl-vendor-benchmark
+ PRIVATE
+ ${MBGL_CORE_PRIVATE_LIBRARIES}
+ mbgl-vendor-benchmark
+ mbgl-core
+ $<$<PLATFORM_ID:Android>:mbgl-core-android>
)
if(CMAKE_SYSTEM_NAME STREQUAL Android)
diff --git a/next/expression-test/CMakeLists.txt b/next/expression-test/CMakeLists.txt
index 1713f0b655..f7d694b054 100644
--- a/next/expression-test/CMakeLists.txt
+++ b/next/expression-test/CMakeLists.txt
@@ -6,6 +6,8 @@ add_executable(
${MBGL_ROOT}/expression-test/expression_test_parser.hpp
${MBGL_ROOT}/expression-test/expression_test_runner.cpp
${MBGL_ROOT}/expression-test/expression_test_runner.hpp
+ ${MBGL_ROOT}/expression-test/test_runner_common.cpp
+ ${MBGL_ROOT}/expression-test/test_runner_common.hpp
${MBGL_ROOT}/expression-test/main.cpp
)
diff --git a/next/platform/android/android.cmake b/next/platform/android/android.cmake
index 70683149d0..06183291db 100644
--- a/next/platform/android/android.cmake
+++ b/next/platform/android/android.cmake
@@ -40,8 +40,6 @@ target_sources(
${MBGL_ROOT}/platform/android/src/conversion/constant.cpp
${MBGL_ROOT}/platform/android/src/conversion/constant.hpp
${MBGL_ROOT}/platform/android/src/conversion/conversion.hpp
- ${MBGL_ROOT}/platform/android/src/file_source.cpp
- ${MBGL_ROOT}/platform/android/src/file_source.hpp
${MBGL_ROOT}/platform/android/src/geojson/feature.cpp
${MBGL_ROOT}/platform/android/src/geojson/feature.hpp
${MBGL_ROOT}/platform/android/src/geojson/feature_collection.cpp
@@ -94,9 +92,8 @@ target_sources(
${MBGL_ROOT}/platform/android/src/jni.hpp
${MBGL_ROOT}/platform/android/src/jni_native.cpp
${MBGL_ROOT}/platform/android/src/jni_native.hpp
- ${MBGL_ROOT}/platform/android/src/logger.cpp
- ${MBGL_ROOT}/platform/android/src/logger.hpp
- ${MBGL_ROOT}/platform/android/src/logging_android.cpp
+ ${MBGL_ROOT}/platform/android/src/mapbox.cpp
+ ${MBGL_ROOT}/platform/android/src/mapbox.hpp
${MBGL_ROOT}/platform/android/src/map/camera_position.cpp
${MBGL_ROOT}/platform/android/src/map/camera_position.hpp
${MBGL_ROOT}/platform/android/src/map/image.cpp
@@ -187,15 +184,8 @@ target_sources(
${MBGL_ROOT}/platform/android/src/style/transition_options.hpp
${MBGL_ROOT}/platform/android/src/style/value.cpp
${MBGL_ROOT}/platform/android/src/style/value.hpp
- ${MBGL_ROOT}/platform/android/src/text/collator.cpp
- ${MBGL_ROOT}/platform/android/src/text/collator_jni.hpp
- ${MBGL_ROOT}/platform/android/src/text/format_number.cpp
- ${MBGL_ROOT}/platform/android/src/text/format_number_jni.hpp
- ${MBGL_ROOT}/platform/android/src/text/local_glyph_rasterizer.cpp
- ${MBGL_ROOT}/platform/android/src/text/local_glyph_rasterizer_jni.hpp
${MBGL_ROOT}/platform/android/src/thread.cpp
${MBGL_ROOT}/platform/android/src/timer.cpp
- ${MBGL_ROOT}/platform/android/src/unaccent.cpp
${MBGL_ROOT}/platform/default/src/mbgl/gfx/headless_backend.cpp
${MBGL_ROOT}/platform/default/src/mbgl/gfx/headless_frontend.cpp
${MBGL_ROOT}/platform/default/src/mbgl/gl/headless_backend.cpp
@@ -212,6 +202,7 @@ target_sources(
${MBGL_ROOT}/platform/default/src/mbgl/storage/sqlite3.cpp
${MBGL_ROOT}/platform/default/src/mbgl/text/bidi.cpp
${MBGL_ROOT}/platform/default/src/mbgl/util/compression.cpp
+ ${MBGL_ROOT}/platform/default/src/mbgl/util/monotonic_timer.cpp
${MBGL_ROOT}/platform/default/src/mbgl/util/png_writer.cpp
${MBGL_ROOT}/platform/default/src/mbgl/util/thread_local.cpp
${MBGL_ROOT}/platform/default/src/mbgl/util/utf.cpp
@@ -242,13 +233,43 @@ target_link_libraries(
)
add_library(
+ mbgl-core-android STATIC
+ ${MBGL_ROOT}/platform/android/src/file_source.cpp
+ ${MBGL_ROOT}/platform/android/src/file_source.hpp
+ ${MBGL_ROOT}/platform/android/src/i18n/collator.cpp
+ ${MBGL_ROOT}/platform/android/src/i18n/collator_jni.hpp
+ ${MBGL_ROOT}/platform/android/src/i18n/number_format.cpp
+ ${MBGL_ROOT}/platform/android/src/i18n/number_format_jni.hpp
+ ${MBGL_ROOT}/platform/android/src/logger.cpp
+ ${MBGL_ROOT}/platform/android/src/logger.hpp
+ ${MBGL_ROOT}/platform/android/src/logging_android.cpp
+ ${MBGL_ROOT}/platform/android/src/text/local_glyph_rasterizer.cpp
+ ${MBGL_ROOT}/platform/android/src/text/local_glyph_rasterizer_jni.hpp
+)
+
+target_include_directories(
+ mbgl-core-android
+ PRIVATE ${MBGL_ROOT}/platform/default/include ${MBGL_ROOT}/src
+)
+
+target_link_libraries(
+ mbgl-core-android
+ PRIVATE Mapbox::Base::jni.hpp mbgl-core
+)
+
+add_library(
mapbox-gl SHARED
${MBGL_ROOT}/platform/android/src/main.cpp
)
+target_include_directories(
+ mapbox-gl
+ PRIVATE ${MBGL_ROOT}/platform/default/include ${MBGL_ROOT}/src
+)
+
target_link_libraries(
mapbox-gl
- PRIVATE Mapbox::Base::jni.hpp mbgl-core
+ PRIVATE Mapbox::Base::jni.hpp mbgl-core mbgl-core-android
)
add_library(
@@ -267,6 +288,7 @@ target_link_libraries(
GLESv2
Mapbox::Base::optional
Mapbox::Base::typewrapper
+ Mapbox::Base::value
Mapbox::Base::weak
log
)
@@ -293,15 +315,40 @@ target_link_libraries(
PRIVATE Mapbox::Base::jni.hpp mapbox-gl mbgl-benchmark
)
+add_library(
+ mbgl-render-test-runner SHARED
+ ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c
+ ${MBGL_ROOT}/platform/android/src/test/render_test_runner.cpp
+ ${MBGL_ROOT}/platform/default/src/mbgl/text/local_glyph_rasterizer.cpp
+ ${MBGL_ROOT}/platform/android/src/test/render_test_collator.cpp
+ ${MBGL_ROOT}/platform/android/src/test/render_test_number_format.cpp
+)
+
+target_include_directories(
+ mbgl-render-test-runner
+ PRIVATE ${ANDROID_NDK}/sources/android/native_app_glue ${MBGL_ROOT}/platform/android/src ${MBGL_ROOT}/src
+)
+
+target_link_libraries(
+ mbgl-render-test-runner
+ PRIVATE
+ Mapbox::Base::jni.hpp
+ android
+ log
+ mbgl-render-test
+)
+
# Android has no concept of MinSizeRel on android.toolchain.cmake and provides configurations tuned for binary size. We can push it a bit
# more with code folding and LTO.
set_target_properties(example-custom-layer PROPERTIES LINK_FLAGS_RELEASE "-fuse-ld=gold -O2 -flto -Wl,--icf=safe")
set_target_properties(mapbox-gl PROPERTIES LINK_FLAGS_RELEASE "-fuse-ld=gold -O2 -flto -Wl,--icf=safe")
set_target_properties(mbgl-benchmark-runner PROPERTIES LINK_FLAGS_RELEASE "-fuse-ld=gold -O2 -flto -Wl,--icf=safe")
+set_target_properties(mbgl-render-test-runner PROPERTIES LINK_FLAGS_RELEASE "-fuse-ld=gold -O2 -flto -Wl,--icf=safe")
set_target_properties(mbgl-test-runner PROPERTIES LINK_FLAGS_RELEASE "-fuse-ld=gold -O2 -flto -Wl,--icf=safe")
target_compile_options(example-custom-layer PRIVATE $<$<CONFIG:Release>:-Qunused-arguments -flto>)
target_compile_options(mapbox-gl PRIVATE $<$<CONFIG:Release>:-Qunused-arguments -flto>)
target_compile_options(mbgl-core PRIVATE $<$<CONFIG:Release>:-Qunused-arguments -flto>)
+target_compile_options(mbgl-render-test-runner PRIVATE $<$<CONFIG:Release>:-Qunused-arguments -flto>)
target_compile_options(mbgl-vendor-icu PRIVATE $<$<CONFIG:Release>:-Qunused-arguments -flto>)
target_compile_options(mbgl-vendor-sqlite PRIVATE $<$<CONFIG:Release>:-Qunused-arguments -flto>)
diff --git a/next/platform/ios/ios.cmake b/next/platform/ios/ios.cmake
index 507150fc9a..10a6839c9d 100644
--- a/next/platform/ios/ios.cmake
+++ b/next/platform/ios/ios.cmake
@@ -3,6 +3,19 @@ target_compile_definitions(
PUBLIC MBGL_USE_GLES2 GLES_SILENCE_DEPRECATION
)
+if(NOT DEFINED IOS_DEPLOYMENT_TARGET)
+ set(IOS_DEPLOYMENT_TARGET "9.0")
+endif()
+
+macro(initialize_ios_target target)
+ set_target_properties(${target} PROPERTIES XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET "${IOS_DEPLOYMENT_TARGET}")
+ set_target_properties(${target} PROPERTIES XCODE_ATTRIBUTE_ENABLE_BITCODE "YES")
+ set_target_properties(${target} PROPERTIES XCODE_ATTRIBUTE_BITCODE_GENERATION_MODE bitcode)
+ set_target_properties(${target} PROPERTIES XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH $<$<CONFIG:Debug>:YES>)
+endmacro()
+
+set_target_properties(mbgl-core PROPERTIES XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC YES)
+
target_sources(
mbgl-core
PRIVATE
@@ -15,6 +28,7 @@ target_sources(
${MBGL_ROOT}/platform/darwin/src/local_glyph_rasterizer.mm
${MBGL_ROOT}/platform/darwin/src/logging_nslog.mm
${MBGL_ROOT}/platform/darwin/src/nsthread.mm
+ ${MBGL_ROOT}/platform/darwin/src/number_format.mm
${MBGL_ROOT}/platform/darwin/src/reachability.m
${MBGL_ROOT}/platform/darwin/src/run_loop.cpp
${MBGL_ROOT}/platform/darwin/src/string_nsstring.mm
@@ -36,6 +50,7 @@ target_sources(
${MBGL_ROOT}/platform/default/src/mbgl/storage/sqlite3.cpp
${MBGL_ROOT}/platform/default/src/mbgl/text/bidi.cpp
${MBGL_ROOT}/platform/default/src/mbgl/util/compression.cpp
+ ${MBGL_ROOT}/platform/default/src/mbgl/util/monotonic_timer.cpp
${MBGL_ROOT}/platform/default/src/mbgl/util/png_writer.cpp
${MBGL_ROOT}/platform/default/src/mbgl/util/thread_local.cpp
${MBGL_ROOT}/platform/default/src/mbgl/util/utf.cpp
@@ -48,6 +63,9 @@ target_include_directories(
include(${PROJECT_SOURCE_DIR}/vendor/icu.cmake)
+initialize_ios_target(mbgl-core)
+initialize_ios_target(mbgl-vendor-icu)
+
target_link_libraries(
mbgl-core
PRIVATE
@@ -69,3 +87,5 @@ target_link_libraries(
sqlite3
z
)
+
+unset(IOS_DEPLOYMENT_TARGET CACHE)
diff --git a/next/platform/linux/linux.cmake b/next/platform/linux/linux.cmake
index 9c8a3830b8..396b9a0ffd 100644
--- a/next/platform/linux/linux.cmake
+++ b/next/platform/linux/linux.cmake
@@ -15,6 +15,8 @@ target_sources(
${MBGL_ROOT}/platform/default/src/mbgl/gfx/headless_backend.cpp
${MBGL_ROOT}/platform/default/src/mbgl/gfx/headless_frontend.cpp
${MBGL_ROOT}/platform/default/src/mbgl/gl/headless_backend.cpp
+ ${MBGL_ROOT}/platform/default/src/mbgl/i18n/collator.cpp
+ ${MBGL_ROOT}/platform/default/src/mbgl/i18n/number_format.cpp
${MBGL_ROOT}/platform/default/src/mbgl/layermanager/layer_manager.cpp
${MBGL_ROOT}/platform/default/src/mbgl/storage/asset_file_source.cpp
${MBGL_ROOT}/platform/default/src/mbgl/storage/default_file_source.cpp
@@ -29,15 +31,13 @@ target_sources(
${MBGL_ROOT}/platform/default/src/mbgl/storage/online_file_source.cpp
${MBGL_ROOT}/platform/default/src/mbgl/storage/sqlite3.cpp
${MBGL_ROOT}/platform/default/src/mbgl/text/bidi.cpp
- ${MBGL_ROOT}/platform/default/src/mbgl/text/collator.cpp
${MBGL_ROOT}/platform/default/src/mbgl/text/local_glyph_rasterizer.cpp
- ${MBGL_ROOT}/platform/default/src/mbgl/text/unaccent.cpp
${MBGL_ROOT}/platform/default/src/mbgl/util/async_task.cpp
${MBGL_ROOT}/platform/default/src/mbgl/util/compression.cpp
- ${MBGL_ROOT}/platform/default/src/mbgl/util/format_number.cpp
${MBGL_ROOT}/platform/default/src/mbgl/util/image.cpp
${MBGL_ROOT}/platform/default/src/mbgl/util/jpeg_reader.cpp
${MBGL_ROOT}/platform/default/src/mbgl/util/logging_stderr.cpp
+ ${MBGL_ROOT}/platform/default/src/mbgl/util/monotonic_timer.cpp
${MBGL_ROOT}/platform/default/src/mbgl/util/png_reader.cpp
${MBGL_ROOT}/platform/default/src/mbgl/util/png_writer.cpp
${MBGL_ROOT}/platform/default/src/mbgl/util/run_loop.cpp
@@ -96,7 +96,6 @@ add_subdirectory(${PROJECT_SOURCE_DIR}/bin)
add_subdirectory(${PROJECT_SOURCE_DIR}/expression-test)
add_subdirectory(${PROJECT_SOURCE_DIR}/platform/glfw)
add_subdirectory(${PROJECT_SOURCE_DIR}/platform/node)
-add_subdirectory(${PROJECT_SOURCE_DIR}/render-test)
add_executable(
mbgl-test-runner
@@ -123,5 +122,34 @@ target_link_libraries(
PRIVATE mbgl-benchmark
)
+add_executable(
+ mbgl-render-test-runner
+ ${MBGL_ROOT}/platform/default/src/mbgl/render-test/main.cpp
+)
+
+target_link_libraries(
+ mbgl-render-test-runner
+ PRIVATE mbgl-render-test
+)
+
add_test(NAME mbgl-benchmark-runner COMMAND mbgl-benchmark-runner WORKING_DIRECTORY ${MBGL_ROOT})
add_test(NAME mbgl-test-runner COMMAND mbgl-test-runner WORKING_DIRECTORY ${MBGL_ROOT})
+
+string(RANDOM LENGTH 5 ALPHABET 0123456789 MBGL_RENDER_TEST_SEED)
+
+add_test(
+ NAME mbgl-render-test
+ COMMAND
+ mbgl-render-test-runner
+ render-tests
+ --recycle-map
+ --shuffle
+ --manifestPath=${MBGL_ROOT}/render-test/linux-manifest.json
+ --seed=${MBGL_RENDER_TEST_SEED}
+)
+
+add_test(
+ NAME mbgl-render-test-probes
+ COMMAND mbgl-render-test-runner tests --manifestPath=${MBGL_ROOT}/render-test/linux-probe-manifest.json
+)
+add_test(NAME mbgl-query-test COMMAND mbgl-render-test-runner query-tests --manifestPath=${MBGL_ROOT}/render-test/linux-manifest.json)
diff --git a/next/platform/macos/macos.cmake b/next/platform/macos/macos.cmake
index d0aad9c3dc..721d19e625 100644
--- a/next/platform/macos/macos.cmake
+++ b/next/platform/macos/macos.cmake
@@ -83,6 +83,7 @@ target_sources(
${MBGL_ROOT}/platform/darwin/src/local_glyph_rasterizer.mm
${MBGL_ROOT}/platform/darwin/src/logging_nslog.mm
${MBGL_ROOT}/platform/darwin/src/nsthread.mm
+ ${MBGL_ROOT}/platform/darwin/src/number_format.mm
${MBGL_ROOT}/platform/darwin/src/reachability.m
${MBGL_ROOT}/platform/darwin/src/run_loop.cpp
${MBGL_ROOT}/platform/darwin/src/string_nsstring.mm
@@ -104,6 +105,7 @@ target_sources(
${MBGL_ROOT}/platform/default/src/mbgl/storage/sqlite3.cpp
${MBGL_ROOT}/platform/default/src/mbgl/text/bidi.cpp
${MBGL_ROOT}/platform/default/src/mbgl/util/compression.cpp
+ ${MBGL_ROOT}/platform/default/src/mbgl/util/monotonic_timer.cpp
${MBGL_ROOT}/platform/default/src/mbgl/util/png_writer.cpp
${MBGL_ROOT}/platform/default/src/mbgl/util/thread_local.cpp
${MBGL_ROOT}/platform/default/src/mbgl/util/utf.cpp
@@ -154,7 +156,6 @@ add_subdirectory(${PROJECT_SOURCE_DIR}/bin)
add_subdirectory(${PROJECT_SOURCE_DIR}/expression-test)
add_subdirectory(${PROJECT_SOURCE_DIR}/platform/glfw)
add_subdirectory(${PROJECT_SOURCE_DIR}/platform/node)
-add_subdirectory(${PROJECT_SOURCE_DIR}/render-test)
add_executable(
mbgl-test-runner
@@ -181,8 +182,36 @@ target_link_libraries(
PRIVATE mbgl-benchmark
)
+add_executable(
+ mbgl-render-test-runner
+ ${MBGL_ROOT}/platform/default/src/mbgl/render-test/main.cpp
+)
+
+target_link_libraries(
+ mbgl-render-test-runner
+ PRIVATE mbgl-render-test
+)
+
set_property(TARGET mbgl-benchmark-runner PROPERTY FOLDER Executables)
set_property(TARGET mbgl-test-runner PROPERTY FOLDER Executables)
+set_property(TARGET mbgl-render-test-runner PROPERTY FOLDER Executables)
add_test(NAME mbgl-benchmark-runner COMMAND mbgl-benchmark-runner WORKING_DIRECTORY ${MBGL_ROOT})
add_test(NAME mbgl-test-runner COMMAND mbgl-test-runner WORKING_DIRECTORY ${MBGL_ROOT})
+
+string(RANDOM LENGTH 5 ALPHABET 0123456789 MBGL_RENDER_TEST_SEED)
+
+add_test(
+ NAME mbgl-render-test
+ COMMAND
+ mbgl-render-test-runner
+ render-tests
+ --recycle-map
+ --shuffle
+ --manifestPath=${MBGL_ROOT}/render-test/mac-manifest.json
+ --seed=${MBGL_RENDER_TEST_SEED}
+)
+
+add_test(NAME mbgl-render-test-probes COMMAND mbgl-render-test-runner tests --manifestPath=${MBGL_ROOT}/render-test/mac-probe-manifest.json)
+
+add_test(NAME mbgl-query-test COMMAND mbgl-render-test-runner query-tests --manifestPath=${MBGL_ROOT}/render-test/mac-manifest.json)
diff --git a/next/platform/node/CMakeLists.txt b/next/platform/node/CMakeLists.txt
index 432979b473..840fab2964 100644
--- a/next/platform/node/CMakeLists.txt
+++ b/next/platform/node/CMakeLists.txt
@@ -18,6 +18,7 @@ add_node_module(
59
67
72
+ 79
)
target_sources(
diff --git a/next/platform/qt/qt.cmake b/next/platform/qt/qt.cmake
index a63b1cd4de..4fb56176d0 100644
--- a/next/platform/qt/qt.cmake
+++ b/next/platform/qt/qt.cmake
@@ -26,10 +26,10 @@ target_sources(
${MBGL_ROOT}/platform/default/include/mbgl/gfx/headless_backend.hpp
${MBGL_ROOT}/platform/default/include/mbgl/gfx/headless_frontend.hpp
${MBGL_ROOT}/platform/default/include/mbgl/gl/headless_backend.hpp
- ${MBGL_ROOT}/platform/default/include/mbgl/text/unaccent.hpp
${MBGL_ROOT}/platform/default/src/mbgl/gfx/headless_backend.cpp
${MBGL_ROOT}/platform/default/src/mbgl/gfx/headless_frontend.cpp
${MBGL_ROOT}/platform/default/src/mbgl/gl/headless_backend.cpp
+ ${MBGL_ROOT}/platform/default/src/mbgl/i18n/collator.cpp
${MBGL_ROOT}/platform/default/src/mbgl/layermanager/layer_manager.cpp
${MBGL_ROOT}/platform/default/src/mbgl/storage/asset_file_source.cpp
${MBGL_ROOT}/platform/default/src/mbgl/storage/default_file_source.cpp
@@ -42,12 +42,11 @@ target_sources(
${MBGL_ROOT}/platform/default/src/mbgl/storage/offline_download.cpp
${MBGL_ROOT}/platform/default/src/mbgl/storage/online_file_source.cpp
${MBGL_ROOT}/platform/default/src/mbgl/storage/sqlite3.cpp
- ${MBGL_ROOT}/platform/default/src/mbgl/text/collator.cpp
- ${MBGL_ROOT}/platform/default/src/mbgl/text/unaccent.cpp
${MBGL_ROOT}/platform/default/src/mbgl/util/compression.cpp
+ ${MBGL_ROOT}/platform/default/src/mbgl/util/monotonic_timer.cpp
${MBGL_ROOT}/platform/qt/src/async_task.cpp
${MBGL_ROOT}/platform/qt/src/async_task_impl.hpp
- ${MBGL_ROOT}/platform/qt/src/format_number.cpp
+ ${MBGL_ROOT}/platform/qt/src/number_format.cpp
${MBGL_ROOT}/platform/qt/src/gl_functions.cpp
${MBGL_ROOT}/platform/qt/src/headless_backend_qt.cpp
${MBGL_ROOT}/platform/qt/src/http_file_source.cpp
diff --git a/next/render-test/CMakeLists.txt b/next/render-test/CMakeLists.txt
index 6cab3fed52..fa4bb0d615 100644
--- a/next/render-test/CMakeLists.txt
+++ b/next/render-test/CMakeLists.txt
@@ -1,12 +1,19 @@
-add_executable(
- mbgl-render-test
+add_library(
+ mbgl-render-test STATIC EXCLUDE_FROM_ALL
+ ${MBGL_ROOT}/expression-test/test_runner_common.cpp
+ ${MBGL_ROOT}/expression-test/test_runner_common.hpp
${MBGL_ROOT}/render-test/allocation_index.cpp
${MBGL_ROOT}/render-test/allocation_index.hpp
+ ${MBGL_ROOT}/render-test/file_source.cpp
+ ${MBGL_ROOT}/render-test/file_source.hpp
${MBGL_ROOT}/render-test/filesystem.hpp
- ${MBGL_ROOT}/render-test/main.cpp
+ ${MBGL_ROOT}/render-test/include/mbgl/render_test.hpp
+ ${MBGL_ROOT}/render-test/manifest_parser.cpp
+ ${MBGL_ROOT}/render-test/manifest_parser.hpp
${MBGL_ROOT}/render-test/metadata.hpp
${MBGL_ROOT}/render-test/parser.cpp
${MBGL_ROOT}/render-test/parser.hpp
+ ${MBGL_ROOT}/render-test/render_test.cpp
${MBGL_ROOT}/render-test/runner.cpp
${MBGL_ROOT}/render-test/runner.hpp
)
@@ -19,7 +26,12 @@ target_compile_definitions(
# FIXME: Should not use core private interface
target_include_directories(
mbgl-render-test
- PRIVATE ${MBGL_ROOT}/src
+ PRIVATE ${MBGL_ROOT}/src ${MBGL_ROOT}/platform/default/include
+)
+
+target_include_directories(
+ mbgl-render-test
+ PUBLIC ${MBGL_ROOT}/render-test/include ${MBGL_ROOT}/include
)
include(${PROJECT_SOURCE_DIR}/vendor/boost.cmake)
@@ -30,21 +42,12 @@ target_link_libraries(
Mapbox::Base::Extras::args
Mapbox::Base::Extras::filesystem
Mapbox::Base::pixelmatch-cpp
- mbgl-core
mbgl-vendor-boost
+ PUBLIC mbgl-core
)
-set_property(TARGET mbgl-render-test PROPERTY FOLDER Executables)
-
-string(RANDOM LENGTH 5 ALPHABET 0123456789 MBGL_RENDER_TEST_SEED)
+if(CMAKE_SYSTEM_NAME STREQUAL Android)
+ set_target_properties(mbgl-render-test PROPERTIES LINK_FLAGS_RELEASE "-fuse-ld=gold -O2 -flto -Wl,--icf=safe")
+endif()
-add_test(
- NAME mbgl-render-test
- COMMAND
- mbgl-render-test
- --recycle-map
- --shuffle
- --seed
- ${MBGL_RENDER_TEST_SEED}
- WORKING_DIRECTORY ${MBGL_ROOT}
-)
+set_property(TARGET mbgl-render-test PROPERTY FOLDER Core)
diff --git a/next/test/CMakeLists.txt b/next/test/CMakeLists.txt
index 586e2bbdaa..4995fa4e56 100644
--- a/next/test/CMakeLists.txt
+++ b/next/test/CMakeLists.txt
@@ -153,6 +153,7 @@ target_link_libraries(
Mapbox::Base::Extras::args
Mapbox::Base::pixelmatch-cpp
mbgl-core
+ $<$<PLATFORM_ID:Android>:mbgl-core-android>
)
target_link_libraries(
diff --git a/package.json b/package.json
index 9b11a039cd..adda5fef5b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@mapbox/mapbox-gl-native",
- "version": "4.3.0",
+ "version": "5.0.0",
"description": "Renders map tiles with Mapbox GL",
"keywords": [
"mapbox",
@@ -49,7 +49,7 @@
"node": ">=6"
},
"scripts": {
- "install": "node-pre-gyp install --fallback-to-build=false || make node",
+ "install": "node-pre-gyp install --fallback-to-build=false",
"test": "tape platform/node/test/js/**/*.test.js",
"test-memory": "node --expose-gc platform/node/test/memory.test.js",
"test-expressions": "node -r esm platform/node/test/expression.test.js",
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
diff --git a/render-test/android-manifest.json b/render-test/android-manifest.json
new file mode 100644
index 0000000000..56223d4753
--- /dev/null
+++ b/render-test/android-manifest.json
@@ -0,0 +1,7 @@
+{
+ "base_test_path":"mapbox-gl-js/test/integration",
+ "expectation_paths":["render-test/expected/render-tests"],
+ "ignore_paths":["platform/node/test/ignores.json", "render-test/linux-ignores.json", "render-test/tests/should-fail.json"],
+ "vendor_path":"vendor",
+ "asset_path": "mapbox-gl-js/test/integration"
+} \ No newline at end of file
diff --git a/render-test/android/README.md b/render-test/android/README.md
new file mode 100644
index 0000000000..270c970fce
--- /dev/null
+++ b/render-test/android/README.md
@@ -0,0 +1,9 @@
+# RenderTestRunner
+
+This app is a purely native application, with no Java source code, that can run **mbgl-render-test-runner** on android devices.
+
+
+## Setup the test environment
+- Run render_test_setup.sh so that all the necessary test resources are pushed to the device.
+
+- Switch on storage permission of the app so that it can read/write data on SD card. \ No newline at end of file
diff --git a/render-test/android/app/build.gradle b/render-test/android/app/build.gradle
new file mode 100644
index 0000000000..60609e3ba2
--- /dev/null
+++ b/render-test/android/app/build.gradle
@@ -0,0 +1,29 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 28
+
+ defaultConfig {
+ applicationId = 'com.mapbox.mapboxsdk.maps.render_test_runner'
+ minSdkVersion 14
+ targetSdkVersion 28
+ externalNativeBuild {
+ cmake {
+ arguments '-DANDROID_CCACHE=ccache'
+ arguments '-DANDROID_STL=c++_static'
+ targets 'mbgl-render-test-runner'
+ }
+ }
+ }
+ externalNativeBuild {
+ cmake {
+ version '3.10.2'
+ path '../../../next/CMakeLists.txt'
+ }
+ }
+}
+
+dependencies {
+ implementation 'androidx.appcompat:appcompat:1.0.2'
+ implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+}
diff --git a/render-test/android/app/src/main/AndroidManifest.xml b/render-test/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..6c7af7ed8f
--- /dev/null
+++ b/render-test/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.native_activity"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <application
+ android:allowBackup="false"
+ android:fullBackupContent="false"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:hasCode="false">
+
+ <activity android:name="android.app.NativeActivity"
+ android:label="@string/app_name"
+ android:screenOrientation="portrait">
+ <meta-data android:name="android.app.lib_name"
+ android:value="mbgl-render-test-runner" />
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/render-test/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/render-test/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000000..cde69bccce
--- /dev/null
+++ b/render-test/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/render-test/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/render-test/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000000..c133a0cbd3
--- /dev/null
+++ b/render-test/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/render-test/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/render-test/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000000..bfa42f0e7b
--- /dev/null
+++ b/render-test/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/render-test/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/render-test/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000000..324e72cdd7
--- /dev/null
+++ b/render-test/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/render-test/android/app/src/main/res/values/strings.xml b/render-test/android/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000000..00b181ca34
--- /dev/null
+++ b/render-test/android/app/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">RenderTestRunner</string>
+</resources>
diff --git a/render-test/android/build.gradle b/render-test/android/build.gradle
new file mode 100644
index 0000000000..e58b831f71
--- /dev/null
+++ b/render-test/android/build.gradle
@@ -0,0 +1,16 @@
+buildscript {
+ repositories {
+ google()
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.5.1'
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+ }
+}
diff --git a/render-test/android/gradle.properties b/render-test/android/gradle.properties
new file mode 100644
index 0000000000..2427fd0bf4
--- /dev/null
+++ b/render-test/android/gradle.properties
@@ -0,0 +1 @@
+org.gradle.jvmargs=-Xmx1536m \ No newline at end of file
diff --git a/render-test/android/gradle/wrapper/gradle-wrapper.jar b/render-test/android/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000..8c0fb64a86
--- /dev/null
+++ b/render-test/android/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/render-test/android/gradle/wrapper/gradle-wrapper.properties b/render-test/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000..2e4993ee89
--- /dev/null
+++ b/render-test/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Oct 21 12:45:47 EEST 2019
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
diff --git a/render-test/android/gradlew b/render-test/android/gradlew
new file mode 100755
index 0000000000..91a7e269e1
--- /dev/null
+++ b/render-test/android/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/render-test/android/gradlew.bat b/render-test/android/gradlew.bat
new file mode 100644
index 0000000000..aec99730b4
--- /dev/null
+++ b/render-test/android/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/render-test/android/render_test_setup.sh b/render-test/android/render_test_setup.sh
new file mode 100755
index 0000000000..1dea44399e
--- /dev/null
+++ b/render-test/android/render_test_setup.sh
@@ -0,0 +1,42 @@
+#!/bin/bash
+
+adb shell rm -rf /sdcard/render-test
+adb shell mkdir /sdcard/render-test
+adb shell mkdir /sdcard/render-test/vendor
+adb shell mkdir /sdcard/render-test/ignores
+adb shell mkdir /sdcard/render-test/render-test/tests
+
+# push test sources
+adb push ../../mapbox-gl-js/test/integration/render-tests /sdcard/render-test/mapbox-gl-js/test/integration/render-tests
+adb push ../../mapbox-gl-js/test/integration/query-tests /sdcard/render-test/mapbox-gl-js/test/integration/query-tests
+adb push ../../mapbox-gl-js/test/integration/tiles /sdcard/render-test/mapbox-gl-js/test/integration/tiles
+adb push ../../mapbox-gl-js/test/integration/glyphs /sdcard/render-test/mapbox-gl-js/test/integration/glyphs
+adb push ../../mapbox-gl-js/test/integration/styles /sdcard/render-test/mapbox-gl-js/test/integration/styles
+adb push ../../mapbox-gl-js/test/integration/tilesets /sdcard/render-test/mapbox-gl-js/test/integration/tilesets
+adb push ../../mapbox-gl-js/test/integration/image /sdcard/render-test/mapbox-gl-js/test/integration/image
+adb push ../../mapbox-gl-js/test/integration/video /sdcard/render-test/mapbox-gl-js/test/integration/video
+adb push ../../vendor/mapbox-gl-styles/styles /sdcard/render-test/vendor/mapbox-gl-styles/styles
+adb push ../../vendor/mapbox-gl-styles/sprites /sdcard/render-test/vendor/mapbox-gl-styles/sprites
+adb push ../../mapbox-gl-js/test/integration/data /sdcard/render-test/mapbox-gl-js/test/integration/data
+adb push ../../mapbox-gl-js/test/integration/geojson /sdcard/render-test/mapbox-gl-js/test/integration/geojson
+mkdir sprites
+cp -r ../../mapbox-gl-js/test/integration/sprites/ sprites
+adb push sprites /sdcard/render-test/mapbox-gl-js/test/integration/sprites
+rm -rf sprites
+
+# push extra expectations
+adb push ../../render-test/expected/render-tests /sdcard/render-test/render-test/expected/render-tests
+
+# push default ignore lists
+adb shell mkdir /sdcard/render-test/platform
+adb shell mkdir /sdcard/render-test/platform/node
+adb shell mkdir /sdcard/render-test/platform/node/test
+adb push ../../platform/node/test/ignores.json /sdcard/render-test/platform/node/test
+adb shell mkdir /sdcard/render-test/render-test
+adb push ../linux-ignores.json /sdcard/render-test/render-test
+adb push ../tests/should-fail.json /sdcard/render-test/render-test/tests
+
+# push manifest
+adb push ../android-manifest.json /sdcard/render-test
+
+adb shell ls /sdcard/render-test/ \ No newline at end of file
diff --git a/render-test/android/settings.gradle b/render-test/android/settings.gradle
new file mode 100644
index 0000000000..573abcb323
--- /dev/null
+++ b/render-test/android/settings.gradle
@@ -0,0 +1,2 @@
+include ':app'
+
diff --git a/render-test/expected/debug/collision-lines-overscaled/expected.png b/render-test/expected/render-tests/debug/collision-lines-overscaled/expected.png
index 38eb0d2da6..38eb0d2da6 100644
--- a/render-test/expected/debug/collision-lines-overscaled/expected.png
+++ b/render-test/expected/render-tests/debug/collision-lines-overscaled/expected.png
Binary files differ
diff --git a/render-test/expected/debug/collision-lines-pitched/expected.png b/render-test/expected/render-tests/debug/collision-lines-pitched/expected.png
index 416d7d5715..416d7d5715 100644
--- a/render-test/expected/debug/collision-lines-pitched/expected.png
+++ b/render-test/expected/render-tests/debug/collision-lines-pitched/expected.png
Binary files differ
diff --git a/render-test/expected/debug/collision-lines/expected.png b/render-test/expected/render-tests/debug/collision-lines/expected.png
index 3f4790a585..3f4790a585 100644
--- a/render-test/expected/debug/collision-lines/expected.png
+++ b/render-test/expected/render-tests/debug/collision-lines/expected.png
Binary files differ
diff --git a/render-test/expected/debug/collision-pitched-wrapped/expected.png b/render-test/expected/render-tests/debug/collision-pitched-wrapped/expected.png
index 9b718c09c0..9b718c09c0 100644
--- a/render-test/expected/debug/collision-pitched-wrapped/expected.png
+++ b/render-test/expected/render-tests/debug/collision-pitched-wrapped/expected.png
Binary files differ
diff --git a/render-test/expected/symbol-visibility/visible/expected.png b/render-test/expected/render-tests/symbol-visibility/visible/expected.png
index 8da157772a..8da157772a 100644
--- a/render-test/expected/symbol-visibility/visible/expected.png
+++ b/render-test/expected/render-tests/symbol-visibility/visible/expected.png
Binary files differ
diff --git a/render-test/expected/text-pitch-alignment/auto-text-rotation-alignment-map/expected.png b/render-test/expected/render-tests/text-pitch-alignment/auto-text-rotation-alignment-map/expected.png
index cd690ca152..cd690ca152 100644
--- a/render-test/expected/text-pitch-alignment/auto-text-rotation-alignment-map/expected.png
+++ b/render-test/expected/render-tests/text-pitch-alignment/auto-text-rotation-alignment-map/expected.png
Binary files differ
diff --git a/render-test/expected/text-pitch-alignment/map-text-rotation-alignment-map/expected.png b/render-test/expected/render-tests/text-pitch-alignment/map-text-rotation-alignment-map/expected.png
index cd690ca152..cd690ca152 100644
--- a/render-test/expected/text-pitch-alignment/map-text-rotation-alignment-map/expected.png
+++ b/render-test/expected/render-tests/text-pitch-alignment/map-text-rotation-alignment-map/expected.png
Binary files differ
diff --git a/render-test/expected/text-pitch-alignment/viewport-text-rotation-alignment-map/expected.png b/render-test/expected/render-tests/text-pitch-alignment/viewport-text-rotation-alignment-map/expected.png
index 764d4a0b24..764d4a0b24 100644
--- a/render-test/expected/text-pitch-alignment/viewport-text-rotation-alignment-map/expected.png
+++ b/render-test/expected/render-tests/text-pitch-alignment/viewport-text-rotation-alignment-map/expected.png
Binary files differ
diff --git a/render-test/expected/text-variable-anchor/pitched-rotated-debug/expected.png b/render-test/expected/render-tests/text-variable-anchor/pitched-rotated-debug/expected.png
index 4e3d012844..4e3d012844 100644
--- a/render-test/expected/text-variable-anchor/pitched-rotated-debug/expected.png
+++ b/render-test/expected/render-tests/text-variable-anchor/pitched-rotated-debug/expected.png
Binary files differ
diff --git a/render-test/expected/text-variable-anchor/rotated-offset/expected.png b/render-test/expected/render-tests/text-variable-anchor/rotated-offset/expected.png
index 13690d147c..13690d147c 100644
--- a/render-test/expected/text-variable-anchor/rotated-offset/expected.png
+++ b/render-test/expected/render-tests/text-variable-anchor/rotated-offset/expected.png
Binary files differ
diff --git a/render-test/file_source.cpp b/render-test/file_source.cpp
new file mode 100644
index 0000000000..0968f1d2f0
--- /dev/null
+++ b/render-test/file_source.cpp
@@ -0,0 +1,65 @@
+#include <mbgl/storage/resource_options.hpp>
+
+#include "file_source.hpp"
+
+namespace mbgl {
+
+std::atomic_size_t requestCount{0};
+std::atomic_size_t transferredSize{0};
+std::atomic_bool active{false};
+
+ProxyFileSource::ProxyFileSource(const std::string& cachePath,
+ const std::string& assetPath,
+ bool supportCacheOnlyRequests_)
+ : DefaultFileSource(cachePath, assetPath, supportCacheOnlyRequests_) {}
+
+ProxyFileSource::ProxyFileSource(const std::string& cachePath,
+ std::unique_ptr<FileSource>&& assetFileSource_,
+ bool supportCacheOnlyRequests_)
+ : DefaultFileSource(cachePath, std::move(assetFileSource_), supportCacheOnlyRequests_) {}
+
+ProxyFileSource::~ProxyFileSource() = default;
+
+std::unique_ptr<AsyncRequest> ProxyFileSource::request(const Resource& resource, Callback callback) {
+ auto result = DefaultFileSource::request(resource, [=](Response response) {
+ std::size_t size = response.data != nullptr ? response.data->size() : 0;
+ if (active) {
+ requestCount++;
+ transferredSize += size;
+ }
+ callback(response);
+ });
+ return result;
+}
+
+std::shared_ptr<FileSource> FileSource::createPlatformFileSource(const ResourceOptions& options) {
+ auto fileSource = std::make_shared<ProxyFileSource>(
+ options.cachePath(), options.assetPath(), options.supportsCacheOnlyRequests());
+ fileSource->setAccessToken(options.accessToken());
+ fileSource->setAPIBaseURL(options.baseURL());
+ return fileSource;
+}
+
+// static
+void ProxyFileSource::setTrackingActive(bool active_) {
+ active = active_;
+ requestCount = 0;
+ transferredSize = 0;
+}
+
+// static
+bool ProxyFileSource::isTrackingActive() {
+ return active;
+}
+
+// static
+size_t ProxyFileSource::getRequestCount() {
+ return requestCount;
+}
+
+// static
+size_t ProxyFileSource::getTransferredSize() {
+ return transferredSize;
+}
+
+} // namespace mbgl
diff --git a/render-test/file_source.hpp b/render-test/file_source.hpp
new file mode 100644
index 0000000000..58acf7b6ad
--- /dev/null
+++ b/render-test/file_source.hpp
@@ -0,0 +1,40 @@
+#pragma once
+
+#include <mbgl/storage/default_file_source.hpp>
+
+namespace mbgl {
+
+class ProxyFileSource : public DefaultFileSource {
+public:
+ ProxyFileSource(const std::string& cachePath, const std::string& assetPath, bool supportCacheOnlyRequests = true);
+ ProxyFileSource(const std::string& cachePath,
+ std::unique_ptr<FileSource>&& assetFileSource,
+ bool supportCacheOnlyRequests = true);
+ ~ProxyFileSource();
+
+ std::unique_ptr<AsyncRequest> request(const Resource&, Callback);
+
+ /**
+ * @brief Starts/stops metrics tracking.
+ */
+ static void setTrackingActive(bool);
+ /**
+ * @brief Returns metrics tracking status.
+ */
+ static bool isTrackingActive();
+ /**
+ * @brief Returns the total amount of requests.
+ *
+ * @return size_t
+ */
+ static size_t getRequestCount();
+
+ /**
+ * @brief Returns the size of transferred data (in bytes).
+ *
+ * @return size_t
+ */
+ static size_t getTransferredSize();
+};
+
+} // namespace mbgl
diff --git a/render-test/include/mbgl/render_test.hpp b/render-test/include/mbgl/render_test.hpp
new file mode 100644
index 0000000000..8a82079bee
--- /dev/null
+++ b/render-test/include/mbgl/render_test.hpp
@@ -0,0 +1,6 @@
+#pragma once
+namespace mbgl {
+
+int runRenderTests(int argc, char* argv[]);
+
+} // namespace mbgl
diff --git a/render-test/linux-manifest.json b/render-test/linux-manifest.json
new file mode 100644
index 0000000000..32a5afdbdb
--- /dev/null
+++ b/render-test/linux-manifest.json
@@ -0,0 +1,7 @@
+{
+ "base_test_path":"../mapbox-gl-js/test/integration",
+ "expectation_paths":["expected/render-tests"],
+ "ignore_paths":["../platform/node/test/ignores.json", "../render-test/linux-ignores.json", "../render-test/tests/should-fail.json"],
+ "vendor_path":"../vendor",
+ "asset_path": "../mapbox-gl-js/test/integration"
+} \ No newline at end of file
diff --git a/render-test/linux-probe-manifest.json b/render-test/linux-probe-manifest.json
new file mode 100644
index 0000000000..0b2ca88988
--- /dev/null
+++ b/render-test/linux-probe-manifest.json
@@ -0,0 +1,7 @@
+{
+ "probe_test_path":".",
+ "expectation_paths":["expected/render-tests"],
+ "ignore_paths":["../render-test/linux-ignores.json", "../render-test/tests/should-fail.json"],
+ "vendor_path":"../vendor",
+ "asset_path": "../mapbox-gl-js/test/integration"
+} \ No newline at end of file
diff --git a/render-test/mac-manifest.json b/render-test/mac-manifest.json
new file mode 100644
index 0000000000..224df81298
--- /dev/null
+++ b/render-test/mac-manifest.json
@@ -0,0 +1,7 @@
+{
+ "base_test_path":"../mapbox-gl-js/test/integration",
+ "expectation_paths":["expected/render-tests", "tests/mac"],
+ "ignore_paths":["../platform/node/test/ignores.json", "../render-test/mac-ignores.json", "../render-test/tests/should-fail.json"],
+ "vendor_path":"../vendor",
+ "asset_path": "../mapbox-gl-js/test/integration"
+} \ No newline at end of file
diff --git a/render-test/mac-probe-manifest.json b/render-test/mac-probe-manifest.json
new file mode 100644
index 0000000000..f3cc56d0a8
--- /dev/null
+++ b/render-test/mac-probe-manifest.json
@@ -0,0 +1,7 @@
+{
+ "probe_test_path":".",
+ "expectation_paths":["expected/render-tests", "tests/mac"],
+ "ignore_paths":["../render-test/mac-ignores.json", "../render-test/tests/should-fail.json"],
+ "vendor_path":"../vendor",
+ "asset_path": "../mapbox-gl-js/test/integration"
+} \ No newline at end of file
diff --git a/render-test/manifest_parser.cpp b/render-test/manifest_parser.cpp
new file mode 100644
index 0000000000..f1884634e5
--- /dev/null
+++ b/render-test/manifest_parser.cpp
@@ -0,0 +1,400 @@
+#include "manifest_parser.hpp"
+#include "filesystem.hpp"
+#include "parser.hpp"
+
+#include <mbgl/util/logging.hpp>
+
+#include <algorithm>
+#include <random>
+
+namespace {
+std::string removeURLArguments(const std::string& url) {
+ std::string::size_type index = url.find('?');
+ if (index != std::string::npos) {
+ return url.substr(0, index);
+ }
+ return url;
+}
+
+std::string prependFileScheme(const std::string& url) {
+ static const std::string fileScheme("file://");
+ return fileScheme + url;
+}
+} // namespace
+
+Manifest::Manifest() = default;
+Manifest::~Manifest() = default;
+
+const std::vector<TestPaths>& Manifest::getTestPaths() const {
+ return testPaths;
+}
+const std::vector<std::pair<std::string, std::string>>& Manifest::getIgnores() const {
+ return ignores;
+}
+const std::string& Manifest::getTestRootPath() const {
+ return testRootPath;
+}
+const std::string& Manifest::getManifestPath() const {
+ return manifestPath;
+}
+
+void Manifest::doShuffle(uint32_t seed) {
+ std::seed_seq sequence{seed};
+ std::mt19937 shuffler(sequence);
+ std::shuffle(testPaths.begin(), testPaths.end(), shuffler);
+}
+
+std::string Manifest::localizeURL(const std::string& url) const {
+ static const std::regex regex{"local://"};
+ if (auto vendorPath = getVendorPath(url, regex)) {
+ return *vendorPath;
+ }
+ return getIntegrationPath(url, "", regex).value_or(url);
+}
+
+void Manifest::localizeSourceURLs(mbgl::JSValue& root, mbgl::JSDocument& document) const {
+ if (root.HasMember("urls") && root["urls"].IsArray()) {
+ for (auto& urlValue : root["urls"].GetArray()) {
+ const std::string path =
+ prependFileScheme(localizeMapboxTilesetURL(urlValue.GetString())
+ .value_or(localizeLocalURL(urlValue.GetString()).value_or(urlValue.GetString())));
+ urlValue.Set<std::string>(path, document.GetAllocator());
+ }
+ }
+
+ if (root.HasMember("url")) {
+ static const std::string image("image");
+ static const std::string video("video");
+
+ mbgl::JSValue& urlValue = root["url"];
+ const std::string path =
+ prependFileScheme(localizeMapboxTilesetURL(urlValue.GetString())
+ .value_or(localizeLocalURL(urlValue.GetString()).value_or(urlValue.GetString())));
+ urlValue.Set<std::string>(path, document.GetAllocator());
+
+ if (root["type"].GetString() != image && root["type"].GetString() != video) {
+ const auto tilesetPath = std::string(urlValue.GetString()).erase(0u, 7u); // remove "file://"
+ auto maybeTileset = readJson(tilesetPath);
+ if (maybeTileset.is<mbgl::JSDocument>()) {
+ const auto& tileset = maybeTileset.get<mbgl::JSDocument>();
+ assert(tileset.HasMember("tiles"));
+ root.AddMember("tiles", (mbgl::JSValue&)tileset["tiles"], document.GetAllocator());
+ root.RemoveMember("url");
+ }
+ }
+ }
+
+ if (root.HasMember("tiles")) {
+ mbgl::JSValue& tilesValue = root["tiles"];
+ assert(tilesValue.IsArray());
+ for (auto& tileValue : tilesValue.GetArray()) {
+ const std::string path = prependFileScheme(
+ localizeMapboxTilesURL(tileValue.GetString())
+ .value_or(localizeLocalURL(tileValue.GetString())
+ .value_or(localizeHttpURL(tileValue.GetString()).value_or(tileValue.GetString()))));
+ tileValue.Set<std::string>(path, document.GetAllocator());
+ }
+ }
+
+ if (root.HasMember("data") && root["data"].IsString()) {
+ mbgl::JSValue& dataValue = root["data"];
+ const std::string path =
+ prependFileScheme(localizeLocalURL(dataValue.GetString()).value_or(dataValue.GetString()));
+ dataValue.Set<std::string>(path, document.GetAllocator());
+ }
+}
+
+void Manifest::localizeStyleURLs(mbgl::JSValue& root, mbgl::JSDocument& document) const {
+ if (root.HasMember("sources")) {
+ mbgl::JSValue& sourcesValue = root["sources"];
+ for (auto& sourceProperty : sourcesValue.GetObject()) {
+ localizeSourceURLs(sourceProperty.value, document);
+ }
+ }
+
+ if (root.HasMember("glyphs")) {
+ mbgl::JSValue& glyphsValue = root["glyphs"];
+ const std::string path = prependFileScheme(
+ localizeMapboxFontsURL(glyphsValue.GetString())
+ .value_or(localizeLocalURL(glyphsValue.GetString(), true).value_or(glyphsValue.GetString())));
+ glyphsValue.Set<std::string>(path, document.GetAllocator());
+ }
+
+ if (root.HasMember("sprite")) {
+ mbgl::JSValue& spriteValue = root["sprite"];
+ const std::string path = prependFileScheme(
+ localizeMapboxSpriteURL(spriteValue.GetString())
+ .value_or(localizeLocalURL(spriteValue.GetString()).value_or(spriteValue.GetString())));
+ spriteValue.Set<std::string>(path, document.GetAllocator());
+ }
+}
+
+mbgl::optional<std::string> Manifest::localizeLocalURL(const std::string& url, bool glyphsPath) const {
+ static const std::regex regex{"local://"};
+ if (auto vendorPath = getVendorPath(url, regex, glyphsPath)) {
+ return vendorPath;
+ }
+ return getIntegrationPath(url, "", regex, glyphsPath);
+}
+
+mbgl::optional<std::string> Manifest::localizeHttpURL(const std::string& url) const {
+ static const std::regex regex{"http://localhost:2900"};
+ if (auto vendorPath = getVendorPath(url, regex)) {
+ return vendorPath;
+ }
+ return getIntegrationPath(url, "", regex);
+}
+
+mbgl::optional<std::string> Manifest::localizeMapboxSpriteURL(const std::string& url) const {
+ static const std::regex regex{"mapbox://"};
+ return getIntegrationPath(url, "", regex);
+}
+
+mbgl::optional<std::string> Manifest::localizeMapboxFontsURL(const std::string& url) const {
+ static const std::regex regex{"mapbox://fonts"};
+ return getIntegrationPath(url, "glyphs/", regex, true);
+}
+
+mbgl::optional<std::string> Manifest::localizeMapboxTilesURL(const std::string& url) const {
+ static const std::regex regex{"mapbox://"};
+ if (auto vendorPath = getVendorPath(url, regex)) {
+ return vendorPath;
+ }
+ return getIntegrationPath(url, "tiles/", regex);
+}
+
+mbgl::optional<std::string> Manifest::localizeMapboxTilesetURL(const std::string& url) const {
+ static const std::regex regex{"mapbox://"};
+ return getIntegrationPath(url, "tilesets/", regex);
+}
+
+mbgl::optional<std::string> Manifest::getVendorPath(const std::string& url,
+ const std::regex& regex,
+ bool glyphsPath) const {
+ mbgl::filesystem::path file = std::regex_replace(url, regex, vendorPath);
+ if (mbgl::filesystem::exists(file.parent_path())) {
+ return removeURLArguments(file.string());
+ }
+
+ if (glyphsPath && mbgl::filesystem::exists(file.parent_path().parent_path())) {
+ return removeURLArguments(file.string());
+ }
+
+ return mbgl::nullopt;
+}
+
+mbgl::optional<std::string> Manifest::getIntegrationPath(const std::string& url,
+ const std::string& parent,
+ const std::regex& regex,
+ bool glyphsPath) const {
+ mbgl::filesystem::path file = std::regex_replace(url, regex, assetPath + parent);
+ if (mbgl::filesystem::exists(file.parent_path())) {
+ return removeURLArguments(file.string());
+ }
+
+ if (glyphsPath && mbgl::filesystem::exists(file.parent_path().parent_path())) {
+ return removeURLArguments(file.string());
+ }
+
+ return mbgl::nullopt;
+}
+
+namespace {
+std::vector<std::pair<std::string, std::string>> parseIgnores(const std::vector<mbgl::filesystem::path>& ignoresPaths) {
+ std::vector<std::pair<std::string, std::string>> ignores;
+ for (const auto& path : ignoresPaths) {
+ auto maybeIgnores = readJson(path);
+ if (!maybeIgnores.is<mbgl::JSDocument>()) {
+ continue;
+ }
+ for (const auto& property : maybeIgnores.get<mbgl::JSDocument>().GetObject()) {
+ const std::string ignore = {property.name.GetString(), property.name.GetStringLength()};
+ const std::string reason = {property.value.GetString(), property.value.GetStringLength()};
+ ignores.emplace_back(std::make_pair(ignore, reason));
+ }
+ }
+
+ return ignores;
+}
+
+std::vector<mbgl::filesystem::path> getTestExpectations(mbgl::filesystem::path testPath,
+ const mbgl::filesystem::path& testsRootPath,
+ std::vector<mbgl::filesystem::path> expectationsPaths) {
+ std::vector<mbgl::filesystem::path> expectations{std::move(testPath.remove_filename())};
+ const auto& defaultTestExpectationsPath = expectations.front().string();
+
+ const std::regex regex{testsRootPath.string()};
+ for (const auto& path : expectationsPaths) {
+ expectations.emplace_back(std::regex_replace(defaultTestExpectationsPath, regex, path.string()));
+ assert(!expectations.back().empty());
+ }
+
+ return expectations;
+}
+
+mbgl::filesystem::path getValidPath(const std::string& manifestPath, const std::string& path) {
+ const static mbgl::filesystem::path BasePath{manifestPath};
+ mbgl::filesystem::path result{path};
+ if (result.is_relative()) {
+ result = BasePath / result;
+ }
+ if (mbgl::filesystem::exists(result)) {
+ return result;
+ }
+ mbgl::Log::Warning(mbgl::Event::General, "Invalid path is provoided inside the manifest file: %s", path.c_str());
+ return mbgl::filesystem::path{};
+}
+
+} // namespace
+
+mbgl::optional<Manifest> ManifestParser::parseManifest(const std::string& manifestPath,
+ const std::vector<std::string>& testNames,
+ const std::string& testFilter) {
+ Manifest manifest;
+ const auto filePath = mbgl::filesystem::path(manifestPath);
+ manifest.manifestPath = manifestPath.substr(0, manifestPath.find(filePath.filename()));
+
+ auto contents = readJson(filePath);
+ if (!contents.is<mbgl::JSDocument>()) {
+ mbgl::Log::Error(mbgl::Event::General, "Provided manifest file: %s is not a valid json", filePath.c_str());
+ return mbgl::nullopt;
+ }
+
+ auto document = std::move(contents.get<mbgl::JSDocument>());
+ if (document.HasMember("asset_path")) {
+ const auto& assetPathValue = document["asset_path"];
+ if (!assetPathValue.IsString()) {
+ mbgl::Log::Warning(
+ mbgl::Event::General, "Invalid assetPath is provoided inside the manifest file: %s", filePath.c_str());
+ return mbgl::nullopt;
+ }
+ manifest.assetPath = (getValidPath(manifest.manifestPath, assetPathValue.GetString()) / "").string();
+ if (manifest.assetPath.empty()) {
+ return mbgl::nullopt;
+ }
+ }
+ if (document.HasMember("vendor_path")) {
+ const auto& vendorPathValue = document["vendor_path"];
+ if (!vendorPathValue.IsString()) {
+ mbgl::Log::Warning(
+ mbgl::Event::General, "Invalid vendorPath is provoided inside the manifest file: %s", filePath.c_str());
+ return mbgl::nullopt;
+ }
+ manifest.vendorPath = (getValidPath(manifest.manifestPath, vendorPathValue.GetString()) / "").string();
+ if (manifest.vendorPath.empty()) {
+ return mbgl::nullopt;
+ }
+ }
+ mbgl::filesystem::path baseTestPath;
+ if (document.HasMember("base_test_path")) {
+ const auto& testPathValue = document["base_test_path"];
+ if (!testPathValue.IsString()) {
+ mbgl::Log::Warning(
+ mbgl::Event::General, "Invalid testPath is provoided inside the manifest file: %s", filePath.c_str());
+ return mbgl::nullopt;
+ }
+ baseTestPath = getValidPath(manifest.manifestPath, testPathValue.GetString());
+ if (baseTestPath.empty()) {
+ return mbgl::nullopt;
+ }
+ }
+ mbgl::filesystem::path probeTestPath;
+ bool enbaleProbeTest{false};
+ if (document.HasMember("probe_test_path")) {
+ const auto& testPathValue = document["probe_test_path"];
+ if (!testPathValue.IsString()) {
+ mbgl::Log::Warning(
+ mbgl::Event::General, "Invalid testPath is provoided inside the manifest file: %s", filePath.c_str());
+ return mbgl::nullopt;
+ }
+ probeTestPath = getValidPath(manifest.manifestPath, testPathValue.GetString());
+ if (probeTestPath.empty()) {
+ return mbgl::nullopt;
+ }
+ enbaleProbeTest = true;
+ }
+ std::vector<mbgl::filesystem::path> expectationPaths{};
+ if (document.HasMember("expectation_paths")) {
+ const auto& expectationPathValue = document["expectation_paths"];
+ if (!expectationPathValue.IsArray()) {
+ mbgl::Log::Warning(mbgl::Event::General,
+ "Provided expectation_paths inside the manifest file: %s is not a valid array",
+ filePath.c_str());
+ return mbgl::nullopt;
+ }
+ for (const auto& value : expectationPathValue.GetArray()) {
+ if (!value.IsString()) {
+ mbgl::Log::Warning(mbgl::Event::General,
+ "Invalid expectation path item is provoided inside the manifest file: %s",
+ filePath.c_str());
+ return mbgl::nullopt;
+ }
+ expectationPaths.emplace_back(getValidPath(manifest.manifestPath, value.GetString()));
+ if (expectationPaths.back().empty()) {
+ return mbgl::nullopt;
+ }
+ }
+ }
+ std::vector<mbgl::filesystem::path> ignorePaths{};
+ if (document.HasMember("ignore_paths")) {
+ const auto& ignorePathValue = document["ignore_paths"];
+ if (!ignorePathValue.IsArray()) {
+ mbgl::Log::Warning(mbgl::Event::General,
+ "Provided ignore_paths inside the manifest file: %s is not a valid array",
+ filePath.c_str());
+ return mbgl::nullopt;
+ }
+ for (const auto& value : ignorePathValue.GetArray()) {
+ if (!value.IsString()) {
+ mbgl::Log::Warning(mbgl::Event::General,
+ "Invalid ignore path item is provoided inside the manifest file: %s",
+ filePath.c_str());
+ return mbgl::nullopt;
+ }
+ ignorePaths.emplace_back(getValidPath(manifest.manifestPath, value.GetString()));
+ if (ignorePaths.back().empty()) {
+ return mbgl::nullopt;
+ }
+ }
+ manifest.ignores = parseIgnores(ignorePaths);
+ }
+
+ manifest.testRootPath = enbaleProbeTest ? probeTestPath.string() : baseTestPath.string();
+ if (manifest.testRootPath.back() == '/') {
+ manifest.testRootPath.pop_back();
+ }
+ if (manifest.manifestPath.back() == '/') {
+ manifest.manifestPath.pop_back();
+ }
+
+ std::vector<mbgl::filesystem::path> paths;
+ for (const auto& id : testNames) {
+ paths.emplace_back(manifest.testRootPath + "/" + id);
+ }
+ if (paths.empty()) {
+ paths.emplace_back(manifest.testRootPath);
+ }
+
+ // Recursively traverse through the test paths and collect test directories containing "style.json".
+ auto& testPaths = manifest.testPaths;
+ testPaths.reserve(paths.size());
+ for (const auto& path : paths) {
+ if (!mbgl::filesystem::exists(path)) {
+ mbgl::Log::Warning(
+ mbgl::Event::General, "Provided test folder '%s' does not exist.", path.string().c_str());
+ continue;
+ }
+ for (auto& testPath : mbgl::filesystem::recursive_directory_iterator(path)) {
+ // Skip paths that fail regexp match.
+ if (!testFilter.empty() && !std::regex_match(testPath.path().string(), std::regex(testFilter))) {
+ continue;
+ }
+
+ if (testPath.path().filename() == "style.json") {
+ testPaths.emplace_back(testPath, getTestExpectations(testPath, path, expectationPaths));
+ }
+ }
+ }
+
+ return mbgl::optional<Manifest>(manifest);
+}
diff --git a/render-test/manifest_parser.hpp b/render-test/manifest_parser.hpp
new file mode 100644
index 0000000000..bc5adf1091
--- /dev/null
+++ b/render-test/manifest_parser.hpp
@@ -0,0 +1,55 @@
+#pragma once
+
+#include "metadata.hpp"
+
+#include <mbgl/util/optional.hpp>
+#include <mbgl/util/rapidjson.hpp>
+
+#include <regex>
+#include <string>
+#include <utility>
+#include <vector>
+
+class Manifest {
+public:
+ Manifest();
+ ~Manifest();
+ const std::vector<std::pair<std::string, std::string>>& getIgnores() const;
+ const std::vector<TestPaths>& getTestPaths() const;
+ const std::string& getTestRootPath() const;
+ const std::string& getManifestPath() const;
+ void doShuffle(uint32_t seed);
+
+ std::string localizeURL(const std::string& url) const;
+ void localizeSourceURLs(mbgl::JSValue& root, mbgl::JSDocument& document) const;
+ void localizeStyleURLs(mbgl::JSValue& root, mbgl::JSDocument& document) const;
+
+private:
+ friend class ManifestParser;
+ mbgl::optional<std::string> localizeLocalURL(const std::string& url, bool glyphsPath = false) const;
+ mbgl::optional<std::string> localizeHttpURL(const std::string& url) const;
+ mbgl::optional<std::string> localizeMapboxSpriteURL(const std::string& url) const;
+ mbgl::optional<std::string> localizeMapboxFontsURL(const std::string& url) const;
+ mbgl::optional<std::string> localizeMapboxTilesURL(const std::string& url) const;
+ mbgl::optional<std::string> localizeMapboxTilesetURL(const std::string& url) const;
+ mbgl::optional<std::string> getVendorPath(const std::string& url,
+ const std::regex& regex,
+ bool glyphsPath = false) const;
+ mbgl::optional<std::string> getIntegrationPath(const std::string& url,
+ const std::string& parent,
+ const std::regex& regex,
+ bool glyphsPath = false) const;
+ std::string manifestPath;
+ std::string testRootPath;
+ std::string vendorPath;
+ std::string assetPath;
+ std::vector<std::pair<std::string, std::string>> ignores;
+ std::vector<TestPaths> testPaths;
+};
+
+class ManifestParser {
+public:
+ static mbgl::optional<Manifest> parseManifest(const std::string& manifestPath,
+ const std::vector<std::string>& testNames,
+ const std::string& testFilter);
+};
diff --git a/render-test/metadata.hpp b/render-test/metadata.hpp
index d23a0fb296..567c89e3fc 100644
--- a/render-test/metadata.hpp
+++ b/render-test/metadata.hpp
@@ -1,14 +1,22 @@
#pragma once
+#include <mbgl/util/geo.hpp>
#include <mbgl/util/rapidjson.hpp>
#include <mbgl/util/size.hpp>
#include <mbgl/map/mode.hpp>
+#include <mbgl/renderer/query.hpp>
#include "filesystem.hpp"
#include <map>
+namespace mbgl {
+namespace gfx {
+struct RenderingStats;
+}
+} // namespace mbgl
+
struct TestStatistics {
TestStatistics() = default;
@@ -20,6 +28,10 @@ struct TestStatistics {
};
struct TestPaths {
+ TestPaths() = default;
+ TestPaths(mbgl::filesystem::path stylePath_, std::vector<mbgl::filesystem::path> expectations_)
+ : stylePath(std::move(stylePath_)), expectations(std::move(expectations_)) {}
+
mbgl::filesystem::path stylePath;
std::vector<mbgl::filesystem::path> expectations;
@@ -29,20 +41,83 @@ struct TestPaths {
}
};
+inline std::tuple<bool, float> checkValue(float expected, float actual, float tolerance) {
+ float delta = expected * tolerance;
+ assert(delta >= 0.0f);
+ return std::make_tuple(std::abs(expected - actual) <= delta, delta);
+}
+
+struct FileSizeProbe {
+ FileSizeProbe() = default;
+ FileSizeProbe(std::string path_, uintmax_t size_, float tolerance_)
+ : path(std::move(path_)), size(size_), tolerance(tolerance_) {}
+
+ std::string path;
+ uintmax_t size;
+ float tolerance;
+};
+
struct MemoryProbe {
MemoryProbe() = default;
- MemoryProbe(size_t peak_, size_t allocations_)
- : peak(peak_)
- , allocations(allocations_) {}
+ MemoryProbe(size_t peak_, size_t allocations_) : peak(peak_), allocations(allocations_), tolerance(0.0f) {}
size_t peak;
size_t allocations;
+ float tolerance;
+
+ static std::tuple<bool, float> checkPeak(const MemoryProbe& expected, const MemoryProbe& actual) {
+ return checkValue(expected.peak, actual.peak, actual.tolerance);
+ }
+
+ static std::tuple<bool, float> checkAllocations(const MemoryProbe& expected, const MemoryProbe& actual) {
+ return checkValue(expected.allocations, actual.allocations, actual.tolerance);
+ }
+};
+
+struct FpsProbe {
+ float average = 0.0;
+ float minOnePc = 0.0;
+ float tolerance = 0.0f;
+};
+
+struct NetworkProbe {
+ NetworkProbe() = default;
+ NetworkProbe(size_t requests_, size_t transferred_) : requests(requests_), transferred(transferred_) {}
+
+ size_t requests;
+ size_t transferred;
+};
+
+struct GfxProbe {
+ struct Memory {
+ Memory() = default;
+ Memory(int allocated_, int peak_) : allocated(allocated_), peak(peak_) {}
+
+ int allocated;
+ int peak;
+ };
+
+ GfxProbe() = default;
+ GfxProbe(const mbgl::gfx::RenderingStats&, const GfxProbe&);
+
+ int numDrawCalls;
+ int numTextures;
+ int numBuffers;
+ int numFrameBuffers;
+
+ Memory memTextures;
+ Memory memIndexBuffers;
+ Memory memVertexBuffers;
};
class TestMetrics {
public:
- bool isEmpty() const { return memory.empty(); }
+ bool isEmpty() const { return fileSize.empty() && memory.empty() && network.empty() && fps.empty() && gfx.empty(); }
+ std::map<std::string, FileSizeProbe> fileSize;
std::map<std::string, MemoryProbe> memory;
+ std::map<std::string, NetworkProbe> network;
+ std::map<std::string, FpsProbe> fps;
+ std::map<std::string, GfxProbe> gfx;
};
struct TestMetadata {
@@ -50,6 +125,8 @@ struct TestMetadata {
TestPaths paths;
mbgl::JSDocument document;
+ bool renderTest = true;
+ bool outputsImage = true;
mbgl::Size size{ 512u, 512u };
float pixelRatio = 1.0f;
@@ -61,6 +138,9 @@ struct TestMetadata {
bool axonometric = false;
double xSkew = 0.0;
double ySkew = 1.0;
+ mbgl::ScreenCoordinate queryGeometry{0u, 0u};
+ mbgl::ScreenBox queryGeometryBox{{0u, 0u}, {0u, 0u}};
+ mbgl::RenderedQueryOptions queryOptions;
// TODO
uint32_t fadeDuration = 0;
@@ -72,6 +152,7 @@ struct TestMetadata {
std::string color;
std::string actual;
+ std::string actualJson;
std::string expected;
std::string diff;
diff --git a/render-test/parser.cpp b/render-test/parser.cpp
index 9b462dee72..b5d48d23a1 100644
--- a/render-test/parser.cpp
+++ b/render-test/parser.cpp
@@ -3,11 +3,14 @@
#include <mbgl/util/rapidjson.hpp>
#include <mbgl/util/string.hpp>
-#include <args.hxx>
-
+#include <rapidjson/prettywriter.h>
#include <rapidjson/stringbuffer.h>
#include <rapidjson/writer.h>
+#include <mapbox/geojson_impl.hpp>
+#include <mbgl/style/conversion/filter.hpp>
+#include <mbgl/style/conversion/json.hpp>
+
#include <boost/archive/iterators/base64_from_binary.hpp>
#include <boost/archive/iterators/insert_linebreaks.hpp>
#include <boost/archive/iterators/transform_width.hpp>
@@ -18,8 +21,8 @@
#include "parser.hpp"
#include "runner.hpp"
-#include <sstream>
#include <regex>
+#include <sstream>
namespace {
@@ -78,109 +81,73 @@ const char* resultsHeaderButtons = R"HTML(
</h1>
)HTML";
-std::string removeURLArguments(const std::string &url) {
- std::string::size_type index = url.find('?');
- if (index != std::string::npos) {
- return url.substr(0, index);
- }
- return url;
-}
-
-std::string prependFileScheme(const std::string &url) {
- static const std::string fileScheme("file://");
- return fileScheme + url;
-}
-
-mbgl::optional<std::string> getVendorPath(const std::string& url, const std::regex& regex, bool glyphsPath = false) {
- static const mbgl::filesystem::path vendorPath(std::string(TEST_RUNNER_ROOT_PATH) + "/vendor/");
-
- mbgl::filesystem::path file = std::regex_replace(url, regex, vendorPath.string());
- if (mbgl::filesystem::exists(file.parent_path())) {
- return removeURLArguments(file.string());
- }
-
- if (glyphsPath && mbgl::filesystem::exists(file.parent_path().parent_path())) {
- return removeURLArguments(file.string());
- }
-
- return {};
-}
-
-mbgl::optional<std::string> getIntegrationPath(const std::string& url, const std::string& parent, const std::regex& regex, bool glyphsPath = false) {
- static const mbgl::filesystem::path integrationPath(std::string(TEST_RUNNER_ROOT_PATH) + "/mapbox-gl-js/test/integration/");
-
- mbgl::filesystem::path file = std::regex_replace(url, regex, integrationPath.string() + parent);
- if (mbgl::filesystem::exists(file.parent_path())) {
- return removeURLArguments(file.string());
- }
-
- if (glyphsPath && mbgl::filesystem::exists(file.parent_path().parent_path())) {
- return removeURLArguments(file.string());
- }
-
- return {};
+void writeJSON(rapidjson::PrettyWriter<rapidjson::StringBuffer>& writer, const mbgl::Value& value) {
+ value.match([&writer](const mbgl::NullValue&) { writer.Null(); },
+ [&writer](bool b) { writer.Bool(b); },
+ [&writer](uint64_t u) { writer.Uint64(u); },
+ [&writer](int64_t i) { writer.Int64(i); },
+ [&writer](double d) { d == std::floor(d) ? writer.Int64(d) : writer.Double(d); },
+ [&writer](const std::string& s) { writer.String(s); },
+ [&writer](const std::vector<mbgl::Value>& arr) {
+ writer.StartArray();
+ for (const auto& item : arr) {
+ writeJSON(writer, item);
+ }
+ writer.EndArray();
+ },
+ [&writer](const std::unordered_map<std::string, mbgl::Value>& obj) {
+ writer.StartObject();
+ std::map<std::string, mbgl::Value> sorted(obj.begin(), obj.end());
+ for (const auto& entry : sorted) {
+ writer.Key(entry.first.c_str());
+ writeJSON(writer, entry.second);
+ }
+ writer.EndObject();
+ });
}
-mbgl::optional<std::string> localizeLocalURL(const std::string& url, bool glyphsPath = false) {
- static const std::regex regex { "local://" };
- if (auto vendorPath = getVendorPath(url, regex, glyphsPath)) {
- return vendorPath;
- } else {
- return getIntegrationPath(url, "", regex, glyphsPath);
- }
-}
+} // namespace
-mbgl::optional<std::string> localizeHttpURL(const std::string& url) {
- static const std::regex regex { "http://localhost:2900" };
- if (auto vendorPath = getVendorPath(url, regex)) {
- return vendorPath;
- } else {
- return getIntegrationPath(url, "", regex);
+std::string toJSON(const mbgl::Value& value, unsigned indent, bool singleLine) {
+ rapidjson::StringBuffer buffer;
+ rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);
+ if (singleLine) {
+ writer.SetFormatOptions(rapidjson::kFormatSingleLineArray);
}
+ writer.SetIndent(' ', indent);
+ writeJSON(writer, value);
+ return buffer.GetString();
}
-mbgl::optional<std::string> localizeMapboxSpriteURL(const std::string& url) {
- static const std::regex regex { "mapbox://" };
- return getIntegrationPath(url, "", regex);
-}
-
-mbgl::optional<std::string> localizeMapboxFontsURL(const std::string& url) {
- static const std::regex regex { "mapbox://fonts" };
- return getIntegrationPath(url, "glyphs/", regex, true);
-}
-
-mbgl::optional<std::string> localizeMapboxTilesURL(const std::string& url) {
- static const std::regex regex { "mapbox://" };
- if (auto vendorPath = getVendorPath(url, regex)) {
- return vendorPath;
- } else {
- return getIntegrationPath(url, "tiles/", regex);
+std::string toJSON(const std::vector<mbgl::Feature>& features, unsigned indent, bool singleLine) {
+ rapidjson::CrtAllocator allocator;
+ rapidjson::StringBuffer buffer;
+ rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);
+ if (singleLine) {
+ writer.SetFormatOptions(rapidjson::kFormatSingleLineArray);
}
-}
-
-mbgl::optional<std::string> localizeMapboxTilesetURL(const std::string& url) {
- static const std::regex regex { "mapbox://" };
- return getIntegrationPath(url, "tilesets/", regex);
-}
-
-TestPaths makeTestPaths(mbgl::filesystem::path stylePath) {
- std::vector<mbgl::filesystem::path> expectations{ stylePath };
- expectations.front().remove_filename();
-
- const static std::regex regex{ TestRunner::getBasePath() };
- for (const std::string& path : TestRunner::getPlatformExpectationsPaths()) {
- expectations.emplace_back(std::regex_replace(expectations.front().string(), regex, path));
- assert(!expectations.back().empty());
+ writer.SetIndent(' ', indent);
+ writer.StartArray();
+ for (const auto& feature : features) {
+ mbgl::JSValue result(rapidjson::kObjectType);
+ result.AddMember("type", "Feature", allocator);
+ if (!feature.id.is<mbgl::NullValue>()) {
+ result.AddMember(
+ "id", mapbox::geojson::identifier::visit(feature.id, mapbox::geojson::to_value{allocator}), allocator);
+ }
+ result.AddMember("geometry", mapbox::geojson::convert(feature.geometry, allocator), allocator);
+ result.AddMember("properties", mapbox::geojson::to_value{allocator}(feature.properties), allocator);
+ result.AddMember("source", feature.source, allocator);
+ if (!feature.sourceLayer.empty()) {
+ result.AddMember("sourceLayer", feature.sourceLayer, allocator);
+ }
+ result.AddMember("state", mapbox::geojson::to_value{allocator}(feature.state), allocator);
+ result.Accept(writer);
}
-
- return {
- std::move(stylePath),
- std::move(expectations)
- };
+ writer.EndArray();
+ return buffer.GetString();
}
-} // namespace
-
JSONReply readJson(const mbgl::filesystem::path& jsonPath) {
auto maybeJSON = mbgl::util::readFile(jsonPath);
if (!maybeJSON) {
@@ -193,7 +160,7 @@ JSONReply readJson(const mbgl::filesystem::path& jsonPath) {
return { mbgl::formatJSONParseError(document) };
}
- return { std::move(document) };
+ return {std::move(document)};
}
std::string serializeJsonValue(const mbgl::JSValue& value) {
@@ -206,30 +173,108 @@ std::string serializeJsonValue(const mbgl::JSValue& value) {
std::string serializeMetrics(const TestMetrics& metrics) {
rapidjson::StringBuffer s;
- rapidjson::Writer<rapidjson::StringBuffer> writer(s);
+ rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(s);
writer.StartObject();
+
+ // Start file-size section.
+ if (!metrics.fileSize.empty()) {
+ writer.Key("file-size");
+ writer.StartArray();
+ for (const auto& fileSizeProbe : metrics.fileSize) {
+ assert(!fileSizeProbe.first.empty());
+ writer.StartArray();
+ writer.String(fileSizeProbe.first.c_str());
+ writer.String(fileSizeProbe.second.path);
+ writer.Uint64(fileSizeProbe.second.size);
+ writer.EndArray();
+ }
+ writer.EndArray();
+ }
+
// Start memory section.
- writer.Key("memory");
- writer.StartArray();
- for (const auto& memoryProbe : metrics.memory) {
- assert(!memoryProbe.first.empty());
+ if (!metrics.memory.empty()) {
+ writer.Key("memory");
writer.StartArray();
- writer.String(memoryProbe.first.c_str());
- writer.Uint64(memoryProbe.second.peak);
- writer.Uint64(memoryProbe.second.allocations);
+ for (const auto& memoryProbe : metrics.memory) {
+ assert(!memoryProbe.first.empty());
+ writer.StartArray();
+ writer.String(memoryProbe.first.c_str());
+ writer.Uint64(memoryProbe.second.peak);
+ writer.Uint64(memoryProbe.second.allocations);
+ writer.EndArray();
+ }
writer.EndArray();
}
- // End memory section.
- writer.EndArray();
+
+ // Start network section
+ if (!metrics.network.empty()) {
+ writer.Key("network");
+ writer.StartArray();
+ for (const auto& networkProbe : metrics.network) {
+ assert(!networkProbe.first.empty());
+ writer.StartArray();
+ writer.String(networkProbe.first.c_str());
+ writer.Uint64(networkProbe.second.requests);
+ writer.Uint64(networkProbe.second.transferred);
+ writer.EndArray();
+ }
+ writer.EndArray();
+ }
+
+ if (!metrics.fps.empty()) {
+ // Start fps section
+ writer.Key("fps");
+ writer.StartArray();
+ for (const auto& fpsProbe : metrics.fps) {
+ assert(!fpsProbe.first.empty());
+ writer.StartArray();
+ writer.String(fpsProbe.first.c_str());
+ writer.Double(fpsProbe.second.average);
+ writer.Double(fpsProbe.second.minOnePc);
+ writer.EndArray();
+ }
+ writer.EndArray();
+ // End fps section
+ }
+
+ if (!metrics.gfx.empty()) {
+ // Start gfx section
+ writer.Key("gfx");
+ writer.StartArray();
+ for (const auto& gfxProbe : metrics.gfx) {
+ assert(!gfxProbe.first.empty());
+ writer.StartArray();
+ writer.String(gfxProbe.first.c_str());
+ writer.Int(gfxProbe.second.numDrawCalls);
+ writer.Int(gfxProbe.second.numTextures);
+ writer.Int(gfxProbe.second.numBuffers);
+ writer.Int(gfxProbe.second.numFrameBuffers);
+ writer.StartArray();
+ writer.Int(gfxProbe.second.memTextures.allocated);
+ writer.Int(gfxProbe.second.memTextures.peak);
+ writer.EndArray();
+ writer.StartArray();
+ writer.Int(gfxProbe.second.memIndexBuffers.allocated);
+ writer.Int(gfxProbe.second.memIndexBuffers.peak);
+ writer.EndArray();
+ writer.StartArray();
+ writer.Int(gfxProbe.second.memVertexBuffers.allocated);
+ writer.Int(gfxProbe.second.memVertexBuffers.peak);
+ writer.EndArray();
+ writer.EndArray();
+ }
+ writer.EndArray();
+ // End gfx section
+ }
+
writer.EndObject();
return s.GetString();
}
-std::vector<std::string> readExpectedEntries(const mbgl::filesystem::path& base) {
- static const std::regex regex(".*expected.*.png");
-
+namespace {
+std::vector<std::string> readExpectedEntries(const std::regex& regex, const mbgl::filesystem::path& base) {
std::vector<std::string> expectedImages;
for (const auto& entry : mbgl::filesystem::directory_iterator(base)) {
if (entry.is_regular_file()) {
@@ -241,120 +286,16 @@ std::vector<std::string> readExpectedEntries(const mbgl::filesystem::path& base)
}
return expectedImages;
}
+} // namespace
-
-ArgumentsTuple parseArguments(int argc, char** argv) {
- args::ArgumentParser argumentParser("Mapbox GL Test Runner");
-
- args::HelpFlag helpFlag(argumentParser, "help", "Display this help menu", { 'h', "help" });
-
- args::Flag recycleMapFlag(argumentParser, "recycle map", "Toggle reusing the map object",
- { 'r', "recycle-map" });
- args::Flag shuffleFlag(argumentParser, "shuffle", "Toggle shuffling the tests order",
- { 's', "shuffle" });
- args::ValueFlag<uint32_t> seedValue(argumentParser, "seed", "Shuffle seed (default: random)",
- { "seed" });
- args::ValueFlag<std::string> testPathValue(argumentParser, "rootPath", "Test root rootPath",
- { 'p', "rootPath" });
- args::ValueFlag<std::regex> testFilterValue(argumentParser, "filter", "Test filter regex",
- { 'f', "filter" });
- args::PositionalList<std::string> testNameValues(argumentParser, "URL", "Test name(s)");
-
- try {
- argumentParser.ParseCLI(argc, argv);
- } catch (const args::Help&) {
- std::ostringstream stream;
- stream << argumentParser;
- mbgl::Log::Info(mbgl::Event::General, stream.str());
- exit(0);
- } catch (const args::ParseError& e) {
- std::ostringstream stream;
- stream << argumentParser;
- mbgl::Log::Info(mbgl::Event::General, stream.str());
- mbgl::Log::Error(mbgl::Event::General, e.what());
- exit(1);
- } catch (const args::ValidationError& e) {
- std::ostringstream stream;
- stream << argumentParser;
- mbgl::Log::Info(mbgl::Event::General, stream.str());
- mbgl::Log::Error(mbgl::Event::General, e.what());
- exit(2);
- } catch (const std::regex_error& e) {
- mbgl::Log::Error(mbgl::Event::General, "Invalid filter regular expression: %s", e.what());
- exit(3);
- }
-
- mbgl::filesystem::path rootPath {testPathValue ? args::get(testPathValue) : TestRunner::getBasePath()};
- if (!mbgl::filesystem::exists(rootPath)) {
- mbgl::Log::Error(mbgl::Event::General, "Provided rootPath '%s' does not exist.", rootPath.string().c_str());
- exit(4);
- }
-
- std::vector<mbgl::filesystem::path> paths;
- for (const auto& id : args::get(testNameValues)) {
- paths.emplace_back(TestRunner::getBasePath() + "/" + id);
- }
-
- if (paths.empty()) {
- paths.emplace_back(TestRunner::getBasePath());
- }
-
- // Recursively traverse through the test paths and collect test directories containing "style.json".
- std::vector<TestPaths> testPaths;
- testPaths.reserve(paths.size());
- for (const auto& path : paths) {
- if (!mbgl::filesystem::exists(path)) {
- mbgl::Log::Warning(mbgl::Event::General, "Provided test folder '%s' does not exist.", path.string().c_str());
- continue;
- }
- for (auto& testPath : mbgl::filesystem::recursive_directory_iterator(path)) {
- // Skip paths that fail regexp match.
- if (testFilterValue && !std::regex_match(testPath.path().string(), args::get(testFilterValue))) {
- continue;
- }
- if (testPath.path().filename() == "style.json") {
- testPaths.emplace_back(makeTestPaths(testPath));
- }
- }
- }
-
- return ArgumentsTuple {
- recycleMapFlag ? args::get(recycleMapFlag) : false,
- shuffleFlag ? args::get(shuffleFlag) : false, seedValue ? args::get(seedValue) : 1u,
- testPathValue ? args::get(testPathValue) : TestRunner::getBasePath(),
- std::move(testPaths)
- };
+std::vector<std::string> readExpectedImageEntries(const mbgl::filesystem::path& base) {
+ static const std::regex regex(".*expected.*.png");
+ return readExpectedEntries(regex, base);
}
-std::vector<std::pair<std::string, std::string>> parseIgnores() {
- std::vector<std::pair<std::string, std::string>> ignores;
-
- auto mainIgnoresPath = mbgl::filesystem::path(TEST_RUNNER_ROOT_PATH).append("platform/node/test/ignores.json");
-
- mbgl::filesystem::path platformSpecificIgnores;
-
-#ifdef __APPLE__
- platformSpecificIgnores = mbgl::filesystem::path(TEST_RUNNER_ROOT_PATH).append("render-test/mac-ignores.json");
-#elif __linux__
- platformSpecificIgnores = mbgl::filesystem::path(TEST_RUNNER_ROOT_PATH).append("render-test/linux-ignores.json");
-#endif
-
- std::vector<mbgl::filesystem::path> ignoresPaths = { mainIgnoresPath, platformSpecificIgnores };
- for (auto path: ignoresPaths) {
- auto maybeIgnores = readJson(path);
- if (!maybeIgnores.is<mbgl::JSDocument>()) {
- continue;
- }
- for (const auto& property : maybeIgnores.get<mbgl::JSDocument>().GetObject()) {
- const std::string ignore = { property.name.GetString(),
- property.name.GetStringLength() };
- const std::string reason = { property.value.GetString(),
- property.value.GetStringLength() };
- ignores.emplace_back(std::make_pair(ignore, reason));
- }
- }
-
- return ignores;
+std::vector<std::string> readExpectedJSONEntries(const mbgl::filesystem::path& base) {
+ static const std::regex regex(".*expected.*.json");
+ return readExpectedEntries(regex, base);
}
TestMetrics readExpectedMetrics(const mbgl::filesystem::path& path) {
@@ -366,6 +307,29 @@ TestMetrics readExpectedMetrics(const mbgl::filesystem::path& path) {
}
const auto& document = maybeJson.get<mbgl::JSDocument>();
+
+ if (document.HasMember("file-size")) {
+ const mbgl::JSValue& fileSizeValue = document["file-size"];
+ assert(fileSizeValue.IsArray());
+ for (auto& probeValue : fileSizeValue.GetArray()) {
+ assert(probeValue.IsArray());
+ assert(probeValue.Size() >= 3u);
+ assert(probeValue[0].IsString());
+ assert(probeValue[1].IsString());
+ assert(probeValue[2].IsNumber());
+
+ std::string mark{probeValue[0].GetString(), probeValue[0].GetStringLength()};
+ assert(!mark.empty());
+
+ std::string filePath{probeValue[1].GetString(), probeValue[1].GetStringLength()};
+ assert(!filePath.empty());
+
+ result.fileSize.emplace(std::piecewise_construct,
+ std::forward_as_tuple(std::move(mark)),
+ std::forward_as_tuple(std::move(filePath), probeValue[2].GetUint64(), 0.f));
+ }
+ }
+
if (document.HasMember("memory")) {
const mbgl::JSValue& memoryValue = document["memory"];
assert(memoryValue.IsArray());
@@ -376,18 +340,88 @@ TestMetrics readExpectedMetrics(const mbgl::filesystem::path& path) {
assert(probeValue[1].IsNumber());
assert(probeValue[2].IsNumber());
- const std::string mark { probeValue[0].GetString(), probeValue[0].GetStringLength() };
+ std::string mark{probeValue[0].GetString(), probeValue[0].GetStringLength()};
assert(!mark.empty());
result.memory.emplace(std::piecewise_construct,
- std::forward_as_tuple(std::move(mark)),
+ std::forward_as_tuple(std::move(mark)),
std::forward_as_tuple(probeValue[1].GetUint64(), probeValue[2].GetUint64()));
}
}
+ if (document.HasMember("network")) {
+ const mbgl::JSValue& networkValue = document["network"];
+ assert(networkValue.IsArray());
+ for (auto& probeValue : networkValue.GetArray()) {
+ assert(probeValue.IsArray());
+ assert(probeValue.Size() >= 3u);
+ assert(probeValue[0].IsString());
+ assert(probeValue[1].IsNumber());
+ assert(probeValue[2].IsNumber());
+
+ std::string mark{probeValue[0].GetString(), probeValue[0].GetStringLength()};
+ assert(!mark.empty());
+
+ result.network.emplace(std::piecewise_construct,
+ std::forward_as_tuple(std::move(mark)),
+ std::forward_as_tuple(probeValue[1].GetUint64(), probeValue[2].GetUint64()));
+ }
+ }
+
+ if (document.HasMember("fps")) {
+ const mbgl::JSValue& fpsValue = document["fps"];
+ assert(fpsValue.IsArray());
+ for (auto& probeValue : fpsValue.GetArray()) {
+ assert(probeValue.IsArray());
+ assert(probeValue.Size() >= 4u);
+ assert(probeValue[0].IsString());
+ assert(probeValue[1].IsNumber()); // Average
+ assert(probeValue[2].IsNumber()); // Minimum
+ assert(probeValue[3].IsNumber()); // Tolerance
+ const std::string mark{probeValue[0].GetString(), probeValue[0].GetStringLength()};
+ assert(!mark.empty());
+ result.fps.insert(
+ {std::move(mark), {probeValue[1].GetFloat(), probeValue[2].GetFloat(), probeValue[3].GetFloat()}});
+ }
+ }
+
+ if (document.HasMember("gfx")) {
+ const mbgl::JSValue& gfxValue = document["gfx"];
+ assert(gfxValue.IsArray());
+ for (auto& probeValue : gfxValue.GetArray()) {
+ assert(probeValue.IsArray());
+ assert(probeValue.Size() >= 8u);
+ assert(probeValue[0].IsString());
+ assert(probeValue[1].IsInt());
+ assert(probeValue[2].IsInt());
+ assert(probeValue[3].IsInt());
+ assert(probeValue[4].IsInt());
+ assert(probeValue[5].IsArray());
+ assert(probeValue[6].IsArray());
+ assert(probeValue[7].IsArray());
+
+ const std::string mark{probeValue[0].GetString(), probeValue[0].GetStringLength()};
+ assert(!mark.empty());
+
+ GfxProbe probe;
+ probe.numDrawCalls = probeValue[1].GetInt();
+ probe.numTextures = probeValue[2].GetInt();
+ probe.numBuffers = probeValue[3].GetInt();
+ probe.numFrameBuffers = probeValue[4].GetInt();
+ probe.memTextures.allocated = probeValue[5].GetArray()[0].GetInt();
+ probe.memTextures.peak = probeValue[5].GetArray()[1].GetInt();
+ probe.memIndexBuffers.allocated = probeValue[6].GetArray()[0].GetInt();
+ probe.memIndexBuffers.peak = probeValue[6].GetArray()[1].GetInt();
+ probe.memVertexBuffers.allocated = probeValue[7].GetArray()[0].GetInt();
+ probe.memVertexBuffers.peak = probeValue[7].GetArray()[1].GetInt();
+
+ result.gfx.insert({mark, std::move(probe)});
+ }
+ }
+
return result;
}
-TestMetadata parseTestMetadata(const TestPaths& paths) {
+TestMetadata parseTestMetadata(const TestPaths& paths, const Manifest& manifest) {
TestMetadata metadata;
metadata.paths = paths;
@@ -398,18 +432,16 @@ TestMetadata parseTestMetadata(const TestPaths& paths) {
}
metadata.document = std::move(maybeJson.get<mbgl::JSDocument>());
- localizeStyleURLs(metadata.document, metadata.document);
+ manifest.localizeStyleURLs(metadata.document, metadata.document);
if (!metadata.document.HasMember("metadata")) {
- mbgl::Log::Warning(mbgl::Event::ParseStyle, "Style has no 'metadata': %s",
- paths.stylePath.c_str());
+ mbgl::Log::Warning(mbgl::Event::ParseStyle, "Style has no 'metadata': %s", paths.stylePath.c_str());
return metadata;
}
const mbgl::JSValue& metadataValue = metadata.document["metadata"];
if (!metadataValue.HasMember("test")) {
- mbgl::Log::Warning(mbgl::Event::ParseStyle, "Style has no 'metadata.test': %s",
- paths.stylePath.c_str());
+ mbgl::Log::Warning(mbgl::Event::ParseStyle, "Style has no 'metadata.test': %s", paths.stylePath.c_str());
return metadata;
}
@@ -437,13 +469,26 @@ TestMetadata parseTestMetadata(const TestPaths& paths) {
if (testValue.HasMember("description")) {
assert(testValue["description"].IsString());
- metadata.description = std::string{ testValue["description"].GetString(),
- testValue["description"].GetStringLength() };
+ metadata.description =
+ std::string{testValue["description"].GetString(), testValue["description"].GetStringLength()};
}
if (testValue.HasMember("mapMode")) {
+ metadata.outputsImage = true;
assert(testValue["mapMode"].IsString());
- metadata.mapMode = testValue["mapMode"].GetString() == std::string("tile") ? mbgl::MapMode::Tile : mbgl::MapMode::Static;
+ std::string mapModeStr = testValue["mapMode"].GetString();
+ if (mapModeStr == "tile")
+ metadata.mapMode = mbgl::MapMode::Tile;
+ else if (mapModeStr == "continuous") {
+ metadata.mapMode = mbgl::MapMode::Continuous;
+ metadata.outputsImage = false;
+ } else if (mapModeStr == "static")
+ metadata.mapMode = mbgl::MapMode::Static;
+ else {
+ mbgl::Log::Warning(
+ mbgl::Event::ParseStyle, "Unknown map mode: %s. Falling back to static mode", mapModeStr.c_str());
+ metadata.mapMode = mbgl::MapMode::Static;
+ }
}
// Test operations handled in runner.cpp.
@@ -476,6 +521,45 @@ TestMetadata parseTestMetadata(const TestPaths& paths) {
metadata.ySkew = testValue["skew"][1].GetDouble();
}
+ if (testValue.HasMember("queryGeometry")) {
+ assert(testValue["queryGeometry"].IsArray());
+ if (testValue["queryGeometry"][0].IsNumber() && testValue["queryGeometry"][1].IsNumber()) {
+ metadata.queryGeometry.x = testValue["queryGeometry"][0].GetDouble();
+ metadata.queryGeometry.y = testValue["queryGeometry"][1].GetDouble();
+ } else if (testValue["queryGeometry"][0].IsArray() && testValue["queryGeometry"][1].IsArray()) {
+ metadata.queryGeometryBox.min.x = testValue["queryGeometry"][0][0].GetDouble();
+ metadata.queryGeometryBox.min.y = testValue["queryGeometry"][0][1].GetDouble();
+ metadata.queryGeometryBox.max.x = testValue["queryGeometry"][1][0].GetDouble();
+ metadata.queryGeometryBox.max.y = testValue["queryGeometry"][1][1].GetDouble();
+ }
+ metadata.renderTest = false;
+ }
+
+ if (testValue.HasMember("queryOptions")) {
+ assert(testValue["queryOptions"].IsObject());
+
+ if (testValue["queryOptions"].HasMember("layers")) {
+ assert(testValue["queryOptions"]["layers"].IsArray());
+ auto layersArray = testValue["queryOptions"]["layers"].GetArray();
+ std::vector<std::string> layersVec;
+ for (uint32_t i = 0; i < layersArray.Size(); i++) {
+ layersVec.emplace_back(testValue["queryOptions"]["layers"][i].GetString());
+ }
+ metadata.queryOptions.layerIDs = layersVec;
+ }
+
+ using namespace mbgl::style;
+ using namespace mbgl::style::conversion;
+ if (testValue["queryOptions"].HasMember("filter")) {
+ assert(testValue["queryOptions"]["filter"].IsArray());
+ auto& filterVal = testValue["queryOptions"]["filter"];
+ Error error;
+ mbgl::optional<Filter> converted = convert<Filter>(filterVal, error);
+ assert(converted);
+ metadata.queryOptions.filter = std::move(*converted);
+ }
+ }
+
// TODO: fadeDuration
// TODO: addFakeCanvas
@@ -493,26 +577,41 @@ std::string encodeBase64(const std::string& data) {
}
std::string createResultItem(const TestMetadata& metadata, bool hasFailedTests) {
- const bool shouldHide = (hasFailedTests && metadata.status == "passed") || (metadata.status.find("ignored") != std::string::npos);
-
+ const bool shouldHide =
+ (hasFailedTests && metadata.status == "passed") || (metadata.status.find("ignored") != std::string::npos);
+
std::string html;
html.append("<div class=\"test " + metadata.status + (shouldHide ? " hide" : "") + "\">\n");
html.append(R"(<h2><span class="label" style="background: )" + metadata.color + "\">" + metadata.status + "</span> " + metadata.id + "</h2>\n");
if (metadata.status != "errored") {
- html.append("<img width=" + mbgl::util::toString(metadata.size.width));
- html.append(" height=" + mbgl::util::toString(metadata.size.height));
- html.append(" src=\"data:image/png;base64," + encodeBase64(metadata.actual) + "\"");
- html.append(" data-alt-src=\"data:image/png;base64," + encodeBase64(metadata.expected) + "\">\n");
-
- html.append("<img width=" + mbgl::util::toString(metadata.size.width));
- html.append(" height=" + mbgl::util::toString(metadata.size.height));
- html.append(" src=\"data:image/png;base64," + encodeBase64(metadata.diff) + "\">\n");
+ if (metadata.outputsImage) {
+ if (metadata.renderTest) {
+ html.append("<img width=" + mbgl::util::toString(metadata.size.width));
+ html.append(" height=" + mbgl::util::toString(metadata.size.height));
+ html.append(" src=\"data:image/png;base64," + encodeBase64(metadata.actual) + "\"");
+ html.append(" data-alt-src=\"data:image/png;base64," + encodeBase64(metadata.expected) + "\">\n");
+
+ html.append("<img width=" + mbgl::util::toString(metadata.size.width));
+ html.append(" height=" + mbgl::util::toString(metadata.size.height));
+ html.append(" src=\"data:image/png;base64," + encodeBase64(metadata.diff) + "\">\n");
+ } else {
+ html.append("<img width=" + mbgl::util::toString(metadata.size.width));
+ html.append(" height=" + mbgl::util::toString(metadata.size.height));
+ html.append(" src=\"data:image/png;base64," + encodeBase64(metadata.actual) + "\">\n");
+ }
+ }
} else {
- assert(!metadata.errorMessage.empty());
+ // FIXME: there are several places that errorMessage is not filled
+ // comment out assert(!metadata.errorMessage.empty());
html.append("<p style=\"color: red\"><strong>Error:</strong> " + metadata.errorMessage + "</p>\n");
}
if (metadata.difference != 0.0) {
- html.append("<p class=\"diff\"><strong>Diff:</strong> " + mbgl::util::toString(metadata.difference) + "</p>\n");
+ if (metadata.renderTest) {
+ html.append("<p class=\"diff\"><strong>Diff:</strong> " + mbgl::util::toString(metadata.difference) +
+ "</p>\n");
+ } else {
+ html.append("<p class=\"diff\"><strong>Diff:</strong> " + metadata.diff + "</p>\n");
+ }
}
html.append("</div>\n");
@@ -595,89 +694,3 @@ std::string createResultPage(const TestStatistics& stats, const std::vector<Test
return resultsPage;
}
-
-std::string localizeURL(const std::string& url) {
- static const std::regex regex { "local://" };
- if (auto vendorPath = getVendorPath(url, regex)) {
- return *vendorPath;
- } else {
- return getIntegrationPath(url, "", regex).value_or(url);
- }
-}
-
-void localizeSourceURLs(mbgl::JSValue& root, mbgl::JSDocument& document) {
- if (root.HasMember("urls") && root["urls"].IsArray()) {
- for (auto& urlValue : root["urls"].GetArray()) {
- const std::string path = prependFileScheme(localizeMapboxTilesetURL(urlValue.GetString())
- .value_or(localizeLocalURL(urlValue.GetString())
- .value_or(urlValue.GetString())));
- urlValue.Set<std::string>(path, document.GetAllocator());
- }
- }
-
- if (root.HasMember("url")) {
- static const std::string image("image");
- static const std::string video("video");
-
- mbgl::JSValue& urlValue = root["url"];
- const std::string path = prependFileScheme(localizeMapboxTilesetURL(urlValue.GetString())
- .value_or(localizeLocalURL(urlValue.GetString())
- .value_or(urlValue.GetString())));
- urlValue.Set<std::string>(path, document.GetAllocator());
-
- if (root["type"].GetString() != image && root["type"].GetString() != video) {
- const auto tilesetPath = std::string(urlValue.GetString()).erase(0u, 7u); // remove "file://"
- auto maybeTileset = readJson(tilesetPath);
- if (maybeTileset.is<mbgl::JSDocument>()) {
- const auto& tileset = maybeTileset.get<mbgl::JSDocument>();
- assert(tileset.HasMember("tiles"));
- root.AddMember("tiles", (mbgl::JSValue&)tileset["tiles"], document.GetAllocator());
- root.RemoveMember("url");
- }
- }
- }
-
- if (root.HasMember("tiles")) {
- mbgl::JSValue& tilesValue = root["tiles"];
- assert(tilesValue.IsArray());
- for (auto& tileValue : tilesValue.GetArray()) {
- const std::string path = prependFileScheme(localizeMapboxTilesURL(tileValue.GetString())
- .value_or(localizeLocalURL(tileValue.GetString())
- .value_or(localizeHttpURL(tileValue.GetString())
- .value_or(tileValue.GetString()))));
- tileValue.Set<std::string>(path, document.GetAllocator());
- }
- }
-
- if (root.HasMember("data") && root["data"].IsString()) {
- mbgl::JSValue& dataValue = root["data"];
- const std::string path = prependFileScheme(localizeLocalURL(dataValue.GetString())
- .value_or(dataValue.GetString()));
- dataValue.Set<std::string>(path, document.GetAllocator());
- }
-}
-
-void localizeStyleURLs(mbgl::JSValue& root, mbgl::JSDocument& document) {
- if (root.HasMember("sources")) {
- mbgl::JSValue& sourcesValue = root["sources"];
- for (auto& sourceProperty : sourcesValue.GetObject()) {
- localizeSourceURLs(sourceProperty.value, document);
- }
- }
-
- if (root.HasMember("glyphs")) {
- mbgl::JSValue& glyphsValue = root["glyphs"];
- const std::string path = prependFileScheme(localizeMapboxFontsURL(glyphsValue.GetString())
- .value_or(localizeLocalURL(glyphsValue.GetString(), true)
- .value_or(glyphsValue.GetString())));
- glyphsValue.Set<std::string>(path, document.GetAllocator());
- }
-
- if (root.HasMember("sprite")) {
- mbgl::JSValue& spriteValue = root["sprite"];
- const std::string path = prependFileScheme(localizeMapboxSpriteURL(spriteValue.GetString())
- .value_or(localizeLocalURL(spriteValue.GetString())
- .value_or(spriteValue.GetString())));
- spriteValue.Set<std::string>(path, document.GetAllocator());
- }
-}
diff --git a/render-test/parser.hpp b/render-test/parser.hpp
index 94fb212944..3d79ac668a 100644
--- a/render-test/parser.hpp
+++ b/render-test/parser.hpp
@@ -5,31 +5,27 @@
#include <mbgl/util/rapidjson.hpp>
#include <mbgl/util/variant.hpp>
-#include <tuple>
#include <string>
+#include <tuple>
#include <vector>
+class Manifest;
+
using ErrorMessage = std::string;
using JSONReply = mbgl::variant<mbgl::JSDocument, ErrorMessage>;
-using ArgumentsTuple = std::tuple<bool, bool, uint32_t, std::string, std::vector<TestPaths>>;
-
JSONReply readJson(const mbgl::filesystem::path&);
std::string serializeJsonValue(const mbgl::JSValue&);
std::string serializeMetrics(const TestMetrics&);
-std::vector<std::string> readExpectedEntries(const mbgl::filesystem::path& base);
+std::vector<std::string> readExpectedImageEntries(const mbgl::filesystem::path& base);
+std::vector<std::string> readExpectedJSONEntries(const mbgl::filesystem::path& base);
TestMetrics readExpectedMetrics(const mbgl::filesystem::path& path);
-ArgumentsTuple parseArguments(int argc, char** argv);
-std::vector<std::pair<std::string, std::string>> parseIgnores();
-
-TestMetadata parseTestMetadata(const TestPaths& paths);
+TestMetadata parseTestMetadata(const TestPaths& paths, const Manifest& manifest);
std::string createResultPage(const TestStatistics&, const std::vector<TestMetadata>&, bool shuffle, uint32_t seed);
-std::string localizeURL(const std::string& url);
-
-void localizeSourceURLs(mbgl::JSValue& root, mbgl::JSDocument& document);
-void localizeStyleURLs(mbgl::JSValue& root, mbgl::JSDocument& document); \ No newline at end of file
+std::string toJSON(const mbgl::Value& value, unsigned indent, bool singleLine);
+std::string toJSON(const std::vector<mbgl::Feature>& features, unsigned indent, bool singleLine);
diff --git a/render-test/main.cpp b/render-test/render_test.cpp
index fcdbe3ab55..38d6c15f3f 100644
--- a/render-test/main.cpp
+++ b/render-test/render_test.cpp
@@ -1,14 +1,17 @@
#include "allocation_index.hpp"
-#include <mbgl/util/run_loop.hpp>
+#include <mbgl/render_test.hpp>
#include <mbgl/util/io.hpp>
+#include <mbgl/util/logging.hpp>
+#include <mbgl/util/run_loop.hpp>
+
+#include <args.hxx>
+#include "manifest_parser.hpp"
#include "metadata.hpp"
#include "parser.hpp"
#include "runner.hpp"
-#include <random>
-
#define ANSI_COLOR_RED "\x1b[31m"
#define ANSI_COLOR_GREEN "\x1b[32m"
#define ANSI_COLOR_YELLOW "\x1b[33m"
@@ -36,36 +39,97 @@ void operator delete(void* ptr, size_t) noexcept {
}
#endif
-int main(int argc, char** argv) {
- bool recycleMap;
- bool shuffle;
- uint32_t seed;
- std::string testRootPath;
- std::vector<TestPaths> testPaths;
+namespace {
+using ArgumentsTuple = std::tuple<bool, bool, uint32_t, std::string, std::vector<std::string>, std::string>;
+ArgumentsTuple parseArguments(int argc, char** argv) {
+ args::ArgumentParser argumentParser("Mapbox GL Test Runner");
+
+ args::HelpFlag helpFlag(argumentParser, "help", "Display this help menu", {'h', "help"});
+
+ args::Flag recycleMapFlag(argumentParser, "recycle map", "Toggle reusing the map object", {'r', "recycle-map"});
+ args::Flag shuffleFlag(argumentParser, "shuffle", "Toggle shuffling the tests order", {'s', "shuffle"});
+ args::ValueFlag<uint32_t> seedValue(argumentParser, "seed", "Shuffle seed (default: random)", {"seed"});
+ args::ValueFlag<std::string> testPathValue(
+ argumentParser, "manifestPath", "Test manifest file path", {'p', "manifestPath"});
+ args::ValueFlag<std::string> testFilterValue(argumentParser, "filter", "Test filter regex", {'f', "filter"});
+ args::PositionalList<std::string> testNameValues(argumentParser, "URL", "Test name(s)");
+
+ try {
+ argumentParser.ParseCLI(argc, argv);
+ } catch (const args::Help&) {
+ std::ostringstream stream;
+ stream << argumentParser;
+ mbgl::Log::Info(mbgl::Event::General, stream.str());
+ exit(0);
+ } catch (const args::ParseError& e) {
+ std::ostringstream stream;
+ stream << argumentParser;
+ mbgl::Log::Info(mbgl::Event::General, stream.str());
+ mbgl::Log::Error(mbgl::Event::General, e.what());
+ exit(1);
+ } catch (const args::ValidationError& e) {
+ std::ostringstream stream;
+ stream << argumentParser;
+ mbgl::Log::Info(mbgl::Event::General, stream.str());
+ mbgl::Log::Error(mbgl::Event::General, e.what());
+ exit(2);
+ } catch (const std::regex_error& e) {
+ mbgl::Log::Error(mbgl::Event::General, "Invalid filter regular expression: %s", e.what());
+ exit(3);
+ }
- std::tie(recycleMap, shuffle, seed, testRootPath, testPaths) = parseArguments(argc, argv);
- const std::string::size_type rootLength = testRootPath.length();
+ mbgl::filesystem::path manifestPath{testPathValue ? args::get(testPathValue) : std::string{TEST_RUNNER_ROOT_PATH}};
+ if (!mbgl::filesystem::exists(manifestPath) || !manifestPath.has_filename()) {
+ mbgl::Log::Error(mbgl::Event::General,
+ "Provided test manifest file path '%s' does not exist",
+ manifestPath.string().c_str());
+ exit(4);
+ }
- const auto ignores = parseIgnores();
+ auto testNames = testNameValues ? args::get(testNameValues) : std::vector<std::string>{};
+ auto testFilter = testFilterValue ? args::get(testFilterValue) : std::string{};
+ const auto shuffle = shuffleFlag ? args::get(shuffleFlag) : false;
+ const auto seed = seedValue ? args::get(seedValue) : 1u;
+ return ArgumentsTuple{recycleMapFlag ? args::get(recycleMapFlag) : false,
+ shuffle,
+ seed,
+ manifestPath.string(),
+ std::move(testNames),
+ std::move(testFilter)};
+}
+} // namespace
+namespace mbgl {
+int runRenderTests(int argc, char** argv) {
+ bool recycleMap;
+ bool shuffle;
+ uint32_t seed;
+ std::string manifestPath;
+ std::vector<std::string> testNames;
+ std::string testFilter;
+
+ std::tie(recycleMap, shuffle, seed, manifestPath, testNames, testFilter) = parseArguments(argc, argv);
+ auto manifestData = ManifestParser::parseManifest(manifestPath, testNames, testFilter);
+ if (!manifestData) {
+ exit(5);
+ }
+ mbgl::util::RunLoop runLoop;
+ TestRunner runner(std::move(*manifestData));
if (shuffle) {
printf(ANSI_COLOR_YELLOW "Shuffle seed: %d" ANSI_COLOR_RESET "\n", seed);
-
- std::seed_seq sequence { seed };
- std::mt19937 shuffler(sequence);
- std::shuffle(testPaths.begin(), testPaths.end(), shuffler);
+ runner.doShuffle(seed);
}
- mbgl::util::RunLoop runLoop;
- TestRunner runner;
-
+ const auto& manifest = runner.getManifest();
+ const auto& ignores = manifest.getIgnores();
+ const auto& testPaths = manifest.getTestPaths();
std::vector<TestMetadata> metadatas;
metadatas.reserve(testPaths.size());
TestStatistics stats;
for (auto& testPath : testPaths) {
- TestMetadata metadata = parseTestMetadata(testPath);
+ TestMetadata metadata = parseTestMetadata(testPath, manifest);
if (!recycleMap) {
runner.reset();
@@ -75,13 +139,14 @@ int main(int argc, char** argv) {
std::string& status = metadata.status;
std::string& color = metadata.color;
+ const std::string::size_type rootLength = manifest.getTestRootPath().length();
id = testPath.defaultExpectations();
id = id.substr(rootLength + 1, id.length() - rootLength - 2);
bool shouldIgnore = false;
std::string ignoreReason;
- const std::string ignoreName = "render-tests/" + id;
+ const std::string ignoreName = id;
const auto it = std::find_if(ignores.cbegin(), ignores.cend(), [&ignoreName](auto pair) { return pair.first == ignoreName; });
if (it != ignores.end()) {
shouldIgnore = true;
@@ -97,7 +162,8 @@ int main(int argc, char** argv) {
errored = !runner.run(metadata) || !metadata.errorMessage.empty();
}
- bool passed = !errored && !metadata.diff.empty() && metadata.difference <= metadata.allowed;
+ bool passed =
+ !errored && (!metadata.outputsImage || !metadata.diff.empty()) && metadata.difference <= metadata.allowed;
if (shouldIgnore) {
if (passed) {
@@ -122,6 +188,7 @@ int main(int argc, char** argv) {
color = "red";
stats.erroredTests++;
printf(ANSI_COLOR_RED "* errored %s" ANSI_COLOR_RESET "\n", id.c_str());
+ printf(ANSI_COLOR_RED "* error: %s" ANSI_COLOR_RESET "\n", metadata.errorMessage.c_str());
} else {
status = "failed";
color = "red";
@@ -132,13 +199,14 @@ int main(int argc, char** argv) {
metadatas.push_back(std::move(metadata));
}
-
+ const auto& testRootPath = manifest.getManifestPath();
+ const auto resultPath =
+ testRootPath + "/" + (testNames.empty() ? "render-tests" : testNames.front()) + "_index.html";
std::string resultsHTML = createResultPage(stats, metadatas, shuffle, seed);
- mbgl::util::write_file(testRootPath + "/index.html", resultsHTML);
+ mbgl::util::write_file(resultPath, resultsHTML);
- const uint32_t count = stats.erroredTests + stats.failedTests +
- stats.ignoreFailedTests + stats.ignorePassedTests +
- stats.passedTests;
+ const uint32_t count =
+ stats.erroredTests + stats.failedTests + stats.ignoreFailedTests + stats.ignorePassedTests + stats.passedTests;
if (stats.passedTests) {
printf(ANSI_COLOR_GREEN "%u passed (%.1lf%%)" ANSI_COLOR_RESET "\n", stats.passedTests, 100.0 * stats.passedTests / count);
@@ -156,7 +224,9 @@ int main(int argc, char** argv) {
printf(ANSI_COLOR_RED "%u errored (%.1lf%%)" ANSI_COLOR_RESET "\n", stats.erroredTests, 100.0 * stats.erroredTests / count);
}
- printf("Results at: %s%s\n", testRootPath.c_str(), "/index.html");
+ printf("Results at: %s\n", resultPath.c_str());
return stats.failedTests + stats.erroredTests == 0 ? 0 : 1;
}
+
+} // namespace mbgl
diff --git a/render-test/runner.cpp b/render-test/runner.cpp
index 5c8f53759f..8a4b0b3b0e 100644
--- a/render-test/runner.cpp
+++ b/render-test/runner.cpp
@@ -1,6 +1,7 @@
#include <mbgl/map/camera.hpp>
#include <mbgl/map/map_observer.hpp>
#include <mbgl/renderer/renderer.hpp>
+#include <mbgl/renderer/renderer_observer.hpp>
#include <mbgl/style/conversion/filter.hpp>
#include <mbgl/style/conversion/layer.hpp>
#include <mbgl/style/conversion/light.hpp>
@@ -19,34 +20,117 @@
#include <mapbox/pixelmatch.hpp>
+#include <../expression-test/test_runner_common.hpp>
#include "allocation_index.hpp"
+#include "file_source.hpp"
#include "metadata.hpp"
#include "parser.hpp"
#include "runner.hpp"
#include <algorithm>
#include <cassert>
-#include <regex>
#include <utility>
#include <sstream>
-// static
-const std::string& TestRunner::getBasePath() {
- const static std::string result =
- std::string(TEST_RUNNER_ROOT_PATH).append("/mapbox-gl-js/test/integration/render-tests");
- return result;
-}
+using namespace mbgl;
+
+GfxProbe::GfxProbe(const mbgl::gfx::RenderingStats& stats, const GfxProbe& prev)
+ : numBuffers(stats.numBuffers),
+ numDrawCalls(stats.numDrawCalls),
+ numFrameBuffers(stats.numFrameBuffers),
+ numTextures(stats.numActiveTextures),
+ memIndexBuffers(stats.memIndexBuffers, std::max(stats.memIndexBuffers, prev.memIndexBuffers.peak)),
+ memVertexBuffers(stats.memVertexBuffers, std::max(stats.memVertexBuffers, prev.memVertexBuffers.peak)),
+ memTextures(stats.memTextures, std::max(stats.memTextures, prev.memTextures.peak)) {}
+
+struct RunContext {
+ RunContext() = default;
+
+ GfxProbe activeGfxProbe;
+ GfxProbe baselineGfxProbe;
+ bool gfxProbeActive;
+};
+
+class TestRunnerMapObserver : public MapObserver {
+public:
+ TestRunnerMapObserver() : mapLoadFailure(false), finishRenderingMap(false), idle(false) {}
+
+ void onDidFailLoadingMap(MapLoadError, const std::string&) override { mapLoadFailure = true; }
+
+ void onDidFinishRenderingMap(RenderMode mode) override final {
+ if (!finishRenderingMap) finishRenderingMap = mode == RenderMode::Full;
+ }
+
+ void onDidBecomeIdle() override final { idle = true; }
+
+ void reset() {
+ mapLoadFailure = false;
+ finishRenderingMap = false;
+ idle = false;
+ }
+
+ bool mapLoadFailure;
+ bool finishRenderingMap;
+ bool idle;
+};
// static
-const std::vector<std::string>& TestRunner::getPlatformExpectationsPaths() {
- // TODO: Populate from command line.
- const static std::vector<std::string> result {
- std::string(TEST_RUNNER_ROOT_PATH).append("/render-test/expected")
- };
- return result;
+gfx::HeadlessBackend::SwapBehaviour swapBehavior(MapMode mode) {
+ return mode == MapMode::Continuous ? gfx::HeadlessBackend::SwapBehaviour::Flush
+ : gfx::HeadlessBackend::SwapBehaviour::NoFlush;
+}
+
+std::string simpleDiff(const Value& result, const Value& expected) {
+ std::vector<std::string> resultTokens{tokenize(toJSON(result, 2, false))};
+ std::vector<std::string> expectedTokens{tokenize(toJSON(expected, 2, false))};
+ std::size_t maxLength = std::max(resultTokens.size(), expectedTokens.size());
+ std::ostringstream diff;
+
+ diff << "<pre>" << std::endl;
+ const auto flush =
+ [](const std::vector<std::string>& vec, std::size_t pos, std::ostringstream& out, std::string separator) {
+ for (std::size_t j = pos; j < vec.size(); ++j) {
+ out << separator << vec[j] << std::endl;
+ }
+ };
+
+ for (std::size_t i = 0; i < maxLength; ++i) {
+ if (resultTokens.size() <= i) {
+ flush(expectedTokens, i, diff, "-");
+ break;
+ }
+
+ if (expectedTokens.size() <= i) {
+ flush(resultTokens, i, diff, "+");
+ break;
+ }
+
+ if (!deepEqual(resultTokens[i], expectedTokens[i])) {
+ diff << "<b>"
+ << "-" << expectedTokens[i] << "</b>" << std::endl;
+ diff << "<b>"
+ << "+" << resultTokens[i] << "</b>" << std::endl;
+ } else {
+ diff << resultTokens[i] << std::endl;
+ }
+ }
+ diff << "</pre>" << std::endl;
+ return diff.str();
+}
+
+TestRunner::TestRunner(Manifest manifest_) : manifest(std::move(manifest_)) {}
+
+const Manifest& TestRunner::getManifest() const {
+ return manifest;
+}
+
+void TestRunner::doShuffle(uint32_t seed) {
+ manifest.doShuffle(seed);
}
-bool TestRunner::checkResults(mbgl::PremultipliedImage&& actualImage, TestMetadata& metadata) {
+bool TestRunner::checkQueryTestResults(mbgl::PremultipliedImage&& actualImage,
+ std::vector<mbgl::Feature>&& features,
+ TestMetadata& metadata) {
const std::string& base = metadata.paths.defaultExpectations();
const std::vector<mbgl::filesystem::path>& expectations = metadata.paths.expectations;
@@ -57,74 +141,193 @@ bool TestRunner::checkResults(mbgl::PremultipliedImage&& actualImage, TestMetada
return false;
}
+ metadata.actualJson = toJSON(features, 2, false);
+
+ if (metadata.actualJson.empty()) {
+ metadata.errorMessage = "Invalid size for actual JSON";
+ return false;
+ }
+
#if !TEST_READ_ONLY
if (getenv("UPDATE_PLATFORM")) {
mbgl::filesystem::create_directories(expectations.back());
- mbgl::util::write_file(expectations.back().string() + "/expected.png", mbgl::encodePNG(actualImage));
+ mbgl::util::write_file(expectations.back().string() + "/expected.json", metadata.actualJson);
return true;
} else if (getenv("UPDATE_DEFAULT")) {
- mbgl::util::write_file(base + "/expected.png", mbgl::encodePNG(actualImage));
+ mbgl::util::write_file(base + "/expected.json", metadata.actualJson);
return true;
- } else if (getenv("UPDATE_METRICS")) {
- if (!metadata.metrics.isEmpty()) {
- mbgl::filesystem::create_directories(expectations.back());
- mbgl::util::write_file(expectations.back().string() + "/metrics.json", serializeMetrics(metadata.metrics));
- return true;
- }
}
- mbgl::util::write_file(base + "/actual.png", metadata.actual);
+ mbgl::util::write_file(base + "/actual.json", metadata.actualJson);
#endif
- mbgl::PremultipliedImage expectedImage { actualImage.size };
- mbgl::PremultipliedImage imageDiff { actualImage.size };
-
- double pixels = 0.0;
- std::vector<std::string> expectedImagesPaths;
+ std::vector<std::string> expectedJsonPaths;
mbgl::filesystem::path expectedMetricsPath;
- for (auto rit = expectations.rbegin(); rit!= expectations.rend(); ++rit) {
+ for (auto rit = expectations.rbegin(); rit != expectations.rend(); ++rit) {
if (mbgl::filesystem::exists(*rit)) {
- if (metadata.expectedMetrics.isEmpty()) {
- mbgl::filesystem::path maybeExpectedMetricsPath{ *rit };
- maybeExpectedMetricsPath.replace_filename("metrics.json");
- metadata.expectedMetrics = readExpectedMetrics(maybeExpectedMetricsPath);
- }
- expectedImagesPaths = readExpectedEntries(*rit);
- if (!expectedImagesPaths.empty()) break;
+ expectedJsonPaths = readExpectedJSONEntries(*rit);
+ if (!expectedJsonPaths.empty()) break;
}
}
- if (expectedImagesPaths.empty()) {
+ if (expectedJsonPaths.empty()) {
metadata.errorMessage = "Failed to find expectations for: " + metadata.paths.stylePath.string();
return false;
}
-
- for (const auto& entry: expectedImagesPaths) {
- mbgl::optional<std::string> maybeExpectedImage = mbgl::util::readFile(entry);
- if (!maybeExpectedImage) {
- metadata.errorMessage = "Failed to load expected image " + entry;
+
+ for (const auto& entry : expectedJsonPaths) {
+ auto maybeExpectedJson = readJson(entry);
+ if (maybeExpectedJson.is<mbgl::JSDocument>()) {
+ auto& expected = maybeExpectedJson.get<mbgl::JSDocument>();
+
+ mbgl::JSDocument actual;
+ actual.Parse<0>(metadata.actualJson);
+ if (actual.HasParseError()) {
+ metadata.errorMessage = "Error parsing actual JSON for: " + metadata.paths.stylePath.string();
+ return false;
+ }
+
+ auto actualVal = mapbox::geojson::convert<mapbox::geojson::value>(actual);
+ auto expectedVal = mapbox::geojson::convert<mapbox::geojson::value>(expected);
+ bool equal = deepEqual(actualVal, expectedVal);
+
+ metadata.difference = !equal;
+ if (equal) {
+ metadata.diff = "Match";
+ } else {
+ metadata.diff = simpleDiff(actualVal, expectedVal);
+ }
+ } else {
+ metadata.errorMessage = "Failed to load expected JSON " + entry;
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool TestRunner::checkRenderTestResults(mbgl::PremultipliedImage&& actualImage, TestMetadata& metadata) {
+ const std::string& base = metadata.paths.defaultExpectations();
+ const std::vector<mbgl::filesystem::path>& expectations = metadata.paths.expectations;
+
+ if (metadata.outputsImage) {
+ metadata.actual = mbgl::encodePNG(actualImage);
+
+ if (actualImage.size.isEmpty()) {
+ metadata.errorMessage = "Invalid size for actual image";
+ return false;
+ }
+
+#if !TEST_READ_ONLY
+ if (getenv("UPDATE_PLATFORM")) {
+ mbgl::filesystem::create_directories(expectations.back());
+ mbgl::util::write_file(expectations.back().string() + "/expected.png", mbgl::encodePNG(actualImage));
+ return true;
+ } else if (getenv("UPDATE_DEFAULT")) {
+ mbgl::util::write_file(base + "/expected.png", mbgl::encodePNG(actualImage));
+ return true;
+ }
+
+ mbgl::util::write_file(base + "/actual.png", metadata.actual);
+#endif
+
+ mbgl::PremultipliedImage expectedImage{actualImage.size};
+ mbgl::PremultipliedImage imageDiff{actualImage.size};
+
+ double pixels = 0.0;
+ std::vector<std::string> expectedImagesPaths;
+ for (auto rit = expectations.rbegin(); rit != expectations.rend(); ++rit) {
+ if (mbgl::filesystem::exists(*rit)) {
+ expectedImagesPaths = readExpectedImageEntries(*rit);
+ if (!expectedImagesPaths.empty()) break;
+ }
+ }
+
+ if (expectedImagesPaths.empty()) {
+ metadata.errorMessage = "Failed to find expectations for: " + metadata.paths.stylePath.string();
return false;
}
- metadata.expected = *maybeExpectedImage;
+ for (const auto& entry : expectedImagesPaths) {
+ mbgl::optional<std::string> maybeExpectedImage = mbgl::util::readFile(entry);
+ if (!maybeExpectedImage) {
+ metadata.errorMessage = "Failed to load expected image " + entry;
+ return false;
+ }
- expectedImage = mbgl::decodeImage(*maybeExpectedImage);
+ metadata.expected = *maybeExpectedImage;
- pixels = // implicitly converting from uint64_t
- mapbox::pixelmatch(actualImage.data.get(), expectedImage.data.get(), expectedImage.size.width,
- expectedImage.size.height, imageDiff.data.get(), 0.1285); // Defined in GL JS
+ expectedImage = mbgl::decodeImage(*maybeExpectedImage);
- metadata.diff = mbgl::encodePNG(imageDiff);
+ pixels = // implicitly converting from uint64_t
+ mapbox::pixelmatch(actualImage.data.get(),
+ expectedImage.data.get(),
+ expectedImage.size.width,
+ expectedImage.size.height,
+ imageDiff.data.get(),
+ 0.1285); // Defined in GL JS
+
+ metadata.diff = mbgl::encodePNG(imageDiff);
#if !TEST_READ_ONLY
- mbgl::util::write_file(base + "/diff.png", metadata.diff);
+ mbgl::util::write_file(base + "/diff.png", metadata.diff);
#endif
- metadata.difference = pixels / expectedImage.size.area();
- if (metadata.difference <= metadata.allowed) {
- break;
+ metadata.difference = pixels / expectedImage.size.area();
+ if (metadata.difference <= metadata.allowed) {
+ break;
+ }
+ }
+ }
+
+#if !TEST_READ_ONLY
+ if (getenv("UPDATE_METRICS")) {
+ if (!metadata.metrics.isEmpty()) {
+ mbgl::filesystem::create_directories(expectations.back());
+ mbgl::util::write_file(expectations.back().string() + "/metrics.json", serializeMetrics(metadata.metrics));
+ return true;
+ }
+ }
+#endif
+
+ mbgl::filesystem::path expectedMetricsPath;
+ for (auto rit = expectations.rbegin(); rit != expectations.rend(); ++rit) {
+ if (mbgl::filesystem::exists(*rit)) {
+ if (metadata.expectedMetrics.isEmpty()) {
+ mbgl::filesystem::path maybeExpectedMetricsPath{*rit};
+ maybeExpectedMetricsPath.replace_filename("metrics.json");
+ metadata.expectedMetrics = readExpectedMetrics(maybeExpectedMetricsPath);
+ }
+ }
+ }
+
+ // Check file size metrics.
+ for (const auto& expected : metadata.expectedMetrics.fileSize) {
+ auto actual = metadata.metrics.fileSize.find(expected.first);
+ if (actual == metadata.metrics.fileSize.end()) {
+ metadata.errorMessage = "Failed to find fileSize probe: " + expected.first;
+ return false;
+ }
+ if (actual->second.path != expected.second.path) {
+ std::stringstream ss;
+ ss << "Comparing different files at probe \"" << expected.first << "\": " << actual->second.path
+ << ", expected is " << expected.second.path << ".";
+ metadata.errorMessage = ss.str();
+
+ return false;
+ }
+
+ auto result = checkValue(expected.second.size, actual->second.size, actual->second.tolerance);
+ if (!std::get<bool>(result)) {
+ std::stringstream ss;
+ ss << "File size does not match at probe \"" << expected.first << "\": " << actual->second.size
+ << ", expected is " << expected.second.size << ".";
+
+ metadata.errorMessage = ss.str();
+ return false;
}
}
+#if !defined(SANITIZE)
// Check memory metrics.
for (const auto& expected : metadata.expectedMetrics.memory) {
auto actual = metadata.metrics.memory.find(expected.first);
@@ -132,35 +335,168 @@ bool TestRunner::checkResults(mbgl::PremultipliedImage&& actualImage, TestMetada
metadata.errorMessage = "Failed to find memory probe: " + expected.first;
return false;
}
- if (actual->second.peak > expected.second.peak) {
- std::stringstream ss;
- ss << "Allocated memory peak size at probe \"" << expected.first << "\" is "
- << actual->second.peak << " bytes, expected is " << expected.second.peak << " bytes.";
+ bool passed{false};
+ float delta{0.0f};
+ std::stringstream errorStream;
+ std::tie(passed, delta) = MemoryProbe::checkPeak(expected.second, actual->second);
+ if (!passed) {
+ errorStream << "Allocated memory peak size at probe \"" << expected.first << "\" is " << actual->second.peak
+ << " bytes, expected is " << expected.second.peak << "±" << delta << " bytes.";
+ }
- metadata.errorMessage = ss.str();
+ std::tie(passed, delta) = MemoryProbe::checkAllocations(expected.second, actual->second);
+ if (!passed) {
+ errorStream << "Number of allocations at probe \"" << expected.first << "\" is "
+ << actual->second.allocations << ", expected is " << expected.second.allocations << "±"
+ << std::round(delta) << " allocations.";
+ }
+
+ metadata.errorMessage = errorStream.str();
+ if (!metadata.errorMessage.empty()) return false;
+ }
+
+ // Check network metrics.
+ for (const auto& expected : metadata.expectedMetrics.network) {
+ auto actual = metadata.metrics.network.find(expected.first);
+ if (actual == metadata.metrics.network.end()) {
+ metadata.errorMessage = "Failed to find network probe: " + expected.first;
return false;
}
+ bool failed = false;
+ if (actual->second.requests != expected.second.requests) {
+ std::stringstream ss;
+ ss << "Number of requests at probe \"" << expected.first << "\" is " << actual->second.requests
+ << ", expected is " << expected.second.requests << ". ";
- if (actual->second.allocations > expected.second.allocations) {
+ metadata.errorMessage = ss.str();
+ failed = true;
+ }
+ if (actual->second.transferred != expected.second.transferred) {
std::stringstream ss;
- ss << "Number of allocations at probe \"" << expected.first << "\" is "
- << actual->second.allocations << ", expected is " << expected.second.allocations << ".";
+ ss << "Transferred data at probe \"" << expected.first << "\" is " << actual->second.transferred
+ << " bytes, expected is " << expected.second.transferred << " bytes.";
+ metadata.errorMessage += ss.str();
+ failed = true;
+ }
+ if (failed) {
+ return false;
+ }
+ }
+#endif // !defined(SANITIZE)
+ // Check fps metrics
+ for (const auto& expected : metadata.expectedMetrics.fps) {
+ auto actual = metadata.metrics.fps.find(expected.first);
+ if (actual == metadata.metrics.fps.end()) {
+ metadata.errorMessage = "Failed to find fps probe: " + expected.first;
+ return false;
+ }
+ auto result = checkValue(expected.second.average, actual->second.average, expected.second.tolerance);
+ if (!std::get<bool>(result)) {
+ std::stringstream ss;
+ ss << "Average fps at probe \"" << expected.first << "\" is " << actual->second.average
+ << ", expected to be " << expected.second.average << " with tolerance of " << expected.second.tolerance;
+ metadata.errorMessage = ss.str();
+ return false;
+ }
+ result = checkValue(expected.second.minOnePc, actual->second.minOnePc, expected.second.tolerance);
+ if (!std::get<bool>(result)) {
+ std::stringstream ss;
+ ss << "Minimum(1%) fps at probe \"" << expected.first << "\" is " << actual->second.minOnePc
+ << ", expected to be " << expected.second.minOnePc << " with tolerance of " << expected.second.tolerance;
metadata.errorMessage = ss.str();
return false;
}
}
+ // Check gfx metrics
+ for (const auto& expected : metadata.expectedMetrics.gfx) {
+ auto actual = metadata.metrics.gfx.find(expected.first);
+ if (actual == metadata.metrics.gfx.end()) {
+ metadata.errorMessage = "Failed to find gfx probe: " + expected.first;
+ return false;
+ }
+
+ const auto& probeName = expected.first;
+ const auto& expectedValue = expected.second;
+ const auto& actualValue = actual->second;
+ bool failed = false;
+
+ if (expectedValue.numDrawCalls != actualValue.numDrawCalls) {
+ std::stringstream ss;
+ if (!metadata.errorMessage.empty()) ss << std::endl;
+ ss << "Number of draw calls at probe\"" << probeName << "\" is " << actualValue.numDrawCalls
+ << ", expected is " << expectedValue.numDrawCalls;
+ metadata.errorMessage += ss.str();
+ failed = true;
+ }
+
+ if (expectedValue.numTextures != actualValue.numTextures) {
+ std::stringstream ss;
+ if (!metadata.errorMessage.empty()) ss << std::endl;
+ ss << "Number of textures at probe \"" << probeName << "\" is " << actualValue.numTextures
+ << ", expected is " << expectedValue.numTextures;
+ metadata.errorMessage += ss.str();
+ failed = true;
+ }
+
+ if (expectedValue.numBuffers != actualValue.numBuffers) {
+ std::stringstream ss;
+ if (!metadata.errorMessage.empty()) ss << std::endl;
+ ss << "Number of vertex and index buffers at probe \"" << probeName << "\" is " << actualValue.numBuffers
+ << ", expected is " << expectedValue.numBuffers;
+ metadata.errorMessage += ss.str();
+ failed = true;
+ }
+
+ if (expectedValue.numFrameBuffers != actualValue.numFrameBuffers) {
+ std::stringstream ss;
+ if (!metadata.errorMessage.empty()) ss << std::endl;
+ ss << "Number of frame buffers at probe \"" << probeName << "\" is " << actualValue.numFrameBuffers
+ << ", expected is " << expectedValue.numFrameBuffers;
+ metadata.errorMessage += ss.str();
+ failed = true;
+ }
+
+ if (expectedValue.memTextures.peak != actualValue.memTextures.peak) {
+ std::stringstream ss;
+ if (!metadata.errorMessage.empty()) ss << std::endl;
+ ss << "Allocated texture memory peak size at probe \"" << probeName << "\" is "
+ << actualValue.memTextures.peak << " bytes, expected is " << expectedValue.memTextures.peak << " bytes";
+ metadata.errorMessage += ss.str();
+ failed = true;
+ }
+
+ if (expectedValue.memIndexBuffers.peak != actualValue.memIndexBuffers.peak) {
+ std::stringstream ss;
+ if (!metadata.errorMessage.empty()) ss << std::endl;
+ ss << "Allocated index buffer memory peak size at probe \"" << probeName << "\" is "
+ << actualValue.memIndexBuffers.peak << " bytes, expected is " << expectedValue.memIndexBuffers.peak
+ << " bytes";
+ metadata.errorMessage += ss.str();
+ failed = true;
+ }
+
+ if (expectedValue.memVertexBuffers.peak != actualValue.memVertexBuffers.peak) {
+ std::stringstream ss;
+ if (!metadata.errorMessage.empty()) ss << std::endl;
+ ss << "Allocated vertex buffer memory peak size at probe \"" << probeName << "\" is "
+ << actualValue.memVertexBuffers.peak << " bytes, expected is " << expectedValue.memVertexBuffers.peak
+ << " bytes";
+ metadata.errorMessage += ss.str();
+ failed = true;
+ }
+
+ if (failed) return false;
+ }
return true;
}
-bool TestRunner::runOperations(const std::string& key, TestMetadata& metadata) {
- if (!metadata.document.HasMember("metadata") ||
- !metadata.document["metadata"].HasMember("test") ||
+bool TestRunner::runOperations(const std::string& key, TestMetadata& metadata, RunContext& ctx) {
+ if (!metadata.document.HasMember("metadata") || !metadata.document["metadata"].HasMember("test") ||
!metadata.document["metadata"]["test"].HasMember("operations")) {
return true;
}
-
assert(metadata.document["metadata"]["test"]["operations"].IsArray());
const auto& operationsArray = metadata.document["metadata"]["test"]["operations"].GetArray();
@@ -176,6 +512,7 @@ bool TestRunner::runOperations(const std::string& key, TestMetadata& metadata) {
auto& frontend = maps[key]->frontend;
auto& map = maps[key]->map;
+ auto& observer = maps[key]->observer;
static const std::string waitOp("wait");
static const std::string sleepOp("sleep");
@@ -186,6 +523,7 @@ bool TestRunner::runOperations(const std::string& key, TestMetadata& metadata) {
static const std::string setCenterOp("setCenter");
static const std::string setZoomOp("setZoom");
static const std::string setBearingOp("setBearing");
+ static const std::string setPitchOp("setPitch");
static const std::string setFilterOp("setFilter");
static const std::string setLayerZoomRangeOp("setLayerZoomRange");
static const std::string setLightOp("setLight");
@@ -195,22 +533,30 @@ bool TestRunner::runOperations(const std::string& key, TestMetadata& metadata) {
static const std::string removeSourceOp("removeSource");
static const std::string setPaintPropertyOp("setPaintProperty");
static const std::string setLayoutPropertyOp("setLayoutProperty");
+ static const std::string fileSizeProbeOp("probeFileSize");
static const std::string memoryProbeOp("probeMemory");
static const std::string memoryProbeStartOp("probeMemoryStart");
static const std::string memoryProbeEndOp("probeMemoryEnd");
+ static const std::string networkProbeOp("probeNetwork");
+ static const std::string networkProbeStartOp("probeNetworkStart");
+ static const std::string networkProbeEndOp("probeNetworkEnd");
static const std::string setFeatureStateOp("setFeatureState");
static const std::string getFeatureStateOp("getFeatureState");
static const std::string removeFeatureStateOp("removeFeatureState");
+ static const std::string panGestureOp("panGesture");
+ static const std::string gfxProbeOp("probeGFX");
+ static const std::string gfxProbeStartOp("probeGFXStart");
+ static const std::string gfxProbeEndOp("probeGFXEnd");
- // wait
if (operationArray[0].GetString() == waitOp) {
+ // wait
try {
frontend.render(map);
} catch (const std::exception&) {
return false;
}
- // sleep
} else if (operationArray[0].GetString() == sleepOp) {
+ // sleep
mbgl::util::Timer sleepTimer;
bool sleeping = true;
@@ -226,9 +572,8 @@ bool TestRunner::runOperations(const std::string& key, TestMetadata& metadata) {
while (sleeping) {
mbgl::util::RunLoop::Get()->runOnce();
}
-
- // addImage | updateImage
} else if (operationArray[0].GetString() == addImageOp || operationArray[0].GetString() == updateImageOp) {
+ // addImage | updateImage
assert(operationArray.Size() >= 3u);
float pixelRatio = 1.0f;
@@ -251,7 +596,7 @@ bool TestRunner::runOperations(const std::string& key, TestMetadata& metadata) {
std::string imagePath = operationArray[2].GetString();
imagePath.erase(std::remove(imagePath.begin(), imagePath.end(), '"'), imagePath.end());
- const mbgl::filesystem::path filePath(std::string(TEST_RUNNER_ROOT_PATH) + "/mapbox-gl-js/test/integration/" + imagePath);
+ const mbgl::filesystem::path filePath = mbgl::filesystem::path(manifest.getTestRootPath()) / imagePath;
mbgl::optional<std::string> maybeImage = mbgl::util::readFile(filePath.string());
if (!maybeImage) {
@@ -260,33 +605,30 @@ bool TestRunner::runOperations(const std::string& key, TestMetadata& metadata) {
}
map.getStyle().addImage(std::make_unique<mbgl::style::Image>(imageName, mbgl::decodeImage(*maybeImage), pixelRatio, sdf));
-
- // removeImage
} else if (operationArray[0].GetString() == removeImageOp) {
+ // removeImage
assert(operationArray.Size() >= 2u);
assert(operationArray[1].IsString());
const std::string imageName { operationArray[1].GetString(), operationArray[1].GetStringLength() };
map.getStyle().removeImage(imageName);
-
- // setStyle
} else if (operationArray[0].GetString() == setStyleOp) {
+ // setStyle
assert(operationArray.Size() >= 2u);
if (operationArray[1].IsString()) {
- std::string stylePath = localizeURL(operationArray[1].GetString());
+ std::string stylePath = manifest.localizeURL(operationArray[1].GetString());
auto maybeStyle = readJson(stylePath);
if (maybeStyle.is<mbgl::JSDocument>()) {
auto& style = maybeStyle.get<mbgl::JSDocument>();
- localizeStyleURLs((mbgl::JSValue&)style, style);
+ manifest.localizeStyleURLs((mbgl::JSValue&)style, style);
map.getStyle().loadJSON(serializeJsonValue(style));
}
} else {
- localizeStyleURLs(operationArray[1], metadata.document);
+ manifest.localizeStyleURLs(operationArray[1], metadata.document);
map.getStyle().loadJSON(serializeJsonValue(operationArray[1]));
}
-
- // setCenter
} else if (operationArray[0].GetString() == setCenterOp) {
+ // setCenter
assert(operationArray.Size() >= 2u);
assert(operationArray[1].IsArray());
@@ -294,21 +636,23 @@ bool TestRunner::runOperations(const std::string& key, TestMetadata& metadata) {
assert(centerArray.Size() == 2u);
map.jumpTo(mbgl::CameraOptions().withCenter(mbgl::LatLng(centerArray[1].GetDouble(), centerArray[0].GetDouble())));
-
- // setZoom
} else if (operationArray[0].GetString() == setZoomOp) {
+ // setZoom
assert(operationArray.Size() >= 2u);
assert(operationArray[1].IsNumber());
map.jumpTo(mbgl::CameraOptions().withZoom(operationArray[1].GetDouble()));
-
- // setBearing
} else if (operationArray[0].GetString() == setBearingOp) {
+ // setBearing
assert(operationArray.Size() >= 2u);
assert(operationArray[1].IsNumber());
map.jumpTo(mbgl::CameraOptions().withBearing(operationArray[1].GetDouble()));
-
- // setFilter
+ } else if (operationArray[0].GetString() == setPitchOp) {
+ // setPitch
+ assert(operationArray.Size() >= 2u);
+ assert(operationArray[1].IsNumber());
+ map.jumpTo(mbgl::CameraOptions().withPitch(operationArray[1].GetDouble()));
} else if (operationArray[0].GetString() == setFilterOp) {
+ // setFilter
assert(operationArray.Size() >= 3u);
assert(operationArray[1].IsString());
@@ -328,9 +672,8 @@ bool TestRunner::runOperations(const std::string& key, TestMetadata& metadata) {
layer->setFilter(std::move(*converted));
}
}
-
- // setLayerZoomRange
} else if (operationArray[0].GetString() == setLayerZoomRangeOp) {
+ // setLayerZoomRange
assert(operationArray.Size() >= 4u);
assert(operationArray[1].IsString());
assert(operationArray[2].IsNumber());
@@ -345,9 +688,8 @@ bool TestRunner::runOperations(const std::string& key, TestMetadata& metadata) {
layer->setMinZoom(operationArray[2].GetFloat());
layer->setMaxZoom(operationArray[3].GetFloat());
}
-
- // setLight
} else if (operationArray[0].GetString() == setLightOp) {
+ // setLight
assert(operationArray.Size() >= 2u);
assert(operationArray[1].IsObject());
@@ -359,9 +701,8 @@ bool TestRunner::runOperations(const std::string& key, TestMetadata& metadata) {
} else {
map.getStyle().setLight(std::make_unique<mbgl::style::Light>(std::move(*converted)));
}
-
- // addLayer
} else if (operationArray[0].GetString() == addLayerOp) {
+ // addLayer
assert(operationArray.Size() >= 2u);
assert(operationArray[1].IsObject());
@@ -373,20 +714,18 @@ bool TestRunner::runOperations(const std::string& key, TestMetadata& metadata) {
} else {
map.getStyle().addLayer(std::move(*converted));
}
-
- // removeLayer
} else if (operationArray[0].GetString() == removeLayerOp) {
+ // removeLayer
assert(operationArray.Size() >= 2u);
assert(operationArray[1].IsString());
map.getStyle().removeLayer(operationArray[1].GetString());
-
- // addSource
} else if (operationArray[0].GetString() == addSourceOp) {
+ // addSource
assert(operationArray.Size() >= 3u);
assert(operationArray[1].IsString());
assert(operationArray[2].IsObject());
- localizeSourceURLs(operationArray[2], metadata.document);
+ manifest.localizeSourceURLs(operationArray[2], metadata.document);
mbgl::style::conversion::Error error;
auto converted = mbgl::style::conversion::convert<std::unique_ptr<mbgl::style::Source>>(operationArray[2], error, operationArray[1].GetString());
@@ -396,15 +735,13 @@ bool TestRunner::runOperations(const std::string& key, TestMetadata& metadata) {
} else {
map.getStyle().addSource(std::move(*converted));
}
-
- // removeSource
} else if (operationArray[0].GetString() == removeSourceOp) {
+ // removeSource
assert(operationArray.Size() >= 2u);
assert(operationArray[1].IsString());
map.getStyle().removeSource(operationArray[1].GetString());
-
- // setPaintProperty
} else if (operationArray[0].GetString() == setPaintPropertyOp) {
+ // setPaintProperty
assert(operationArray.Size() >= 4u);
assert(operationArray[1].IsString());
assert(operationArray[2].IsString());
@@ -420,9 +757,8 @@ bool TestRunner::runOperations(const std::string& key, TestMetadata& metadata) {
const mbgl::JSValue* propertyValue = &operationArray[3];
layer->setPaintProperty(propertyName, propertyValue);
}
-
- // setLayoutProperty
} else if (operationArray[0].GetString() == setLayoutPropertyOp) {
+ // setLayoutProperty
assert(operationArray.Size() >= 4u);
assert(operationArray[1].IsString());
assert(operationArray[2].IsString());
@@ -438,28 +774,79 @@ bool TestRunner::runOperations(const std::string& key, TestMetadata& metadata) {
const mbgl::JSValue* propertyValue = &operationArray[3];
layer->setLayoutProperty(propertyName, propertyValue);
}
- // probeMemoryStart
+ } else if (operationArray[0].GetString() == fileSizeProbeOp) {
+ // probeFileSize
+ assert(operationArray.Size() >= 4u);
+ assert(operationArray[1].IsString());
+ assert(operationArray[2].IsString());
+ assert(operationArray[3].IsNumber());
+
+ std::string mark = std::string(operationArray[1].GetString(), operationArray[1].GetStringLength());
+ std::string path = std::string(operationArray[2].GetString(), operationArray[2].GetStringLength());
+ assert(!path.empty());
+
+ float tolerance = operationArray[3].GetDouble();
+ mbgl::filesystem::path filePath(path);
+
+ if (!filePath.is_absolute()) {
+ filePath = metadata.paths.defaultExpectations() / filePath;
+ }
+
+ if (mbgl::filesystem::exists(filePath)) {
+ auto size = mbgl::filesystem::file_size(filePath);
+ metadata.metrics.fileSize.emplace(std::piecewise_construct,
+ std::forward_as_tuple(std::move(mark)),
+ std::forward_as_tuple(std::move(path), size, tolerance));
+ } else {
+ metadata.errorMessage = std::string("File not found: ") + path;
+ return false;
+ }
} else if (operationArray[0].GetString() == memoryProbeStartOp) {
+ // probeMemoryStart
assert(!AllocationIndex::isActive());
AllocationIndex::setActive(true);
- // probeMemory
} else if (operationArray[0].GetString() == memoryProbeOp) {
+ // probeMemory
assert(AllocationIndex::isActive());
assert(operationArray.Size() >= 2u);
assert(operationArray[1].IsString());
std::string mark = std::string(operationArray[1].GetString(), operationArray[1].GetStringLength());
- metadata.metrics.memory.emplace(std::piecewise_construct,
- std::forward_as_tuple(std::move(mark)),
- std::forward_as_tuple(AllocationIndex::getAllocatedSizePeak(), AllocationIndex::getAllocationsCount()));
- // probeMemoryEnd
+ auto emplaced = metadata.metrics.memory.emplace(
+ std::piecewise_construct,
+ std::forward_as_tuple(std::move(mark)),
+ std::forward_as_tuple(AllocationIndex::getAllocatedSizePeak(), AllocationIndex::getAllocationsCount()));
+ assert(emplaced.second);
+ if (operationArray.Size() >= 3u) {
+ assert(operationArray[2].IsNumber());
+ emplaced.first->second.tolerance = float(operationArray[2].GetDouble());
+ }
} else if (operationArray[0].GetString() == memoryProbeEndOp) {
+ // probeMemoryEnd
assert(AllocationIndex::isActive());
AllocationIndex::setActive(false);
AllocationIndex::reset();
+ } else if (operationArray[0].GetString() == networkProbeStartOp) {
+ // probeNetworkStart
+ assert(!ProxyFileSource::isTrackingActive());
+ ProxyFileSource::setTrackingActive(true);
+ } else if (operationArray[0].GetString() == networkProbeOp) {
+ // probeNetwork
+ assert(ProxyFileSource::isTrackingActive());
+ assert(operationArray.Size() >= 2u);
+ assert(operationArray[1].IsString());
+ std::string mark = std::string(operationArray[1].GetString(), operationArray[1].GetStringLength());
- // setFeatureState
+ metadata.metrics.network.emplace(
+ std::piecewise_construct,
+ std::forward_as_tuple(std::move(mark)),
+ std::forward_as_tuple(ProxyFileSource::getRequestCount(), ProxyFileSource::getTransferredSize()));
+ } else if (operationArray[0].GetString() == networkProbeEndOp) {
+ // probeNetworkEnd
+ assert(ProxyFileSource::isTrackingActive());
+ ProxyFileSource::setTrackingActive(false);
} else if (operationArray[0].GetString() == setFeatureStateOp) {
+ // setFeatureState
assert(operationArray.Size() >= 3u);
assert(operationArray[1].IsObject());
assert(operationArray[2].IsObject());
@@ -483,7 +870,11 @@ bool TestRunner::runOperations(const std::string& key, TestMetadata& metadata) {
sourceLayer = {featureOptions["sourceLayer"].GetString()};
}
if (featureOptions.HasMember("id")) {
- featureID = featureOptions["id"].GetString();
+ if (featureOptions["id"].IsString()) {
+ featureID = featureOptions["id"].GetString();
+ } else if (featureOptions["id"].IsNumber()) {
+ featureID = mbgl::util::toString(featureOptions["id"].GetUint64());
+ }
}
const JSValue* state = &operationArray[2];
@@ -507,7 +898,7 @@ bool TestRunner::runOperations(const std::string& key, TestMetadata& metadata) {
result[k] = std::move(array);
stateValue = std::move(result);
valueParsed = true;
- return {};
+ return nullopt;
} else if (isObject(v)) {
eachMember(v, convertFn);
@@ -530,9 +921,8 @@ bool TestRunner::runOperations(const std::string& key, TestMetadata& metadata) {
return false;
}
frontend.getRenderer()->setFeatureState(sourceID, sourceLayer, featureID, parsedState);
-
- // getFeatureState
} else if (operationArray[0].GetString() == getFeatureStateOp) {
+ // getFeatureState
assert(operationArray.Size() >= 2u);
assert(operationArray[1].IsObject());
@@ -548,7 +938,11 @@ bool TestRunner::runOperations(const std::string& key, TestMetadata& metadata) {
sourceLayer = {featureOptions["sourceLayer"].GetString()};
}
if (featureOptions.HasMember("id")) {
- featureID = featureOptions["id"].GetString();
+ if (featureOptions["id"].IsString()) {
+ featureID = featureOptions["id"].GetString();
+ } else if (featureOptions["id"].IsNumber()) {
+ featureID = mbgl::util::toString(featureOptions["id"].GetUint64());
+ }
}
try {
@@ -558,9 +952,8 @@ bool TestRunner::runOperations(const std::string& key, TestMetadata& metadata) {
}
mbgl::FeatureState state;
frontend.getRenderer()->getFeatureState(state, sourceID, sourceLayer, featureID);
-
- // removeFeatureState
} else if (operationArray[0].GetString() == removeFeatureStateOp) {
+ // removeFeatureState
assert(operationArray.Size() >= 2u);
assert(operationArray[1].IsObject());
@@ -577,7 +970,11 @@ bool TestRunner::runOperations(const std::string& key, TestMetadata& metadata) {
sourceLayer = {featureOptions["sourceLayer"].GetString()};
}
if (featureOptions.HasMember("id")) {
- featureID = featureOptions["id"].GetString();
+ if (featureOptions["id"].IsString()) {
+ featureID = featureOptions["id"].GetString();
+ } else if (featureOptions["id"].IsNumber()) {
+ featureID = mbgl::util::toString(featureOptions["id"].GetUint64());
+ }
}
if (operationArray.Size() >= 3u) {
@@ -591,20 +988,122 @@ bool TestRunner::runOperations(const std::string& key, TestMetadata& metadata) {
return false;
}
frontend.getRenderer()->removeFeatureState(sourceID, sourceLayer, featureID, stateKey);
+ } else if (operationArray[0].GetString() == panGestureOp) {
+ // benchmarkPanGesture
+ assert(operationArray.Size() >= 4u);
+ assert(operationArray[1].IsString()); // identifier
+ assert(operationArray[2].IsNumber()); // duration
+ assert(operationArray[3].IsArray()); // start [lat, lng, zoom]
+ assert(operationArray[4].IsArray()); // end [lat, lng, zoom]
+
+ if (metadata.mapMode != mbgl::MapMode::Continuous) {
+ metadata.errorMessage = "Map mode must be Continous for " + panGestureOp + " operation";
+ return false;
+ }
+
+ std::string mark = operationArray[1].GetString();
+ int duration = operationArray[2].GetFloat();
+ LatLng startPos, endPos;
+ double startZoom, endZoom;
+ std::vector<float> samples;
+
+ auto parsePosition = [](auto arr) -> std::tuple<LatLng, double> {
+ assert(arr.Size() >= 3);
+ return {{arr[1].GetDouble(), arr[0].GetDouble()}, arr[2].GetDouble()};
+ };
+
+ std::tie(startPos, startZoom) = parsePosition(operationArray[3].GetArray());
+ std::tie(endPos, endZoom) = parsePosition(operationArray[4].GetArray());
+
+ // Jump to the starting point of the segment and make sure there's something to render
+ map.jumpTo(mbgl::CameraOptions().withCenter(startPos).withZoom(startZoom));
+
+ observer->reset();
+ while (!observer->finishRenderingMap) {
+ frontend.renderOnce(map);
+ }
+
+ if (observer->mapLoadFailure) return false;
+
+ size_t frames = 0;
+ float totalTime = 0.0;
+ bool transitionFinished = false;
+
+ mbgl::AnimationOptions animationOptions(mbgl::Milliseconds(duration * 1000));
+ animationOptions.minZoom = util::min(startZoom, endZoom);
+ animationOptions.transitionFinishFn = [&]() { transitionFinished = true; };
+
+ map.flyTo(mbgl::CameraOptions().withCenter(endPos).withZoom(endZoom), animationOptions);
+
+ for (; !transitionFinished; frames++) {
+ frontend.renderOnce(map);
+ float frameTime = (float)frontend.getFrameTime();
+ totalTime += frameTime;
+
+ samples.push_back(frameTime);
+ }
+
+ float averageFps = totalTime > 0.0 ? frames / totalTime : 0.0;
+ float minFrameTime = 0.0;
+
+ // Use 1% of the longest frames to compute the minimum fps
+ std::sort(samples.begin(), samples.end());
+
+ int sampleCount = util::max(1, (int)samples.size() / 100);
+ for (auto it = samples.rbegin(); it != samples.rbegin() + sampleCount; it++) minFrameTime += *it;
+
+ float minOnePcFps = sampleCount / minFrameTime;
+
+ metadata.metrics.fps.insert({std::move(mark), {averageFps, minOnePcFps, 0.0f}});
+
+ } else if (operationArray[0].GetString() == gfxProbeStartOp) {
+ // probeGFXStart
+ assert(!ctx.gfxProbeActive);
+ ctx.gfxProbeActive = true;
+ ctx.baselineGfxProbe = ctx.activeGfxProbe;
+ } else if (operationArray[0].GetString() == gfxProbeEndOp) {
+ // probeGFXEnd
+ assert(ctx.gfxProbeActive);
+ ctx.gfxProbeActive = false;
+ } else if (operationArray[0].GetString() == gfxProbeOp) {
+ // probeGFX
+ assert(operationArray.Size() >= 2u);
+ assert(operationArray[1].IsString());
+
+ std::string mark = std::string(operationArray[1].GetString(), operationArray[1].GetStringLength());
+
+ // Render the map and fetch rendering stats
+ gfx::RenderingStats stats;
+
+ try {
+ stats = frontend.render(map).stats;
+ } catch (const std::exception&) {
+ return false;
+ }
+
+ ctx.activeGfxProbe = GfxProbe(stats, ctx.activeGfxProbe);
+
+ // Compare memory allocations to the baseline probe
+ GfxProbe metricProbe = ctx.activeGfxProbe;
+ metricProbe.memIndexBuffers.peak -= ctx.baselineGfxProbe.memIndexBuffers.peak;
+ metricProbe.memVertexBuffers.peak -= ctx.baselineGfxProbe.memVertexBuffers.peak;
+ metricProbe.memTextures.peak -= ctx.baselineGfxProbe.memTextures.peak;
+ metadata.metrics.gfx.insert({mark, metricProbe});
} else {
- metadata.errorMessage = std::string("Unsupported operation: ") + operationArray[0].GetString();
+ metadata.errorMessage = std::string("Unsupported operation: ") + operationArray[0].GetString();
return false;
}
operationsArray.Erase(operationIt);
- return runOperations(key, metadata);
+ return runOperations(key, metadata, ctx);
}
TestRunner::Impl::Impl(const TestMetadata& metadata)
- : frontend(metadata.size, metadata.pixelRatio),
+ : observer(std::make_unique<TestRunnerMapObserver>()),
+ frontend(metadata.size, metadata.pixelRatio, swapBehavior(metadata.mapMode)),
map(frontend,
- mbgl::MapObserver::nullObserver(),
+ *observer.get(),
mbgl::MapOptions()
.withMapMode(metadata.mapMode)
.withSize(metadata.size)
@@ -612,9 +1111,12 @@ TestRunner::Impl::Impl(const TestMetadata& metadata)
.withCrossSourceCollisions(metadata.crossSourceCollisions),
mbgl::ResourceOptions().withCacheOnlyRequestsSupport(false)) {}
+TestRunner::Impl::~Impl() {}
+
bool TestRunner::run(TestMetadata& metadata) {
AllocationIndex::setActive(false);
AllocationIndex::reset();
+ ProxyFileSource::setTrackingActive(false);
std::string key = mbgl::util::toString(uint32_t(metadata.mapMode))
+ "/" + mbgl::util::toString(metadata.pixelRatio)
+ "/" + mbgl::util::toString(uint32_t(metadata.crossSourceCollisions));
@@ -635,18 +1137,32 @@ bool TestRunner::run(TestMetadata& metadata) {
map.getStyle().loadJSON(serializeJsonValue(metadata.document));
map.jumpTo(map.getStyle().getDefaultCamera());
- if (!runOperations(key, metadata)) {
+ RunContext ctx{};
+
+ if (!runOperations(key, metadata, ctx)) {
return false;
}
mbgl::PremultipliedImage image;
try {
- image = frontend.render(map);
+ if (metadata.outputsImage) image = frontend.render(map).image;
} catch (const std::exception&) {
return false;
}
- return checkResults(std::move(image), metadata);
+ if (metadata.renderTest) {
+ return checkRenderTestResults(std::move(image), metadata);
+ } else {
+ std::vector<mbgl::Feature> features;
+ assert(metadata.document["metadata"]["test"]["queryGeometry"].IsArray());
+ if (metadata.document["metadata"]["test"]["queryGeometry"][0].IsNumber() &&
+ metadata.document["metadata"]["test"]["queryGeometry"][1].IsNumber()) {
+ features = frontend.getRenderer()->queryRenderedFeatures(metadata.queryGeometry, metadata.queryOptions);
+ } else {
+ features = frontend.getRenderer()->queryRenderedFeatures(metadata.queryGeometryBox, metadata.queryOptions);
+ }
+ return checkQueryTestResults(std::move(image), std::move(features), metadata);
+ }
}
void TestRunner::reset() {
diff --git a/render-test/runner.hpp b/render-test/runner.hpp
index fdea65e104..4f80286c0b 100644
--- a/render-test/runner.hpp
+++ b/render-test/runner.hpp
@@ -3,31 +3,40 @@
#include <mbgl/gfx/headless_frontend.hpp>
#include <mbgl/map/map.hpp>
+#include "manifest_parser.hpp"
+
#include <memory>
+#include <string>
+struct RunContext;
+class TestRunnerMapObserver;
struct TestMetadata;
class TestRunner {
public:
- TestRunner() = default;
-
+ explicit TestRunner(Manifest);
bool run(TestMetadata&);
void reset();
- /// Returns path of the render tests root directory.
- static const std::string& getBasePath();
- /// Returns path of mapbox-gl-native expectations directory.
- static const std::vector<std::string>& getPlatformExpectationsPaths();
+ // Manifest
+ const Manifest& getManifest() const;
+ void doShuffle(uint32_t seed);
private:
- bool runOperations(const std::string& key, TestMetadata&);
- bool checkResults(mbgl::PremultipliedImage&& image, TestMetadata&);
+ bool runOperations(const std::string& key, TestMetadata&, RunContext&);
+ bool checkQueryTestResults(mbgl::PremultipliedImage&& actualImage,
+ std::vector<mbgl::Feature>&& features,
+ TestMetadata&);
+ bool checkRenderTestResults(mbgl::PremultipliedImage&& image, TestMetadata&);
struct Impl {
Impl(const TestMetadata&);
+ ~Impl();
+ std::unique_ptr<TestRunnerMapObserver> observer;
mbgl::HeadlessFrontend frontend;
mbgl::Map map;
};
std::unordered_map<std::string, std::unique_ptr<Impl>> maps;
-}; \ No newline at end of file
+ Manifest manifest;
+};
diff --git a/render-test/tests/file-size/fail-file-doesnt-match/expected.png b/render-test/tests/file-size/fail-file-doesnt-match/expected.png
new file mode 100644
index 0000000000..83d01c4e5d
--- /dev/null
+++ b/render-test/tests/file-size/fail-file-doesnt-match/expected.png
Binary files differ
diff --git a/render-test/tests/file-size/fail-file-doesnt-match/metrics.json b/render-test/tests/file-size/fail-file-doesnt-match/metrics.json
new file mode 100644
index 0000000000..c0002163d6
--- /dev/null
+++ b/render-test/tests/file-size/fail-file-doesnt-match/metrics.json
@@ -0,0 +1,14 @@
+{
+ "file-size": [
+ [
+ "image",
+ "expected.aaa",
+ 169
+ ],
+ [
+ "style",
+ "style.bbbb",
+ 510
+ ]
+ ]
+}
diff --git a/render-test/tests/file-size/fail-file-doesnt-match/style.json b/render-test/tests/file-size/fail-file-doesnt-match/style.json
new file mode 100644
index 0000000000..839a8d4a12
--- /dev/null
+++ b/render-test/tests/file-size/fail-file-doesnt-match/style.json
@@ -0,0 +1,32 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "operations": [
+ ["probeFileSize", "style", "style.json", 0],
+ ["probeFileSize", "image", "expected.png", 0]
+ ],
+ "width": 64,
+ "height": 64
+ }
+ },
+ "sources": {
+ "geojson": {
+ "type": "geojson",
+ "data": {
+ "type": "Point",
+ "coordinates": [
+ 0,
+ 0
+ ]
+ }
+ }
+ },
+ "layers": [
+ {
+ "id": "circle",
+ "type": "circle",
+ "source": "geojson"
+ }
+ ]
+}
diff --git a/render-test/tests/file-size/fail-file-not-found/expected.png b/render-test/tests/file-size/fail-file-not-found/expected.png
new file mode 100644
index 0000000000..83d01c4e5d
--- /dev/null
+++ b/render-test/tests/file-size/fail-file-not-found/expected.png
Binary files differ
diff --git a/render-test/tests/file-size/fail-file-not-found/style.json b/render-test/tests/file-size/fail-file-not-found/style.json
new file mode 100644
index 0000000000..74054e1f40
--- /dev/null
+++ b/render-test/tests/file-size/fail-file-not-found/style.json
@@ -0,0 +1,32 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "operations": [
+ ["probeFileSize", "style", "style.aaaa", 0],
+ ["probeFileSize", "image", "expected.bbb", 0]
+ ],
+ "width": 64,
+ "height": 64
+ }
+ },
+ "sources": {
+ "geojson": {
+ "type": "geojson",
+ "data": {
+ "type": "Point",
+ "coordinates": [
+ 0,
+ 0
+ ]
+ }
+ }
+ },
+ "layers": [
+ {
+ "id": "circle",
+ "type": "circle",
+ "source": "geojson"
+ }
+ ]
+}
diff --git a/render-test/tests/file-size/fail-size-is-over/expected.png b/render-test/tests/file-size/fail-size-is-over/expected.png
new file mode 100644
index 0000000000..83d01c4e5d
--- /dev/null
+++ b/render-test/tests/file-size/fail-size-is-over/expected.png
Binary files differ
diff --git a/render-test/tests/file-size/fail-size-is-over/metrics.json b/render-test/tests/file-size/fail-size-is-over/metrics.json
new file mode 100644
index 0000000000..bc194081cf
--- /dev/null
+++ b/render-test/tests/file-size/fail-size-is-over/metrics.json
@@ -0,0 +1,14 @@
+{
+ "file-size": [
+ [
+ "image",
+ "expected.png",
+ 999
+ ],
+ [
+ "style",
+ "style.json",
+ 999
+ ]
+ ]
+}
diff --git a/render-test/tests/file-size/fail-size-is-over/style.json b/render-test/tests/file-size/fail-size-is-over/style.json
new file mode 100644
index 0000000000..839a8d4a12
--- /dev/null
+++ b/render-test/tests/file-size/fail-size-is-over/style.json
@@ -0,0 +1,32 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "operations": [
+ ["probeFileSize", "style", "style.json", 0],
+ ["probeFileSize", "image", "expected.png", 0]
+ ],
+ "width": 64,
+ "height": 64
+ }
+ },
+ "sources": {
+ "geojson": {
+ "type": "geojson",
+ "data": {
+ "type": "Point",
+ "coordinates": [
+ 0,
+ 0
+ ]
+ }
+ }
+ },
+ "layers": [
+ {
+ "id": "circle",
+ "type": "circle",
+ "source": "geojson"
+ }
+ ]
+}
diff --git a/render-test/tests/file-size/fail-size-is-under/expected.png b/render-test/tests/file-size/fail-size-is-under/expected.png
new file mode 100644
index 0000000000..83d01c4e5d
--- /dev/null
+++ b/render-test/tests/file-size/fail-size-is-under/expected.png
Binary files differ
diff --git a/render-test/tests/file-size/fail-size-is-under/metrics.json b/render-test/tests/file-size/fail-size-is-under/metrics.json
new file mode 100644
index 0000000000..d288c2ceee
--- /dev/null
+++ b/render-test/tests/file-size/fail-size-is-under/metrics.json
@@ -0,0 +1,14 @@
+{
+ "file-size": [
+ [
+ "image",
+ "expected.png",
+ 100
+ ],
+ [
+ "style",
+ "style.json",
+ 100
+ ]
+ ]
+}
diff --git a/render-test/tests/file-size/fail-size-is-under/style.json b/render-test/tests/file-size/fail-size-is-under/style.json
new file mode 100644
index 0000000000..839a8d4a12
--- /dev/null
+++ b/render-test/tests/file-size/fail-size-is-under/style.json
@@ -0,0 +1,32 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "operations": [
+ ["probeFileSize", "style", "style.json", 0],
+ ["probeFileSize", "image", "expected.png", 0]
+ ],
+ "width": 64,
+ "height": 64
+ }
+ },
+ "sources": {
+ "geojson": {
+ "type": "geojson",
+ "data": {
+ "type": "Point",
+ "coordinates": [
+ 0,
+ 0
+ ]
+ }
+ }
+ },
+ "layers": [
+ {
+ "id": "circle",
+ "type": "circle",
+ "source": "geojson"
+ }
+ ]
+}
diff --git a/render-test/tests/file-size/pass-size-is-in-tolerance-higher/expected.png b/render-test/tests/file-size/pass-size-is-in-tolerance-higher/expected.png
new file mode 100644
index 0000000000..83d01c4e5d
--- /dev/null
+++ b/render-test/tests/file-size/pass-size-is-in-tolerance-higher/expected.png
Binary files differ
diff --git a/render-test/tests/file-size/pass-size-is-in-tolerance-higher/metrics.json b/render-test/tests/file-size/pass-size-is-in-tolerance-higher/metrics.json
new file mode 100644
index 0000000000..d0c0c9d7b6
--- /dev/null
+++ b/render-test/tests/file-size/pass-size-is-in-tolerance-higher/metrics.json
@@ -0,0 +1,14 @@
+{
+ "file-size": [
+ [
+ "image",
+ "expected.png",
+ 177
+ ],
+ [
+ "style",
+ "style.json",
+ 548
+ ]
+ ]
+}
diff --git a/render-test/tests/file-size/pass-size-is-in-tolerance-higher/style.json b/render-test/tests/file-size/pass-size-is-in-tolerance-higher/style.json
new file mode 100644
index 0000000000..d021673ba5
--- /dev/null
+++ b/render-test/tests/file-size/pass-size-is-in-tolerance-higher/style.json
@@ -0,0 +1,32 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "operations": [
+ ["probeFileSize", "style", "style.json", 0.05],
+ ["probeFileSize", "image", "expected.png", 0.05]
+ ],
+ "width": 64,
+ "height": 64
+ }
+ },
+ "sources": {
+ "geojson": {
+ "type": "geojson",
+ "data": {
+ "type": "Point",
+ "coordinates": [
+ 0,
+ 0
+ ]
+ }
+ }
+ },
+ "layers": [
+ {
+ "id": "circle",
+ "type": "circle",
+ "source": "geojson"
+ }
+ ]
+}
diff --git a/render-test/tests/file-size/pass-size-is-in-tolerance-lower/expected.png b/render-test/tests/file-size/pass-size-is-in-tolerance-lower/expected.png
new file mode 100644
index 0000000000..83d01c4e5d
--- /dev/null
+++ b/render-test/tests/file-size/pass-size-is-in-tolerance-lower/expected.png
Binary files differ
diff --git a/render-test/tests/file-size/pass-size-is-in-tolerance-lower/metrics.json b/render-test/tests/file-size/pass-size-is-in-tolerance-lower/metrics.json
new file mode 100644
index 0000000000..d62b751d5a
--- /dev/null
+++ b/render-test/tests/file-size/pass-size-is-in-tolerance-lower/metrics.json
@@ -0,0 +1,14 @@
+{
+ "file-size": [
+ [
+ "image",
+ "expected.png",
+ 161
+ ],
+ [
+ "style",
+ "style.json",
+ 498
+ ]
+ ]
+}
diff --git a/render-test/tests/file-size/pass-size-is-in-tolerance-lower/style.json b/render-test/tests/file-size/pass-size-is-in-tolerance-lower/style.json
new file mode 100644
index 0000000000..d021673ba5
--- /dev/null
+++ b/render-test/tests/file-size/pass-size-is-in-tolerance-lower/style.json
@@ -0,0 +1,32 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "operations": [
+ ["probeFileSize", "style", "style.json", 0.05],
+ ["probeFileSize", "image", "expected.png", 0.05]
+ ],
+ "width": 64,
+ "height": 64
+ }
+ },
+ "sources": {
+ "geojson": {
+ "type": "geojson",
+ "data": {
+ "type": "Point",
+ "coordinates": [
+ 0,
+ 0
+ ]
+ }
+ }
+ },
+ "layers": [
+ {
+ "id": "circle",
+ "type": "circle",
+ "source": "geojson"
+ }
+ ]
+}
diff --git a/render-test/tests/file-size/pass-size-is-same/expected.png b/render-test/tests/file-size/pass-size-is-same/expected.png
new file mode 100644
index 0000000000..83d01c4e5d
--- /dev/null
+++ b/render-test/tests/file-size/pass-size-is-same/expected.png
Binary files differ
diff --git a/render-test/tests/file-size/pass-size-is-same/metrics.json b/render-test/tests/file-size/pass-size-is-same/metrics.json
new file mode 100644
index 0000000000..3d560bd610
--- /dev/null
+++ b/render-test/tests/file-size/pass-size-is-same/metrics.json
@@ -0,0 +1,14 @@
+{
+ "file-size": [
+ [
+ "image",
+ "expected.png",
+ 169
+ ],
+ [
+ "style",
+ "style.json",
+ 516
+ ]
+ ]
+} \ No newline at end of file
diff --git a/render-test/tests/file-size/pass-size-is-same/style.json b/render-test/tests/file-size/pass-size-is-same/style.json
new file mode 100644
index 0000000000..839a8d4a12
--- /dev/null
+++ b/render-test/tests/file-size/pass-size-is-same/style.json
@@ -0,0 +1,32 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "operations": [
+ ["probeFileSize", "style", "style.json", 0],
+ ["probeFileSize", "image", "expected.png", 0]
+ ],
+ "width": 64,
+ "height": 64
+ }
+ },
+ "sources": {
+ "geojson": {
+ "type": "geojson",
+ "data": {
+ "type": "Point",
+ "coordinates": [
+ 0,
+ 0
+ ]
+ }
+ }
+ },
+ "layers": [
+ {
+ "id": "circle",
+ "type": "circle",
+ "source": "geojson"
+ }
+ ]
+}
diff --git a/render-test/tests/gfx/fail-ib-mem-mismatch/expected.png b/render-test/tests/gfx/fail-ib-mem-mismatch/expected.png
new file mode 100644
index 0000000000..4b5ea75a25
--- /dev/null
+++ b/render-test/tests/gfx/fail-ib-mem-mismatch/expected.png
Binary files differ
diff --git a/render-test/tests/gfx/fail-ib-mem-mismatch/metrics.json b/render-test/tests/gfx/fail-ib-mem-mismatch/metrics.json
new file mode 100644
index 0000000000..2b5c9c3eda
--- /dev/null
+++ b/render-test/tests/gfx/fail-ib-mem-mismatch/metrics.json
@@ -0,0 +1,5 @@
+{
+ "gfx":[
+ ["gfx 0", 36, 14, 63, 1, [371208, 371208], [68598, 65536], [74592, 74592]]
+ ]
+}
diff --git a/render-test/tests/gfx/fail-ib-mem-mismatch/style.json b/render-test/tests/gfx/fail-ib-mem-mismatch/style.json
new file mode 100644
index 0000000000..6ae3eecb8c
--- /dev/null
+++ b/render-test/tests/gfx/fail-ib-mem-mismatch/style.json
@@ -0,0 +1,20 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "width": 512,
+ "height": 512,
+ "operations": [
+ ["setStyle", "local://styles/uruguay.json"],
+ ["setZoom", 9 ],
+ ["probeGFXStart"],
+ ["setCenter", [-56.509552, -32.865788] ],
+ ["probeGFX", "gfx 0"],
+ ["probeGFXEnd"]
+ ]
+ }
+ },
+ "sources": {},
+ "layers": []
+ }
+ \ No newline at end of file
diff --git a/render-test/tests/gfx/fail-negative-framebuffer-count/expected.png b/render-test/tests/gfx/fail-negative-framebuffer-count/expected.png
new file mode 100644
index 0000000000..4b5ea75a25
--- /dev/null
+++ b/render-test/tests/gfx/fail-negative-framebuffer-count/expected.png
Binary files differ
diff --git a/render-test/tests/gfx/fail-negative-framebuffer-count/metrics.json b/render-test/tests/gfx/fail-negative-framebuffer-count/metrics.json
new file mode 100644
index 0000000000..e92cffa571
--- /dev/null
+++ b/render-test/tests/gfx/fail-negative-framebuffer-count/metrics.json
@@ -0,0 +1,5 @@
+{
+ "gfx":[
+ ["gfx 0", 36, 14, 63, -1, [371208, 371208], [68598, 68598], [74592, 74592]]
+ ]
+}
diff --git a/render-test/tests/gfx/fail-negative-framebuffer-count/style.json b/render-test/tests/gfx/fail-negative-framebuffer-count/style.json
new file mode 100644
index 0000000000..6ae3eecb8c
--- /dev/null
+++ b/render-test/tests/gfx/fail-negative-framebuffer-count/style.json
@@ -0,0 +1,20 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "width": 512,
+ "height": 512,
+ "operations": [
+ ["setStyle", "local://styles/uruguay.json"],
+ ["setZoom", 9 ],
+ ["probeGFXStart"],
+ ["setCenter", [-56.509552, -32.865788] ],
+ ["probeGFX", "gfx 0"],
+ ["probeGFXEnd"]
+ ]
+ }
+ },
+ "sources": {},
+ "layers": []
+ }
+ \ No newline at end of file
diff --git a/render-test/tests/gfx/fail-texture-mem-mismatch/expected.png b/render-test/tests/gfx/fail-texture-mem-mismatch/expected.png
new file mode 100644
index 0000000000..4b5ea75a25
--- /dev/null
+++ b/render-test/tests/gfx/fail-texture-mem-mismatch/expected.png
Binary files differ
diff --git a/render-test/tests/gfx/fail-texture-mem-mismatch/metrics.json b/render-test/tests/gfx/fail-texture-mem-mismatch/metrics.json
new file mode 100644
index 0000000000..fc6f55025f
--- /dev/null
+++ b/render-test/tests/gfx/fail-texture-mem-mismatch/metrics.json
@@ -0,0 +1,5 @@
+{
+ "gfx":[
+ ["gfx 0", 36, 14, 63, 1, [371208, 370000], [68598, 68598], [74592, 74592]]
+ ]
+}
diff --git a/render-test/tests/gfx/fail-texture-mem-mismatch/style.json b/render-test/tests/gfx/fail-texture-mem-mismatch/style.json
new file mode 100644
index 0000000000..6ae3eecb8c
--- /dev/null
+++ b/render-test/tests/gfx/fail-texture-mem-mismatch/style.json
@@ -0,0 +1,20 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "width": 512,
+ "height": 512,
+ "operations": [
+ ["setStyle", "local://styles/uruguay.json"],
+ ["setZoom", 9 ],
+ ["probeGFXStart"],
+ ["setCenter", [-56.509552, -32.865788] ],
+ ["probeGFX", "gfx 0"],
+ ["probeGFXEnd"]
+ ]
+ }
+ },
+ "sources": {},
+ "layers": []
+ }
+ \ No newline at end of file
diff --git a/render-test/tests/gfx/fail-too-few-buffers/expected.png b/render-test/tests/gfx/fail-too-few-buffers/expected.png
new file mode 100644
index 0000000000..4b5ea75a25
--- /dev/null
+++ b/render-test/tests/gfx/fail-too-few-buffers/expected.png
Binary files differ
diff --git a/render-test/tests/gfx/fail-too-few-buffers/metrics.json b/render-test/tests/gfx/fail-too-few-buffers/metrics.json
new file mode 100644
index 0000000000..07b59adeed
--- /dev/null
+++ b/render-test/tests/gfx/fail-too-few-buffers/metrics.json
@@ -0,0 +1,5 @@
+{
+ "gfx":[
+ ["gfx 0", 36, 14, 1000, 1, [371208, 371208], [68598, 68598], [74592, 74592]]
+ ]
+}
diff --git a/render-test/tests/gfx/fail-too-few-buffers/style.json b/render-test/tests/gfx/fail-too-few-buffers/style.json
new file mode 100644
index 0000000000..6ae3eecb8c
--- /dev/null
+++ b/render-test/tests/gfx/fail-too-few-buffers/style.json
@@ -0,0 +1,20 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "width": 512,
+ "height": 512,
+ "operations": [
+ ["setStyle", "local://styles/uruguay.json"],
+ ["setZoom", 9 ],
+ ["probeGFXStart"],
+ ["setCenter", [-56.509552, -32.865788] ],
+ ["probeGFX", "gfx 0"],
+ ["probeGFXEnd"]
+ ]
+ }
+ },
+ "sources": {},
+ "layers": []
+ }
+ \ No newline at end of file
diff --git a/render-test/tests/gfx/fail-too-few-textures/expected.png b/render-test/tests/gfx/fail-too-few-textures/expected.png
new file mode 100644
index 0000000000..4b5ea75a25
--- /dev/null
+++ b/render-test/tests/gfx/fail-too-few-textures/expected.png
Binary files differ
diff --git a/render-test/tests/gfx/fail-too-few-textures/metrics.json b/render-test/tests/gfx/fail-too-few-textures/metrics.json
new file mode 100644
index 0000000000..805d7035e2
--- /dev/null
+++ b/render-test/tests/gfx/fail-too-few-textures/metrics.json
@@ -0,0 +1,5 @@
+{
+ "gfx":[
+ ["gfx 0", 36, 1000, 63, 1, [371208, 371208], [68598, 68598], [74592, 74592]]
+ ]
+}
diff --git a/render-test/tests/gfx/fail-too-few-textures/style.json b/render-test/tests/gfx/fail-too-few-textures/style.json
new file mode 100644
index 0000000000..6ae3eecb8c
--- /dev/null
+++ b/render-test/tests/gfx/fail-too-few-textures/style.json
@@ -0,0 +1,20 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "width": 512,
+ "height": 512,
+ "operations": [
+ ["setStyle", "local://styles/uruguay.json"],
+ ["setZoom", 9 ],
+ ["probeGFXStart"],
+ ["setCenter", [-56.509552, -32.865788] ],
+ ["probeGFX", "gfx 0"],
+ ["probeGFXEnd"]
+ ]
+ }
+ },
+ "sources": {},
+ "layers": []
+ }
+ \ No newline at end of file
diff --git a/render-test/tests/gfx/fail-too-many-drawcalls/expected.png b/render-test/tests/gfx/fail-too-many-drawcalls/expected.png
new file mode 100644
index 0000000000..4b5ea75a25
--- /dev/null
+++ b/render-test/tests/gfx/fail-too-many-drawcalls/expected.png
Binary files differ
diff --git a/render-test/tests/gfx/fail-too-many-drawcalls/metrics.json b/render-test/tests/gfx/fail-too-many-drawcalls/metrics.json
new file mode 100644
index 0000000000..9abb546450
--- /dev/null
+++ b/render-test/tests/gfx/fail-too-many-drawcalls/metrics.json
@@ -0,0 +1,5 @@
+{
+ "gfx":[
+ ["gfx 0", 28, 14, 63, 1, [371208, 371208], [68598, 68598], [74592, 74592]]
+ ]
+}
diff --git a/render-test/tests/gfx/fail-too-many-drawcalls/style.json b/render-test/tests/gfx/fail-too-many-drawcalls/style.json
new file mode 100644
index 0000000000..6ae3eecb8c
--- /dev/null
+++ b/render-test/tests/gfx/fail-too-many-drawcalls/style.json
@@ -0,0 +1,20 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "width": 512,
+ "height": 512,
+ "operations": [
+ ["setStyle", "local://styles/uruguay.json"],
+ ["setZoom", 9 ],
+ ["probeGFXStart"],
+ ["setCenter", [-56.509552, -32.865788] ],
+ ["probeGFX", "gfx 0"],
+ ["probeGFXEnd"]
+ ]
+ }
+ },
+ "sources": {},
+ "layers": []
+ }
+ \ No newline at end of file
diff --git a/render-test/tests/gfx/fail-vb-mem-mismatch/expected.png b/render-test/tests/gfx/fail-vb-mem-mismatch/expected.png
new file mode 100644
index 0000000000..4b5ea75a25
--- /dev/null
+++ b/render-test/tests/gfx/fail-vb-mem-mismatch/expected.png
Binary files differ
diff --git a/render-test/tests/gfx/fail-vb-mem-mismatch/metrics.json b/render-test/tests/gfx/fail-vb-mem-mismatch/metrics.json
new file mode 100644
index 0000000000..fd25dbd68b
--- /dev/null
+++ b/render-test/tests/gfx/fail-vb-mem-mismatch/metrics.json
@@ -0,0 +1,5 @@
+{
+ "gfx":[
+ ["gfx 0", 36, 14, 63, 1, [371208, 371208], [68598, 68598], [74592, 7654321]]
+ ]
+}
diff --git a/render-test/tests/gfx/fail-vb-mem-mismatch/style.json b/render-test/tests/gfx/fail-vb-mem-mismatch/style.json
new file mode 100644
index 0000000000..6ae3eecb8c
--- /dev/null
+++ b/render-test/tests/gfx/fail-vb-mem-mismatch/style.json
@@ -0,0 +1,20 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "width": 512,
+ "height": 512,
+ "operations": [
+ ["setStyle", "local://styles/uruguay.json"],
+ ["setZoom", 9 ],
+ ["probeGFXStart"],
+ ["setCenter", [-56.509552, -32.865788] ],
+ ["probeGFX", "gfx 0"],
+ ["probeGFXEnd"]
+ ]
+ }
+ },
+ "sources": {},
+ "layers": []
+ }
+ \ No newline at end of file
diff --git a/render-test/tests/gfx/pass-double-probe/expected.png b/render-test/tests/gfx/pass-double-probe/expected.png
new file mode 100644
index 0000000000..9f4e48ab24
--- /dev/null
+++ b/render-test/tests/gfx/pass-double-probe/expected.png
Binary files differ
diff --git a/render-test/tests/gfx/pass-double-probe/metrics.json b/render-test/tests/gfx/pass-double-probe/metrics.json
new file mode 100644
index 0000000000..f2641a9a16
--- /dev/null
+++ b/render-test/tests/gfx/pass-double-probe/metrics.json
@@ -0,0 +1,6 @@
+{
+ "gfx":[
+ ["gfx 0", 36, 13, 63, 1, [240136, 240136], [68598, 68598], [74592, 74592]],
+ ["gfx 1", 32, 17, 85, 1, [325008, 325008], [84926, 84926], [100224, 100224]]
+ ]
+}
diff --git a/render-test/tests/gfx/pass-double-probe/style.json b/render-test/tests/gfx/pass-double-probe/style.json
new file mode 100644
index 0000000000..395f4ce50a
--- /dev/null
+++ b/render-test/tests/gfx/pass-double-probe/style.json
@@ -0,0 +1,22 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "width": 512,
+ "height": 512,
+ "operations": [
+ ["setStyle", "local://styles/uruguay.json"],
+ ["setZoom", 9 ],
+ ["probeGFXStart"],
+ ["setCenter", [-56.509552, -32.865788] ],
+ ["probeGFX", "gfx 0"],
+ ["setCenter", [-56.509552, -32.745788] ],
+ ["probeGFX", "gfx 1"],
+ ["probeGFXEnd"]
+ ]
+ }
+ },
+ "sources": {},
+ "layers": []
+ }
+ \ No newline at end of file
diff --git a/render-test/tests/gfx/pass-probe-reset/expected.png b/render-test/tests/gfx/pass-probe-reset/expected.png
new file mode 100644
index 0000000000..9f4e48ab24
--- /dev/null
+++ b/render-test/tests/gfx/pass-probe-reset/expected.png
Binary files differ
diff --git a/render-test/tests/gfx/pass-probe-reset/metrics.json b/render-test/tests/gfx/pass-probe-reset/metrics.json
new file mode 100644
index 0000000000..0e42bc1697
--- /dev/null
+++ b/render-test/tests/gfx/pass-probe-reset/metrics.json
@@ -0,0 +1,6 @@
+{
+ "gfx":[
+ ["gfx 0", 36, 13, 63, 1, [240136, 240136], [68598, 68598], [74592, 74592]],
+ ["gfx 1", 32, 17, 85, 1, [84872, 84872], [16328, 16328], [25632, 25632]]
+ ]
+}
diff --git a/render-test/tests/gfx/pass-probe-reset/style.json b/render-test/tests/gfx/pass-probe-reset/style.json
new file mode 100644
index 0000000000..698d95bb74
--- /dev/null
+++ b/render-test/tests/gfx/pass-probe-reset/style.json
@@ -0,0 +1,24 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "width": 512,
+ "height": 512,
+ "operations": [
+ ["setStyle", "local://styles/uruguay.json"],
+ ["setZoom", 9 ],
+ ["probeGFXStart"],
+ ["setCenter", [-56.509552, -32.865788] ],
+ ["probeGFX", "gfx 0"],
+ ["probeGFXEnd"],
+ ["probeGFXStart"],
+ ["setCenter", [-56.509552, -32.745788] ],
+ ["probeGFX", "gfx 1"],
+ ["probeGFXEnd"]
+ ]
+ }
+ },
+ "sources": {},
+ "layers": []
+ }
+ \ No newline at end of file
diff --git a/render-test/tests/gfx/pass/expected.png b/render-test/tests/gfx/pass/expected.png
new file mode 100644
index 0000000000..4b5ea75a25
--- /dev/null
+++ b/render-test/tests/gfx/pass/expected.png
Binary files differ
diff --git a/render-test/tests/gfx/pass/metrics.json b/render-test/tests/gfx/pass/metrics.json
new file mode 100644
index 0000000000..67ecec5d78
--- /dev/null
+++ b/render-test/tests/gfx/pass/metrics.json
@@ -0,0 +1,5 @@
+{
+ "gfx":[
+ ["gfx 0", 36, 13, 63, 1, [240136, 240136], [68598, 68598], [74592, 74592]]
+ ]
+}
diff --git a/render-test/tests/gfx/pass/style.json b/render-test/tests/gfx/pass/style.json
new file mode 100644
index 0000000000..6ae3eecb8c
--- /dev/null
+++ b/render-test/tests/gfx/pass/style.json
@@ -0,0 +1,20 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "width": 512,
+ "height": 512,
+ "operations": [
+ ["setStyle", "local://styles/uruguay.json"],
+ ["setZoom", 9 ],
+ ["probeGFXStart"],
+ ["setCenter", [-56.509552, -32.865788] ],
+ ["probeGFX", "gfx 0"],
+ ["probeGFXEnd"]
+ ]
+ }
+ },
+ "sources": {},
+ "layers": []
+ }
+ \ No newline at end of file
diff --git a/render-test/tests/mac/memory/pass-memory-size-is-same/expected.png b/render-test/tests/mac/memory/pass-memory-size-is-same/expected.png
new file mode 100644
index 0000000000..0858c19f05
--- /dev/null
+++ b/render-test/tests/mac/memory/pass-memory-size-is-same/expected.png
Binary files differ
diff --git a/render-test/tests/mac/memory/pass-memory-size-is-same/metrics.json b/render-test/tests/mac/memory/pass-memory-size-is-same/metrics.json
new file mode 100644
index 0000000000..cc7d1c9fab
--- /dev/null
+++ b/render-test/tests/mac/memory/pass-memory-size-is-same/metrics.json
@@ -0,0 +1,19 @@
+{
+ "memory": [
+ [
+ "after setZoom 0.9",
+ 33271,
+ 51
+ ],
+ [
+ "end",
+ 47552,
+ 101
+ ],
+ [
+ "start",
+ 0,
+ 0
+ ]
+ ]
+}
diff --git a/render-test/tests/memory/fail-memory-size-is-too-big/expected.png b/render-test/tests/memory/fail-memory-size-is-too-big/expected.png
new file mode 100644
index 0000000000..0858c19f05
--- /dev/null
+++ b/render-test/tests/memory/fail-memory-size-is-too-big/expected.png
Binary files differ
diff --git a/render-test/tests/memory/fail-memory-size-is-too-big/metrics.json b/render-test/tests/memory/fail-memory-size-is-too-big/metrics.json
new file mode 100644
index 0000000000..3e55e0c60e
--- /dev/null
+++ b/render-test/tests/memory/fail-memory-size-is-too-big/metrics.json
@@ -0,0 +1,19 @@
+{
+ "memory": [
+ [
+ "after setZoom 0.9",
+ 33271,
+ 62
+ ],
+ [
+ "end",
+ 40000,
+ 118
+ ],
+ [
+ "start",
+ 0,
+ 0
+ ]
+ ]
+}
diff --git a/render-test/tests/memory/fail-memory-size-is-too-big/style.json b/render-test/tests/memory/fail-memory-size-is-too-big/style.json
new file mode 100644
index 0000000000..b2805ef34f
--- /dev/null
+++ b/render-test/tests/memory/fail-memory-size-is-too-big/style.json
@@ -0,0 +1,52 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "width": 64,
+ "height": 64,
+ "operations": [
+ [ "wait" ],
+ [ "probeMemoryStart" ],
+ [ "probeMemory", "start" ],
+ [
+ "setZoom",
+ 0.9
+ ],
+ [
+ "wait"
+ ],
+ [ "probeMemory", "after setZoom 0.9", 0.005 ],
+ [
+ "setLayerZoomRange",
+ "circle",
+ 1,
+ 2
+ ],
+ [
+ "wait"
+ ],
+ [ "probeMemory", "end", 0.005 ],
+ [ "probeMemoryEnd" ]
+ ]
+ }
+ },
+ "sources": {
+ "geojson": {
+ "type": "geojson",
+ "data": {
+ "type": "Point",
+ "coordinates": [
+ 0,
+ 0
+ ]
+ }
+ }
+ },
+ "layers": [
+ {
+ "id": "circle",
+ "type": "circle",
+ "source": "geojson"
+ }
+ ]
+}
diff --git a/render-test/tests/memory/fail-memory-size-is-too-small/expected.png b/render-test/tests/memory/fail-memory-size-is-too-small/expected.png
new file mode 100644
index 0000000000..0858c19f05
--- /dev/null
+++ b/render-test/tests/memory/fail-memory-size-is-too-small/expected.png
Binary files differ
diff --git a/render-test/tests/memory/fail-memory-size-is-too-small/metrics.json b/render-test/tests/memory/fail-memory-size-is-too-small/metrics.json
new file mode 100644
index 0000000000..7e3997675b
--- /dev/null
+++ b/render-test/tests/memory/fail-memory-size-is-too-small/metrics.json
@@ -0,0 +1,19 @@
+{
+ "memory": [
+ [
+ "after setZoom 0.9",
+ 33271,
+ 62
+ ],
+ [
+ "end",
+ 60000,
+ 118
+ ],
+ [
+ "start",
+ 0,
+ 0
+ ]
+ ]
+}
diff --git a/render-test/tests/memory/fail-memory-size-is-too-small/style.json b/render-test/tests/memory/fail-memory-size-is-too-small/style.json
new file mode 100644
index 0000000000..b2805ef34f
--- /dev/null
+++ b/render-test/tests/memory/fail-memory-size-is-too-small/style.json
@@ -0,0 +1,52 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "width": 64,
+ "height": 64,
+ "operations": [
+ [ "wait" ],
+ [ "probeMemoryStart" ],
+ [ "probeMemory", "start" ],
+ [
+ "setZoom",
+ 0.9
+ ],
+ [
+ "wait"
+ ],
+ [ "probeMemory", "after setZoom 0.9", 0.005 ],
+ [
+ "setLayerZoomRange",
+ "circle",
+ 1,
+ 2
+ ],
+ [
+ "wait"
+ ],
+ [ "probeMemory", "end", 0.005 ],
+ [ "probeMemoryEnd" ]
+ ]
+ }
+ },
+ "sources": {
+ "geojson": {
+ "type": "geojson",
+ "data": {
+ "type": "Point",
+ "coordinates": [
+ 0,
+ 0
+ ]
+ }
+ }
+ },
+ "layers": [
+ {
+ "id": "circle",
+ "type": "circle",
+ "source": "geojson"
+ }
+ ]
+}
diff --git a/render-test/tests/memory/pass-memory-size-is-same/expected.png b/render-test/tests/memory/pass-memory-size-is-same/expected.png
new file mode 100644
index 0000000000..0858c19f05
--- /dev/null
+++ b/render-test/tests/memory/pass-memory-size-is-same/expected.png
Binary files differ
diff --git a/render-test/tests/memory/pass-memory-size-is-same/metrics.json b/render-test/tests/memory/pass-memory-size-is-same/metrics.json
new file mode 100644
index 0000000000..4baa38c7f7
--- /dev/null
+++ b/render-test/tests/memory/pass-memory-size-is-same/metrics.json
@@ -0,0 +1,19 @@
+{
+ "memory": [
+ [
+ "after setZoom 0.9",
+ 33271,
+ 62
+ ],
+ [
+ "end",
+ 47833,
+ 118
+ ],
+ [
+ "start",
+ 0,
+ 0
+ ]
+ ]
+} \ No newline at end of file
diff --git a/render-test/tests/memory/pass-memory-size-is-same/style.json b/render-test/tests/memory/pass-memory-size-is-same/style.json
new file mode 100644
index 0000000000..d1c09f69f9
--- /dev/null
+++ b/render-test/tests/memory/pass-memory-size-is-same/style.json
@@ -0,0 +1,52 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "width": 64,
+ "height": 64,
+ "operations": [
+ [ "wait" ],
+ [ "probeMemoryStart" ],
+ [ "probeMemory", "start" ],
+ [
+ "setZoom",
+ 0.9
+ ],
+ [
+ "wait"
+ ],
+ [ "probeMemory", "after setZoom 0.9", 0.01 ],
+ [
+ "setLayerZoomRange",
+ "circle",
+ 1,
+ 2
+ ],
+ [
+ "wait"
+ ],
+ [ "probeMemory", "end", 0.01 ],
+ [ "probeMemoryEnd" ]
+ ]
+ }
+ },
+ "sources": {
+ "geojson": {
+ "type": "geojson",
+ "data": {
+ "type": "Point",
+ "coordinates": [
+ 0,
+ 0
+ ]
+ }
+ }
+ },
+ "layers": [
+ {
+ "id": "circle",
+ "type": "circle",
+ "source": "geojson"
+ }
+ ]
+}
diff --git a/render-test/tests/network/fail-requests-transferred/expected.png b/render-test/tests/network/fail-requests-transferred/expected.png
new file mode 100644
index 0000000000..b63b151765
--- /dev/null
+++ b/render-test/tests/network/fail-requests-transferred/expected.png
Binary files differ
diff --git a/render-test/tests/network/fail-requests-transferred/metrics.json b/render-test/tests/network/fail-requests-transferred/metrics.json
new file mode 100644
index 0000000000..1a200ca38f
--- /dev/null
+++ b/render-test/tests/network/fail-requests-transferred/metrics.json
@@ -0,0 +1,14 @@
+{
+ "network": [
+ [
+ "end",
+ 2,
+ 200000
+ ],
+ [
+ "start",
+ 0,
+ 0
+ ]
+ ]
+} \ No newline at end of file
diff --git a/render-test/tests/network/fail-requests-transferred/style.json b/render-test/tests/network/fail-requests-transferred/style.json
new file mode 100644
index 0000000000..ef94ddc748
--- /dev/null
+++ b/render-test/tests/network/fail-requests-transferred/style.json
@@ -0,0 +1,67 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "operations": [
+ ["probeNetworkStart"],
+ ["probeNetwork", "start"],
+ ["wait"],
+ ["probeNetwork", "end"],
+ ["probeNetworkEnd"]
+ ],
+ "height": 256,
+ "width": 1024
+ }
+ },
+ "center": [
+ -73,
+ 15
+ ],
+ "zoom": 4.5,
+ "sources": {
+ "mapbox": {
+ "type": "vector",
+ "maxzoom": 14,
+ "tiles": [
+ "local://tiles/mapbox.mapbox-streets-v7/{z}-{x}-{y}.mvt"
+ ]
+ }
+ },
+ "glyphs": "local://glyphs/{fontstack}/{range}.pbf",
+ "layers": [
+ {
+ "id": "background",
+ "type": "background",
+ "paint": {
+ "background-color": "white"
+ }
+ },
+ {
+ "id": "line-center",
+ "type": "symbol",
+ "source": "mapbox",
+ "source-layer": "marine_label",
+ "layout": {
+ "text-field": "{name_en}",
+ "symbol-placement": "line-center",
+ "text-allow-overlap": true,
+ "text-size": 35,
+ "text-letter-spacing": 0.4,
+ "text-offset": [3, 0],
+ "text-font": [
+ "Open Sans Semibold",
+ "Arial Unicode MS Bold"
+ ]
+ }
+ },
+ {
+ "id": "line",
+ "type": "line",
+ "source": "mapbox",
+ "source-layer": "marine_label",
+ "paint": {
+ "line-width": 1
+ }
+ }
+ ]
+}
diff --git a/render-test/tests/network/fail-requests/expected.png b/render-test/tests/network/fail-requests/expected.png
new file mode 100644
index 0000000000..b63b151765
--- /dev/null
+++ b/render-test/tests/network/fail-requests/expected.png
Binary files differ
diff --git a/render-test/tests/network/fail-requests/metrics.json b/render-test/tests/network/fail-requests/metrics.json
new file mode 100644
index 0000000000..81c9b8a5d4
--- /dev/null
+++ b/render-test/tests/network/fail-requests/metrics.json
@@ -0,0 +1,14 @@
+{
+ "network": [
+ [
+ "end",
+ 2,
+ 183111
+ ],
+ [
+ "start",
+ 0,
+ 0
+ ]
+ ]
+} \ No newline at end of file
diff --git a/render-test/tests/network/fail-requests/style.json b/render-test/tests/network/fail-requests/style.json
new file mode 100644
index 0000000000..ef94ddc748
--- /dev/null
+++ b/render-test/tests/network/fail-requests/style.json
@@ -0,0 +1,67 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "operations": [
+ ["probeNetworkStart"],
+ ["probeNetwork", "start"],
+ ["wait"],
+ ["probeNetwork", "end"],
+ ["probeNetworkEnd"]
+ ],
+ "height": 256,
+ "width": 1024
+ }
+ },
+ "center": [
+ -73,
+ 15
+ ],
+ "zoom": 4.5,
+ "sources": {
+ "mapbox": {
+ "type": "vector",
+ "maxzoom": 14,
+ "tiles": [
+ "local://tiles/mapbox.mapbox-streets-v7/{z}-{x}-{y}.mvt"
+ ]
+ }
+ },
+ "glyphs": "local://glyphs/{fontstack}/{range}.pbf",
+ "layers": [
+ {
+ "id": "background",
+ "type": "background",
+ "paint": {
+ "background-color": "white"
+ }
+ },
+ {
+ "id": "line-center",
+ "type": "symbol",
+ "source": "mapbox",
+ "source-layer": "marine_label",
+ "layout": {
+ "text-field": "{name_en}",
+ "symbol-placement": "line-center",
+ "text-allow-overlap": true,
+ "text-size": 35,
+ "text-letter-spacing": 0.4,
+ "text-offset": [3, 0],
+ "text-font": [
+ "Open Sans Semibold",
+ "Arial Unicode MS Bold"
+ ]
+ }
+ },
+ {
+ "id": "line",
+ "type": "line",
+ "source": "mapbox",
+ "source-layer": "marine_label",
+ "paint": {
+ "line-width": 1
+ }
+ }
+ ]
+}
diff --git a/render-test/tests/network/fail-transferred/expected.png b/render-test/tests/network/fail-transferred/expected.png
new file mode 100644
index 0000000000..b63b151765
--- /dev/null
+++ b/render-test/tests/network/fail-transferred/expected.png
Binary files differ
diff --git a/render-test/tests/network/fail-transferred/metrics.json b/render-test/tests/network/fail-transferred/metrics.json
new file mode 100644
index 0000000000..20b42d5b9b
--- /dev/null
+++ b/render-test/tests/network/fail-transferred/metrics.json
@@ -0,0 +1,14 @@
+{
+ "network": [
+ [
+ "end",
+ 3,
+ 100000
+ ],
+ [
+ "start",
+ 0,
+ 0
+ ]
+ ]
+} \ No newline at end of file
diff --git a/render-test/tests/network/fail-transferred/style.json b/render-test/tests/network/fail-transferred/style.json
new file mode 100644
index 0000000000..ef94ddc748
--- /dev/null
+++ b/render-test/tests/network/fail-transferred/style.json
@@ -0,0 +1,67 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "operations": [
+ ["probeNetworkStart"],
+ ["probeNetwork", "start"],
+ ["wait"],
+ ["probeNetwork", "end"],
+ ["probeNetworkEnd"]
+ ],
+ "height": 256,
+ "width": 1024
+ }
+ },
+ "center": [
+ -73,
+ 15
+ ],
+ "zoom": 4.5,
+ "sources": {
+ "mapbox": {
+ "type": "vector",
+ "maxzoom": 14,
+ "tiles": [
+ "local://tiles/mapbox.mapbox-streets-v7/{z}-{x}-{y}.mvt"
+ ]
+ }
+ },
+ "glyphs": "local://glyphs/{fontstack}/{range}.pbf",
+ "layers": [
+ {
+ "id": "background",
+ "type": "background",
+ "paint": {
+ "background-color": "white"
+ }
+ },
+ {
+ "id": "line-center",
+ "type": "symbol",
+ "source": "mapbox",
+ "source-layer": "marine_label",
+ "layout": {
+ "text-field": "{name_en}",
+ "symbol-placement": "line-center",
+ "text-allow-overlap": true,
+ "text-size": 35,
+ "text-letter-spacing": 0.4,
+ "text-offset": [3, 0],
+ "text-font": [
+ "Open Sans Semibold",
+ "Arial Unicode MS Bold"
+ ]
+ }
+ },
+ {
+ "id": "line",
+ "type": "line",
+ "source": "mapbox",
+ "source-layer": "marine_label",
+ "paint": {
+ "line-width": 1
+ }
+ }
+ ]
+}
diff --git a/render-test/tests/network/pass/expected.png b/render-test/tests/network/pass/expected.png
new file mode 100644
index 0000000000..b63b151765
--- /dev/null
+++ b/render-test/tests/network/pass/expected.png
Binary files differ
diff --git a/render-test/tests/network/pass/metrics.json b/render-test/tests/network/pass/metrics.json
new file mode 100644
index 0000000000..6afd106a45
--- /dev/null
+++ b/render-test/tests/network/pass/metrics.json
@@ -0,0 +1,14 @@
+{
+ "network": [
+ [
+ "end",
+ 3,
+ 183111
+ ],
+ [
+ "start",
+ 0,
+ 0
+ ]
+ ]
+} \ No newline at end of file
diff --git a/render-test/tests/network/pass/style.json b/render-test/tests/network/pass/style.json
new file mode 100644
index 0000000000..ef94ddc748
--- /dev/null
+++ b/render-test/tests/network/pass/style.json
@@ -0,0 +1,67 @@
+{
+ "version": 8,
+ "metadata": {
+ "test": {
+ "operations": [
+ ["probeNetworkStart"],
+ ["probeNetwork", "start"],
+ ["wait"],
+ ["probeNetwork", "end"],
+ ["probeNetworkEnd"]
+ ],
+ "height": 256,
+ "width": 1024
+ }
+ },
+ "center": [
+ -73,
+ 15
+ ],
+ "zoom": 4.5,
+ "sources": {
+ "mapbox": {
+ "type": "vector",
+ "maxzoom": 14,
+ "tiles": [
+ "local://tiles/mapbox.mapbox-streets-v7/{z}-{x}-{y}.mvt"
+ ]
+ }
+ },
+ "glyphs": "local://glyphs/{fontstack}/{range}.pbf",
+ "layers": [
+ {
+ "id": "background",
+ "type": "background",
+ "paint": {
+ "background-color": "white"
+ }
+ },
+ {
+ "id": "line-center",
+ "type": "symbol",
+ "source": "mapbox",
+ "source-layer": "marine_label",
+ "layout": {
+ "text-field": "{name_en}",
+ "symbol-placement": "line-center",
+ "text-allow-overlap": true,
+ "text-size": 35,
+ "text-letter-spacing": 0.4,
+ "text-offset": [3, 0],
+ "text-font": [
+ "Open Sans Semibold",
+ "Arial Unicode MS Bold"
+ ]
+ }
+ },
+ {
+ "id": "line",
+ "type": "line",
+ "source": "mapbox",
+ "source-layer": "marine_label",
+ "paint": {
+ "line-width": 1
+ }
+ }
+ ]
+}
diff --git a/render-test/tests/should-fail.json b/render-test/tests/should-fail.json
new file mode 100644
index 0000000000..b539d66019
--- /dev/null
+++ b/render-test/tests/should-fail.json
@@ -0,0 +1,18 @@
+{
+ "tests/file-size/fail-size-is-over": "Should fail, size is bigger than expected.",
+ "tests/file-size/fail-size-is-under": "Should fail, size is smaller than expected.",
+ "tests/file-size/fail-file-doesnt-match": "Should fail, doesn't match the expectation.",
+ "tests/file-size/fail-file-not-found": "Should fail, file not found.",
+ "tests/network/fail-requests": "Should fail, number of requests higher than expected.",
+ "tests/network/fail-transferred": "Should fail, amount of transferred data higher than expected.",
+ "tests/network/fail-requests-transferred": "Should fail, number of requests higher than expected and amount of transferred data less than expected.",
+ "tests/memory/fail-memory-size-is-too-big": "Should fail, memory size is bigger than expected.",
+ "tests/memory/fail-memory-size-is-too-small": "Should fail, memory size is smaller than expected.",
+ "tests/gfx/fail-ib-mem-mismatch": "Should fail, combined byte size of index buffers doesn't match the expectation.",
+ "tests/gfx/fail-negative-framebuffer-count": "Should fail, number of frame buffers is higher than expected.",
+ "tests/gfx/fail-texture-mem-mismatch": "Should fail, combined byte size of textures doesn't match the expectation.",
+ "tests/gfx/fail-too-many-drawcalls": "Should fail, number of draw calls higher than expected.",
+ "tests/gfx/fail-too-few-buffers": "Should fail, number of vertex and index buffers is smaller than expected.",
+ "tests/gfx/fail-too-few-textures": "Should fail, number of textures is smaller than expected.",
+ "tests/gfx/fail-vb-mem-mismatch": "Should fail, combined byte size of index buffers doesn't match the expectation."
+}
diff --git a/scripts/ci/Dockerfile b/scripts/ci/Dockerfile
index 648581044f..09ae07997d 100644
--- a/scripts/ci/Dockerfile
+++ b/scripts/ci/Dockerfile
@@ -40,7 +40,7 @@ RUN set -eu && apt-get install -y \
software-properties-common \
xvfb
-RUN pip3 install cmake_format
+RUN pip3 install cmake-format==0.5.5
# Linux dependencies
RUN set -eu && apt-get install -y \
@@ -103,6 +103,12 @@ RUN set -eu \
"extras;m2repository;com;android;support;constraint;constraint-layout;1.0.2" \
"cmake;3.10.2.4988404"
+# Install gcloud for Firebase testing
+RUN set -eu \
+ && echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] http://packages.cloud.google.com/apt cloud-sdk main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list \
+ && curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key --keyring /usr/share/keyrings/cloud.google.gpg add - \
+ && apt-get update -y && apt-get install google-cloud-sdk -y
+
# Configure ccache
RUN set -eu && /usr/sbin/update-ccache-symlinks
diff --git a/scripts/generate-file-lists.js b/scripts/generate-file-lists.js
index d1f5e77585..546ce38235 100755
--- a/scripts/generate-file-lists.js
+++ b/scripts/generate-file-lists.js
@@ -145,7 +145,8 @@ generateFileList('vendor/mapbox-base-files.json',
'vendor/mapbox-base/mapbox/geojson.hpp',
'vendor/mapbox-base/mapbox/jni.hpp',
'vendor/mapbox-base/mapbox/weak',
- 'vendor/mapbox-base/mapbox/typewrapper' ],
+ 'vendor/mapbox-base/mapbox/typewrapper',
+ 'vendor/mapbox-base/mapbox/value'],
vendorRegex, [ "include/*.hpp", "include/**/*.hpp", "include/**/*.h", "optional.hpp", ":!:include/jni/string_conversion.hpp" ]);
generateFileList('vendor/polylabel-files.json', [ 'vendor/polylabel' ], vendorRegex, [ "include/**/*.hpp" ]);
generateFileList('vendor/protozero-files.json', [ 'vendor/protozero' ], vendorRegex, [ "include/**/*.hpp" ]);
diff --git a/scripts/generate-style-code.js b/scripts/generate-style-code.js
index 1f26e6fd86..0aedba909b 100755
--- a/scripts/generate-style-code.js
+++ b/scripts/generate-style-code.js
@@ -36,6 +36,7 @@ global.expressionType = function (property) {
case 'enum':
return 'NumberType';
case 'string':
+ case 'image': // TODO: replace once we implement image expressions
return 'StringType';
case 'color':
return `ColorType`;
@@ -68,6 +69,7 @@ global.evaluatedType = function (property) {
case 'formatted':
return 'expression::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`;
@@ -166,6 +168,7 @@ global.defaultValue = function (property) {
}
case 'formatted':
case 'string':
+ case 'image': // TODO: replace once we implement image expressions
return JSON.stringify(property.default || "");
case 'enum':
if (property.default === undefined) {
diff --git a/src/core-files.json b/src/core-files.json
index d881e6259d..c25e8273d4 100644
--- a/src/core-files.json
+++ b/src/core-files.json
@@ -17,20 +17,24 @@
"src/mbgl/geometry/line_atlas.cpp",
"src/mbgl/gfx/attribute.cpp",
"src/mbgl/gfx/renderer_backend.cpp",
+ "src/mbgl/gfx/rendering_stats.cpp",
"src/mbgl/gl/attribute.cpp",
"src/mbgl/gl/command_encoder.cpp",
"src/mbgl/gl/context.cpp",
"src/mbgl/gl/debugging_extension.cpp",
"src/mbgl/gl/enum.cpp",
+ "src/mbgl/gl/index_buffer_resource.cpp",
"src/mbgl/gl/object.cpp",
"src/mbgl/gl/offscreen_texture.cpp",
"src/mbgl/gl/render_pass.cpp",
"src/mbgl/gl/renderer_backend.cpp",
"src/mbgl/gl/texture.cpp",
+ "src/mbgl/gl/texture_resource.cpp",
"src/mbgl/gl/uniform.cpp",
"src/mbgl/gl/upload_pass.cpp",
"src/mbgl/gl/value.cpp",
"src/mbgl/gl/vertex_array.cpp",
+ "src/mbgl/gl/vertex_buffer_resource.cpp",
"src/mbgl/layermanager/background_layer_factory.cpp",
"src/mbgl/layermanager/circle_layer_factory.cpp",
"src/mbgl/layermanager/custom_layer_factory.cpp",
@@ -178,6 +182,7 @@
"src/mbgl/style/expression/check_subtype.cpp",
"src/mbgl/style/expression/coalesce.cpp",
"src/mbgl/style/expression/coercion.cpp",
+ "src/mbgl/style/expression/collator.cpp",
"src/mbgl/style/expression/collator_expression.cpp",
"src/mbgl/style/expression/comparison.cpp",
"src/mbgl/style/expression/compound_expression.cpp",
@@ -330,8 +335,11 @@
"mbgl/gfx/backend_scope.hpp": "include/mbgl/gfx/backend_scope.hpp",
"mbgl/gfx/renderable.hpp": "include/mbgl/gfx/renderable.hpp",
"mbgl/gfx/renderer_backend.hpp": "include/mbgl/gfx/renderer_backend.hpp",
+ "mbgl/gfx/rendering_stats.hpp": "include/mbgl/gfx/rendering_stats.hpp",
"mbgl/gl/renderable_resource.hpp": "include/mbgl/gl/renderable_resource.hpp",
"mbgl/gl/renderer_backend.hpp": "include/mbgl/gl/renderer_backend.hpp",
+ "mbgl/i18n/collator.hpp": "include/mbgl/i18n/collator.hpp",
+ "mbgl/i18n/number_format.hpp": "include/mbgl/i18n/number_format.hpp",
"mbgl/layermanager/background_layer_factory.hpp": "include/mbgl/layermanager/background_layer_factory.hpp",
"mbgl/layermanager/circle_layer_factory.hpp": "include/mbgl/layermanager/circle_layer_factory.hpp",
"mbgl/layermanager/custom_layer_factory.hpp": "include/mbgl/layermanager/custom_layer_factory.hpp",
@@ -449,6 +457,7 @@
"mbgl/style/sources/raster_source.hpp": "include/mbgl/style/sources/raster_source.hpp",
"mbgl/style/sources/vector_source.hpp": "include/mbgl/style/sources/vector_source.hpp",
"mbgl/style/style.hpp": "include/mbgl/style/style.hpp",
+ "mbgl/style/style_property.hpp": "include/mbgl/style/style_property.hpp",
"mbgl/style/transition_options.hpp": "include/mbgl/style/transition_options.hpp",
"mbgl/style/types.hpp": "include/mbgl/style/types.hpp",
"mbgl/style/undefined.hpp": "include/mbgl/style/undefined.hpp",
@@ -479,6 +488,7 @@
"mbgl/util/indexed_tuple.hpp": "include/mbgl/util/indexed_tuple.hpp",
"mbgl/util/interpolate.hpp": "include/mbgl/util/interpolate.hpp",
"mbgl/util/logging.hpp": "include/mbgl/util/logging.hpp",
+ "mbgl/util/monotonic_timer.hpp": "include/mbgl/util/monotonic_timer.hpp",
"mbgl/util/noncopyable.hpp": "include/mbgl/util/noncopyable.hpp",
"mbgl/util/optional.hpp": "include/mbgl/util/optional.hpp",
"mbgl/util/platform.hpp": "include/mbgl/util/platform.hpp",
diff --git a/src/mbgl/actor/mailbox.cpp b/src/mbgl/actor/mailbox.cpp
index dfe0520790..070e14bdb0 100644
--- a/src/mbgl/actor/mailbox.cpp
+++ b/src/mbgl/actor/mailbox.cpp
@@ -27,7 +27,7 @@ void Mailbox::open(Scheduler& scheduler_) {
}
if (!queue.empty()) {
- (*scheduler)->schedule(shared_from_this());
+ (*scheduler)->schedule(makeClosure(shared_from_this()));
}
}
@@ -57,7 +57,7 @@ void Mailbox::push(std::unique_ptr<Message> message) {
bool wasEmpty = queue.empty();
queue.push(std::move(message));
if (wasEmpty && scheduler) {
- (*scheduler)->schedule(shared_from_this());
+ (*scheduler)->schedule(makeClosure(shared_from_this()));
}
}
@@ -84,14 +84,20 @@ void Mailbox::receive() {
(*message)();
if (!wasEmpty) {
- (*scheduler)->schedule(shared_from_this());
+ (*scheduler)->schedule(makeClosure(shared_from_this()));
}
}
+// static
void Mailbox::maybeReceive(std::weak_ptr<Mailbox> mailbox) {
if (auto locked = mailbox.lock()) {
locked->receive();
}
}
+// static
+std::function<void()> Mailbox::makeClosure(std::weak_ptr<Mailbox> mailbox) {
+ return [mailbox]() { maybeReceive(mailbox); };
+}
+
} // namespace mbgl
diff --git a/src/mbgl/actor/scheduler.cpp b/src/mbgl/actor/scheduler.cpp
index cb0c7728ec..81e259fe1f 100644
--- a/src/mbgl/actor/scheduler.cpp
+++ b/src/mbgl/actor/scheduler.cpp
@@ -4,7 +4,14 @@
namespace mbgl {
-util::ThreadLocal<Scheduler> g_currentScheduler;
+std::function<void()> Scheduler::bindOnce(std::function<void()> fn) {
+ assert(fn);
+ return [scheduler = makeWeakPtr(), scheduled = std::move(fn)]() mutable {
+ if (!scheduled) return; // Repeated call.
+ auto schedulerGuard = scheduler.lock();
+ if (scheduler) scheduler->schedule(std::move(scheduled));
+ };
+}
static auto& current() {
static util::ThreadLocal<Scheduler> scheduler;
@@ -28,7 +35,7 @@ std::shared_ptr<Scheduler> Scheduler::GetBackground() {
std::shared_ptr<Scheduler> scheduler = weak.lock();
if (!scheduler) {
- weak = scheduler = std::make_shared<ThreadPool>(4);
+ weak = scheduler = std::make_shared<ThreadPool>();
}
return scheduler;
diff --git a/src/mbgl/geometry/line_atlas.cpp b/src/mbgl/geometry/line_atlas.cpp
index 106a24d015..2e4de3edbe 100644
--- a/src/mbgl/geometry/line_atlas.cpp
+++ b/src/mbgl/geometry/line_atlas.cpp
@@ -1,50 +1,33 @@
#include <mbgl/geometry/line_atlas.hpp>
#include <mbgl/gfx/upload_pass.hpp>
+#include <mbgl/math/minmax.hpp>
+#include <mbgl/util/hash.hpp>
#include <mbgl/util/logging.hpp>
#include <mbgl/util/platform.hpp>
-#include <mbgl/util/hash.hpp>
#include <cmath>
namespace mbgl {
+namespace {
-LineAtlas::LineAtlas(const Size size)
- : image(size),
- dirty(true) {
-}
-
-LineAtlas::~LineAtlas() = default;
-
-LinePatternPos LineAtlas::getDashPosition(const std::vector<float>& dasharray,
- LinePatternCap patternCap) {
- size_t key = patternCap == LinePatternCap::Round ? std::numeric_limits<size_t>::min()
- : std::numeric_limits<size_t>::max();
+size_t getDashPatternHash(const std::vector<float>& dasharray, const LinePatternCap patternCap) {
+ size_t key =
+ patternCap == LinePatternCap::Round ? std::numeric_limits<size_t>::min() : std::numeric_limits<size_t>::max();
for (const float part : dasharray) {
util::hash_combine<float>(key, part);
}
-
- // Note: We're not handling hash collisions here.
- const auto it = positions.find(key);
- if (it == positions.end()) {
- auto inserted = positions.emplace(key, addDash(dasharray, patternCap));
- assert(inserted.second);
- return inserted.first->second;
- } else {
- return it->second;
- }
+ return key;
}
-LinePatternPos LineAtlas::addDash(const std::vector<float>& dasharray, LinePatternCap patternCap) {
+LinePatternPos addDashPattern(AlphaImage& image,
+ const int32_t yOffset,
+ const std::vector<float>& dasharray,
+ const LinePatternCap patternCap) {
const uint8_t n = patternCap == LinePatternCap::Round ? 7 : 0;
- const uint8_t dashheight = 2 * n + 1;
- const uint8_t offset = 128;
+ constexpr const uint8_t offset = 128;
if (dasharray.size() < 2) {
- return LinePatternPos();
- }
-
- if (nextRow + dashheight > image.size.height) {
- Log::Warning(Event::OpenGL, "line atlas bitmap overflow");
+ Log::Warning(Event::ParseStyle, "line dasharray requires at least two elements");
return LinePatternPos();
}
@@ -60,7 +43,7 @@ LinePatternPos LineAtlas::addDash(const std::vector<float>& dasharray, LinePatte
bool oddLength = dasharray.size() % 2 == 1;
for (int y = -n; y <= n; y++) {
- int row = nextRow + n + y;
+ int row = yOffset + n + y;
int index = image.size.width * row;
float left = 0;
@@ -72,7 +55,6 @@ LinePatternPos LineAtlas::addDash(const std::vector<float>& dasharray, LinePatte
}
for (uint32_t x = 0; x < image.size.width; x++) {
-
while (right < x / stretch) {
left = right;
if (partIndex >= dasharray.size()) {
@@ -111,37 +93,79 @@ LinePatternPos LineAtlas::addDash(const std::vector<float>& dasharray, LinePatte
}
LinePatternPos position;
- position.y = (0.5 + nextRow + n) / image.size.height;
- position.height = (2.0 * n) / image.size.height;
+ position.y = (0.5 + yOffset + n) / image.size.height;
+ position.height = (2.0 * n + 1) / image.size.height;
position.width = length;
- nextRow += dashheight;
-
- dirty = true;
-
return position;
}
-Size LineAtlas::getSize() const {
- return image.size;
+} // namespace
+
+DashPatternTexture::DashPatternTexture(const std::vector<float>& from_,
+ const std::vector<float>& to_,
+ const LinePatternCap cap) {
+ const bool patternsIdentical = from_ == to_;
+ const int32_t patternHeight = cap == LinePatternCap::Round ? 15 : 1;
+
+ AlphaImage image({256, static_cast<uint32_t>((patternsIdentical ? 1 : 2) * patternHeight)});
+
+ from = addDashPattern(image, 0, from_, cap);
+ to = patternsIdentical ? from : addDashPattern(image, patternHeight, to_, cap);
+
+ texture = std::move(image);
}
-void LineAtlas::upload(gfx::UploadPass& uploadPass) {
- if (!texture) {
- texture = uploadPass.createTexture(image);
- } else if (dirty) {
- uploadPass.updateTexture(*texture, image);
+void DashPatternTexture::upload(gfx::UploadPass& uploadPass) {
+ if (texture.is<AlphaImage>()) {
+ texture = uploadPass.createTexture(texture.get<AlphaImage>());
}
+}
+
+gfx::TextureBinding DashPatternTexture::textureBinding() const {
+ // The texture needs to have been uploaded already.
+ assert(texture.is<gfx::Texture>());
+ return {texture.get<gfx::Texture>().getResource(),
+ gfx::TextureFilterType::Linear,
+ gfx::TextureMipMapType::No,
+ gfx::TextureWrapType::Repeat,
+ gfx::TextureWrapType::Clamp};
+}
- dirty = false;
+Size DashPatternTexture::getSize() const {
+ return texture.match([](const auto& obj) { return obj.size; });
}
-gfx::TextureBinding LineAtlas::textureBinding() {
- assert(texture);
- // All _changes_ to the texture should've been made and uploaded already.
- assert(!dirty);
- return { texture->getResource(), gfx::TextureFilterType::Linear, gfx::TextureMipMapType::No,
- gfx::TextureWrapType::Repeat, gfx::TextureWrapType::Clamp };
+LineAtlas::LineAtlas() = default;
+
+LineAtlas::~LineAtlas() = default;
+
+DashPatternTexture& LineAtlas::getDashPatternTexture(const std::vector<float>& from,
+ const std::vector<float>& to,
+ const LinePatternCap cap) {
+ const size_t hash = util::hash(getDashPatternHash(from, cap), getDashPatternHash(to, cap));
+
+ // Note: We're not handling hash collisions here.
+ const auto it = textures.find(hash);
+ if (it == textures.end()) {
+ auto inserted = textures.emplace(
+ std::piecewise_construct, std::forward_as_tuple(hash), std::forward_as_tuple(from, to, cap));
+ assert(inserted.second);
+ needsUpload.emplace_back(hash);
+ return inserted.first->second;
+ } else {
+ return it->second;
+ }
+}
+
+void LineAtlas::upload(gfx::UploadPass& uploadPass) {
+ for (const size_t hash : needsUpload) {
+ const auto it = textures.find(hash);
+ if (it != textures.end()) {
+ it->second.upload(uploadPass);
+ }
+ }
+ needsUpload.clear();
}
} // namespace mbgl
diff --git a/src/mbgl/geometry/line_atlas.hpp b/src/mbgl/geometry/line_atlas.hpp
index b43583c9c8..853305d138 100644
--- a/src/mbgl/geometry/line_atlas.hpp
+++ b/src/mbgl/geometry/line_atlas.hpp
@@ -3,10 +3,11 @@
#include <mbgl/gfx/texture.hpp>
#include <mbgl/util/image.hpp>
#include <mbgl/util/optional.hpp>
+#include <mbgl/util/variant.hpp>
-#include <vector>
-#include <unordered_map>
+#include <map>
#include <memory>
+#include <vector>
namespace mbgl {
@@ -26,29 +27,46 @@ enum class LinePatternCap : bool {
Round = true,
};
-class LineAtlas {
+class DashPatternTexture {
public:
- LineAtlas(Size);
- ~LineAtlas();
-
- // Binds the atlas texture to the GPU, and uploads data if it is out of date.
- gfx::TextureBinding textureBinding();
+ DashPatternTexture(const std::vector<float>& from, const std::vector<float>& to, LinePatternCap);
// Uploads the texture to the GPU to be available when we need it. This is a lazy operation;
- // the texture is only bound when the data is out of date (=dirty).
+ // the texture is only bound when the data is uploaded for the first time.
void upload(gfx::UploadPass&);
- LinePatternPos getDashPosition(const std::vector<float>&, LinePatternCap);
- LinePatternPos addDash(const std::vector<float>& dasharray, LinePatternCap);
+ // Binds the atlas texture to the GPU, and uploads data if it is out of date.
+ gfx::TextureBinding textureBinding() const;
+ // Returns the size of the texture image.
Size getSize() const;
+ const LinePatternPos& getFrom() const { return from; }
+ const LinePatternPos& getTo() const { return to; }
+
private:
- const AlphaImage image;
- bool dirty;
- optional<gfx::Texture> texture;
- uint32_t nextRow = 0;
- std::unordered_map<size_t, LinePatternPos> positions;
+ LinePatternPos from, to;
+ variant<AlphaImage, gfx::Texture> texture;
+};
+
+class LineAtlas {
+public:
+ LineAtlas();
+ ~LineAtlas();
+
+ // Obtains or creates a texture that has both line patterns in it
+ DashPatternTexture& getDashPatternTexture(const std::vector<float>& from,
+ const std::vector<float>& to,
+ LinePatternCap);
+
+ // Uploads the textures to the GPU to be available when we need it.
+ void upload(gfx::UploadPass&);
+
+private:
+ std::map<size_t, DashPatternTexture> textures;
+
+ // Stores a list of hashes of texture objcts that need uploading.
+ std::vector<size_t> needsUpload;
};
} // namespace mbgl
diff --git a/src/mbgl/gfx/context.hpp b/src/mbgl/gfx/context.hpp
index 2c7cb14899..fe0851be11 100644
--- a/src/mbgl/gfx/context.hpp
+++ b/src/mbgl/gfx/context.hpp
@@ -1,12 +1,13 @@
#pragma once
#include <mbgl/gfx/backend.hpp>
-#include <mbgl/gfx/renderbuffer.hpp>
#include <mbgl/gfx/command_encoder.hpp>
#include <mbgl/gfx/draw_scope.hpp>
#include <mbgl/gfx/program.hpp>
-#include <mbgl/gfx/types.hpp>
+#include <mbgl/gfx/renderbuffer.hpp>
+#include <mbgl/gfx/rendering_stats.hpp>
#include <mbgl/gfx/texture.hpp>
+#include <mbgl/gfx/types.hpp>
namespace mbgl {
@@ -85,6 +86,8 @@ public:
public:
virtual std::unique_ptr<CommandEncoder> createCommandEncoder() = 0;
+ virtual const RenderingStats& renderingStats() const = 0;
+
#if not defined(NDEBUG)
public:
virtual void visualizeStencilBuffer() = 0;
diff --git a/src/mbgl/gfx/rendering_stats.cpp b/src/mbgl/gfx/rendering_stats.cpp
new file mode 100644
index 0000000000..0a239ebd11
--- /dev/null
+++ b/src/mbgl/gfx/rendering_stats.cpp
@@ -0,0 +1,12 @@
+#include <mbgl/gfx/rendering_stats.hpp>
+
+namespace mbgl {
+namespace gfx {
+
+bool RenderingStats::isZero() const {
+ return numActiveTextures == 0 && numCreatedTextures == 0 && numBuffers == 0 && numFrameBuffers == 0 &&
+ memTextures == 0 && memIndexBuffers == 0 && memVertexBuffers == 0;
+}
+
+} // namespace gfx
+} // namespace mbgl \ No newline at end of file
diff --git a/src/mbgl/gl/context.cpp b/src/mbgl/gl/context.cpp
index 18b376e3dc..e2bcc7fadc 100644
--- a/src/mbgl/gl/context.cpp
+++ b/src/mbgl/gl/context.cpp
@@ -54,13 +54,15 @@ Context::Context(RendererBackend& backend_)
GLint value;
MBGL_CHECK_ERROR(glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &value));
return value;
- }()), backend(backend_) {
-}
+ }()),
+ backend(backend_),
+ stats() {}
Context::~Context() {
if (cleanupOnDestruction) {
reset();
}
+ assert(stats.isZero());
}
void Context::initializeExtensions(const std::function<gl::ProcAddress(const char*)>& getProcAddress) {
@@ -206,10 +208,12 @@ UniqueTexture Context::createUniqueTexture() {
if (pooledTextures.empty()) {
pooledTextures.resize(TextureMax);
MBGL_CHECK_ERROR(glGenTextures(TextureMax, pooledTextures.data()));
+ stats.numCreatedTextures += TextureMax;
}
TextureID id = pooledTextures.back();
pooledTextures.pop_back();
+ stats.numActiveTextures++;
// NOLINTNEXTLINE(performance-move-const-arg)
return UniqueTexture{std::move(id), {this}};
}
@@ -238,6 +242,7 @@ VertexArray Context::createVertexArray() {
UniqueFramebuffer Context::createFramebuffer() {
FramebufferID id = 0;
MBGL_CHECK_ERROR(glGenFramebuffers(1, &id));
+ stats.numFrameBuffers++;
// NOLINTNEXTLINE(performance-move-const-arg)
return UniqueFramebuffer{ std::move(id), { this } };
}
@@ -245,8 +250,10 @@ UniqueFramebuffer Context::createFramebuffer() {
std::unique_ptr<gfx::TextureResource> Context::createTextureResource(
const Size size, const gfx::TexturePixelType format, const gfx::TextureChannelDataType type) {
auto obj = createUniqueTexture();
+ int textureByteSize = gl::TextureResource::getStorageSize(size, format, type);
+ stats.memTextures += textureByteSize;
std::unique_ptr<gfx::TextureResource> resource =
- std::make_unique<gl::TextureResource>(std::move(obj));
+ std::make_unique<gl::TextureResource>(std::move(obj), textureByteSize);
// Always use texture unit 0 for manipulating it.
activeTextureUnit = 0;
@@ -517,6 +524,8 @@ void Context::clear(optional<mbgl::Color> color,
}
MBGL_CHECK_ERROR(glClear(mask));
+
+ stats.numDrawCalls = 0;
}
void Context::setCullFaceMode(const gfx::CullFaceMode& mode) {
@@ -583,6 +592,18 @@ std::unique_ptr<gfx::CommandEncoder> Context::createCommandEncoder() {
return std::make_unique<gl::CommandEncoder>(*this);
}
+gfx::RenderingStats& Context::renderingStats() {
+ return stats;
+}
+
+const gfx::RenderingStats& Context::renderingStats() const {
+ return stats;
+}
+
+void Context::finish() {
+ MBGL_CHECK_ERROR(glFinish());
+}
+
void Context::draw(const gfx::DrawMode& drawMode,
std::size_t indexOffset,
std::size_t indexLength) {
@@ -607,6 +628,8 @@ void Context::draw(const gfx::DrawMode& drawMode,
static_cast<GLsizei>(indexLength),
GL_UNSIGNED_SHORT,
reinterpret_cast<GLvoid*>(sizeof(uint16_t) * indexOffset)));
+
+ stats.numDrawCalls++;
}
void Context::performCleanup() {
@@ -643,6 +666,7 @@ void Context::performCleanup() {
}
}
MBGL_CHECK_ERROR(glDeleteBuffers(int(abandonedBuffers.size()), abandonedBuffers.data()));
+ stats.numBuffers -= int(abandonedBuffers.size());
abandonedBuffers.clear();
}
@@ -655,6 +679,8 @@ void Context::performCleanup() {
}
}
MBGL_CHECK_ERROR(glDeleteTextures(int(abandonedTextures.size()), abandonedTextures.data()));
+ stats.numCreatedTextures -= int(abandonedTextures.size());
+ assert(stats.numCreatedTextures >= 0);
abandonedTextures.clear();
}
@@ -678,6 +704,8 @@ void Context::performCleanup() {
}
MBGL_CHECK_ERROR(
glDeleteFramebuffers(int(abandonedFramebuffers.size()), abandonedFramebuffers.data()));
+ stats.numFrameBuffers -= int(abandonedFramebuffers.size());
+ assert(stats.numFrameBuffers >= 0);
abandonedFramebuffers.clear();
}
diff --git a/src/mbgl/gl/context.hpp b/src/mbgl/gl/context.hpp
index 70f12e5a8d..b0d043ff9f 100644
--- a/src/mbgl/gl/context.hpp
+++ b/src/mbgl/gl/context.hpp
@@ -43,6 +43,9 @@ public:
std::unique_ptr<gfx::CommandEncoder> createCommandEncoder() override;
+ gfx::RenderingStats& renderingStats();
+ const gfx::RenderingStats& renderingStats() const override;
+
void initializeExtensions(const std::function<gl::ProcAddress(const char*)>&);
void enableDebugging();
@@ -92,6 +95,8 @@ public:
std::size_t indexOffset,
std::size_t indexLength);
+ void finish();
+
// Actually remove the objects we marked as abandoned with the above methods.
// Only call this while the OpenGL context is exclusive to this thread.
void performCleanup() override;
@@ -130,6 +135,7 @@ private:
RendererBackend& backend;
bool cleanupOnDestruction = true;
+ gfx::RenderingStats stats;
std::unique_ptr<extension::Debugging> debugging;
std::unique_ptr<extension::VertexArray> vertexArray;
diff --git a/src/mbgl/gl/index_buffer_resource.cpp b/src/mbgl/gl/index_buffer_resource.cpp
new file mode 100644
index 0000000000..2ef178846b
--- /dev/null
+++ b/src/mbgl/gl/index_buffer_resource.cpp
@@ -0,0 +1,14 @@
+#include <mbgl/gl/context.hpp>
+#include <mbgl/gl/index_buffer_resource.hpp>
+
+namespace mbgl {
+namespace gl {
+
+IndexBufferResource::~IndexBufferResource() {
+ auto& stats = buffer.get_deleter().context.renderingStats();
+ stats.memIndexBuffers -= byteSize;
+ assert(stats.memIndexBuffers >= 0);
+}
+
+} // namespace gl
+} // namespace mbgl \ No newline at end of file
diff --git a/src/mbgl/gl/index_buffer_resource.hpp b/src/mbgl/gl/index_buffer_resource.hpp
index 2da25fdb96..00c66be5b5 100644
--- a/src/mbgl/gl/index_buffer_resource.hpp
+++ b/src/mbgl/gl/index_buffer_resource.hpp
@@ -8,10 +8,11 @@ namespace gl {
class IndexBufferResource : public gfx::IndexBufferResource {
public:
- IndexBufferResource(UniqueBuffer&& buffer_) : buffer(std::move(buffer_)) {
- }
+ IndexBufferResource(UniqueBuffer&& buffer_, int byteSize_) : buffer(std::move(buffer_)), byteSize(byteSize_) {}
+ ~IndexBufferResource();
UniqueBuffer buffer;
+ int byteSize;
};
} // namespace gl
diff --git a/src/mbgl/gl/object.cpp b/src/mbgl/gl/object.cpp
index ec2998a27d..c075aa8f13 100644
--- a/src/mbgl/gl/object.cpp
+++ b/src/mbgl/gl/object.cpp
@@ -28,6 +28,8 @@ void TextureDeleter::operator()(TextureID id) const {
} else {
context->pooledTextures.push_back(id);
}
+ context->renderingStats().numActiveTextures--;
+ assert(context->renderingStats().numActiveTextures >= 0);
}
void VertexArrayDeleter::operator()(VertexArrayID id) const {
diff --git a/src/mbgl/gl/texture_resource.cpp b/src/mbgl/gl/texture_resource.cpp
new file mode 100644
index 0000000000..b9bf620eea
--- /dev/null
+++ b/src/mbgl/gl/texture_resource.cpp
@@ -0,0 +1,45 @@
+#include <mbgl/gl/context.hpp>
+#include <mbgl/gl/texture_resource.hpp>
+
+namespace mbgl {
+namespace gl {
+
+static int channelCount(gfx::TexturePixelType format) {
+ switch (format) {
+ case gfx::TexturePixelType::Alpha:
+ case gfx::TexturePixelType::Depth:
+ case gfx::TexturePixelType::Luminance:
+ case gfx::TexturePixelType::Stencil:
+ return 1;
+ case gfx::TexturePixelType::RGBA:
+ return 4;
+ default:
+ assert(!"Unknown texture pixel type");
+ return 0;
+ }
+}
+
+static int channelStorageSize(gfx::TextureChannelDataType type) {
+ switch (type) {
+ case gfx::TextureChannelDataType::HalfFloat:
+ return 2;
+ case gfx::TextureChannelDataType::UnsignedByte:
+ return 1;
+ default:
+ assert(!"Unknown texture channel data type");
+ return 0;
+ }
+}
+
+TextureResource::~TextureResource() {
+ auto& stats = texture.get_deleter().context->renderingStats();
+ stats.memTextures -= byteSize;
+ assert(stats.memTextures >= 0);
+}
+
+int TextureResource::getStorageSize(const Size& size, gfx::TexturePixelType format, gfx::TextureChannelDataType type) {
+ return size.width * size.height * channelCount(format) * channelStorageSize(type);
+}
+
+} // namespace gl
+} // namespace mbgl \ No newline at end of file
diff --git a/src/mbgl/gl/texture_resource.hpp b/src/mbgl/gl/texture_resource.hpp
index ed742e75b7..494e5ae8a3 100644
--- a/src/mbgl/gl/texture_resource.hpp
+++ b/src/mbgl/gl/texture_resource.hpp
@@ -8,14 +8,17 @@ namespace gl {
class TextureResource : public gfx::TextureResource {
public:
- TextureResource(UniqueTexture&& texture_) : texture(std::move(texture_)) {
- }
+ TextureResource(UniqueTexture&& texture_, int byteSize_) : texture(std::move(texture_)), byteSize(byteSize_) {}
+ ~TextureResource();
+
+ static int getStorageSize(const Size& size, gfx::TexturePixelType format, gfx::TextureChannelDataType type);
UniqueTexture texture;
gfx::TextureFilterType filter = gfx::TextureFilterType::Nearest;
gfx::TextureMipMapType mipmap = gfx::TextureMipMapType::No;
gfx::TextureWrapType wrapX = gfx::TextureWrapType::Clamp;
gfx::TextureWrapType wrapY = gfx::TextureWrapType::Clamp;
+ int byteSize;
};
} // namespace gl
diff --git a/src/mbgl/gl/upload_pass.cpp b/src/mbgl/gl/upload_pass.cpp
index 4312488fb4..962bc72239 100644
--- a/src/mbgl/gl/upload_pass.cpp
+++ b/src/mbgl/gl/upload_pass.cpp
@@ -20,12 +20,14 @@ std::unique_ptr<gfx::VertexBufferResource> UploadPass::createVertexBufferResourc
const void* data, std::size_t size, const gfx::BufferUsageType usage) {
BufferID id = 0;
MBGL_CHECK_ERROR(glGenBuffers(1, &id));
+ commandEncoder.context.renderingStats().numBuffers++;
+ commandEncoder.context.renderingStats().memVertexBuffers += size;
// NOLINTNEXTLINE(performance-move-const-arg)
UniqueBuffer result{ std::move(id), { commandEncoder.context } };
commandEncoder.context.vertexBuffer = result;
MBGL_CHECK_ERROR(
glBufferData(GL_ARRAY_BUFFER, size, data, Enum<gfx::BufferUsageType>::to(usage)));
- return std::make_unique<gl::VertexBufferResource>(std::move(result));
+ return std::make_unique<gl::VertexBufferResource>(std::move(result), size);
}
void UploadPass::updateVertexBufferResource(gfx::VertexBufferResource& resource,
@@ -39,13 +41,15 @@ std::unique_ptr<gfx::IndexBufferResource> UploadPass::createIndexBufferResource(
const void* data, std::size_t size, const gfx::BufferUsageType usage) {
BufferID id = 0;
MBGL_CHECK_ERROR(glGenBuffers(1, &id));
+ commandEncoder.context.renderingStats().numBuffers++;
+ commandEncoder.context.renderingStats().memIndexBuffers += size;
// NOLINTNEXTLINE(performance-move-const-arg)
UniqueBuffer result{ std::move(id), { commandEncoder.context } };
commandEncoder.context.bindVertexArray = 0;
commandEncoder.context.globalVertexArrayState.indexBuffer = result;
MBGL_CHECK_ERROR(
glBufferData(GL_ELEMENT_ARRAY_BUFFER, size, data, Enum<gfx::BufferUsageType>::to(usage)));
- return std::make_unique<gl::IndexBufferResource>(std::move(result));
+ return std::make_unique<gl::IndexBufferResource>(std::move(result), size);
}
void UploadPass::updateIndexBufferResource(gfx::IndexBufferResource& resource,
@@ -65,8 +69,10 @@ UploadPass::createTextureResource(const Size size,
gfx::TexturePixelType format,
gfx::TextureChannelDataType type) {
auto obj = commandEncoder.context.createUniqueTexture();
+ int textureByteSize = gl::TextureResource::getStorageSize(size, format, type);
+ commandEncoder.context.renderingStats().memTextures += textureByteSize;
std::unique_ptr<gfx::TextureResource> resource =
- std::make_unique<gl::TextureResource>(std::move(obj));
+ std::make_unique<gl::TextureResource>(std::move(obj), textureByteSize);
commandEncoder.context.pixelStoreUnpack = { 1 };
updateTextureResource(*resource, size, data, format, type);
// We are using clamp to edge here since OpenGL ES doesn't allow GL_REPEAT on NPOT textures.
diff --git a/src/mbgl/gl/vertex_buffer_resource.cpp b/src/mbgl/gl/vertex_buffer_resource.cpp
new file mode 100644
index 0000000000..cddbdd43d0
--- /dev/null
+++ b/src/mbgl/gl/vertex_buffer_resource.cpp
@@ -0,0 +1,14 @@
+#include <mbgl/gl/context.hpp>
+#include <mbgl/gl/vertex_buffer_resource.hpp>
+
+namespace mbgl {
+namespace gl {
+
+VertexBufferResource::~VertexBufferResource() {
+ auto& stats = buffer.get_deleter().context.renderingStats();
+ stats.memVertexBuffers -= byteSize;
+ assert(stats.memVertexBuffers >= 0);
+}
+
+} // namespace gl
+} // namespace mbgl \ No newline at end of file
diff --git a/src/mbgl/gl/vertex_buffer_resource.hpp b/src/mbgl/gl/vertex_buffer_resource.hpp
index 95e5e75d45..f9c599c757 100644
--- a/src/mbgl/gl/vertex_buffer_resource.hpp
+++ b/src/mbgl/gl/vertex_buffer_resource.hpp
@@ -8,10 +8,11 @@ namespace gl {
class VertexBufferResource : public gfx::VertexBufferResource {
public:
- VertexBufferResource(UniqueBuffer&& buffer_) : buffer(std::move(buffer_)) {
- }
+ VertexBufferResource(UniqueBuffer&& buffer_, int byteSize_) : buffer(std::move(buffer_)), byteSize(byteSize_) {}
+ ~VertexBufferResource();
UniqueBuffer buffer;
+ int byteSize;
};
} // namespace gl
diff --git a/src/mbgl/layout/symbol_layout.cpp b/src/mbgl/layout/symbol_layout.cpp
index 81d1d9a5b6..d0227c36c5 100644
--- a/src/mbgl/layout/symbol_layout.cpp
+++ b/src/mbgl/layout/symbol_layout.cpp
@@ -121,7 +121,7 @@ SymbolLayout::SymbolLayout(const BucketParameters& parameters,
allowVerticalPlacement = allowVerticalPlacement || placementMode == style::TextWritingModeType::Vertical;
return !seen.insert(placementMode).second;
});
- modes.erase(end, modes.end());
+ modes.erase(end, modes.end());
placementModes = std::move(modes);
}
@@ -525,21 +525,22 @@ void SymbolLayout::addFeature(const std::size_t layoutFeatureIndex,
const float textRepeatDistance = symbolSpacing / 2;
const auto evaluatedLayoutProperties = layout->evaluate(zoom, feature);
IndexedSubfeature indexedFeature(feature.index, sourceLayer->getName(), bucketLeaderID, symbolInstances.size());
- const bool hasIconTextFit = evaluatedLayoutProperties.get<style::IconTextFit>() != IconTextFitType::None;
+ const auto iconTextFit = evaluatedLayoutProperties.get<style::IconTextFit>();
// Adjust shaped icon size when icon-text-fit is used.
optional<PositionedIcon> verticallyShapedIcon;
- if (shapedIcon && hasIconTextFit) {
+ if (shapedIcon && iconTextFit != IconTextFitType::None) {
// Create vertically shaped icon for vertical writing mode if needed.
if (allowVerticalPlacement && shapedTextOrientations.vertical) {
verticallyShapedIcon = shapedIcon;
- verticallyShapedIcon->fitIconToText(evaluatedLayoutProperties,
- shapedTextOrientations.vertical,
- layoutTextSize);
+ verticallyShapedIcon->fitIconToText(
+ shapedTextOrientations.vertical, iconTextFit, layout->get<IconTextFitPadding>(), iconOffset, fontScale);
+ }
+ const auto shapedText = getDefaultHorizontalShaping(shapedTextOrientations);
+ if (shapedText) {
+ shapedIcon->fitIconToText(
+ shapedText, iconTextFit, layout->get<IconTextFitPadding>(), iconOffset, fontScale);
}
- shapedIcon->fitIconToText(evaluatedLayoutProperties,
- getDefaultHorizontalShaping(shapedTextOrientations),
- layoutTextSize);
}
auto addSymbolInstance = [&] (Anchor& anchor, std::shared_ptr<SymbolInstanceSharedData> sharedData) {
diff --git a/src/mbgl/renderer/image_manager.cpp b/src/mbgl/renderer/image_manager.cpp
index d001084f92..2ea753d8aa 100644
--- a/src/mbgl/renderer/image_manager.cpp
+++ b/src/mbgl/renderer/image_manager.cpp
@@ -138,8 +138,9 @@ void ImageManager::removeRequestor(ImageRequestor& requestor) {
void ImageManager::notifyIfMissingImageAdded() {
for (auto it = missingImageRequestors.begin(); it != missingImageRequestors.end();) {
- if (it->second.callbacks.empty()) {
- notify(*it->first, it->second.pair);
+ ImageRequestor& requestor = *it->first;
+ if (!requestor.hasPendingRequests()) {
+ notify(requestor, it->second);
it = missingImageRequestors.erase(it);
} else {
++it;
@@ -169,38 +170,56 @@ void ImageManager::reduceMemoryUseIfCacheSizeExceedsLimit() {
}
void ImageManager::checkMissingAndNotify(ImageRequestor& requestor, const ImageRequestPair& pair) {
- std::vector<std::string> missingImages;
- missingImages.reserve(pair.first.size());
+ ImageDependencies missingDependencies;
+
for (const auto& dependency : pair.first) {
if (images.find(dependency.first) == images.end()) {
- missingImages.push_back(dependency.first);
+ missingDependencies.emplace(dependency);
}
}
- if (!missingImages.empty()) {
+ if (!missingDependencies.empty()) {
ImageRequestor* requestorPtr = &requestor;
+ assert(!missingImageRequestors.count(requestorPtr));
+ missingImageRequestors.emplace(requestorPtr, pair);
- auto emplaced = missingImageRequestors.emplace(requestorPtr, MissingImageRequestPair { pair, {} });
- assert(emplaced.second);
-
- for (const auto& missingImage : missingImages) {
+ for (const auto& dependency : missingDependencies) {
+ const std::string& missingImage = dependency.first;
assert(observer != nullptr);
- requestedImages[missingImage].emplace(&requestor);
- auto callback = std::make_unique<ActorCallback>(
- *Scheduler::GetCurrent(),
- [this, requestorPtr, missingImage] {
- auto requestorIt = missingImageRequestors.find(requestorPtr);
- if (requestorIt != missingImageRequestors.end()) {
- assert(requestorIt->second.callbacks.find(missingImage) != requestorIt->second.callbacks.end());
- requestorIt->second.callbacks.erase(missingImage);
- }
- });
-
- auto actorRef = callback->self();
- emplaced.first->second.callbacks.emplace(missingImage, std::move(callback));
- observer->onStyleImageMissing(missingImage, [actorRef] {
- actorRef.invoke(&Callback::operator());
- });
+
+ auto existingRequestorsIt = requestedImages.find(missingImage);
+ if (existingRequestorsIt != requestedImages.end()) { // Already asked client about this image.
+ std::set<ImageRequestor*>& existingRequestors = existingRequestorsIt->second;
+ // existingRequestors is empty if all the previous requestors are deleted.
+ if (!existingRequestors.empty() &&
+ (*existingRequestors.begin())
+ ->hasPendingRequest(missingImage)) { // Still waiting for the client response for this image.
+ requestorPtr->addPendingRequest(missingImage);
+ existingRequestors.emplace(requestorPtr);
+ continue;
+ }
+ // The request for this image has been already delivered
+ // to the client, so we do not treat it as pending.
+ existingRequestors.emplace(requestorPtr);
+ // TODO: we could `continue;` here, but we need to call `observer->onStyleImageMissing`,
+ // so that rendering is re-launched from the handler at Map::Impl.
+ } else {
+ requestedImages[missingImage].emplace(requestorPtr);
+ requestor.addPendingRequest(missingImage);
+ }
+
+ auto removePendingRequests = [this, missingImage] {
+ auto existingRequest = requestedImages.find(missingImage);
+ if (existingRequest == requestedImages.end()) {
+ return;
+ }
+
+ for (auto* req : existingRequest->second) {
+ req->removePendingRequest(missingImage);
+ }
+ };
+ observer->onStyleImageMissing(missingImage,
+ Scheduler::GetCurrent()->bindOnce(std::move(removePendingRequests)));
}
} else {
// Associate requestor with an image that was provided by the client.
@@ -230,7 +249,7 @@ void ImageManager::notify(ImageRequestor& requestor, const ImageRequestPair& pai
}
}
- requestor.onImagesAvailable(iconMap, patternMap, std::move(versionMap), pair.second);
+ requestor.onImagesAvailable(std::move(iconMap), std::move(patternMap), std::move(versionMap), pair.second);
}
void ImageManager::dumpDebugLogs() const {
diff --git a/src/mbgl/renderer/image_manager.hpp b/src/mbgl/renderer/image_manager.hpp
index 9097418681..5ed6e237f0 100644
--- a/src/mbgl/renderer/image_manager.hpp
+++ b/src/mbgl/renderer/image_manager.hpp
@@ -58,13 +58,7 @@ private:
bool loaded = false;
std::map<ImageRequestor*, ImageRequestPair> requestors;
- using Callback = std::function<void()>;
- using ActorCallback = Actor<Callback>;
- struct MissingImageRequestPair {
- ImageRequestPair pair;
- std::map<std::string, std::unique_ptr<ActorCallback>> callbacks;
- };
- std::map<ImageRequestor*, MissingImageRequestPair> missingImageRequestors;
+ std::map<ImageRequestor*, ImageRequestPair> missingImageRequestors;
std::map<std::string, std::set<ImageRequestor*>> requestedImages;
std::size_t requestedImagesCacheSize = 0ul;
ImageMap images;
@@ -77,8 +71,17 @@ public:
explicit ImageRequestor(ImageManager&);
virtual ~ImageRequestor();
virtual void onImagesAvailable(ImageMap icons, ImageMap patterns, ImageVersionMap versionMap, uint64_t imageCorrelationID) = 0;
+
+ void addPendingRequest(const std::string& imageId) { pendingRequests.insert(imageId); }
+ bool hasPendingRequest(const std::string& imageId) const { return pendingRequests.count(imageId); }
+ bool hasPendingRequests() const { return !pendingRequests.empty(); }
+ void removePendingRequest(const std::string& imageId) { pendingRequests.erase(imageId); }
+
private:
ImageManager& imageManager;
+
+ // Pending requests are image requests that are waiting to be dispatched to the client.
+ std::set<std::string> pendingRequests;
};
} // namespace mbgl
diff --git a/src/mbgl/renderer/layers/render_line_layer.cpp b/src/mbgl/renderer/layers/render_line_layer.cpp
index 36665c1db4..e5bbe74bf9 100644
--- a/src/mbgl/renderer/layers/render_line_layer.cpp
+++ b/src/mbgl/renderer/layers/render_line_layer.cpp
@@ -78,8 +78,8 @@ void RenderLineLayer::prepare(const LayerPrepareParameters& params) {
const LinePatternCap cap = bucket.layout.get<LineCap>() == LineCapType::Round
? LinePatternCap::Round : LinePatternCap::Square;
// Ensures that the dash data gets added to the atlas.
- params.lineAtlas.getDashPosition(evaluated.get<LineDasharray>().from, cap);
- params.lineAtlas.getDashPosition(evaluated.get<LineDasharray>().to, cap);
+ params.lineAtlas.getDashPatternTexture(
+ evaluated.get<LineDasharray>().from, evaluated.get<LineDasharray>().to, cap);
}
}
@@ -146,27 +146,26 @@ void RenderLineLayer::render(PaintParameters& parameters) {
};
if (!evaluated.get<LineDasharray>().from.empty()) {
- const LinePatternCap cap = bucket.layout.get<LineCap>() == LineCapType::Round
- ? LinePatternCap::Round : LinePatternCap::Square;
- LinePatternPos posA = parameters.lineAtlas.getDashPosition(evaluated.get<LineDasharray>().from, cap);
- LinePatternPos posB = parameters.lineAtlas.getDashPosition(evaluated.get<LineDasharray>().to, cap);
+ const LinePatternCap cap =
+ bucket.layout.get<LineCap>() == LineCapType::Round ? LinePatternCap::Round : LinePatternCap::Square;
+ const auto& dashPatternTexture = parameters.lineAtlas.getDashPatternTexture(
+ evaluated.get<LineDasharray>().from, evaluated.get<LineDasharray>().to, cap);
draw(parameters.programs.getLineLayerPrograms().lineSDF,
- LineSDFProgram::layoutUniformValues(
- evaluated,
- parameters.pixelRatio,
- tile,
- parameters.state,
- parameters.pixelsToGLUnits,
- posA,
- posB,
- crossfade,
- parameters.lineAtlas.getSize().width),
- {},
- {},
- LineSDFProgram::TextureBindings{
- parameters.lineAtlas.textureBinding(),
- });
+ LineSDFProgram::layoutUniformValues(evaluated,
+ parameters.pixelRatio,
+ tile,
+ parameters.state,
+ parameters.pixelsToGLUnits,
+ dashPatternTexture.getFrom(),
+ dashPatternTexture.getTo(),
+ crossfade,
+ dashPatternTexture.getSize().width),
+ {},
+ {},
+ LineSDFProgram::TextureBindings{
+ dashPatternTexture.textureBinding(),
+ });
} else if (!unevaluated.get<LinePattern>().isUndefined()) {
const auto& linePatternValue = evaluated.get<LinePattern>().constantOr(Faded<std::basic_string<char>>{ "", ""});
diff --git a/src/mbgl/renderer/paint_property_binder.hpp b/src/mbgl/renderer/paint_property_binder.hpp
index 1a36f8a2e5..db9f61411a 100644
--- a/src/mbgl/renderer/paint_property_binder.hpp
+++ b/src/mbgl/renderer/paint_property_binder.hpp
@@ -370,11 +370,10 @@ public:
}
std::tuple<float> interpolationFactor(float currentZoom) const override {
- if (expression.useIntegerZoom) {
- return std::tuple<float> { expression.interpolationFactor(zoomRange, std::floor(currentZoom)) };
- } else {
- return std::tuple<float> { expression.interpolationFactor(zoomRange, currentZoom) };
- }
+ const float possiblyRoundedZoom = expression.useIntegerZoom ? std::floor(currentZoom) : currentZoom;
+
+ return std::tuple<float>{
+ ::fmax(0.0, ::fmin(1.0, expression.interpolationFactor(zoomRange, possiblyRoundedZoom)))};
}
std::tuple<T> uniformValue(const PossiblyEvaluatedPropertyValue<T>& currentValue) const override {
diff --git a/src/mbgl/renderer/render_orchestrator.cpp b/src/mbgl/renderer/render_orchestrator.cpp
index 28f6ab3199..e13a439ae4 100644
--- a/src/mbgl/renderer/render_orchestrator.cpp
+++ b/src/mbgl/renderer/render_orchestrator.cpp
@@ -103,19 +103,17 @@ public:
} // namespace
-RenderOrchestrator::RenderOrchestrator(
- bool backgroundLayerAsColor_,
- optional<std::string> localFontFamily_)
- : observer(&nullObserver())
- , glyphManager(std::make_unique<GlyphManager>(std::make_unique<LocalGlyphRasterizer>(std::move(localFontFamily_))))
- , imageManager(std::make_unique<ImageManager>())
- , lineAtlas(std::make_unique<LineAtlas>(Size{ 256, 512 }))
- , patternAtlas(std::make_unique<PatternAtlas>())
- , imageImpls(makeMutable<std::vector<Immutable<style::Image::Impl>>>())
- , sourceImpls(makeMutable<std::vector<Immutable<style::Source::Impl>>>())
- , layerImpls(makeMutable<std::vector<Immutable<style::Layer::Impl>>>())
- , renderLight(makeMutable<Light::Impl>())
- , backgroundLayerAsColor(backgroundLayerAsColor_) {
+RenderOrchestrator::RenderOrchestrator(bool backgroundLayerAsColor_, optional<std::string> localFontFamily_)
+ : observer(&nullObserver()),
+ glyphManager(std::make_unique<GlyphManager>(std::make_unique<LocalGlyphRasterizer>(std::move(localFontFamily_)))),
+ imageManager(std::make_unique<ImageManager>()),
+ lineAtlas(std::make_unique<LineAtlas>()),
+ patternAtlas(std::make_unique<PatternAtlas>()),
+ imageImpls(makeMutable<std::vector<Immutable<style::Image::Impl>>>()),
+ sourceImpls(makeMutable<std::vector<Immutable<style::Source::Impl>>>()),
+ layerImpls(makeMutable<std::vector<Immutable<style::Layer::Impl>>>()),
+ renderLight(makeMutable<Light::Impl>()),
+ backgroundLayerAsColor(backgroundLayerAsColor_) {
glyphManager->setObserver(this);
imageManager->setObserver(this);
}
@@ -220,6 +218,7 @@ std::unique_ptr<RenderTree> RenderOrchestrator::createRenderTree(const UpdatePar
const LayerDifference layerDiff = diffLayers(layerImpls, updateParameters.layers);
layerImpls = updateParameters.layers;
+ const bool layersAddedOrRemoved = !layerDiff.added.empty() || !layerDiff.removed.empty();
// Remove render layers for removed layers.
for (const auto& entry : layerDiff.removed) {
@@ -238,20 +237,31 @@ std::unique_ptr<RenderTree> RenderOrchestrator::createRenderTree(const UpdatePar
renderLayers.at(entry.first)->transition(transitionParameters, entry.second.after);
}
- if (!layerDiff.removed.empty() || !layerDiff.added.empty() || !layerDiff.changed.empty()) {
- glyphManager->evict(fontStacks(*updateParameters.layers));
+ if (layersAddedOrRemoved) {
+ orderedLayers.clear();
+ orderedLayers.reserve(layerImpls->size());
+ for (const auto& layerImpl : *layerImpls) {
+ RenderLayer* layer = renderLayers.at(layerImpl->id).get();
+ assert(layer);
+ orderedLayers.emplace_back(*layer);
+ }
+ }
+ assert(orderedLayers.size() == renderLayers.size());
+
+ if (layersAddedOrRemoved || !layerDiff.changed.empty()) {
+ glyphManager->evict(fontStacks(*layerImpls));
}
// Update layers for class and zoom changes.
std::unordered_set<std::string> constantsMaskChanged;
- for (const auto& entry : renderLayers) {
- RenderLayer& layer = *entry.second;
- const bool layerAddedOrChanged = layerDiff.added.count(entry.first) || layerDiff.changed.count(entry.first);
+ for (RenderLayer& layer : orderedLayers) {
+ const std::string& id = layer.getID();
+ const bool layerAddedOrChanged = layerDiff.added.count(id) || layerDiff.changed.count(id);
if (layerAddedOrChanged || zoomChanged || layer.hasTransition() || layer.hasCrossfade()) {
auto previousMask = layer.evaluatedProperties->constantsMask();
layer.evaluate(evaluationParameters);
if (previousMask != layer.evaluatedProperties->constantsMask()) {
- constantsMaskChanged.insert(layer.getID());
+ constantsMaskChanged.insert(id);
}
}
}
@@ -281,7 +291,7 @@ std::unique_ptr<RenderTree> RenderOrchestrator::createRenderTree(const UpdatePar
renderLight.getEvaluated());
std::set<LayerRenderItem> layerRenderItems;
- std::vector<std::reference_wrapper<RenderLayer>> layersNeedPlacement;
+ layersNeedPlacement.clear();
auto renderItemsEmplaceHint = layerRenderItems.begin();
// Reserve size for filteredLayersForSource if there are sources.
@@ -293,27 +303,26 @@ std::unique_ptr<RenderTree> RenderOrchestrator::createRenderTree(const UpdatePar
for (const auto& sourceImpl : *sourceImpls) {
RenderSource* source = renderSources.at(sourceImpl->id).get();
bool sourceNeedsRendering = false;
- bool sourceNeedsRelayout = false;
-
- uint32_t index = 0u;
- const auto begin = layerImpls->begin();
- const auto end = layerImpls->end();
- for (auto it = begin; it != end; ++it, ++index) {
- const Immutable<Layer::Impl>& layerImpl = *it;
- RenderLayer* layer = getRenderLayer(layerImpl->id);
- const auto* layerInfo = layerImpl->getTypeInfo();
- const bool layerIsVisible = layer->baseImpl->visibility != style::VisibilityType::None;
- const bool zoomFitsLayer = layer->supportsZoom(zoomHistory.lastZoom);
+ bool sourceNeedsRelayout = false;
+
+ for (uint32_t index = 0u; index < orderedLayers.size(); ++index) {
+ RenderLayer& layer = orderedLayers[index];
+ const auto* layerInfo = layer.baseImpl->getTypeInfo();
+ const bool layerIsVisible = layer.baseImpl->visibility != style::VisibilityType::None;
+ const bool zoomFitsLayer = layer.supportsZoom(zoomHistory.lastZoom);
renderTreeParameters->has3D |= (layerInfo->pass3d == LayerTypeInfo::Pass3D::Required);
if (layerInfo->source != LayerTypeInfo::Source::NotRequired) {
- if (layerImpl->source == sourceImpl->id) {
- sourceNeedsRelayout = (sourceNeedsRelayout || hasImageDiff || constantsMaskChanged.count(layerImpl->id) || hasLayoutDifference(layerDiff, layerImpl->id));
+ if (layer.baseImpl->source == sourceImpl->id) {
+ const std::string& layerId = layer.getID();
+ sourceNeedsRelayout = (sourceNeedsRelayout || hasImageDiff || constantsMaskChanged.count(layerId) ||
+ hasLayoutDifference(layerDiff, layerId));
if (layerIsVisible) {
- filteredLayersForSource.push_back(layer->evaluatedProperties);
+ filteredLayersForSource.push_back(layer.evaluatedProperties);
if (zoomFitsLayer) {
sourceNeedsRendering = true;
- renderItemsEmplaceHint = layerRenderItems.emplace_hint(renderItemsEmplaceHint, *layer, source, index);
+ renderItemsEmplaceHint =
+ layerRenderItems.emplace_hint(renderItemsEmplaceHint, layer, source, index);
}
}
}
@@ -322,14 +331,14 @@ std::unique_ptr<RenderTree> RenderOrchestrator::createRenderTree(const UpdatePar
// Handle layers without source.
if (layerIsVisible && zoomFitsLayer && sourceImpl.get() == sourceImpls->at(0).get()) {
- if (backgroundLayerAsColor && layerImpl.get() == layerImpls->at(0).get()) {
- const auto& solidBackground = layer->getSolidBackground();
+ if (backgroundLayerAsColor && layer.baseImpl == layerImpls->front()) {
+ const auto& solidBackground = layer.getSolidBackground();
if (solidBackground) {
renderTreeParameters->backgroundColor = *solidBackground;
continue; // This layer is shown with background color, and it shall not be added to render items.
}
}
- renderItemsEmplaceHint = layerRenderItems.emplace_hint(renderItemsEmplaceHint, *layer, nullptr, index);
+ renderItemsEmplaceHint = layerRenderItems.emplace_hint(renderItemsEmplaceHint, layer, nullptr, index);
}
}
source->update(sourceImpl,
diff --git a/src/mbgl/renderer/render_orchestrator.hpp b/src/mbgl/renderer/render_orchestrator.hpp
index 9b63498a2a..c2b44c2792 100644
--- a/src/mbgl/renderer/render_orchestrator.hpp
+++ b/src/mbgl/renderer/render_orchestrator.hpp
@@ -127,9 +127,11 @@ private:
const bool backgroundLayerAsColor;
bool contextLost = false;
- // Vector with reserved capacity of layerImpls->size() to avoid reallocation
+ // Vectors with reserved capacity of layerImpls->size() to avoid reallocation
// on each frame.
std::vector<Immutable<style::LayerProperties>> filteredLayersForSource;
+ std::vector<std::reference_wrapper<RenderLayer>> orderedLayers;
+ std::vector<std::reference_wrapper<RenderLayer>> layersNeedPlacement;
};
} // namespace mbgl
diff --git a/src/mbgl/renderer/tile_pyramid.cpp b/src/mbgl/renderer/tile_pyramid.cpp
index 2bf6e2e1a9..586d3b5a8a 100644
--- a/src/mbgl/renderer/tile_pyramid.cpp
+++ b/src/mbgl/renderer/tile_pyramid.cpp
@@ -68,6 +68,10 @@ void TilePyramid::update(const std::vector<Immutable<style::LayerProperties>>& l
if (!needsRendering) {
if (!needsRelayout) {
for (auto& entry : tiles) {
+ // These tiles are invisible, we set optional necessity
+ // for them and thus suppress network requests on
+ // tiles expiration (see `OnlineFileRequest`).
+ entry.second->setNecessity(TileNecessity::Optional);
cache.add(entry.first, std::move(entry.second));
}
}
diff --git a/src/mbgl/style/expression/assertion.cpp b/src/mbgl/style/expression/assertion.cpp
index 8e5a8b555d..17f8925511 100644
--- a/src/mbgl/style/expression/assertion.cpp
+++ b/src/mbgl/style/expression/assertion.cpp
@@ -16,12 +16,12 @@ Assertion::Assertion(type::Type type_, std::vector<std::unique_ptr<Expression>>
}
ParseResult Assertion::parse(const Convertible& value, ParsingContext& ctx) {
- static std::unordered_map<std::string, type::Type> types {
+ static std::unordered_map<std::string, type::Type> types{
{"string", type::String},
+ {"image", type::String}, // TODO: replace once we implement image expressions
{"number", type::Number},
{"boolean", type::Boolean},
- {"object", type::Object}
- };
+ {"object", type::Object}};
std::size_t length = arrayLength(value);
diff --git a/src/mbgl/style/expression/collator.cpp b/src/mbgl/style/expression/collator.cpp
new file mode 100644
index 0000000000..185d713150
--- /dev/null
+++ b/src/mbgl/style/expression/collator.cpp
@@ -0,0 +1,24 @@
+#include <mbgl/style/expression/collator.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+Collator::Collator(bool caseSensitive, bool diacriticSensitive, optional<std::string> locale)
+ : collator(platform::Collator(caseSensitive, diacriticSensitive, std::move(locale))) {}
+
+bool Collator::operator==(const Collator& other) const {
+ return collator == other.collator;
+}
+
+int Collator::compare(const std::string& lhs, const std::string& rhs) const {
+ return collator.compare(lhs, rhs);
+}
+
+std::string Collator::resolvedLocale() const {
+ return collator.resolvedLocale();
+}
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/expression/formatted.cpp b/src/mbgl/style/expression/formatted.cpp
index 5d45806ecb..4591a50ed1 100644
--- a/src/mbgl/style/expression/formatted.cpp
+++ b/src/mbgl/style/expression/formatted.cpp
@@ -35,6 +35,35 @@ std::string Formatted::toString() const {
return result;
}
+mbgl::Value Formatted::toObject() const {
+ mapbox::base::ValueObject result;
+ mapbox::base::ValueArray sectionValues;
+ sectionValues.reserve(sections.size());
+ for (const auto& section : sections) {
+ mapbox::base::ValueObject serializedSection;
+ serializedSection.emplace("text", section.text);
+ if (section.fontScale) {
+ serializedSection.emplace("scale", *section.fontScale);
+ } else {
+ serializedSection.emplace("scale", NullValue());
+ }
+ if (section.fontStack) {
+ std::string fontStackString;
+ serializedSection.emplace("fontStack", fontStackToString(*section.fontStack));
+ } else {
+ serializedSection.emplace("fontStack", NullValue());
+ }
+ if (section.textColor) {
+ serializedSection.emplace("textColor", section.textColor->toObject());
+ } else {
+ serializedSection.emplace("textColor", NullValue());
+ }
+ sectionValues.emplace_back(serializedSection);
+ }
+ result.emplace("sections", std::move(sectionValues));
+ return result;
+}
+
} // namespace expression
namespace conversion {
diff --git a/src/mbgl/style/expression/number_format.cpp b/src/mbgl/style/expression/number_format.cpp
index e31a9ce398..c2de032ff4 100644
--- a/src/mbgl/style/expression/number_format.cpp
+++ b/src/mbgl/style/expression/number_format.cpp
@@ -1,6 +1,6 @@
-#include <mbgl/style/expression/number_format.hpp>
+#include <mbgl/i18n/number_format.hpp>
#include <mbgl/style/conversion_impl.hpp>
-#include <mbgl/util/platform.hpp>
+#include <mbgl/style/expression/number_format.hpp>
namespace mbgl {
namespace style {
diff --git a/src/mbgl/style/expression/parsing_context.cpp b/src/mbgl/style/expression/parsing_context.cpp
index 6ce3a9bfaa..699190608b 100644
--- a/src/mbgl/style/expression/parsing_context.cpp
+++ b/src/mbgl/style/expression/parsing_context.cpp
@@ -100,38 +100,40 @@ ParseResult ParsingContext::parse(const Convertible& value, std::size_t index_,
}
using ParseFunction = ParseResult (*)(const conversion::Convertible&, ParsingContext&);
-MAPBOX_ETERNAL_CONSTEXPR const auto expressionRegistry = mapbox::eternal::hash_map<mapbox::eternal::string, ParseFunction>({
- {"==", parseComparison},
- {"!=", parseComparison},
- {">", parseComparison},
- {"<", parseComparison},
- {">=", parseComparison},
- {"<=", parseComparison},
- {"all", All::parse},
- {"any", Any::parse},
- {"array", Assertion::parse},
- {"at", At::parse},
- {"boolean", Assertion::parse},
- {"case", Case::parse},
- {"coalesce", Coalesce::parse},
- {"collator", CollatorExpression::parse},
- {"format", FormatExpression::parse},
- {"interpolate", parseInterpolate},
- {"length", Length::parse},
- {"let", Let::parse},
- {"literal", Literal::parse},
- {"match", parseMatch},
- {"number", Assertion::parse},
- {"number-format", NumberFormat::parse},
- {"object", Assertion::parse},
- {"step", Step::parse},
- {"string", Assertion::parse},
- {"to-boolean", Coercion::parse},
- {"to-color", Coercion::parse},
- {"to-number", Coercion::parse},
- {"to-string", Coercion::parse},
- {"var", Var::parse},
-});
+MAPBOX_ETERNAL_CONSTEXPR const auto expressionRegistry =
+ mapbox::eternal::hash_map<mapbox::eternal::string, ParseFunction>({
+ {"==", parseComparison},
+ {"!=", parseComparison},
+ {">", parseComparison},
+ {"<", parseComparison},
+ {">=", parseComparison},
+ {"<=", parseComparison},
+ {"all", All::parse},
+ {"any", Any::parse},
+ {"array", Assertion::parse},
+ {"at", At::parse},
+ {"boolean", Assertion::parse},
+ {"case", Case::parse},
+ {"coalesce", Coalesce::parse},
+ {"collator", CollatorExpression::parse},
+ {"format", FormatExpression::parse},
+ {"image", Assertion::parse}, // TODO: replace once we implement image expressions
+ {"interpolate", parseInterpolate},
+ {"length", Length::parse},
+ {"let", Let::parse},
+ {"literal", Literal::parse},
+ {"match", parseMatch},
+ {"number", Assertion::parse},
+ {"number-format", NumberFormat::parse},
+ {"object", Assertion::parse},
+ {"step", Step::parse},
+ {"string", Assertion::parse},
+ {"to-boolean", Coercion::parse},
+ {"to-color", Coercion::parse},
+ {"to-number", Coercion::parse},
+ {"to-string", Coercion::parse},
+ {"var", Var::parse},
+ });
bool isExpression(const std::string& name) {
return expressionRegistry.contains(name.c_str());
diff --git a/src/mbgl/style/layers/background_layer.cpp b/src/mbgl/style/layers/background_layer.cpp
index 7a186a3354..9187784452 100644
--- a/src/mbgl/style/layers/background_layer.cpp
+++ b/src/mbgl/style/layers/background_layer.cpp
@@ -1,5 +1,3 @@
-// clang-format off
-
// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`.
#include <mbgl/style/layers/background_layer.hpp>
@@ -148,28 +146,37 @@ TransitionOptions BackgroundLayer::getBackgroundPatternTransition() const {
using namespace conversion;
+namespace {
+
+enum class Property : uint8_t {
+ BackgroundColor,
+ BackgroundOpacity,
+ BackgroundPattern,
+ BackgroundColorTransition,
+ BackgroundOpacityTransition,
+ BackgroundPatternTransition,
+};
+
+template <typename T>
+constexpr uint8_t toUint8(T t) noexcept {
+ return uint8_t(mbgl::underlying_type(t));
+}
+
+MAPBOX_ETERNAL_CONSTEXPR const auto layerProperties = mapbox::eternal::hash_map<mapbox::eternal::string, uint8_t>(
+ {{"background-color", toUint8(Property::BackgroundColor)},
+ {"background-opacity", toUint8(Property::BackgroundOpacity)},
+ {"background-pattern", toUint8(Property::BackgroundPattern)},
+ {"background-color-transition", toUint8(Property::BackgroundColorTransition)},
+ {"background-opacity-transition", toUint8(Property::BackgroundOpacityTransition)},
+ {"background-pattern-transition", toUint8(Property::BackgroundPatternTransition)}});
+
+constexpr uint8_t lastPaintPropertyIndex = toUint8(Property::BackgroundPatternTransition);
+} // namespace
+
optional<Error> BackgroundLayer::setPaintProperty(const std::string& name, const Convertible& value) {
- enum class Property {
- BackgroundColor,
- BackgroundOpacity,
- BackgroundPattern,
- BackgroundColorTransition,
- BackgroundOpacityTransition,
- BackgroundPatternTransition,
- };
-
- MAPBOX_ETERNAL_CONSTEXPR const auto properties = mapbox::eternal::hash_map<mapbox::eternal::string, uint8_t>({
- { "background-color", mbgl::underlying_type(Property::BackgroundColor) },
- { "background-opacity", mbgl::underlying_type(Property::BackgroundOpacity) },
- { "background-pattern", mbgl::underlying_type(Property::BackgroundPattern) },
- { "background-color-transition", mbgl::underlying_type(Property::BackgroundColorTransition) },
- { "background-opacity-transition", mbgl::underlying_type(Property::BackgroundOpacityTransition) },
- { "background-pattern-transition", mbgl::underlying_type(Property::BackgroundPatternTransition) }
- });
-
- const auto it = properties.find(name.c_str());
- if (it == properties.end()) {
- return Error { "layer doesn't support this property" };
+ const auto it = layerProperties.find(name.c_str());
+ if (it == layerProperties.end() || it->second > lastPaintPropertyIndex) {
+ return Error{"layer doesn't support this property"};
}
auto property = static_cast<Property>(it->second);
@@ -217,24 +224,46 @@ optional<Error> BackgroundLayer::setPaintProperty(const std::string& name, const
if (!transition) {
return error;
}
-
+
if (property == Property::BackgroundColorTransition) {
setBackgroundColorTransition(*transition);
return nullopt;
}
-
+
if (property == Property::BackgroundOpacityTransition) {
setBackgroundOpacityTransition(*transition);
return nullopt;
}
-
+
if (property == Property::BackgroundPatternTransition) {
setBackgroundPatternTransition(*transition);
return nullopt;
}
-
- return Error { "layer doesn't support this property" };
+ return Error{"layer doesn't support this property"};
+}
+
+StyleProperty BackgroundLayer::getProperty(const std::string& name) const {
+ const auto it = layerProperties.find(name.c_str());
+ if (it == layerProperties.end()) {
+ return {};
+ }
+
+ switch (static_cast<Property>(it->second)) {
+ case Property::BackgroundColor:
+ return makeStyleProperty(getBackgroundColor());
+ case Property::BackgroundOpacity:
+ return makeStyleProperty(getBackgroundOpacity());
+ case Property::BackgroundPattern:
+ return makeStyleProperty(getBackgroundPattern());
+ case Property::BackgroundColorTransition:
+ return makeStyleProperty(getBackgroundColorTransition());
+ case Property::BackgroundOpacityTransition:
+ return makeStyleProperty(getBackgroundOpacityTransition());
+ case Property::BackgroundPatternTransition:
+ return makeStyleProperty(getBackgroundPatternTransition());
+ }
+ return {};
}
optional<Error> BackgroundLayer::setLayoutProperty(const std::string& name, const Convertible& value) {
@@ -251,5 +280,3 @@ Mutable<Layer::Impl> BackgroundLayer::mutableBaseImpl() const {
} // namespace style
} // namespace mbgl
-
-// clang-format on
diff --git a/src/mbgl/style/layers/circle_layer.cpp b/src/mbgl/style/layers/circle_layer.cpp
index 2f68bcccf3..145d76a9a8 100644
--- a/src/mbgl/style/layers/circle_layer.cpp
+++ b/src/mbgl/style/layers/circle_layer.cpp
@@ -1,5 +1,3 @@
-// clang-format off
-
// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`.
#include <mbgl/style/layers/circle_layer.hpp>
@@ -364,60 +362,69 @@ TransitionOptions CircleLayer::getCircleTranslateAnchorTransition() const {
using namespace conversion;
+namespace {
+
+enum class Property : uint8_t {
+ CircleBlur,
+ CircleColor,
+ CircleOpacity,
+ CirclePitchAlignment,
+ CirclePitchScale,
+ CircleRadius,
+ CircleStrokeColor,
+ CircleStrokeOpacity,
+ CircleStrokeWidth,
+ CircleTranslate,
+ CircleTranslateAnchor,
+ CircleBlurTransition,
+ CircleColorTransition,
+ CircleOpacityTransition,
+ CirclePitchAlignmentTransition,
+ CirclePitchScaleTransition,
+ CircleRadiusTransition,
+ CircleStrokeColorTransition,
+ CircleStrokeOpacityTransition,
+ CircleStrokeWidthTransition,
+ CircleTranslateTransition,
+ CircleTranslateAnchorTransition,
+};
+
+template <typename T>
+constexpr uint8_t toUint8(T t) noexcept {
+ return uint8_t(mbgl::underlying_type(t));
+}
+
+MAPBOX_ETERNAL_CONSTEXPR const auto layerProperties = mapbox::eternal::hash_map<mapbox::eternal::string, uint8_t>(
+ {{"circle-blur", toUint8(Property::CircleBlur)},
+ {"circle-color", toUint8(Property::CircleColor)},
+ {"circle-opacity", toUint8(Property::CircleOpacity)},
+ {"circle-pitch-alignment", toUint8(Property::CirclePitchAlignment)},
+ {"circle-pitch-scale", toUint8(Property::CirclePitchScale)},
+ {"circle-radius", toUint8(Property::CircleRadius)},
+ {"circle-stroke-color", toUint8(Property::CircleStrokeColor)},
+ {"circle-stroke-opacity", toUint8(Property::CircleStrokeOpacity)},
+ {"circle-stroke-width", toUint8(Property::CircleStrokeWidth)},
+ {"circle-translate", toUint8(Property::CircleTranslate)},
+ {"circle-translate-anchor", toUint8(Property::CircleTranslateAnchor)},
+ {"circle-blur-transition", toUint8(Property::CircleBlurTransition)},
+ {"circle-color-transition", toUint8(Property::CircleColorTransition)},
+ {"circle-opacity-transition", toUint8(Property::CircleOpacityTransition)},
+ {"circle-pitch-alignment-transition", toUint8(Property::CirclePitchAlignmentTransition)},
+ {"circle-pitch-scale-transition", toUint8(Property::CirclePitchScaleTransition)},
+ {"circle-radius-transition", toUint8(Property::CircleRadiusTransition)},
+ {"circle-stroke-color-transition", toUint8(Property::CircleStrokeColorTransition)},
+ {"circle-stroke-opacity-transition", toUint8(Property::CircleStrokeOpacityTransition)},
+ {"circle-stroke-width-transition", toUint8(Property::CircleStrokeWidthTransition)},
+ {"circle-translate-transition", toUint8(Property::CircleTranslateTransition)},
+ {"circle-translate-anchor-transition", toUint8(Property::CircleTranslateAnchorTransition)}});
+
+constexpr uint8_t lastPaintPropertyIndex = toUint8(Property::CircleTranslateAnchorTransition);
+} // namespace
+
optional<Error> CircleLayer::setPaintProperty(const std::string& name, const Convertible& value) {
- enum class Property {
- CircleBlur,
- CircleColor,
- CircleOpacity,
- CirclePitchAlignment,
- CirclePitchScale,
- CircleRadius,
- CircleStrokeColor,
- CircleStrokeOpacity,
- CircleStrokeWidth,
- CircleTranslate,
- CircleTranslateAnchor,
- CircleBlurTransition,
- CircleColorTransition,
- CircleOpacityTransition,
- CirclePitchAlignmentTransition,
- CirclePitchScaleTransition,
- CircleRadiusTransition,
- CircleStrokeColorTransition,
- CircleStrokeOpacityTransition,
- CircleStrokeWidthTransition,
- CircleTranslateTransition,
- CircleTranslateAnchorTransition,
- };
-
- MAPBOX_ETERNAL_CONSTEXPR const auto properties = mapbox::eternal::hash_map<mapbox::eternal::string, uint8_t>({
- { "circle-blur", mbgl::underlying_type(Property::CircleBlur) },
- { "circle-color", mbgl::underlying_type(Property::CircleColor) },
- { "circle-opacity", mbgl::underlying_type(Property::CircleOpacity) },
- { "circle-pitch-alignment", mbgl::underlying_type(Property::CirclePitchAlignment) },
- { "circle-pitch-scale", mbgl::underlying_type(Property::CirclePitchScale) },
- { "circle-radius", mbgl::underlying_type(Property::CircleRadius) },
- { "circle-stroke-color", mbgl::underlying_type(Property::CircleStrokeColor) },
- { "circle-stroke-opacity", mbgl::underlying_type(Property::CircleStrokeOpacity) },
- { "circle-stroke-width", mbgl::underlying_type(Property::CircleStrokeWidth) },
- { "circle-translate", mbgl::underlying_type(Property::CircleTranslate) },
- { "circle-translate-anchor", mbgl::underlying_type(Property::CircleTranslateAnchor) },
- { "circle-blur-transition", mbgl::underlying_type(Property::CircleBlurTransition) },
- { "circle-color-transition", mbgl::underlying_type(Property::CircleColorTransition) },
- { "circle-opacity-transition", mbgl::underlying_type(Property::CircleOpacityTransition) },
- { "circle-pitch-alignment-transition", mbgl::underlying_type(Property::CirclePitchAlignmentTransition) },
- { "circle-pitch-scale-transition", mbgl::underlying_type(Property::CirclePitchScaleTransition) },
- { "circle-radius-transition", mbgl::underlying_type(Property::CircleRadiusTransition) },
- { "circle-stroke-color-transition", mbgl::underlying_type(Property::CircleStrokeColorTransition) },
- { "circle-stroke-opacity-transition", mbgl::underlying_type(Property::CircleStrokeOpacityTransition) },
- { "circle-stroke-width-transition", mbgl::underlying_type(Property::CircleStrokeWidthTransition) },
- { "circle-translate-transition", mbgl::underlying_type(Property::CircleTranslateTransition) },
- { "circle-translate-anchor-transition", mbgl::underlying_type(Property::CircleTranslateAnchorTransition) }
- });
-
- const auto it = properties.find(name.c_str());
- if (it == properties.end()) {
- return Error { "layer doesn't support this property" };
+ const auto it = layerProperties.find(name.c_str());
+ if (it == layerProperties.end() || it->second > lastPaintPropertyIndex) {
+ return Error{"layer doesn't support this property"};
}
auto property = static_cast<Property>(it->second);
@@ -530,64 +537,118 @@ optional<Error> CircleLayer::setPaintProperty(const std::string& name, const Con
if (!transition) {
return error;
}
-
+
if (property == Property::CircleBlurTransition) {
setCircleBlurTransition(*transition);
return nullopt;
}
-
+
if (property == Property::CircleColorTransition) {
setCircleColorTransition(*transition);
return nullopt;
}
-
+
if (property == Property::CircleOpacityTransition) {
setCircleOpacityTransition(*transition);
return nullopt;
}
-
+
if (property == Property::CirclePitchAlignmentTransition) {
setCirclePitchAlignmentTransition(*transition);
return nullopt;
}
-
+
if (property == Property::CirclePitchScaleTransition) {
setCirclePitchScaleTransition(*transition);
return nullopt;
}
-
+
if (property == Property::CircleRadiusTransition) {
setCircleRadiusTransition(*transition);
return nullopt;
}
-
+
if (property == Property::CircleStrokeColorTransition) {
setCircleStrokeColorTransition(*transition);
return nullopt;
}
-
+
if (property == Property::CircleStrokeOpacityTransition) {
setCircleStrokeOpacityTransition(*transition);
return nullopt;
}
-
+
if (property == Property::CircleStrokeWidthTransition) {
setCircleStrokeWidthTransition(*transition);
return nullopt;
}
-
+
if (property == Property::CircleTranslateTransition) {
setCircleTranslateTransition(*transition);
return nullopt;
}
-
+
if (property == Property::CircleTranslateAnchorTransition) {
setCircleTranslateAnchorTransition(*transition);
return nullopt;
}
-
- return Error { "layer doesn't support this property" };
+ return Error{"layer doesn't support this property"};
+}
+
+StyleProperty CircleLayer::getProperty(const std::string& name) const {
+ const auto it = layerProperties.find(name.c_str());
+ if (it == layerProperties.end()) {
+ return {};
+ }
+
+ switch (static_cast<Property>(it->second)) {
+ case Property::CircleBlur:
+ return makeStyleProperty(getCircleBlur());
+ case Property::CircleColor:
+ return makeStyleProperty(getCircleColor());
+ case Property::CircleOpacity:
+ return makeStyleProperty(getCircleOpacity());
+ case Property::CirclePitchAlignment:
+ return makeStyleProperty(getCirclePitchAlignment());
+ case Property::CirclePitchScale:
+ return makeStyleProperty(getCirclePitchScale());
+ case Property::CircleRadius:
+ return makeStyleProperty(getCircleRadius());
+ case Property::CircleStrokeColor:
+ return makeStyleProperty(getCircleStrokeColor());
+ case Property::CircleStrokeOpacity:
+ return makeStyleProperty(getCircleStrokeOpacity());
+ case Property::CircleStrokeWidth:
+ return makeStyleProperty(getCircleStrokeWidth());
+ case Property::CircleTranslate:
+ return makeStyleProperty(getCircleTranslate());
+ case Property::CircleTranslateAnchor:
+ return makeStyleProperty(getCircleTranslateAnchor());
+ case Property::CircleBlurTransition:
+ return makeStyleProperty(getCircleBlurTransition());
+ case Property::CircleColorTransition:
+ return makeStyleProperty(getCircleColorTransition());
+ case Property::CircleOpacityTransition:
+ return makeStyleProperty(getCircleOpacityTransition());
+ case Property::CirclePitchAlignmentTransition:
+ return makeStyleProperty(getCirclePitchAlignmentTransition());
+ case Property::CirclePitchScaleTransition:
+ return makeStyleProperty(getCirclePitchScaleTransition());
+ case Property::CircleRadiusTransition:
+ return makeStyleProperty(getCircleRadiusTransition());
+ case Property::CircleStrokeColorTransition:
+ return makeStyleProperty(getCircleStrokeColorTransition());
+ case Property::CircleStrokeOpacityTransition:
+ return makeStyleProperty(getCircleStrokeOpacityTransition());
+ case Property::CircleStrokeWidthTransition:
+ return makeStyleProperty(getCircleStrokeWidthTransition());
+ case Property::CircleTranslateTransition:
+ return makeStyleProperty(getCircleTranslateTransition());
+ case Property::CircleTranslateAnchorTransition:
+ return makeStyleProperty(getCircleTranslateAnchorTransition());
+ }
+ return {};
}
optional<Error> CircleLayer::setLayoutProperty(const std::string& name, const Convertible& value) {
@@ -604,5 +665,3 @@ Mutable<Layer::Impl> CircleLayer::mutableBaseImpl() const {
} // namespace style
} // namespace mbgl
-
-// clang-format on
diff --git a/src/mbgl/style/layers/custom_layer.cpp b/src/mbgl/style/layers/custom_layer.cpp
index 8b9e17ea25..e13cf0069a 100644
--- a/src/mbgl/style/layers/custom_layer.cpp
+++ b/src/mbgl/style/layers/custom_layer.cpp
@@ -47,6 +47,10 @@ optional<Error> CustomLayer::setLayoutProperty(const std::string&, const Convert
return Error { "layer doesn't support this property" };
}
+StyleProperty CustomLayer::getProperty(const std::string&) const {
+ return {};
+}
+
Mutable<Layer::Impl> CustomLayer::mutableBaseImpl() const {
return staticMutableCast<Layer::Impl>(mutableImpl());
}
diff --git a/src/mbgl/style/layers/fill_extrusion_layer.cpp b/src/mbgl/style/layers/fill_extrusion_layer.cpp
index 2686f7d044..87e196926f 100644
--- a/src/mbgl/style/layers/fill_extrusion_layer.cpp
+++ b/src/mbgl/style/layers/fill_extrusion_layer.cpp
@@ -1,5 +1,3 @@
-// clang-format off
-
// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`.
#include <mbgl/style/layers/fill_extrusion_layer.hpp>
@@ -283,48 +281,57 @@ TransitionOptions FillExtrusionLayer::getFillExtrusionVerticalGradientTransition
using namespace conversion;
+namespace {
+
+enum class Property : uint8_t {
+ FillExtrusionBase,
+ FillExtrusionColor,
+ FillExtrusionHeight,
+ FillExtrusionOpacity,
+ FillExtrusionPattern,
+ FillExtrusionTranslate,
+ FillExtrusionTranslateAnchor,
+ FillExtrusionVerticalGradient,
+ FillExtrusionBaseTransition,
+ FillExtrusionColorTransition,
+ FillExtrusionHeightTransition,
+ FillExtrusionOpacityTransition,
+ FillExtrusionPatternTransition,
+ FillExtrusionTranslateTransition,
+ FillExtrusionTranslateAnchorTransition,
+ FillExtrusionVerticalGradientTransition,
+};
+
+template <typename T>
+constexpr uint8_t toUint8(T t) noexcept {
+ return uint8_t(mbgl::underlying_type(t));
+}
+
+MAPBOX_ETERNAL_CONSTEXPR const auto layerProperties = mapbox::eternal::hash_map<mapbox::eternal::string, uint8_t>(
+ {{"fill-extrusion-base", toUint8(Property::FillExtrusionBase)},
+ {"fill-extrusion-color", toUint8(Property::FillExtrusionColor)},
+ {"fill-extrusion-height", toUint8(Property::FillExtrusionHeight)},
+ {"fill-extrusion-opacity", toUint8(Property::FillExtrusionOpacity)},
+ {"fill-extrusion-pattern", toUint8(Property::FillExtrusionPattern)},
+ {"fill-extrusion-translate", toUint8(Property::FillExtrusionTranslate)},
+ {"fill-extrusion-translate-anchor", toUint8(Property::FillExtrusionTranslateAnchor)},
+ {"fill-extrusion-vertical-gradient", toUint8(Property::FillExtrusionVerticalGradient)},
+ {"fill-extrusion-base-transition", toUint8(Property::FillExtrusionBaseTransition)},
+ {"fill-extrusion-color-transition", toUint8(Property::FillExtrusionColorTransition)},
+ {"fill-extrusion-height-transition", toUint8(Property::FillExtrusionHeightTransition)},
+ {"fill-extrusion-opacity-transition", toUint8(Property::FillExtrusionOpacityTransition)},
+ {"fill-extrusion-pattern-transition", toUint8(Property::FillExtrusionPatternTransition)},
+ {"fill-extrusion-translate-transition", toUint8(Property::FillExtrusionTranslateTransition)},
+ {"fill-extrusion-translate-anchor-transition", toUint8(Property::FillExtrusionTranslateAnchorTransition)},
+ {"fill-extrusion-vertical-gradient-transition", toUint8(Property::FillExtrusionVerticalGradientTransition)}});
+
+constexpr uint8_t lastPaintPropertyIndex = toUint8(Property::FillExtrusionVerticalGradientTransition);
+} // namespace
+
optional<Error> FillExtrusionLayer::setPaintProperty(const std::string& name, const Convertible& value) {
- enum class Property {
- FillExtrusionBase,
- FillExtrusionColor,
- FillExtrusionHeight,
- FillExtrusionOpacity,
- FillExtrusionPattern,
- FillExtrusionTranslate,
- FillExtrusionTranslateAnchor,
- FillExtrusionVerticalGradient,
- FillExtrusionBaseTransition,
- FillExtrusionColorTransition,
- FillExtrusionHeightTransition,
- FillExtrusionOpacityTransition,
- FillExtrusionPatternTransition,
- FillExtrusionTranslateTransition,
- FillExtrusionTranslateAnchorTransition,
- FillExtrusionVerticalGradientTransition,
- };
-
- MAPBOX_ETERNAL_CONSTEXPR const auto properties = mapbox::eternal::hash_map<mapbox::eternal::string, uint8_t>({
- { "fill-extrusion-base", mbgl::underlying_type(Property::FillExtrusionBase) },
- { "fill-extrusion-color", mbgl::underlying_type(Property::FillExtrusionColor) },
- { "fill-extrusion-height", mbgl::underlying_type(Property::FillExtrusionHeight) },
- { "fill-extrusion-opacity", mbgl::underlying_type(Property::FillExtrusionOpacity) },
- { "fill-extrusion-pattern", mbgl::underlying_type(Property::FillExtrusionPattern) },
- { "fill-extrusion-translate", mbgl::underlying_type(Property::FillExtrusionTranslate) },
- { "fill-extrusion-translate-anchor", mbgl::underlying_type(Property::FillExtrusionTranslateAnchor) },
- { "fill-extrusion-vertical-gradient", mbgl::underlying_type(Property::FillExtrusionVerticalGradient) },
- { "fill-extrusion-base-transition", mbgl::underlying_type(Property::FillExtrusionBaseTransition) },
- { "fill-extrusion-color-transition", mbgl::underlying_type(Property::FillExtrusionColorTransition) },
- { "fill-extrusion-height-transition", mbgl::underlying_type(Property::FillExtrusionHeightTransition) },
- { "fill-extrusion-opacity-transition", mbgl::underlying_type(Property::FillExtrusionOpacityTransition) },
- { "fill-extrusion-pattern-transition", mbgl::underlying_type(Property::FillExtrusionPatternTransition) },
- { "fill-extrusion-translate-transition", mbgl::underlying_type(Property::FillExtrusionTranslateTransition) },
- { "fill-extrusion-translate-anchor-transition", mbgl::underlying_type(Property::FillExtrusionTranslateAnchorTransition) },
- { "fill-extrusion-vertical-gradient-transition", mbgl::underlying_type(Property::FillExtrusionVerticalGradientTransition) }
- });
-
- const auto it = properties.find(name.c_str());
- if (it == properties.end()) {
- return Error { "layer doesn't support this property" };
+ const auto it = layerProperties.find(name.c_str());
+ if (it == layerProperties.end() || it->second > lastPaintPropertyIndex) {
+ return Error{"layer doesn't support this property"};
}
auto property = static_cast<Property>(it->second);
@@ -427,49 +434,91 @@ optional<Error> FillExtrusionLayer::setPaintProperty(const std::string& name, co
if (!transition) {
return error;
}
-
+
if (property == Property::FillExtrusionBaseTransition) {
setFillExtrusionBaseTransition(*transition);
return nullopt;
}
-
+
if (property == Property::FillExtrusionColorTransition) {
setFillExtrusionColorTransition(*transition);
return nullopt;
}
-
+
if (property == Property::FillExtrusionHeightTransition) {
setFillExtrusionHeightTransition(*transition);
return nullopt;
}
-
+
if (property == Property::FillExtrusionOpacityTransition) {
setFillExtrusionOpacityTransition(*transition);
return nullopt;
}
-
+
if (property == Property::FillExtrusionPatternTransition) {
setFillExtrusionPatternTransition(*transition);
return nullopt;
}
-
+
if (property == Property::FillExtrusionTranslateTransition) {
setFillExtrusionTranslateTransition(*transition);
return nullopt;
}
-
+
if (property == Property::FillExtrusionTranslateAnchorTransition) {
setFillExtrusionTranslateAnchorTransition(*transition);
return nullopt;
}
-
+
if (property == Property::FillExtrusionVerticalGradientTransition) {
setFillExtrusionVerticalGradientTransition(*transition);
return nullopt;
}
-
- return Error { "layer doesn't support this property" };
+ return Error{"layer doesn't support this property"};
+}
+
+StyleProperty FillExtrusionLayer::getProperty(const std::string& name) const {
+ const auto it = layerProperties.find(name.c_str());
+ if (it == layerProperties.end()) {
+ return {};
+ }
+
+ switch (static_cast<Property>(it->second)) {
+ case Property::FillExtrusionBase:
+ return makeStyleProperty(getFillExtrusionBase());
+ case Property::FillExtrusionColor:
+ return makeStyleProperty(getFillExtrusionColor());
+ case Property::FillExtrusionHeight:
+ return makeStyleProperty(getFillExtrusionHeight());
+ case Property::FillExtrusionOpacity:
+ return makeStyleProperty(getFillExtrusionOpacity());
+ case Property::FillExtrusionPattern:
+ return makeStyleProperty(getFillExtrusionPattern());
+ case Property::FillExtrusionTranslate:
+ return makeStyleProperty(getFillExtrusionTranslate());
+ case Property::FillExtrusionTranslateAnchor:
+ return makeStyleProperty(getFillExtrusionTranslateAnchor());
+ case Property::FillExtrusionVerticalGradient:
+ return makeStyleProperty(getFillExtrusionVerticalGradient());
+ case Property::FillExtrusionBaseTransition:
+ return makeStyleProperty(getFillExtrusionBaseTransition());
+ case Property::FillExtrusionColorTransition:
+ return makeStyleProperty(getFillExtrusionColorTransition());
+ case Property::FillExtrusionHeightTransition:
+ return makeStyleProperty(getFillExtrusionHeightTransition());
+ case Property::FillExtrusionOpacityTransition:
+ return makeStyleProperty(getFillExtrusionOpacityTransition());
+ case Property::FillExtrusionPatternTransition:
+ return makeStyleProperty(getFillExtrusionPatternTransition());
+ case Property::FillExtrusionTranslateTransition:
+ return makeStyleProperty(getFillExtrusionTranslateTransition());
+ case Property::FillExtrusionTranslateAnchorTransition:
+ return makeStyleProperty(getFillExtrusionTranslateAnchorTransition());
+ case Property::FillExtrusionVerticalGradientTransition:
+ return makeStyleProperty(getFillExtrusionVerticalGradientTransition());
+ }
+ return {};
}
optional<Error> FillExtrusionLayer::setLayoutProperty(const std::string& name, const Convertible& value) {
@@ -486,5 +535,3 @@ Mutable<Layer::Impl> FillExtrusionLayer::mutableBaseImpl() const {
} // namespace style
} // namespace mbgl
-
-// clang-format on
diff --git a/src/mbgl/style/layers/fill_layer.cpp b/src/mbgl/style/layers/fill_layer.cpp
index 52f2b166b3..e3d6c6c708 100644
--- a/src/mbgl/style/layers/fill_layer.cpp
+++ b/src/mbgl/style/layers/fill_layer.cpp
@@ -1,5 +1,3 @@
-// clang-format off
-
// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`.
#include <mbgl/style/layers/fill_layer.hpp>
@@ -256,44 +254,53 @@ TransitionOptions FillLayer::getFillTranslateAnchorTransition() const {
using namespace conversion;
+namespace {
+
+enum class Property : uint8_t {
+ FillAntialias,
+ FillColor,
+ FillOpacity,
+ FillOutlineColor,
+ FillPattern,
+ FillTranslate,
+ FillTranslateAnchor,
+ FillAntialiasTransition,
+ FillColorTransition,
+ FillOpacityTransition,
+ FillOutlineColorTransition,
+ FillPatternTransition,
+ FillTranslateTransition,
+ FillTranslateAnchorTransition,
+};
+
+template <typename T>
+constexpr uint8_t toUint8(T t) noexcept {
+ return uint8_t(mbgl::underlying_type(t));
+}
+
+MAPBOX_ETERNAL_CONSTEXPR const auto layerProperties = mapbox::eternal::hash_map<mapbox::eternal::string, uint8_t>(
+ {{"fill-antialias", toUint8(Property::FillAntialias)},
+ {"fill-color", toUint8(Property::FillColor)},
+ {"fill-opacity", toUint8(Property::FillOpacity)},
+ {"fill-outline-color", toUint8(Property::FillOutlineColor)},
+ {"fill-pattern", toUint8(Property::FillPattern)},
+ {"fill-translate", toUint8(Property::FillTranslate)},
+ {"fill-translate-anchor", toUint8(Property::FillTranslateAnchor)},
+ {"fill-antialias-transition", toUint8(Property::FillAntialiasTransition)},
+ {"fill-color-transition", toUint8(Property::FillColorTransition)},
+ {"fill-opacity-transition", toUint8(Property::FillOpacityTransition)},
+ {"fill-outline-color-transition", toUint8(Property::FillOutlineColorTransition)},
+ {"fill-pattern-transition", toUint8(Property::FillPatternTransition)},
+ {"fill-translate-transition", toUint8(Property::FillTranslateTransition)},
+ {"fill-translate-anchor-transition", toUint8(Property::FillTranslateAnchorTransition)}});
+
+constexpr uint8_t lastPaintPropertyIndex = toUint8(Property::FillTranslateAnchorTransition);
+} // namespace
+
optional<Error> FillLayer::setPaintProperty(const std::string& name, const Convertible& value) {
- enum class Property {
- FillAntialias,
- FillColor,
- FillOpacity,
- FillOutlineColor,
- FillPattern,
- FillTranslate,
- FillTranslateAnchor,
- FillAntialiasTransition,
- FillColorTransition,
- FillOpacityTransition,
- FillOutlineColorTransition,
- FillPatternTransition,
- FillTranslateTransition,
- FillTranslateAnchorTransition,
- };
-
- MAPBOX_ETERNAL_CONSTEXPR const auto properties = mapbox::eternal::hash_map<mapbox::eternal::string, uint8_t>({
- { "fill-antialias", mbgl::underlying_type(Property::FillAntialias) },
- { "fill-color", mbgl::underlying_type(Property::FillColor) },
- { "fill-opacity", mbgl::underlying_type(Property::FillOpacity) },
- { "fill-outline-color", mbgl::underlying_type(Property::FillOutlineColor) },
- { "fill-pattern", mbgl::underlying_type(Property::FillPattern) },
- { "fill-translate", mbgl::underlying_type(Property::FillTranslate) },
- { "fill-translate-anchor", mbgl::underlying_type(Property::FillTranslateAnchor) },
- { "fill-antialias-transition", mbgl::underlying_type(Property::FillAntialiasTransition) },
- { "fill-color-transition", mbgl::underlying_type(Property::FillColorTransition) },
- { "fill-opacity-transition", mbgl::underlying_type(Property::FillOpacityTransition) },
- { "fill-outline-color-transition", mbgl::underlying_type(Property::FillOutlineColorTransition) },
- { "fill-pattern-transition", mbgl::underlying_type(Property::FillPatternTransition) },
- { "fill-translate-transition", mbgl::underlying_type(Property::FillTranslateTransition) },
- { "fill-translate-anchor-transition", mbgl::underlying_type(Property::FillTranslateAnchorTransition) }
- });
-
- const auto it = properties.find(name.c_str());
- if (it == properties.end()) {
- return Error { "layer doesn't support this property" };
+ const auto it = layerProperties.find(name.c_str());
+ if (it == layerProperties.end() || it->second > lastPaintPropertyIndex) {
+ return Error{"layer doesn't support this property"};
}
auto property = static_cast<Property>(it->second);
@@ -384,44 +391,82 @@ optional<Error> FillLayer::setPaintProperty(const std::string& name, const Conve
if (!transition) {
return error;
}
-
+
if (property == Property::FillAntialiasTransition) {
setFillAntialiasTransition(*transition);
return nullopt;
}
-
+
if (property == Property::FillColorTransition) {
setFillColorTransition(*transition);
return nullopt;
}
-
+
if (property == Property::FillOpacityTransition) {
setFillOpacityTransition(*transition);
return nullopt;
}
-
+
if (property == Property::FillOutlineColorTransition) {
setFillOutlineColorTransition(*transition);
return nullopt;
}
-
+
if (property == Property::FillPatternTransition) {
setFillPatternTransition(*transition);
return nullopt;
}
-
+
if (property == Property::FillTranslateTransition) {
setFillTranslateTransition(*transition);
return nullopt;
}
-
+
if (property == Property::FillTranslateAnchorTransition) {
setFillTranslateAnchorTransition(*transition);
return nullopt;
}
-
- return Error { "layer doesn't support this property" };
+ return Error{"layer doesn't support this property"};
+}
+
+StyleProperty FillLayer::getProperty(const std::string& name) const {
+ const auto it = layerProperties.find(name.c_str());
+ if (it == layerProperties.end()) {
+ return {};
+ }
+
+ switch (static_cast<Property>(it->second)) {
+ case Property::FillAntialias:
+ return makeStyleProperty(getFillAntialias());
+ case Property::FillColor:
+ return makeStyleProperty(getFillColor());
+ case Property::FillOpacity:
+ return makeStyleProperty(getFillOpacity());
+ case Property::FillOutlineColor:
+ return makeStyleProperty(getFillOutlineColor());
+ case Property::FillPattern:
+ return makeStyleProperty(getFillPattern());
+ case Property::FillTranslate:
+ return makeStyleProperty(getFillTranslate());
+ case Property::FillTranslateAnchor:
+ return makeStyleProperty(getFillTranslateAnchor());
+ case Property::FillAntialiasTransition:
+ return makeStyleProperty(getFillAntialiasTransition());
+ case Property::FillColorTransition:
+ return makeStyleProperty(getFillColorTransition());
+ case Property::FillOpacityTransition:
+ return makeStyleProperty(getFillOpacityTransition());
+ case Property::FillOutlineColorTransition:
+ return makeStyleProperty(getFillOutlineColorTransition());
+ case Property::FillPatternTransition:
+ return makeStyleProperty(getFillPatternTransition());
+ case Property::FillTranslateTransition:
+ return makeStyleProperty(getFillTranslateTransition());
+ case Property::FillTranslateAnchorTransition:
+ return makeStyleProperty(getFillTranslateAnchorTransition());
+ }
+ return {};
}
optional<Error> FillLayer::setLayoutProperty(const std::string& name, const Convertible& value) {
@@ -438,5 +483,3 @@ Mutable<Layer::Impl> FillLayer::mutableBaseImpl() const {
} // namespace style
} // namespace mbgl
-
-// clang-format on
diff --git a/src/mbgl/style/layers/heatmap_layer.cpp b/src/mbgl/style/layers/heatmap_layer.cpp
index 86bb2d6de1..332c65c6b4 100644
--- a/src/mbgl/style/layers/heatmap_layer.cpp
+++ b/src/mbgl/style/layers/heatmap_layer.cpp
@@ -1,5 +1,3 @@
-// clang-format off
-
// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`.
#include <mbgl/style/layers/heatmap_layer.hpp>
@@ -204,36 +202,45 @@ TransitionOptions HeatmapLayer::getHeatmapWeightTransition() const {
using namespace conversion;
+namespace {
+
+enum class Property : uint8_t {
+ HeatmapColor,
+ HeatmapIntensity,
+ HeatmapOpacity,
+ HeatmapRadius,
+ HeatmapWeight,
+ HeatmapColorTransition,
+ HeatmapIntensityTransition,
+ HeatmapOpacityTransition,
+ HeatmapRadiusTransition,
+ HeatmapWeightTransition,
+};
+
+template <typename T>
+constexpr uint8_t toUint8(T t) noexcept {
+ return uint8_t(mbgl::underlying_type(t));
+}
+
+MAPBOX_ETERNAL_CONSTEXPR const auto layerProperties = mapbox::eternal::hash_map<mapbox::eternal::string, uint8_t>(
+ {{"heatmap-color", toUint8(Property::HeatmapColor)},
+ {"heatmap-intensity", toUint8(Property::HeatmapIntensity)},
+ {"heatmap-opacity", toUint8(Property::HeatmapOpacity)},
+ {"heatmap-radius", toUint8(Property::HeatmapRadius)},
+ {"heatmap-weight", toUint8(Property::HeatmapWeight)},
+ {"heatmap-color-transition", toUint8(Property::HeatmapColorTransition)},
+ {"heatmap-intensity-transition", toUint8(Property::HeatmapIntensityTransition)},
+ {"heatmap-opacity-transition", toUint8(Property::HeatmapOpacityTransition)},
+ {"heatmap-radius-transition", toUint8(Property::HeatmapRadiusTransition)},
+ {"heatmap-weight-transition", toUint8(Property::HeatmapWeightTransition)}});
+
+constexpr uint8_t lastPaintPropertyIndex = toUint8(Property::HeatmapWeightTransition);
+} // namespace
+
optional<Error> HeatmapLayer::setPaintProperty(const std::string& name, const Convertible& value) {
- enum class Property {
- HeatmapColor,
- HeatmapIntensity,
- HeatmapOpacity,
- HeatmapRadius,
- HeatmapWeight,
- HeatmapColorTransition,
- HeatmapIntensityTransition,
- HeatmapOpacityTransition,
- HeatmapRadiusTransition,
- HeatmapWeightTransition,
- };
-
- MAPBOX_ETERNAL_CONSTEXPR const auto properties = mapbox::eternal::hash_map<mapbox::eternal::string, uint8_t>({
- { "heatmap-color", mbgl::underlying_type(Property::HeatmapColor) },
- { "heatmap-intensity", mbgl::underlying_type(Property::HeatmapIntensity) },
- { "heatmap-opacity", mbgl::underlying_type(Property::HeatmapOpacity) },
- { "heatmap-radius", mbgl::underlying_type(Property::HeatmapRadius) },
- { "heatmap-weight", mbgl::underlying_type(Property::HeatmapWeight) },
- { "heatmap-color-transition", mbgl::underlying_type(Property::HeatmapColorTransition) },
- { "heatmap-intensity-transition", mbgl::underlying_type(Property::HeatmapIntensityTransition) },
- { "heatmap-opacity-transition", mbgl::underlying_type(Property::HeatmapOpacityTransition) },
- { "heatmap-radius-transition", mbgl::underlying_type(Property::HeatmapRadiusTransition) },
- { "heatmap-weight-transition", mbgl::underlying_type(Property::HeatmapWeightTransition) }
- });
-
- const auto it = properties.find(name.c_str());
- if (it == properties.end()) {
- return Error { "layer doesn't support this property" };
+ const auto it = layerProperties.find(name.c_str());
+ if (it == layerProperties.end() || it->second > lastPaintPropertyIndex) {
+ return Error{"layer doesn't support this property"};
}
auto property = static_cast<Property>(it->second);
@@ -295,34 +302,64 @@ optional<Error> HeatmapLayer::setPaintProperty(const std::string& name, const Co
if (!transition) {
return error;
}
-
+
if (property == Property::HeatmapColorTransition) {
setHeatmapColorTransition(*transition);
return nullopt;
}
-
+
if (property == Property::HeatmapIntensityTransition) {
setHeatmapIntensityTransition(*transition);
return nullopt;
}
-
+
if (property == Property::HeatmapOpacityTransition) {
setHeatmapOpacityTransition(*transition);
return nullopt;
}
-
+
if (property == Property::HeatmapRadiusTransition) {
setHeatmapRadiusTransition(*transition);
return nullopt;
}
-
+
if (property == Property::HeatmapWeightTransition) {
setHeatmapWeightTransition(*transition);
return nullopt;
}
-
- return Error { "layer doesn't support this property" };
+ return Error{"layer doesn't support this property"};
+}
+
+StyleProperty HeatmapLayer::getProperty(const std::string& name) const {
+ const auto it = layerProperties.find(name.c_str());
+ if (it == layerProperties.end()) {
+ return {};
+ }
+
+ switch (static_cast<Property>(it->second)) {
+ case Property::HeatmapColor:
+ return makeStyleProperty(getHeatmapColor());
+ case Property::HeatmapIntensity:
+ return makeStyleProperty(getHeatmapIntensity());
+ case Property::HeatmapOpacity:
+ return makeStyleProperty(getHeatmapOpacity());
+ case Property::HeatmapRadius:
+ return makeStyleProperty(getHeatmapRadius());
+ case Property::HeatmapWeight:
+ return makeStyleProperty(getHeatmapWeight());
+ case Property::HeatmapColorTransition:
+ return makeStyleProperty(getHeatmapColorTransition());
+ case Property::HeatmapIntensityTransition:
+ return makeStyleProperty(getHeatmapIntensityTransition());
+ case Property::HeatmapOpacityTransition:
+ return makeStyleProperty(getHeatmapOpacityTransition());
+ case Property::HeatmapRadiusTransition:
+ return makeStyleProperty(getHeatmapRadiusTransition());
+ case Property::HeatmapWeightTransition:
+ return makeStyleProperty(getHeatmapWeightTransition());
+ }
+ return {};
}
optional<Error> HeatmapLayer::setLayoutProperty(const std::string& name, const Convertible& value) {
@@ -339,5 +376,3 @@ Mutable<Layer::Impl> HeatmapLayer::mutableBaseImpl() const {
} // namespace style
} // namespace mbgl
-
-// clang-format on
diff --git a/src/mbgl/style/layers/hillshade_layer.cpp b/src/mbgl/style/layers/hillshade_layer.cpp
index 710efe5844..a55c079c49 100644
--- a/src/mbgl/style/layers/hillshade_layer.cpp
+++ b/src/mbgl/style/layers/hillshade_layer.cpp
@@ -1,5 +1,3 @@
-// clang-format off
-
// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`.
#include <mbgl/style/layers/hillshade_layer.hpp>
@@ -229,40 +227,49 @@ TransitionOptions HillshadeLayer::getHillshadeShadowColorTransition() const {
using namespace conversion;
+namespace {
+
+enum class Property : uint8_t {
+ HillshadeAccentColor,
+ HillshadeExaggeration,
+ HillshadeHighlightColor,
+ HillshadeIlluminationAnchor,
+ HillshadeIlluminationDirection,
+ HillshadeShadowColor,
+ HillshadeAccentColorTransition,
+ HillshadeExaggerationTransition,
+ HillshadeHighlightColorTransition,
+ HillshadeIlluminationAnchorTransition,
+ HillshadeIlluminationDirectionTransition,
+ HillshadeShadowColorTransition,
+};
+
+template <typename T>
+constexpr uint8_t toUint8(T t) noexcept {
+ return uint8_t(mbgl::underlying_type(t));
+}
+
+MAPBOX_ETERNAL_CONSTEXPR const auto layerProperties = mapbox::eternal::hash_map<mapbox::eternal::string, uint8_t>(
+ {{"hillshade-accent-color", toUint8(Property::HillshadeAccentColor)},
+ {"hillshade-exaggeration", toUint8(Property::HillshadeExaggeration)},
+ {"hillshade-highlight-color", toUint8(Property::HillshadeHighlightColor)},
+ {"hillshade-illumination-anchor", toUint8(Property::HillshadeIlluminationAnchor)},
+ {"hillshade-illumination-direction", toUint8(Property::HillshadeIlluminationDirection)},
+ {"hillshade-shadow-color", toUint8(Property::HillshadeShadowColor)},
+ {"hillshade-accent-color-transition", toUint8(Property::HillshadeAccentColorTransition)},
+ {"hillshade-exaggeration-transition", toUint8(Property::HillshadeExaggerationTransition)},
+ {"hillshade-highlight-color-transition", toUint8(Property::HillshadeHighlightColorTransition)},
+ {"hillshade-illumination-anchor-transition", toUint8(Property::HillshadeIlluminationAnchorTransition)},
+ {"hillshade-illumination-direction-transition", toUint8(Property::HillshadeIlluminationDirectionTransition)},
+ {"hillshade-shadow-color-transition", toUint8(Property::HillshadeShadowColorTransition)}});
+
+constexpr uint8_t lastPaintPropertyIndex = toUint8(Property::HillshadeShadowColorTransition);
+} // namespace
+
optional<Error> HillshadeLayer::setPaintProperty(const std::string& name, const Convertible& value) {
- enum class Property {
- HillshadeAccentColor,
- HillshadeExaggeration,
- HillshadeHighlightColor,
- HillshadeIlluminationAnchor,
- HillshadeIlluminationDirection,
- HillshadeShadowColor,
- HillshadeAccentColorTransition,
- HillshadeExaggerationTransition,
- HillshadeHighlightColorTransition,
- HillshadeIlluminationAnchorTransition,
- HillshadeIlluminationDirectionTransition,
- HillshadeShadowColorTransition,
- };
-
- MAPBOX_ETERNAL_CONSTEXPR const auto properties = mapbox::eternal::hash_map<mapbox::eternal::string, uint8_t>({
- { "hillshade-accent-color", mbgl::underlying_type(Property::HillshadeAccentColor) },
- { "hillshade-exaggeration", mbgl::underlying_type(Property::HillshadeExaggeration) },
- { "hillshade-highlight-color", mbgl::underlying_type(Property::HillshadeHighlightColor) },
- { "hillshade-illumination-anchor", mbgl::underlying_type(Property::HillshadeIlluminationAnchor) },
- { "hillshade-illumination-direction", mbgl::underlying_type(Property::HillshadeIlluminationDirection) },
- { "hillshade-shadow-color", mbgl::underlying_type(Property::HillshadeShadowColor) },
- { "hillshade-accent-color-transition", mbgl::underlying_type(Property::HillshadeAccentColorTransition) },
- { "hillshade-exaggeration-transition", mbgl::underlying_type(Property::HillshadeExaggerationTransition) },
- { "hillshade-highlight-color-transition", mbgl::underlying_type(Property::HillshadeHighlightColorTransition) },
- { "hillshade-illumination-anchor-transition", mbgl::underlying_type(Property::HillshadeIlluminationAnchorTransition) },
- { "hillshade-illumination-direction-transition", mbgl::underlying_type(Property::HillshadeIlluminationDirectionTransition) },
- { "hillshade-shadow-color-transition", mbgl::underlying_type(Property::HillshadeShadowColorTransition) }
- });
-
- const auto it = properties.find(name.c_str());
- if (it == properties.end()) {
- return Error { "layer doesn't support this property" };
+ const auto it = layerProperties.find(name.c_str());
+ if (it == layerProperties.end() || it->second > lastPaintPropertyIndex) {
+ return Error{"layer doesn't support this property"};
}
auto property = static_cast<Property>(it->second);
@@ -329,39 +336,73 @@ optional<Error> HillshadeLayer::setPaintProperty(const std::string& name, const
if (!transition) {
return error;
}
-
+
if (property == Property::HillshadeAccentColorTransition) {
setHillshadeAccentColorTransition(*transition);
return nullopt;
}
-
+
if (property == Property::HillshadeExaggerationTransition) {
setHillshadeExaggerationTransition(*transition);
return nullopt;
}
-
+
if (property == Property::HillshadeHighlightColorTransition) {
setHillshadeHighlightColorTransition(*transition);
return nullopt;
}
-
+
if (property == Property::HillshadeIlluminationAnchorTransition) {
setHillshadeIlluminationAnchorTransition(*transition);
return nullopt;
}
-
+
if (property == Property::HillshadeIlluminationDirectionTransition) {
setHillshadeIlluminationDirectionTransition(*transition);
return nullopt;
}
-
+
if (property == Property::HillshadeShadowColorTransition) {
setHillshadeShadowColorTransition(*transition);
return nullopt;
}
-
- return Error { "layer doesn't support this property" };
+ return Error{"layer doesn't support this property"};
+}
+
+StyleProperty HillshadeLayer::getProperty(const std::string& name) const {
+ const auto it = layerProperties.find(name.c_str());
+ if (it == layerProperties.end()) {
+ return {};
+ }
+
+ switch (static_cast<Property>(it->second)) {
+ case Property::HillshadeAccentColor:
+ return makeStyleProperty(getHillshadeAccentColor());
+ case Property::HillshadeExaggeration:
+ return makeStyleProperty(getHillshadeExaggeration());
+ case Property::HillshadeHighlightColor:
+ return makeStyleProperty(getHillshadeHighlightColor());
+ case Property::HillshadeIlluminationAnchor:
+ return makeStyleProperty(getHillshadeIlluminationAnchor());
+ case Property::HillshadeIlluminationDirection:
+ return makeStyleProperty(getHillshadeIlluminationDirection());
+ case Property::HillshadeShadowColor:
+ return makeStyleProperty(getHillshadeShadowColor());
+ case Property::HillshadeAccentColorTransition:
+ return makeStyleProperty(getHillshadeAccentColorTransition());
+ case Property::HillshadeExaggerationTransition:
+ return makeStyleProperty(getHillshadeExaggerationTransition());
+ case Property::HillshadeHighlightColorTransition:
+ return makeStyleProperty(getHillshadeHighlightColorTransition());
+ case Property::HillshadeIlluminationAnchorTransition:
+ return makeStyleProperty(getHillshadeIlluminationAnchorTransition());
+ case Property::HillshadeIlluminationDirectionTransition:
+ return makeStyleProperty(getHillshadeIlluminationDirectionTransition());
+ case Property::HillshadeShadowColorTransition:
+ return makeStyleProperty(getHillshadeShadowColorTransition());
+ }
+ return {};
}
optional<Error> HillshadeLayer::setLayoutProperty(const std::string& name, const Convertible& value) {
@@ -378,5 +419,3 @@ Mutable<Layer::Impl> HillshadeLayer::mutableBaseImpl() const {
} // namespace style
} // namespace mbgl
-
-// clang-format on
diff --git a/src/mbgl/style/layers/layer.cpp.ejs b/src/mbgl/style/layers/layer.cpp.ejs
index 0cd9a82d75..775288264f 100644
--- a/src/mbgl/style/layers/layer.cpp.ejs
+++ b/src/mbgl/style/layers/layer.cpp.ejs
@@ -3,8 +3,6 @@
const layoutProperties = locals.layoutProperties;
const paintProperties = locals.paintProperties;
-%>
-// clang-format off
-
// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`.
#include <mbgl/style/layers/<%- type.replace('-', '_') %>_layer.hpp>
@@ -190,24 +188,42 @@ TransitionOptions <%- camelize(type) %>Layer::get<%- camelize(property.name) %>T
using namespace conversion;
-optional<Error> <%- camelize(type) %>Layer::setPaintProperty(const std::string& name, const Convertible& value) {
- enum class Property {
+namespace {
+
+enum class Property : uint8_t {
<% for (const property of paintProperties) { -%>
- <%- camelize(property.name) %>,
+ <%- camelize(property.name) %>,
<% } -%>
<% for (const property of paintProperties) { -%>
- <%- camelize(property.name) %>Transition,
+ <%- camelize(property.name) %>Transition,
+<% } -%>
+<% for (const property of layoutProperties) { -%>
+ <%- camelize(property.name) %>,
<% } -%>
- };
+};
- MAPBOX_ETERNAL_CONSTEXPR const auto properties = mapbox::eternal::hash_map<mapbox::eternal::string, uint8_t>({
- <%- paintProperties.map(p => `{ "${p.name}", mbgl::underlying_type(Property::${camelize(p.name)}) }`).join(',\n ') %>,
- <%- paintProperties.map(p => `{ "${p.name}-transition", mbgl::underlying_type(Property::${camelize(p.name)}Transition) }`).join(',\n ') %>
- });
+template <typename T>
+constexpr uint8_t toUint8(T t) noexcept {
+ return uint8_t(mbgl::underlying_type(t));
+}
- const auto it = properties.find(name.c_str());
- if (it == properties.end()) {
- return Error { "layer doesn't support this property" };
+MAPBOX_ETERNAL_CONSTEXPR const auto layerProperties = mapbox::eternal::hash_map<mapbox::eternal::string, uint8_t>(
+ {<%- paintProperties.map(p => `{"${p.name}", toUint8(Property::${camelize(p.name)})}`).join(',\n ') %>,
+<% if (!layoutProperties.length) { -%>
+ <%- paintProperties.map(p => `{"${p.name}-transition", toUint8(Property::${camelize(p.name)}Transition)}`).join(',\n ') %>});
+<% } else { -%>
+ <%- paintProperties.map(p => `{"${p.name}-transition", toUint8(Property::${camelize(p.name)}Transition)}`).join(',\n ') %>,
+ <%- layoutProperties.map(p => `{"${p.name}", toUint8(Property::${camelize(p.name)})}`).join(',\n ') %>});
+<% } -%>
+
+<% const lastPaintProperty = paintProperties[paintProperties.length - 1]; -%>
+<%-`constexpr uint8_t lastPaintPropertyIndex = toUint8(Property::${camelize(lastPaintProperty.name)}Transition);` %>
+} // namespace
+
+optional<Error> <%- camelize(type) %>Layer::setPaintProperty(const std::string& name, const Convertible& value) {
+ const auto it = layerProperties.find(name.c_str());
+ if (it == layerProperties.end() || it->second > lastPaintPropertyIndex) {
+ return Error{"layer doesn't support this property"};
}
auto property = static_cast<Property>(it->second);
@@ -248,14 +264,36 @@ optional<Error> <%- camelize(type) %>Layer::setPaintProperty(const std::string&
if (!transition) {
return error;
}
- <% for (const property of paintProperties) { %>
+<% for (const property of paintProperties) { %>
if (property == Property::<%- camelize(property.name) %>Transition) {
set<%- camelize(property.name) %>Transition(*transition);
return nullopt;
}
- <% } %>
+<% } %>
+ return Error{"layer doesn't support this property"};
+}
- return Error { "layer doesn't support this property" };
+StyleProperty <%- camelize(type) %>Layer::getProperty(const std::string& name) const {
+ const auto it = layerProperties.find(name.c_str());
+ if (it == layerProperties.end()) {
+ return {};
+ }
+
+ switch (static_cast<Property>(it->second)) {
+<% for (const property of paintProperties) { -%>
+ case Property::<%- camelize(property.name) %>:
+ return makeStyleProperty(get<%- camelize(property.name) %>());
+<% } -%>
+<% for (const property of paintProperties) { -%>
+ case Property::<%- camelize(property.name) %>Transition:
+ return makeStyleProperty(get<%- camelize(property.name) %>Transition());
+<% } -%>
+<% for (const property of layoutProperties) { -%>
+ case Property::<%- camelize(property.name) %>:
+ return makeStyleProperty(get<%- camelize(property.name) %>());
+<% } -%>
+ }
+ return {};
}
optional<Error> <%- camelize(type) %>Layer::setLayoutProperty(const std::string& name, const Convertible& value) {
@@ -263,17 +301,8 @@ optional<Error> <%- camelize(type) %>Layer::setLayoutProperty(const std::string&
return Layer::setVisibility(value);
}
<% if (layoutProperties.length) { -%>
- enum class Property {
-<% for (const property of layoutProperties) { -%>
- <%- camelize(property.name) %>,
-<% } -%>
- };
- MAPBOX_ETERNAL_CONSTEXPR const auto properties = mapbox::eternal::hash_map<mapbox::eternal::string, uint8_t>({
- <%- layoutProperties.map(p => `{ "${p.name}", mbgl::underlying_type(Property::${camelize(p.name)}) }`).join(',\n ') %>
- });
-
- const auto it = properties.find(name.c_str());
- if (it == properties.end()) {
+ const auto it = layerProperties.find(name.c_str());
+ if (it == layerProperties.end() || it->second <= lastPaintPropertyIndex) {
return Error { "layer doesn't support this property" };
}
@@ -319,5 +348,3 @@ Mutable<Layer::Impl> <%- camelize(type) %>Layer::mutableBaseImpl() const {
} // namespace style
} // namespace mbgl
-
-// clang-format on
diff --git a/src/mbgl/style/layers/line_layer.cpp b/src/mbgl/style/layers/line_layer.cpp
index bd29f7f6fd..309a81ae2b 100644
--- a/src/mbgl/style/layers/line_layer.cpp
+++ b/src/mbgl/style/layers/line_layer.cpp
@@ -1,5 +1,3 @@
-// clang-format off
-
// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`.
#include <mbgl/style/layers/line_layer.hpp>
@@ -430,60 +428,77 @@ TransitionOptions LineLayer::getLineWidthTransition() const {
using namespace conversion;
+namespace {
+
+enum class Property : uint8_t {
+ LineBlur,
+ LineColor,
+ LineDasharray,
+ LineGapWidth,
+ LineGradient,
+ LineOffset,
+ LineOpacity,
+ LinePattern,
+ LineTranslate,
+ LineTranslateAnchor,
+ LineWidth,
+ LineBlurTransition,
+ LineColorTransition,
+ LineDasharrayTransition,
+ LineGapWidthTransition,
+ LineGradientTransition,
+ LineOffsetTransition,
+ LineOpacityTransition,
+ LinePatternTransition,
+ LineTranslateTransition,
+ LineTranslateAnchorTransition,
+ LineWidthTransition,
+ LineCap,
+ LineJoin,
+ LineMiterLimit,
+ LineRoundLimit,
+};
+
+template <typename T>
+constexpr uint8_t toUint8(T t) noexcept {
+ return uint8_t(mbgl::underlying_type(t));
+}
+
+MAPBOX_ETERNAL_CONSTEXPR const auto layerProperties = mapbox::eternal::hash_map<mapbox::eternal::string, uint8_t>(
+ {{"line-blur", toUint8(Property::LineBlur)},
+ {"line-color", toUint8(Property::LineColor)},
+ {"line-dasharray", toUint8(Property::LineDasharray)},
+ {"line-gap-width", toUint8(Property::LineGapWidth)},
+ {"line-gradient", toUint8(Property::LineGradient)},
+ {"line-offset", toUint8(Property::LineOffset)},
+ {"line-opacity", toUint8(Property::LineOpacity)},
+ {"line-pattern", toUint8(Property::LinePattern)},
+ {"line-translate", toUint8(Property::LineTranslate)},
+ {"line-translate-anchor", toUint8(Property::LineTranslateAnchor)},
+ {"line-width", toUint8(Property::LineWidth)},
+ {"line-blur-transition", toUint8(Property::LineBlurTransition)},
+ {"line-color-transition", toUint8(Property::LineColorTransition)},
+ {"line-dasharray-transition", toUint8(Property::LineDasharrayTransition)},
+ {"line-gap-width-transition", toUint8(Property::LineGapWidthTransition)},
+ {"line-gradient-transition", toUint8(Property::LineGradientTransition)},
+ {"line-offset-transition", toUint8(Property::LineOffsetTransition)},
+ {"line-opacity-transition", toUint8(Property::LineOpacityTransition)},
+ {"line-pattern-transition", toUint8(Property::LinePatternTransition)},
+ {"line-translate-transition", toUint8(Property::LineTranslateTransition)},
+ {"line-translate-anchor-transition", toUint8(Property::LineTranslateAnchorTransition)},
+ {"line-width-transition", toUint8(Property::LineWidthTransition)},
+ {"line-cap", toUint8(Property::LineCap)},
+ {"line-join", toUint8(Property::LineJoin)},
+ {"line-miter-limit", toUint8(Property::LineMiterLimit)},
+ {"line-round-limit", toUint8(Property::LineRoundLimit)}});
+
+constexpr uint8_t lastPaintPropertyIndex = toUint8(Property::LineWidthTransition);
+} // namespace
+
optional<Error> LineLayer::setPaintProperty(const std::string& name, const Convertible& value) {
- enum class Property {
- LineBlur,
- LineColor,
- LineDasharray,
- LineGapWidth,
- LineGradient,
- LineOffset,
- LineOpacity,
- LinePattern,
- LineTranslate,
- LineTranslateAnchor,
- LineWidth,
- LineBlurTransition,
- LineColorTransition,
- LineDasharrayTransition,
- LineGapWidthTransition,
- LineGradientTransition,
- LineOffsetTransition,
- LineOpacityTransition,
- LinePatternTransition,
- LineTranslateTransition,
- LineTranslateAnchorTransition,
- LineWidthTransition,
- };
-
- MAPBOX_ETERNAL_CONSTEXPR const auto properties = mapbox::eternal::hash_map<mapbox::eternal::string, uint8_t>({
- { "line-blur", mbgl::underlying_type(Property::LineBlur) },
- { "line-color", mbgl::underlying_type(Property::LineColor) },
- { "line-dasharray", mbgl::underlying_type(Property::LineDasharray) },
- { "line-gap-width", mbgl::underlying_type(Property::LineGapWidth) },
- { "line-gradient", mbgl::underlying_type(Property::LineGradient) },
- { "line-offset", mbgl::underlying_type(Property::LineOffset) },
- { "line-opacity", mbgl::underlying_type(Property::LineOpacity) },
- { "line-pattern", mbgl::underlying_type(Property::LinePattern) },
- { "line-translate", mbgl::underlying_type(Property::LineTranslate) },
- { "line-translate-anchor", mbgl::underlying_type(Property::LineTranslateAnchor) },
- { "line-width", mbgl::underlying_type(Property::LineWidth) },
- { "line-blur-transition", mbgl::underlying_type(Property::LineBlurTransition) },
- { "line-color-transition", mbgl::underlying_type(Property::LineColorTransition) },
- { "line-dasharray-transition", mbgl::underlying_type(Property::LineDasharrayTransition) },
- { "line-gap-width-transition", mbgl::underlying_type(Property::LineGapWidthTransition) },
- { "line-gradient-transition", mbgl::underlying_type(Property::LineGradientTransition) },
- { "line-offset-transition", mbgl::underlying_type(Property::LineOffsetTransition) },
- { "line-opacity-transition", mbgl::underlying_type(Property::LineOpacityTransition) },
- { "line-pattern-transition", mbgl::underlying_type(Property::LinePatternTransition) },
- { "line-translate-transition", mbgl::underlying_type(Property::LineTranslateTransition) },
- { "line-translate-anchor-transition", mbgl::underlying_type(Property::LineTranslateAnchorTransition) },
- { "line-width-transition", mbgl::underlying_type(Property::LineWidthTransition) }
- });
-
- const auto it = properties.find(name.c_str());
- if (it == properties.end()) {
- return Error { "layer doesn't support this property" };
+ const auto it = layerProperties.find(name.c_str());
+ if (it == layerProperties.end() || it->second > lastPaintPropertyIndex) {
+ return Error{"layer doesn't support this property"};
}
auto property = static_cast<Property>(it->second);
@@ -601,85 +616,134 @@ optional<Error> LineLayer::setPaintProperty(const std::string& name, const Conve
if (!transition) {
return error;
}
-
+
if (property == Property::LineBlurTransition) {
setLineBlurTransition(*transition);
return nullopt;
}
-
+
if (property == Property::LineColorTransition) {
setLineColorTransition(*transition);
return nullopt;
}
-
+
if (property == Property::LineDasharrayTransition) {
setLineDasharrayTransition(*transition);
return nullopt;
}
-
+
if (property == Property::LineGapWidthTransition) {
setLineGapWidthTransition(*transition);
return nullopt;
}
-
+
if (property == Property::LineGradientTransition) {
setLineGradientTransition(*transition);
return nullopt;
}
-
+
if (property == Property::LineOffsetTransition) {
setLineOffsetTransition(*transition);
return nullopt;
}
-
+
if (property == Property::LineOpacityTransition) {
setLineOpacityTransition(*transition);
return nullopt;
}
-
+
if (property == Property::LinePatternTransition) {
setLinePatternTransition(*transition);
return nullopt;
}
-
+
if (property == Property::LineTranslateTransition) {
setLineTranslateTransition(*transition);
return nullopt;
}
-
+
if (property == Property::LineTranslateAnchorTransition) {
setLineTranslateAnchorTransition(*transition);
return nullopt;
}
-
+
if (property == Property::LineWidthTransition) {
setLineWidthTransition(*transition);
return nullopt;
}
-
- return Error { "layer doesn't support this property" };
+ return Error{"layer doesn't support this property"};
+}
+
+StyleProperty LineLayer::getProperty(const std::string& name) const {
+ const auto it = layerProperties.find(name.c_str());
+ if (it == layerProperties.end()) {
+ return {};
+ }
+
+ switch (static_cast<Property>(it->second)) {
+ case Property::LineBlur:
+ return makeStyleProperty(getLineBlur());
+ case Property::LineColor:
+ return makeStyleProperty(getLineColor());
+ case Property::LineDasharray:
+ return makeStyleProperty(getLineDasharray());
+ case Property::LineGapWidth:
+ return makeStyleProperty(getLineGapWidth());
+ case Property::LineGradient:
+ return makeStyleProperty(getLineGradient());
+ case Property::LineOffset:
+ return makeStyleProperty(getLineOffset());
+ case Property::LineOpacity:
+ return makeStyleProperty(getLineOpacity());
+ case Property::LinePattern:
+ return makeStyleProperty(getLinePattern());
+ case Property::LineTranslate:
+ return makeStyleProperty(getLineTranslate());
+ case Property::LineTranslateAnchor:
+ return makeStyleProperty(getLineTranslateAnchor());
+ case Property::LineWidth:
+ return makeStyleProperty(getLineWidth());
+ case Property::LineBlurTransition:
+ return makeStyleProperty(getLineBlurTransition());
+ case Property::LineColorTransition:
+ return makeStyleProperty(getLineColorTransition());
+ case Property::LineDasharrayTransition:
+ return makeStyleProperty(getLineDasharrayTransition());
+ case Property::LineGapWidthTransition:
+ return makeStyleProperty(getLineGapWidthTransition());
+ case Property::LineGradientTransition:
+ return makeStyleProperty(getLineGradientTransition());
+ case Property::LineOffsetTransition:
+ return makeStyleProperty(getLineOffsetTransition());
+ case Property::LineOpacityTransition:
+ return makeStyleProperty(getLineOpacityTransition());
+ case Property::LinePatternTransition:
+ return makeStyleProperty(getLinePatternTransition());
+ case Property::LineTranslateTransition:
+ return makeStyleProperty(getLineTranslateTransition());
+ case Property::LineTranslateAnchorTransition:
+ return makeStyleProperty(getLineTranslateAnchorTransition());
+ case Property::LineWidthTransition:
+ return makeStyleProperty(getLineWidthTransition());
+ case Property::LineCap:
+ return makeStyleProperty(getLineCap());
+ case Property::LineJoin:
+ return makeStyleProperty(getLineJoin());
+ case Property::LineMiterLimit:
+ return makeStyleProperty(getLineMiterLimit());
+ case Property::LineRoundLimit:
+ return makeStyleProperty(getLineRoundLimit());
+ }
+ return {};
}
optional<Error> LineLayer::setLayoutProperty(const std::string& name, const Convertible& value) {
if (name == "visibility") {
return Layer::setVisibility(value);
}
- enum class Property {
- LineCap,
- LineJoin,
- LineMiterLimit,
- LineRoundLimit,
- };
- MAPBOX_ETERNAL_CONSTEXPR const auto properties = mapbox::eternal::hash_map<mapbox::eternal::string, uint8_t>({
- { "line-cap", mbgl::underlying_type(Property::LineCap) },
- { "line-join", mbgl::underlying_type(Property::LineJoin) },
- { "line-miter-limit", mbgl::underlying_type(Property::LineMiterLimit) },
- { "line-round-limit", mbgl::underlying_type(Property::LineRoundLimit) }
- });
-
- const auto it = properties.find(name.c_str());
- if (it == properties.end()) {
+ const auto it = layerProperties.find(name.c_str());
+ if (it == layerProperties.end() || it->second <= lastPaintPropertyIndex) {
return Error { "layer doesn't support this property" };
}
@@ -739,5 +803,3 @@ Mutable<Layer::Impl> LineLayer::mutableBaseImpl() const {
} // namespace style
} // namespace mbgl
-
-// clang-format on
diff --git a/src/mbgl/style/layers/raster_layer.cpp b/src/mbgl/style/layers/raster_layer.cpp
index fde1df838d..359e502f3e 100644
--- a/src/mbgl/style/layers/raster_layer.cpp
+++ b/src/mbgl/style/layers/raster_layer.cpp
@@ -1,5 +1,3 @@
-// clang-format off
-
// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`.
#include <mbgl/style/layers/raster_layer.hpp>
@@ -283,48 +281,57 @@ TransitionOptions RasterLayer::getRasterSaturationTransition() const {
using namespace conversion;
+namespace {
+
+enum class Property : uint8_t {
+ RasterBrightnessMax,
+ RasterBrightnessMin,
+ RasterContrast,
+ RasterFadeDuration,
+ RasterHueRotate,
+ RasterOpacity,
+ RasterResampling,
+ RasterSaturation,
+ RasterBrightnessMaxTransition,
+ RasterBrightnessMinTransition,
+ RasterContrastTransition,
+ RasterFadeDurationTransition,
+ RasterHueRotateTransition,
+ RasterOpacityTransition,
+ RasterResamplingTransition,
+ RasterSaturationTransition,
+};
+
+template <typename T>
+constexpr uint8_t toUint8(T t) noexcept {
+ return uint8_t(mbgl::underlying_type(t));
+}
+
+MAPBOX_ETERNAL_CONSTEXPR const auto layerProperties = mapbox::eternal::hash_map<mapbox::eternal::string, uint8_t>(
+ {{"raster-brightness-max", toUint8(Property::RasterBrightnessMax)},
+ {"raster-brightness-min", toUint8(Property::RasterBrightnessMin)},
+ {"raster-contrast", toUint8(Property::RasterContrast)},
+ {"raster-fade-duration", toUint8(Property::RasterFadeDuration)},
+ {"raster-hue-rotate", toUint8(Property::RasterHueRotate)},
+ {"raster-opacity", toUint8(Property::RasterOpacity)},
+ {"raster-resampling", toUint8(Property::RasterResampling)},
+ {"raster-saturation", toUint8(Property::RasterSaturation)},
+ {"raster-brightness-max-transition", toUint8(Property::RasterBrightnessMaxTransition)},
+ {"raster-brightness-min-transition", toUint8(Property::RasterBrightnessMinTransition)},
+ {"raster-contrast-transition", toUint8(Property::RasterContrastTransition)},
+ {"raster-fade-duration-transition", toUint8(Property::RasterFadeDurationTransition)},
+ {"raster-hue-rotate-transition", toUint8(Property::RasterHueRotateTransition)},
+ {"raster-opacity-transition", toUint8(Property::RasterOpacityTransition)},
+ {"raster-resampling-transition", toUint8(Property::RasterResamplingTransition)},
+ {"raster-saturation-transition", toUint8(Property::RasterSaturationTransition)}});
+
+constexpr uint8_t lastPaintPropertyIndex = toUint8(Property::RasterSaturationTransition);
+} // namespace
+
optional<Error> RasterLayer::setPaintProperty(const std::string& name, const Convertible& value) {
- enum class Property {
- RasterBrightnessMax,
- RasterBrightnessMin,
- RasterContrast,
- RasterFadeDuration,
- RasterHueRotate,
- RasterOpacity,
- RasterResampling,
- RasterSaturation,
- RasterBrightnessMaxTransition,
- RasterBrightnessMinTransition,
- RasterContrastTransition,
- RasterFadeDurationTransition,
- RasterHueRotateTransition,
- RasterOpacityTransition,
- RasterResamplingTransition,
- RasterSaturationTransition,
- };
-
- MAPBOX_ETERNAL_CONSTEXPR const auto properties = mapbox::eternal::hash_map<mapbox::eternal::string, uint8_t>({
- { "raster-brightness-max", mbgl::underlying_type(Property::RasterBrightnessMax) },
- { "raster-brightness-min", mbgl::underlying_type(Property::RasterBrightnessMin) },
- { "raster-contrast", mbgl::underlying_type(Property::RasterContrast) },
- { "raster-fade-duration", mbgl::underlying_type(Property::RasterFadeDuration) },
- { "raster-hue-rotate", mbgl::underlying_type(Property::RasterHueRotate) },
- { "raster-opacity", mbgl::underlying_type(Property::RasterOpacity) },
- { "raster-resampling", mbgl::underlying_type(Property::RasterResampling) },
- { "raster-saturation", mbgl::underlying_type(Property::RasterSaturation) },
- { "raster-brightness-max-transition", mbgl::underlying_type(Property::RasterBrightnessMaxTransition) },
- { "raster-brightness-min-transition", mbgl::underlying_type(Property::RasterBrightnessMinTransition) },
- { "raster-contrast-transition", mbgl::underlying_type(Property::RasterContrastTransition) },
- { "raster-fade-duration-transition", mbgl::underlying_type(Property::RasterFadeDurationTransition) },
- { "raster-hue-rotate-transition", mbgl::underlying_type(Property::RasterHueRotateTransition) },
- { "raster-opacity-transition", mbgl::underlying_type(Property::RasterOpacityTransition) },
- { "raster-resampling-transition", mbgl::underlying_type(Property::RasterResamplingTransition) },
- { "raster-saturation-transition", mbgl::underlying_type(Property::RasterSaturationTransition) }
- });
-
- const auto it = properties.find(name.c_str());
- if (it == properties.end()) {
- return Error { "layer doesn't support this property" };
+ const auto it = layerProperties.find(name.c_str());
+ if (it == layerProperties.end() || it->second > lastPaintPropertyIndex) {
+ return Error{"layer doesn't support this property"};
}
auto property = static_cast<Property>(it->second);
@@ -392,49 +399,91 @@ optional<Error> RasterLayer::setPaintProperty(const std::string& name, const Con
if (!transition) {
return error;
}
-
+
if (property == Property::RasterBrightnessMaxTransition) {
setRasterBrightnessMaxTransition(*transition);
return nullopt;
}
-
+
if (property == Property::RasterBrightnessMinTransition) {
setRasterBrightnessMinTransition(*transition);
return nullopt;
}
-
+
if (property == Property::RasterContrastTransition) {
setRasterContrastTransition(*transition);
return nullopt;
}
-
+
if (property == Property::RasterFadeDurationTransition) {
setRasterFadeDurationTransition(*transition);
return nullopt;
}
-
+
if (property == Property::RasterHueRotateTransition) {
setRasterHueRotateTransition(*transition);
return nullopt;
}
-
+
if (property == Property::RasterOpacityTransition) {
setRasterOpacityTransition(*transition);
return nullopt;
}
-
+
if (property == Property::RasterResamplingTransition) {
setRasterResamplingTransition(*transition);
return nullopt;
}
-
+
if (property == Property::RasterSaturationTransition) {
setRasterSaturationTransition(*transition);
return nullopt;
}
-
- return Error { "layer doesn't support this property" };
+ return Error{"layer doesn't support this property"};
+}
+
+StyleProperty RasterLayer::getProperty(const std::string& name) const {
+ const auto it = layerProperties.find(name.c_str());
+ if (it == layerProperties.end()) {
+ return {};
+ }
+
+ switch (static_cast<Property>(it->second)) {
+ case Property::RasterBrightnessMax:
+ return makeStyleProperty(getRasterBrightnessMax());
+ case Property::RasterBrightnessMin:
+ return makeStyleProperty(getRasterBrightnessMin());
+ case Property::RasterContrast:
+ return makeStyleProperty(getRasterContrast());
+ case Property::RasterFadeDuration:
+ return makeStyleProperty(getRasterFadeDuration());
+ case Property::RasterHueRotate:
+ return makeStyleProperty(getRasterHueRotate());
+ case Property::RasterOpacity:
+ return makeStyleProperty(getRasterOpacity());
+ case Property::RasterResampling:
+ return makeStyleProperty(getRasterResampling());
+ case Property::RasterSaturation:
+ return makeStyleProperty(getRasterSaturation());
+ case Property::RasterBrightnessMaxTransition:
+ return makeStyleProperty(getRasterBrightnessMaxTransition());
+ case Property::RasterBrightnessMinTransition:
+ return makeStyleProperty(getRasterBrightnessMinTransition());
+ case Property::RasterContrastTransition:
+ return makeStyleProperty(getRasterContrastTransition());
+ case Property::RasterFadeDurationTransition:
+ return makeStyleProperty(getRasterFadeDurationTransition());
+ case Property::RasterHueRotateTransition:
+ return makeStyleProperty(getRasterHueRotateTransition());
+ case Property::RasterOpacityTransition:
+ return makeStyleProperty(getRasterOpacityTransition());
+ case Property::RasterResamplingTransition:
+ return makeStyleProperty(getRasterResamplingTransition());
+ case Property::RasterSaturationTransition:
+ return makeStyleProperty(getRasterSaturationTransition());
+ }
+ return {};
}
optional<Error> RasterLayer::setLayoutProperty(const std::string& name, const Convertible& value) {
@@ -451,5 +500,3 @@ Mutable<Layer::Impl> RasterLayer::mutableBaseImpl() const {
} // namespace style
} // namespace mbgl
-
-// clang-format on
diff --git a/src/mbgl/style/layers/symbol_layer.cpp b/src/mbgl/style/layers/symbol_layer.cpp
index af5daa4dc0..b3ee0f698c 100644
--- a/src/mbgl/style/layers/symbol_layer.cpp
+++ b/src/mbgl/style/layers/symbol_layer.cpp
@@ -1,5 +1,3 @@
-// clang-format off
-
// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`.
#include <mbgl/style/layers/symbol_layer.hpp>
@@ -1102,72 +1100,163 @@ TransitionOptions SymbolLayer::getTextTranslateAnchorTransition() const {
using namespace conversion;
+namespace {
+
+enum class Property : uint8_t {
+ IconColor,
+ IconHaloBlur,
+ IconHaloColor,
+ IconHaloWidth,
+ IconOpacity,
+ IconTranslate,
+ IconTranslateAnchor,
+ TextColor,
+ TextHaloBlur,
+ TextHaloColor,
+ TextHaloWidth,
+ TextOpacity,
+ TextTranslate,
+ TextTranslateAnchor,
+ IconColorTransition,
+ IconHaloBlurTransition,
+ IconHaloColorTransition,
+ IconHaloWidthTransition,
+ IconOpacityTransition,
+ IconTranslateTransition,
+ IconTranslateAnchorTransition,
+ TextColorTransition,
+ TextHaloBlurTransition,
+ TextHaloColorTransition,
+ TextHaloWidthTransition,
+ TextOpacityTransition,
+ TextTranslateTransition,
+ TextTranslateAnchorTransition,
+ IconAllowOverlap,
+ IconAnchor,
+ IconIgnorePlacement,
+ IconImage,
+ IconKeepUpright,
+ IconOffset,
+ IconOptional,
+ IconPadding,
+ IconPitchAlignment,
+ IconRotate,
+ IconRotationAlignment,
+ IconSize,
+ IconTextFit,
+ IconTextFitPadding,
+ SymbolAvoidEdges,
+ SymbolPlacement,
+ SymbolSortKey,
+ SymbolSpacing,
+ SymbolZOrder,
+ TextAllowOverlap,
+ TextAnchor,
+ TextField,
+ TextFont,
+ TextIgnorePlacement,
+ TextJustify,
+ TextKeepUpright,
+ TextLetterSpacing,
+ TextLineHeight,
+ TextMaxAngle,
+ TextMaxWidth,
+ TextOffset,
+ TextOptional,
+ TextPadding,
+ TextPitchAlignment,
+ TextRadialOffset,
+ TextRotate,
+ TextRotationAlignment,
+ TextSize,
+ TextTransform,
+ TextVariableAnchor,
+ TextWritingMode,
+};
+
+template <typename T>
+constexpr uint8_t toUint8(T t) noexcept {
+ return uint8_t(mbgl::underlying_type(t));
+}
+
+MAPBOX_ETERNAL_CONSTEXPR const auto layerProperties = mapbox::eternal::hash_map<mapbox::eternal::string, uint8_t>(
+ {{"icon-color", toUint8(Property::IconColor)},
+ {"icon-halo-blur", toUint8(Property::IconHaloBlur)},
+ {"icon-halo-color", toUint8(Property::IconHaloColor)},
+ {"icon-halo-width", toUint8(Property::IconHaloWidth)},
+ {"icon-opacity", toUint8(Property::IconOpacity)},
+ {"icon-translate", toUint8(Property::IconTranslate)},
+ {"icon-translate-anchor", toUint8(Property::IconTranslateAnchor)},
+ {"text-color", toUint8(Property::TextColor)},
+ {"text-halo-blur", toUint8(Property::TextHaloBlur)},
+ {"text-halo-color", toUint8(Property::TextHaloColor)},
+ {"text-halo-width", toUint8(Property::TextHaloWidth)},
+ {"text-opacity", toUint8(Property::TextOpacity)},
+ {"text-translate", toUint8(Property::TextTranslate)},
+ {"text-translate-anchor", toUint8(Property::TextTranslateAnchor)},
+ {"icon-color-transition", toUint8(Property::IconColorTransition)},
+ {"icon-halo-blur-transition", toUint8(Property::IconHaloBlurTransition)},
+ {"icon-halo-color-transition", toUint8(Property::IconHaloColorTransition)},
+ {"icon-halo-width-transition", toUint8(Property::IconHaloWidthTransition)},
+ {"icon-opacity-transition", toUint8(Property::IconOpacityTransition)},
+ {"icon-translate-transition", toUint8(Property::IconTranslateTransition)},
+ {"icon-translate-anchor-transition", toUint8(Property::IconTranslateAnchorTransition)},
+ {"text-color-transition", toUint8(Property::TextColorTransition)},
+ {"text-halo-blur-transition", toUint8(Property::TextHaloBlurTransition)},
+ {"text-halo-color-transition", toUint8(Property::TextHaloColorTransition)},
+ {"text-halo-width-transition", toUint8(Property::TextHaloWidthTransition)},
+ {"text-opacity-transition", toUint8(Property::TextOpacityTransition)},
+ {"text-translate-transition", toUint8(Property::TextTranslateTransition)},
+ {"text-translate-anchor-transition", toUint8(Property::TextTranslateAnchorTransition)},
+ {"icon-allow-overlap", toUint8(Property::IconAllowOverlap)},
+ {"icon-anchor", toUint8(Property::IconAnchor)},
+ {"icon-ignore-placement", toUint8(Property::IconIgnorePlacement)},
+ {"icon-image", toUint8(Property::IconImage)},
+ {"icon-keep-upright", toUint8(Property::IconKeepUpright)},
+ {"icon-offset", toUint8(Property::IconOffset)},
+ {"icon-optional", toUint8(Property::IconOptional)},
+ {"icon-padding", toUint8(Property::IconPadding)},
+ {"icon-pitch-alignment", toUint8(Property::IconPitchAlignment)},
+ {"icon-rotate", toUint8(Property::IconRotate)},
+ {"icon-rotation-alignment", toUint8(Property::IconRotationAlignment)},
+ {"icon-size", toUint8(Property::IconSize)},
+ {"icon-text-fit", toUint8(Property::IconTextFit)},
+ {"icon-text-fit-padding", toUint8(Property::IconTextFitPadding)},
+ {"symbol-avoid-edges", toUint8(Property::SymbolAvoidEdges)},
+ {"symbol-placement", toUint8(Property::SymbolPlacement)},
+ {"symbol-sort-key", toUint8(Property::SymbolSortKey)},
+ {"symbol-spacing", toUint8(Property::SymbolSpacing)},
+ {"symbol-z-order", toUint8(Property::SymbolZOrder)},
+ {"text-allow-overlap", toUint8(Property::TextAllowOverlap)},
+ {"text-anchor", toUint8(Property::TextAnchor)},
+ {"text-field", toUint8(Property::TextField)},
+ {"text-font", toUint8(Property::TextFont)},
+ {"text-ignore-placement", toUint8(Property::TextIgnorePlacement)},
+ {"text-justify", toUint8(Property::TextJustify)},
+ {"text-keep-upright", toUint8(Property::TextKeepUpright)},
+ {"text-letter-spacing", toUint8(Property::TextLetterSpacing)},
+ {"text-line-height", toUint8(Property::TextLineHeight)},
+ {"text-max-angle", toUint8(Property::TextMaxAngle)},
+ {"text-max-width", toUint8(Property::TextMaxWidth)},
+ {"text-offset", toUint8(Property::TextOffset)},
+ {"text-optional", toUint8(Property::TextOptional)},
+ {"text-padding", toUint8(Property::TextPadding)},
+ {"text-pitch-alignment", toUint8(Property::TextPitchAlignment)},
+ {"text-radial-offset", toUint8(Property::TextRadialOffset)},
+ {"text-rotate", toUint8(Property::TextRotate)},
+ {"text-rotation-alignment", toUint8(Property::TextRotationAlignment)},
+ {"text-size", toUint8(Property::TextSize)},
+ {"text-transform", toUint8(Property::TextTransform)},
+ {"text-variable-anchor", toUint8(Property::TextVariableAnchor)},
+ {"text-writing-mode", toUint8(Property::TextWritingMode)}});
+
+constexpr uint8_t lastPaintPropertyIndex = toUint8(Property::TextTranslateAnchorTransition);
+} // namespace
+
optional<Error> SymbolLayer::setPaintProperty(const std::string& name, const Convertible& value) {
- enum class Property {
- IconColor,
- IconHaloBlur,
- IconHaloColor,
- IconHaloWidth,
- IconOpacity,
- IconTranslate,
- IconTranslateAnchor,
- TextColor,
- TextHaloBlur,
- TextHaloColor,
- TextHaloWidth,
- TextOpacity,
- TextTranslate,
- TextTranslateAnchor,
- IconColorTransition,
- IconHaloBlurTransition,
- IconHaloColorTransition,
- IconHaloWidthTransition,
- IconOpacityTransition,
- IconTranslateTransition,
- IconTranslateAnchorTransition,
- TextColorTransition,
- TextHaloBlurTransition,
- TextHaloColorTransition,
- TextHaloWidthTransition,
- TextOpacityTransition,
- TextTranslateTransition,
- TextTranslateAnchorTransition,
- };
-
- MAPBOX_ETERNAL_CONSTEXPR const auto properties = mapbox::eternal::hash_map<mapbox::eternal::string, uint8_t>({
- { "icon-color", mbgl::underlying_type(Property::IconColor) },
- { "icon-halo-blur", mbgl::underlying_type(Property::IconHaloBlur) },
- { "icon-halo-color", mbgl::underlying_type(Property::IconHaloColor) },
- { "icon-halo-width", mbgl::underlying_type(Property::IconHaloWidth) },
- { "icon-opacity", mbgl::underlying_type(Property::IconOpacity) },
- { "icon-translate", mbgl::underlying_type(Property::IconTranslate) },
- { "icon-translate-anchor", mbgl::underlying_type(Property::IconTranslateAnchor) },
- { "text-color", mbgl::underlying_type(Property::TextColor) },
- { "text-halo-blur", mbgl::underlying_type(Property::TextHaloBlur) },
- { "text-halo-color", mbgl::underlying_type(Property::TextHaloColor) },
- { "text-halo-width", mbgl::underlying_type(Property::TextHaloWidth) },
- { "text-opacity", mbgl::underlying_type(Property::TextOpacity) },
- { "text-translate", mbgl::underlying_type(Property::TextTranslate) },
- { "text-translate-anchor", mbgl::underlying_type(Property::TextTranslateAnchor) },
- { "icon-color-transition", mbgl::underlying_type(Property::IconColorTransition) },
- { "icon-halo-blur-transition", mbgl::underlying_type(Property::IconHaloBlurTransition) },
- { "icon-halo-color-transition", mbgl::underlying_type(Property::IconHaloColorTransition) },
- { "icon-halo-width-transition", mbgl::underlying_type(Property::IconHaloWidthTransition) },
- { "icon-opacity-transition", mbgl::underlying_type(Property::IconOpacityTransition) },
- { "icon-translate-transition", mbgl::underlying_type(Property::IconTranslateTransition) },
- { "icon-translate-anchor-transition", mbgl::underlying_type(Property::IconTranslateAnchorTransition) },
- { "text-color-transition", mbgl::underlying_type(Property::TextColorTransition) },
- { "text-halo-blur-transition", mbgl::underlying_type(Property::TextHaloBlurTransition) },
- { "text-halo-color-transition", mbgl::underlying_type(Property::TextHaloColorTransition) },
- { "text-halo-width-transition", mbgl::underlying_type(Property::TextHaloWidthTransition) },
- { "text-opacity-transition", mbgl::underlying_type(Property::TextOpacityTransition) },
- { "text-translate-transition", mbgl::underlying_type(Property::TextTranslateTransition) },
- { "text-translate-anchor-transition", mbgl::underlying_type(Property::TextTranslateAnchorTransition) }
- });
-
- const auto it = properties.find(name.c_str());
- if (it == properties.end()) {
- return Error { "layer doesn't support this property" };
+ const auto it = layerProperties.find(name.c_str());
+ if (it == layerProperties.end() || it->second > lastPaintPropertyIndex) {
+ return Error{"layer doesn't support this property"};
}
auto property = static_cast<Property>(it->second);
@@ -1285,174 +1374,235 @@ optional<Error> SymbolLayer::setPaintProperty(const std::string& name, const Con
if (!transition) {
return error;
}
-
+
if (property == Property::IconColorTransition) {
setIconColorTransition(*transition);
return nullopt;
}
-
+
if (property == Property::IconHaloBlurTransition) {
setIconHaloBlurTransition(*transition);
return nullopt;
}
-
+
if (property == Property::IconHaloColorTransition) {
setIconHaloColorTransition(*transition);
return nullopt;
}
-
+
if (property == Property::IconHaloWidthTransition) {
setIconHaloWidthTransition(*transition);
return nullopt;
}
-
+
if (property == Property::IconOpacityTransition) {
setIconOpacityTransition(*transition);
return nullopt;
}
-
+
if (property == Property::IconTranslateTransition) {
setIconTranslateTransition(*transition);
return nullopt;
}
-
+
if (property == Property::IconTranslateAnchorTransition) {
setIconTranslateAnchorTransition(*transition);
return nullopt;
}
-
+
if (property == Property::TextColorTransition) {
setTextColorTransition(*transition);
return nullopt;
}
-
+
if (property == Property::TextHaloBlurTransition) {
setTextHaloBlurTransition(*transition);
return nullopt;
}
-
+
if (property == Property::TextHaloColorTransition) {
setTextHaloColorTransition(*transition);
return nullopt;
}
-
+
if (property == Property::TextHaloWidthTransition) {
setTextHaloWidthTransition(*transition);
return nullopt;
}
-
+
if (property == Property::TextOpacityTransition) {
setTextOpacityTransition(*transition);
return nullopt;
}
-
+
if (property == Property::TextTranslateTransition) {
setTextTranslateTransition(*transition);
return nullopt;
}
-
+
if (property == Property::TextTranslateAnchorTransition) {
setTextTranslateAnchorTransition(*transition);
return nullopt;
}
-
- return Error { "layer doesn't support this property" };
+ return Error{"layer doesn't support this property"};
+}
+
+StyleProperty SymbolLayer::getProperty(const std::string& name) const {
+ const auto it = layerProperties.find(name.c_str());
+ if (it == layerProperties.end()) {
+ return {};
+ }
+
+ switch (static_cast<Property>(it->second)) {
+ case Property::IconColor:
+ return makeStyleProperty(getIconColor());
+ case Property::IconHaloBlur:
+ return makeStyleProperty(getIconHaloBlur());
+ case Property::IconHaloColor:
+ return makeStyleProperty(getIconHaloColor());
+ case Property::IconHaloWidth:
+ return makeStyleProperty(getIconHaloWidth());
+ case Property::IconOpacity:
+ return makeStyleProperty(getIconOpacity());
+ case Property::IconTranslate:
+ return makeStyleProperty(getIconTranslate());
+ case Property::IconTranslateAnchor:
+ return makeStyleProperty(getIconTranslateAnchor());
+ case Property::TextColor:
+ return makeStyleProperty(getTextColor());
+ case Property::TextHaloBlur:
+ return makeStyleProperty(getTextHaloBlur());
+ case Property::TextHaloColor:
+ return makeStyleProperty(getTextHaloColor());
+ case Property::TextHaloWidth:
+ return makeStyleProperty(getTextHaloWidth());
+ case Property::TextOpacity:
+ return makeStyleProperty(getTextOpacity());
+ case Property::TextTranslate:
+ return makeStyleProperty(getTextTranslate());
+ case Property::TextTranslateAnchor:
+ return makeStyleProperty(getTextTranslateAnchor());
+ case Property::IconColorTransition:
+ return makeStyleProperty(getIconColorTransition());
+ case Property::IconHaloBlurTransition:
+ return makeStyleProperty(getIconHaloBlurTransition());
+ case Property::IconHaloColorTransition:
+ return makeStyleProperty(getIconHaloColorTransition());
+ case Property::IconHaloWidthTransition:
+ return makeStyleProperty(getIconHaloWidthTransition());
+ case Property::IconOpacityTransition:
+ return makeStyleProperty(getIconOpacityTransition());
+ case Property::IconTranslateTransition:
+ return makeStyleProperty(getIconTranslateTransition());
+ case Property::IconTranslateAnchorTransition:
+ return makeStyleProperty(getIconTranslateAnchorTransition());
+ case Property::TextColorTransition:
+ return makeStyleProperty(getTextColorTransition());
+ case Property::TextHaloBlurTransition:
+ return makeStyleProperty(getTextHaloBlurTransition());
+ case Property::TextHaloColorTransition:
+ return makeStyleProperty(getTextHaloColorTransition());
+ case Property::TextHaloWidthTransition:
+ return makeStyleProperty(getTextHaloWidthTransition());
+ case Property::TextOpacityTransition:
+ return makeStyleProperty(getTextOpacityTransition());
+ case Property::TextTranslateTransition:
+ return makeStyleProperty(getTextTranslateTransition());
+ case Property::TextTranslateAnchorTransition:
+ return makeStyleProperty(getTextTranslateAnchorTransition());
+ case Property::IconAllowOverlap:
+ return makeStyleProperty(getIconAllowOverlap());
+ case Property::IconAnchor:
+ return makeStyleProperty(getIconAnchor());
+ case Property::IconIgnorePlacement:
+ return makeStyleProperty(getIconIgnorePlacement());
+ case Property::IconImage:
+ return makeStyleProperty(getIconImage());
+ case Property::IconKeepUpright:
+ return makeStyleProperty(getIconKeepUpright());
+ case Property::IconOffset:
+ return makeStyleProperty(getIconOffset());
+ case Property::IconOptional:
+ return makeStyleProperty(getIconOptional());
+ case Property::IconPadding:
+ return makeStyleProperty(getIconPadding());
+ case Property::IconPitchAlignment:
+ return makeStyleProperty(getIconPitchAlignment());
+ case Property::IconRotate:
+ return makeStyleProperty(getIconRotate());
+ case Property::IconRotationAlignment:
+ return makeStyleProperty(getIconRotationAlignment());
+ case Property::IconSize:
+ return makeStyleProperty(getIconSize());
+ case Property::IconTextFit:
+ return makeStyleProperty(getIconTextFit());
+ case Property::IconTextFitPadding:
+ return makeStyleProperty(getIconTextFitPadding());
+ case Property::SymbolAvoidEdges:
+ return makeStyleProperty(getSymbolAvoidEdges());
+ case Property::SymbolPlacement:
+ return makeStyleProperty(getSymbolPlacement());
+ case Property::SymbolSortKey:
+ return makeStyleProperty(getSymbolSortKey());
+ case Property::SymbolSpacing:
+ return makeStyleProperty(getSymbolSpacing());
+ case Property::SymbolZOrder:
+ return makeStyleProperty(getSymbolZOrder());
+ case Property::TextAllowOverlap:
+ return makeStyleProperty(getTextAllowOverlap());
+ case Property::TextAnchor:
+ return makeStyleProperty(getTextAnchor());
+ case Property::TextField:
+ return makeStyleProperty(getTextField());
+ case Property::TextFont:
+ return makeStyleProperty(getTextFont());
+ case Property::TextIgnorePlacement:
+ return makeStyleProperty(getTextIgnorePlacement());
+ case Property::TextJustify:
+ return makeStyleProperty(getTextJustify());
+ case Property::TextKeepUpright:
+ return makeStyleProperty(getTextKeepUpright());
+ case Property::TextLetterSpacing:
+ return makeStyleProperty(getTextLetterSpacing());
+ case Property::TextLineHeight:
+ return makeStyleProperty(getTextLineHeight());
+ case Property::TextMaxAngle:
+ return makeStyleProperty(getTextMaxAngle());
+ case Property::TextMaxWidth:
+ return makeStyleProperty(getTextMaxWidth());
+ case Property::TextOffset:
+ return makeStyleProperty(getTextOffset());
+ case Property::TextOptional:
+ return makeStyleProperty(getTextOptional());
+ case Property::TextPadding:
+ return makeStyleProperty(getTextPadding());
+ case Property::TextPitchAlignment:
+ return makeStyleProperty(getTextPitchAlignment());
+ case Property::TextRadialOffset:
+ return makeStyleProperty(getTextRadialOffset());
+ case Property::TextRotate:
+ return makeStyleProperty(getTextRotate());
+ case Property::TextRotationAlignment:
+ return makeStyleProperty(getTextRotationAlignment());
+ case Property::TextSize:
+ return makeStyleProperty(getTextSize());
+ case Property::TextTransform:
+ return makeStyleProperty(getTextTransform());
+ case Property::TextVariableAnchor:
+ return makeStyleProperty(getTextVariableAnchor());
+ case Property::TextWritingMode:
+ return makeStyleProperty(getTextWritingMode());
+ }
+ return {};
}
optional<Error> SymbolLayer::setLayoutProperty(const std::string& name, const Convertible& value) {
if (name == "visibility") {
return Layer::setVisibility(value);
}
- enum class Property {
- IconAllowOverlap,
- IconAnchor,
- IconIgnorePlacement,
- IconImage,
- IconKeepUpright,
- IconOffset,
- IconOptional,
- IconPadding,
- IconPitchAlignment,
- IconRotate,
- IconRotationAlignment,
- IconSize,
- IconTextFit,
- IconTextFitPadding,
- SymbolAvoidEdges,
- SymbolPlacement,
- SymbolSortKey,
- SymbolSpacing,
- SymbolZOrder,
- TextAllowOverlap,
- TextAnchor,
- TextField,
- TextFont,
- TextIgnorePlacement,
- TextJustify,
- TextKeepUpright,
- TextLetterSpacing,
- TextLineHeight,
- TextMaxAngle,
- TextMaxWidth,
- TextOffset,
- TextOptional,
- TextPadding,
- TextPitchAlignment,
- TextRadialOffset,
- TextRotate,
- TextRotationAlignment,
- TextSize,
- TextTransform,
- TextVariableAnchor,
- TextWritingMode,
- };
- MAPBOX_ETERNAL_CONSTEXPR const auto properties = mapbox::eternal::hash_map<mapbox::eternal::string, uint8_t>({
- { "icon-allow-overlap", mbgl::underlying_type(Property::IconAllowOverlap) },
- { "icon-anchor", mbgl::underlying_type(Property::IconAnchor) },
- { "icon-ignore-placement", mbgl::underlying_type(Property::IconIgnorePlacement) },
- { "icon-image", mbgl::underlying_type(Property::IconImage) },
- { "icon-keep-upright", mbgl::underlying_type(Property::IconKeepUpright) },
- { "icon-offset", mbgl::underlying_type(Property::IconOffset) },
- { "icon-optional", mbgl::underlying_type(Property::IconOptional) },
- { "icon-padding", mbgl::underlying_type(Property::IconPadding) },
- { "icon-pitch-alignment", mbgl::underlying_type(Property::IconPitchAlignment) },
- { "icon-rotate", mbgl::underlying_type(Property::IconRotate) },
- { "icon-rotation-alignment", mbgl::underlying_type(Property::IconRotationAlignment) },
- { "icon-size", mbgl::underlying_type(Property::IconSize) },
- { "icon-text-fit", mbgl::underlying_type(Property::IconTextFit) },
- { "icon-text-fit-padding", mbgl::underlying_type(Property::IconTextFitPadding) },
- { "symbol-avoid-edges", mbgl::underlying_type(Property::SymbolAvoidEdges) },
- { "symbol-placement", mbgl::underlying_type(Property::SymbolPlacement) },
- { "symbol-sort-key", mbgl::underlying_type(Property::SymbolSortKey) },
- { "symbol-spacing", mbgl::underlying_type(Property::SymbolSpacing) },
- { "symbol-z-order", mbgl::underlying_type(Property::SymbolZOrder) },
- { "text-allow-overlap", mbgl::underlying_type(Property::TextAllowOverlap) },
- { "text-anchor", mbgl::underlying_type(Property::TextAnchor) },
- { "text-field", mbgl::underlying_type(Property::TextField) },
- { "text-font", mbgl::underlying_type(Property::TextFont) },
- { "text-ignore-placement", mbgl::underlying_type(Property::TextIgnorePlacement) },
- { "text-justify", mbgl::underlying_type(Property::TextJustify) },
- { "text-keep-upright", mbgl::underlying_type(Property::TextKeepUpright) },
- { "text-letter-spacing", mbgl::underlying_type(Property::TextLetterSpacing) },
- { "text-line-height", mbgl::underlying_type(Property::TextLineHeight) },
- { "text-max-angle", mbgl::underlying_type(Property::TextMaxAngle) },
- { "text-max-width", mbgl::underlying_type(Property::TextMaxWidth) },
- { "text-offset", mbgl::underlying_type(Property::TextOffset) },
- { "text-optional", mbgl::underlying_type(Property::TextOptional) },
- { "text-padding", mbgl::underlying_type(Property::TextPadding) },
- { "text-pitch-alignment", mbgl::underlying_type(Property::TextPitchAlignment) },
- { "text-radial-offset", mbgl::underlying_type(Property::TextRadialOffset) },
- { "text-rotate", mbgl::underlying_type(Property::TextRotate) },
- { "text-rotation-alignment", mbgl::underlying_type(Property::TextRotationAlignment) },
- { "text-size", mbgl::underlying_type(Property::TextSize) },
- { "text-transform", mbgl::underlying_type(Property::TextTransform) },
- { "text-variable-anchor", mbgl::underlying_type(Property::TextVariableAnchor) },
- { "text-writing-mode", mbgl::underlying_type(Property::TextWritingMode) }
- });
-
- const auto it = properties.find(name.c_str());
- if (it == properties.end()) {
+ const auto it = layerProperties.find(name.c_str());
+ if (it == layerProperties.end() || it->second <= lastPaintPropertyIndex) {
return Error { "layer doesn't support this property" };
}
@@ -1805,5 +1955,3 @@ Mutable<Layer::Impl> SymbolLayer::mutableBaseImpl() const {
} // namespace style
} // namespace mbgl
-
-// clang-format on
diff --git a/src/mbgl/style/light.cpp b/src/mbgl/style/light.cpp
index be56a0d93b..a10e693073 100644
--- a/src/mbgl/style/light.cpp
+++ b/src/mbgl/style/light.cpp
@@ -1,5 +1,3 @@
-// clang-format off
-
// This file is generated. Do not edit.
#include <mbgl/style/light.hpp>
@@ -36,29 +34,37 @@ Mutable<Light::Impl> Light::mutableImpl() const {
using namespace conversion;
-optional<Error> Light::setProperty(const std::string& name, const Convertible& value) {
- enum class Property {
- Anchor,
- Color,
- Intensity,
- Position,
- AnchorTransition,
- ColorTransition,
- IntensityTransition,
- PositionTransition,
- };
-
- MAPBOX_ETERNAL_CONSTEXPR const auto properties = mapbox::eternal::hash_map<mapbox::eternal::string, uint8_t>({
- { "anchor", mbgl::underlying_type(Property::Anchor) },
- { "color", mbgl::underlying_type(Property::Color) },
- { "intensity", mbgl::underlying_type(Property::Intensity) },
- { "position", mbgl::underlying_type(Property::Position) },
- { "anchor-transition", mbgl::underlying_type(Property::AnchorTransition) },
- { "color-transition", mbgl::underlying_type(Property::ColorTransition) },
- { "intensity-transition", mbgl::underlying_type(Property::IntensityTransition) },
- { "position-transition", mbgl::underlying_type(Property::PositionTransition) }
- });
+namespace {
+
+enum class Property : uint8_t {
+ Anchor,
+ Color,
+ Intensity,
+ Position,
+ AnchorTransition,
+ ColorTransition,
+ IntensityTransition,
+ PositionTransition,
+};
+
+template <typename T>
+constexpr uint8_t toUint8(T t) noexcept {
+ return uint8_t(mbgl::underlying_type(t));
+}
+
+MAPBOX_ETERNAL_CONSTEXPR const auto properties = mapbox::eternal::hash_map<mapbox::eternal::string, uint8_t>(
+ {{"anchor", toUint8(Property::Anchor)},
+ {"color", toUint8(Property::Color)},
+ {"intensity", toUint8(Property::Intensity)},
+ {"position", toUint8(Property::Position)},
+ {"anchor-transition", toUint8(Property::AnchorTransition)},
+ {"color-transition", toUint8(Property::ColorTransition)},
+ {"intensity-transition", toUint8(Property::IntensityTransition)},
+ {"position-transition", toUint8(Property::PositionTransition)}});
+} // namespace
+
+optional<Error> Light::setProperty(const std::string& name, const Convertible& value) {
const auto it = properties.find(name.c_str());
if (it == properties.end()) {
return Error { "light doesn't support this property" };
@@ -146,6 +152,33 @@ optional<Error> Light::setProperty(const std::string& name, const Convertible& v
return Error { "light doesn't support this property" };
}
+StyleProperty Light::getProperty(const std::string& name) const {
+ const auto it = properties.find(name.c_str());
+ if (it == properties.end()) {
+ return {};
+ }
+
+ switch (static_cast<Property>(it->second)) {
+ case Property::Anchor:
+ return makeStyleProperty(getAnchor());
+ case Property::Color:
+ return makeStyleProperty(getColor());
+ case Property::Intensity:
+ return makeStyleProperty(getIntensity());
+ case Property::Position:
+ return makeStyleProperty(getPosition());
+ case Property::AnchorTransition:
+ return makeStyleProperty(getAnchorTransition());
+ case Property::ColorTransition:
+ return makeStyleProperty(getColorTransition());
+ case Property::IntensityTransition:
+ return makeStyleProperty(getIntensityTransition());
+ case Property::PositionTransition:
+ return makeStyleProperty(getPositionTransition());
+ }
+ return {};
+}
+
LightAnchorType Light::getDefaultAnchor() {
return LightAnchor::defaultValue();
}
@@ -253,5 +286,3 @@ TransitionOptions Light::getPositionTransition() const {
} // namespace style
} // namespace mbgl
-
-// clang-format on
diff --git a/src/mbgl/style/light.cpp.ejs b/src/mbgl/style/light.cpp.ejs
index 55cc013827..68c1bf59e2 100644
--- a/src/mbgl/style/light.cpp.ejs
+++ b/src/mbgl/style/light.cpp.ejs
@@ -1,8 +1,6 @@
<%
const properties = locals.properties;
-%>
-// clang-format off
-
// This file is generated. Do not edit.
#include <mbgl/style/light.hpp>
@@ -39,21 +37,29 @@ Mutable<Light::Impl> Light::mutableImpl() const {
using namespace conversion;
-optional<Error> Light::setProperty(const std::string& name, const Convertible& value) {
- enum class Property {
+namespace {
+
+enum class Property : uint8_t {
<% for (const property of properties) { -%>
- <%- camelize(property.name) %>,
+ <%- camelize(property.name) %>,
<% } -%>
<% for (const property of properties) { -%>
- <%- camelize(property.name) %>Transition,
+ <%- camelize(property.name) %>Transition,
<% } -%>
- };
+};
+
+template <typename T>
+constexpr uint8_t toUint8(T t) noexcept {
+ return uint8_t(mbgl::underlying_type(t));
+}
- MAPBOX_ETERNAL_CONSTEXPR const auto properties = mapbox::eternal::hash_map<mapbox::eternal::string, uint8_t>({
- <%- properties.map(p => `{ "${p.name}", mbgl::underlying_type(Property::${camelize(p.name)}) }`).join(',\n ') %>,
- <%- properties.map(p => `{ "${p.name}-transition", mbgl::underlying_type(Property::${camelize(p.name)}Transition) }`).join(',\n ') %>
- });
+MAPBOX_ETERNAL_CONSTEXPR const auto properties = mapbox::eternal::hash_map<mapbox::eternal::string, uint8_t>(
+ {<%- properties.map(p => `{"${p.name}", toUint8(Property::${camelize(p.name)})}`).join(',\n ') %>,
+ <%- properties.map(p => `{"${p.name}-transition", toUint8(Property::${camelize(p.name)}Transition)}`).join(',\n ') %>});
+} // namespace
+
+optional<Error> Light::setProperty(const std::string& name, const Convertible& value) {
const auto it = properties.find(name.c_str());
if (it == properties.end()) {
return Error { "light doesn't support this property" };
@@ -107,6 +113,25 @@ optional<Error> Light::setProperty(const std::string& name, const Convertible& v
return Error { "light doesn't support this property" };
}
+StyleProperty Light::getProperty(const std::string& name) const {
+ const auto it = properties.find(name.c_str());
+ if (it == properties.end()) {
+ return {};
+ }
+
+ switch (static_cast<Property>(it->second)) {
+<% for (const property of properties) { -%>
+ case Property::<%- camelize(property.name) %>:
+ return makeStyleProperty(get<%- camelize(property.name) %>());
+<% } -%>
+<% for (const property of properties) { -%>
+ case Property::<%- camelize(property.name) %>Transition:
+ return makeStyleProperty(get<%- camelize(property.name) %>Transition());
+<% } -%>
+ }
+ return {};
+}
+
<% for (const property of properties) { -%>
<%- evaluatedType(property) %> Light::getDefault<%- camelize(property.name) %>() {
return Light<%- camelize(property.name) %>::defaultValue();
@@ -138,5 +163,3 @@ TransitionOptions Light::get<%- camelize(property.name) %>Transition() const {
} // namespace style
} // namespace mbgl
-
-// clang-format on
diff --git a/src/mbgl/style/sources/geojson_source.cpp b/src/mbgl/style/sources/geojson_source.cpp
index 5171c7c8d9..3832977cd4 100644
--- a/src/mbgl/style/sources/geojson_source.cpp
+++ b/src/mbgl/style/sources/geojson_source.cpp
@@ -32,8 +32,12 @@ void GeoJSONSource::setURL(const std::string& url_) {
}
void GeoJSONSource::setGeoJSON(const mapbox::geojson::geojson& geoJSON) {
+ setGeoJSONData(GeoJSONData::create(geoJSON, impl().getOptions()));
+}
+
+void GeoJSONSource::setGeoJSONData(std::shared_ptr<GeoJSONData> geoJSONData) {
req.reset();
- baseImpl = makeMutable<Impl>(impl(), geoJSON);
+ baseImpl = makeMutable<Impl>(impl(), std::move(geoJSONData));
observer->onSourceChanged(*this);
}
@@ -41,6 +45,10 @@ optional<std::string> GeoJSONSource::getURL() const {
return url;
}
+const GeoJSONOptions& GeoJSONSource::getOptions() const {
+ return impl().getOptions();
+}
+
void GeoJSONSource::loadDescription(FileSource& fileSource) {
if (!url) {
loaded = true;
@@ -62,17 +70,15 @@ void GeoJSONSource::loadDescription(FileSource& fileSource) {
*this, std::make_exception_ptr(std::runtime_error("unexpectedly empty GeoJSON")));
} else {
conversion::Error error;
- optional<GeoJSON> geoJSON = conversion::convertJSON<GeoJSON>(*res.data, error);
- if (!geoJSON) {
+ std::shared_ptr<GeoJSONData> geoJSONData;
+ if (optional<GeoJSON> geoJSON = conversion::convertJSON<GeoJSON>(*res.data, error)) {
+ geoJSONData = GeoJSONData::create(*geoJSON, impl().getOptions());
+ } else {
+ // Create an empty GeoJSON VT object to make sure we're not infinitely waiting for tiles to load.
Log::Error(Event::ParseStyle, "Failed to parse GeoJSON data: %s",
error.message.c_str());
- // Create an empty GeoJSON VT object to make sure we're not infinitely waiting for
- // tiles to load.
- baseImpl = makeMutable<Impl>(impl(), GeoJSON{ FeatureCollection{} });
- } else {
- baseImpl = makeMutable<Impl>(impl(), *geoJSON);
}
-
+ baseImpl = makeMutable<Impl>(impl(), std::move(geoJSONData));
loaded = true;
observer->onSourceLoaded(*this);
}
diff --git a/src/mbgl/style/sources/geojson_source_impl.cpp b/src/mbgl/style/sources/geojson_source_impl.cpp
index c3cb942709..468deb6134 100644
--- a/src/mbgl/style/sources/geojson_source_impl.cpp
+++ b/src/mbgl/style/sources/geojson_source_impl.cpp
@@ -82,13 +82,8 @@ T evaluateFeature(const mapbox::feature::feature<double>& f,
return T();
}
-GeoJSONSource::Impl::Impl(std::string id_, optional<GeoJSONOptions> options_)
- : Source::Impl(SourceType::GeoJSON, std::move(id_)) {
- options = options_ ? std::move(*options_) : GeoJSONOptions{};
-}
-
-GeoJSONSource::Impl::Impl(const Impl& other, const GeoJSON& geoJSON)
- : Source::Impl(other), options(other.options) {
+// static
+std::shared_ptr<GeoJSONData> GeoJSONData::create(const GeoJSON& geoJSON, const GeoJSONOptions& options) {
constexpr double scale = util::EXTENT / util::tileSize;
if (options.cluster && geoJSON.is<mapbox::feature::feature_collection<double>>() &&
!geoJSON.get<mapbox::feature::feature_collection<double>>().empty()) {
@@ -99,6 +94,7 @@ GeoJSONSource::Impl::Impl(const Impl& other, const GeoJSON& geoJSON)
Feature feature;
clusterOptions.map = [&](const PropertyMap& properties) -> PropertyMap {
PropertyMap ret{};
+ if (properties.empty()) return ret;
for (const auto& p : options.clusterProperties) {
feature.properties = properties;
ret[p.first] = evaluateFeature<Value>(feature, p.second.first);
@@ -115,19 +111,27 @@ GeoJSONSource::Impl::Impl(const Impl& other, const GeoJSON& geoJSON)
toReturn[p.first] = evaluateFeature<Value>(feature, p.second.second, accumulated);
}
};
- data = std::make_shared<SuperclusterData>(
- geoJSON.get<mapbox::feature::feature_collection<double>>(), clusterOptions);
- } else {
- mapbox::geojsonvt::Options vtOptions;
- vtOptions.maxZoom = options.maxzoom;
- vtOptions.extent = util::EXTENT;
- vtOptions.buffer = ::round(scale * options.buffer);
- vtOptions.tolerance = scale * options.tolerance;
- vtOptions.lineMetrics = options.lineMetrics;
- data = std::make_shared<GeoJSONVTData>(geoJSON, vtOptions);
+ return std::make_shared<SuperclusterData>(geoJSON.get<mapbox::feature::feature_collection<double>>(),
+ clusterOptions);
}
+
+ mapbox::geojsonvt::Options vtOptions;
+ vtOptions.maxZoom = options.maxzoom;
+ vtOptions.extent = util::EXTENT;
+ vtOptions.buffer = ::round(scale * options.buffer);
+ vtOptions.tolerance = scale * options.tolerance;
+ vtOptions.lineMetrics = options.lineMetrics;
+ return std::make_shared<GeoJSONVTData>(geoJSON, vtOptions);
}
+GeoJSONSource::Impl::Impl(std::string id_, optional<GeoJSONOptions> options_)
+ : Source::Impl(SourceType::GeoJSON, std::move(id_)) {
+ options = options_ ? std::move(*options_) : GeoJSONOptions{};
+}
+
+GeoJSONSource::Impl::Impl(const GeoJSONSource::Impl& other, std::shared_ptr<GeoJSONData> data_)
+ : Source::Impl(other), options(other.options), data(std::move(data_)) {}
+
GeoJSONSource::Impl::~Impl() = default;
Range<uint8_t> GeoJSONSource::Impl::getZoomRange() const {
diff --git a/src/mbgl/style/sources/geojson_source_impl.hpp b/src/mbgl/style/sources/geojson_source_impl.hpp
index 26b9d95a39..da2673a38c 100644
--- a/src/mbgl/style/sources/geojson_source_impl.hpp
+++ b/src/mbgl/style/sources/geojson_source_impl.hpp
@@ -11,27 +11,15 @@ class CanonicalTileID;
namespace style {
-class GeoJSONData {
-public:
- virtual ~GeoJSONData() = default;
- virtual mapbox::feature::feature_collection<int16_t> getTile(const CanonicalTileID&) = 0;
-
- // SuperclusterData
- virtual mapbox::feature::feature_collection<double> getChildren(const std::uint32_t) = 0;
- virtual mapbox::feature::feature_collection<double> getLeaves(const std::uint32_t,
- const std::uint32_t limit = 10u,
- const std::uint32_t offset = 0u) = 0;
- virtual std::uint8_t getClusterExpansionZoom(std::uint32_t) = 0;
-};
-
class GeoJSONSource::Impl : public Source::Impl {
public:
Impl(std::string id, optional<GeoJSONOptions>);
- Impl(const GeoJSONSource::Impl&, const GeoJSON&);
+ Impl(const GeoJSONSource::Impl&, std::shared_ptr<GeoJSONData>);
~Impl() final;
Range<uint8_t> getZoomRange() const;
std::weak_ptr<GeoJSONData> getData() const;
+ const GeoJSONOptions& getOptions() const { return options; }
optional<std::string> getAttribution() const final;
diff --git a/src/mbgl/text/placement.cpp b/src/mbgl/text/placement.cpp
index 8b0a3975fc..ecc8f93032 100644
--- a/src/mbgl/text/placement.cpp
+++ b/src/mbgl/text/placement.cpp
@@ -164,12 +164,12 @@ void Placement::placeBucket(
auto partiallyEvaluatedIconSize = bucket.iconSizeBinder->evaluateForZoom(state.getZoom());
optional<CollisionTileBoundaries> avoidEdges;
- if (mapMode == MapMode::Tile &&
- (layout.get<style::SymbolAvoidEdges>() ||
- layout.get<style::SymbolPlacement>() == style::SymbolPlacementType::Line)) {
+ if (mapMode == MapMode::Tile && (layout.get<style::SymbolAvoidEdges>() ||
+ layout.get<style::SymbolPlacement>() == style::SymbolPlacementType::Line ||
+ !layout.get<style::TextVariableAnchor>().empty())) {
avoidEdges = collisionIndex.projectTileBoundaries(posMatrix);
}
-
+
const bool textAllowOverlap = layout.get<style::TextAllowOverlap>();
const bool iconAllowOverlap = layout.get<style::IconAllowOverlap>();
// This logic is similar to the "defaultOpacityState" logic below in updateBucketOpacities
@@ -220,7 +220,6 @@ void Placement::placeBucket(
if (horizontalTextIndex) {
const PlacedSymbol& placedSymbol = bucket.text.placedSymbols.at(*horizontalTextIndex);
const float fontSize = evaluateSizeForFeature(partiallyEvaluatedTextSize, placedSymbol);
- const CollisionFeature& textCollisionFeature = symbolInstance.textCollisionFeature;
const auto updatePreviousOrientationIfNotPlaced = [&](bool isPlaced) {
if (bucket.allowVerticalPlacement && !isPlaced && getPrevPlacement()) {
@@ -282,7 +281,8 @@ void Placement::placeBucket(
placeText = placed.first;
offscreen &= placed.second;
- } else if (!textCollisionFeature.alongLine && !textCollisionFeature.boxes.empty()) {
+ } else if (!symbolInstance.textCollisionFeature.alongLine &&
+ !symbolInstance.textCollisionFeature.boxes.empty()) {
// If this symbol was in the last placement, shift the previously used
// anchor to the front of the anchor list, only if the previous anchor
// is still in the anchor list.
@@ -305,8 +305,13 @@ void Placement::placeBucket(
}
}
- const auto placeFeatureForVariableAnchors = [&] (const CollisionFeature& collisionFeature, style::TextWritingModeType orientation) {
- const CollisionBox& textBox = collisionFeature.boxes[0];
+ const bool doVariableIconPlacement =
+ hasIconTextFit && !iconAllowOverlap && symbolInstance.placedIconIndex;
+
+ const auto placeFeatureForVariableAnchors = [&](const CollisionFeature& textCollisionFeature,
+ style::TextWritingModeType orientation,
+ const CollisionFeature& iconCollisionFeature) {
+ const CollisionBox& textBox = textCollisionFeature.boxes[0];
const float width = textBox.x2 - textBox.x1;
const float height = textBox.y2 - textBox.y1;
const float textBoxScale = symbolInstance.textBoxScale;
@@ -323,12 +328,40 @@ void Placement::placeBucket(
}
textBoxes.clear();
- placedFeature = collisionIndex.placeFeature(collisionFeature, shift,
- posMatrix, mat4(), pixelRatio,
- placedSymbol, scale, fontSize,
+ placedFeature = collisionIndex.placeFeature(textCollisionFeature,
+ shift,
+ posMatrix,
+ mat4(),
+ pixelRatio,
+ placedSymbol,
+ scale,
+ fontSize,
allowOverlap,
pitchWithMap,
- params.showCollisionBoxes, avoidEdges, collisionGroup.second, textBoxes);
+ params.showCollisionBoxes,
+ avoidEdges,
+ collisionGroup.second,
+ textBoxes);
+
+ if (doVariableIconPlacement) {
+ auto placedIconFeature = collisionIndex.placeFeature(iconCollisionFeature,
+ shift,
+ posMatrix,
+ iconLabelPlaneMatrix,
+ pixelRatio,
+ placedSymbol,
+ scale,
+ fontSize,
+ iconAllowOverlap,
+ pitchWithMap,
+ params.showCollisionBoxes,
+ avoidEdges,
+ collisionGroup.second,
+ iconBoxes);
+ iconBoxes.clear();
+ if (!placedIconFeature.first) continue;
+ }
+
if (placedFeature.first) {
assert(symbolInstance.crossTileID != 0u);
optional<style::TextVariableAnchorType> prevAnchor;
@@ -366,12 +399,18 @@ void Placement::placeBucket(
};
const auto placeHorizontal = [&] {
- return placeFeatureForVariableAnchors(symbolInstance.textCollisionFeature, style::TextWritingModeType::Horizontal);
+ return placeFeatureForVariableAnchors(symbolInstance.textCollisionFeature,
+ style::TextWritingModeType::Horizontal,
+ symbolInstance.iconCollisionFeature);
};
const auto placeVertical = [&] {
if (bucket.allowVerticalPlacement && !placed.first && symbolInstance.verticalTextCollisionFeature) {
- return placeFeatureForVariableAnchors(*symbolInstance.verticalTextCollisionFeature, style::TextWritingModeType::Vertical);
+ return placeFeatureForVariableAnchors(*symbolInstance.verticalTextCollisionFeature,
+ style::TextWritingModeType::Vertical,
+ symbolInstance.verticalIconCollisionFeature
+ ? *symbolInstance.verticalIconCollisionFeature
+ : symbolInstance.iconCollisionFeature);
}
return std::pair<bool, bool>{false, false};
};
diff --git a/src/mbgl/text/quads.cpp b/src/mbgl/text/quads.cpp
index 281c5d99de..a94bfee336 100644
--- a/src/mbgl/text/quads.cpp
+++ b/src/mbgl/text/quads.cpp
@@ -20,12 +20,28 @@ SymbolQuad getIconQuad(const PositionedIcon& shapedIcon,
// If you have a 10px icon that isn't perfectly aligned to the pixel grid it will cover 11 actual
// pixels. The quad needs to be padded to account for this, otherwise they'll look slightly clipped
// on one edge in some cases.
- const float border = 1.0;
-
- float top = shapedIcon.top() - border / image.pixelRatio;
- float left = shapedIcon.left() - border / image.pixelRatio;
- float bottom = shapedIcon.bottom() + border / image.pixelRatio;
- float right = shapedIcon.right() + border / image.pixelRatio;
+ constexpr const float border = 1.0f;
+
+ // Expand the box to respect the 1 pixel border in the atlas image. We're using `image.paddedRect - border`
+ // instead of image.displaySize because we only pad with one pixel for retina images as well, and the
+ // displaySize uses the logical dimensions, not the physical pixel dimensions.
+ // Unlike the JavaScript version, we're _not_ including the padding in the texture rect, so the
+ // logic "dimension * padded / non-padded - dimension" is swapped.
+ const float iconWidth = shapedIcon.right() - shapedIcon.left();
+ const float expandX = (iconWidth * (static_cast<float>(image.textureRect.w) + 2.0f * border) /
+ static_cast<float>(image.textureRect.w) -
+ iconWidth) /
+ 2.0f;
+ const float left = shapedIcon.left() - expandX;
+ const float right = shapedIcon.right() + expandX;
+
+ const float iconHeight = shapedIcon.bottom() - shapedIcon.top();
+ const float expandY = (iconHeight * (static_cast<float>(image.textureRect.h) + 2.0f * border) /
+ static_cast<float>(image.textureRect.h) -
+ iconHeight) /
+ 2.0f;
+ const float top = shapedIcon.top() - expandY;
+ const float bottom = shapedIcon.bottom() + expandY;
Point<float> tl{left, top};
Point<float> tr{right, top};
diff --git a/src/mbgl/text/shaping.cpp b/src/mbgl/text/shaping.cpp
index d6d9a3d34e..8eb885af5d 100644
--- a/src/mbgl/text/shaping.cpp
+++ b/src/mbgl/text/shaping.cpp
@@ -32,7 +32,7 @@ AnchorAlignment AnchorAlignment::getAnchorAlignment(style::SymbolAnchorType anch
result.horizontalAlign = 0.0f;
break;
default:
- break;
+ break;
}
switch (anchor) {
@@ -83,33 +83,41 @@ PositionedIcon PositionedIcon::shapeIcon(const ImagePosition& image,
return PositionedIcon { image, top, bottom, left, right, iconRotation };
}
-void PositionedIcon::fitIconToText(const style::SymbolLayoutProperties::Evaluated& layout,
- const Shaping& shapedText,
- float layoutTextSize) {
- using namespace style;
- assert(layout.get<IconTextFit>() != IconTextFitType::None);
- if (shapedText) {
- auto iconWidth = _right - _left;
- auto iconHeight = _bottom - _top;
- auto size = layoutTextSize / 24.0f;
- auto textLeft = shapedText.left * size;
- auto textRight = shapedText.right * size;
- auto textTop = shapedText.top * size;
- auto textBottom = shapedText.bottom * size;
- auto textWidth = textRight - textLeft;
- auto textHeight = textBottom - textTop;
- auto padT = layout.get<IconTextFitPadding>()[0];
- auto padR = layout.get<IconTextFitPadding>()[1];
- auto padB = layout.get<IconTextFitPadding>()[2];
- auto padL = layout.get<IconTextFitPadding>()[3];
- auto offsetY = layout.get<IconTextFit>() == IconTextFitType::Width ? (textHeight - iconHeight) * 0.5 : 0;
- auto offsetX = layout.get<IconTextFit>() == IconTextFitType::Height ? (textWidth - iconWidth) * 0.5 : 0;
- auto width = layout.get<IconTextFit>() == IconTextFitType::Width || layout.get<IconTextFit>() == IconTextFitType::Both ? textWidth : iconWidth;
- auto height = layout.get<IconTextFit>() == IconTextFitType::Height || layout.get<IconTextFit>() == IconTextFitType::Both ? textHeight : iconHeight;
- _left = textLeft + offsetX - padL;
- _top = textTop + offsetY - padT;
- _right = textLeft + offsetX + padR + width;
- _bottom = textTop + offsetY + padB + height;
+void PositionedIcon::fitIconToText(const Shaping& shapedText,
+ const style::IconTextFitType textFit,
+ const std::array<float, 4>& padding,
+ const std::array<float, 2>& iconOffset,
+ const float fontScale) {
+ assert(textFit != style::IconTextFitType::None);
+ assert(shapedText);
+
+ // We don't respect the icon-anchor, because icon-text-fit is set. Instead,
+ // the icon will be centered on the text, then stretched in the given
+ // dimensions.
+
+ const float textLeft = shapedText.left * fontScale;
+ const float textRight = shapedText.right * fontScale;
+
+ if (textFit == style::IconTextFitType::Width || textFit == style::IconTextFitType::Both) {
+ // Stretched horizontally to the text width
+ _left = iconOffset[0] + textLeft - padding[3];
+ _right = iconOffset[0] + textRight + padding[1];
+ } else {
+ // Centered on the text
+ _left = iconOffset[0] + (textLeft + textRight - image().displaySize()[0]) / 2.0f;
+ _right = _left + image().displaySize()[0];
+ }
+
+ const float textTop = shapedText.top * fontScale;
+ const float textBottom = shapedText.bottom * fontScale;
+ if (textFit == style::IconTextFitType::Height || textFit == style::IconTextFitType::Both) {
+ // Stretched vertically to the text height
+ _top = iconOffset[1] + textTop - padding[0];
+ _bottom = iconOffset[1] + textBottom + padding[2];
+ } else {
+ // Centered on the text
+ _top = iconOffset[1] + (textTop + textBottom - image().displaySize()[1]) / 2.0f;
+ _bottom = _top + image().displaySize()[1];
}
}
diff --git a/src/mbgl/text/shaping.hpp b/src/mbgl/text/shaping.hpp
index 28730e9db9..6ed1b5cb0e 100644
--- a/src/mbgl/text/shaping.hpp
+++ b/src/mbgl/text/shaping.hpp
@@ -45,9 +45,11 @@ public:
// Updates shaped icon's bounds based on shaped text's bounds and provided
// layout properties.
- void fitIconToText(const style::SymbolLayoutProperties::Evaluated& layout,
- const Shaping& shapedText,
- float layoutTextSize);
+ void fitIconToText(const Shaping& shapedText,
+ const style::IconTextFitType textFit,
+ const std::array<float, 4>& padding,
+ const std::array<float, 2>& iconOffset,
+ const float fontScale);
const ImagePosition& image() const { return _image; }
float top() const { return _top; }
diff --git a/src/mbgl/util/color.cpp b/src/mbgl/util/color.cpp
index 4c2814cf14..44f815e8b8 100644
--- a/src/mbgl/util/color.cpp
+++ b/src/mbgl/util/color.cpp
@@ -45,10 +45,7 @@ std::array<double, 4> Color::toArray() const {
}
mbgl::Value Color::toObject() const {
- return std::unordered_map<std::string, mbgl::Value>{{"r", double(r)},
- {"g", double(g)},
- {"b", double(b)},
- {"a", double(a)}};
+ return mapbox::base::ValueObject{{"r", double(r)}, {"g", double(g)}, {"b", double(b)}, {"a", double(a)}};
}
} // namespace mbgl
diff --git a/src/mbgl/util/thread_pool.cpp b/src/mbgl/util/thread_pool.cpp
index e839d1b4be..040e996dd4 100644
--- a/src/mbgl/util/thread_pool.cpp
+++ b/src/mbgl/util/thread_pool.cpp
@@ -6,53 +6,44 @@
namespace mbgl {
-ThreadPool::ThreadPool(std::size_t count) {
- threads.reserve(count);
+ThreadedSchedulerBase::~ThreadedSchedulerBase() = default;
- for (std::size_t i = 0; i < count; ++i) {
- threads.emplace_back([this, i]() {
- platform::setCurrentThreadName(std::string{ "Worker " } + util::toString(i + 1));
- platform::attachThread();
-
- while (true) {
- std::unique_lock<std::mutex> lock(mutex);
+void ThreadedSchedulerBase::terminate() {
+ {
+ std::lock_guard<std::mutex> lock(mutex);
+ terminated = true;
+ }
+ cv.notify_all();
+}
- cv.wait(lock, [this] {
- return !queue.empty() || terminate;
- });
+std::thread ThreadedSchedulerBase::makeSchedulerThread(size_t index) {
+ return std::thread([this, index]() {
+ platform::setCurrentThreadName(std::string{"Worker "} + util::toString(index + 1));
+ platform::attachThread();
- if (terminate) {
- platform::detachThread();
- return;
- }
+ while (true) {
+ std::unique_lock<std::mutex> lock(mutex);
- auto mailbox = queue.front();
- queue.pop();
- lock.unlock();
+ cv.wait(lock, [this] { return !queue.empty() || terminated; });
- Mailbox::maybeReceive(mailbox);
+ if (terminated) {
+ platform::detachThread();
+ return;
}
- });
- }
-}
-ThreadPool::~ThreadPool() {
- {
- std::lock_guard<std::mutex> lock(mutex);
- terminate = true;
- }
-
- cv.notify_all();
-
- for (auto& thread : threads) {
- thread.join();
- }
+ auto function = std::move(queue.front());
+ queue.pop();
+ lock.unlock();
+ if (function) function();
+ }
+ });
}
-void ThreadPool::schedule(std::weak_ptr<Mailbox> mailbox) {
+void ThreadedSchedulerBase::schedule(std::function<void()> fn) {
+ assert(fn);
{
std::lock_guard<std::mutex> lock(mutex);
- queue.push(mailbox);
+ queue.push(std::move(fn));
}
cv.notify_one();
diff --git a/src/mbgl/util/thread_pool.hpp b/src/mbgl/util/thread_pool.hpp
index 509fd06061..7642f9b4ca 100644
--- a/src/mbgl/util/thread_pool.hpp
+++ b/src/mbgl/util/thread_pool.hpp
@@ -3,6 +3,7 @@
#include <mbgl/actor/mailbox.hpp>
#include <mbgl/actor/scheduler.hpp>
+#include <array>
#include <condition_variable>
#include <mutex>
#include <queue>
@@ -10,19 +11,60 @@
namespace mbgl {
-class ThreadPool final : public Scheduler {
+class ThreadedSchedulerBase : public Scheduler {
public:
- explicit ThreadPool(std::size_t count);
- ~ThreadPool() override;
+ void schedule(std::function<void()>) override;
- void schedule(std::weak_ptr<Mailbox>) override;
+protected:
+ ThreadedSchedulerBase() = default;
+ ~ThreadedSchedulerBase() override;
-private:
- std::vector<std::thread> threads;
- std::queue<std::weak_ptr<Mailbox>> queue;
+ void terminate();
+ std::thread makeSchedulerThread(size_t index);
+
+ std::queue<std::function<void()>> queue;
std::mutex mutex;
std::condition_variable cv;
- bool terminate{ false };
+ bool terminated{false};
+};
+
+/**
+ * @brief ThreadScheduler implements Scheduler interface using a lightweight event loop
+ *
+ * @tparam N number of threads
+ *
+ * Note: If N == 1 all scheduled tasks are guaranteed to execute consequently;
+ * otherwise, some of the scheduled tasks might be executed in parallel.
+ */
+template <std::size_t N>
+class ThreadedScheduler : public ThreadedSchedulerBase {
+public:
+ ThreadedScheduler() {
+ for (std::size_t i = 0u; i < N; ++i) {
+ threads[i] = makeSchedulerThread(i);
+ }
+ }
+
+ ~ThreadedScheduler() override {
+ terminate();
+ for (auto& thread : threads) {
+ thread.join();
+ }
+ }
+
+ mapbox::base::WeakPtr<Scheduler> makeWeakPtr() override { return weakFactory.makeWeakPtr(); }
+
+private:
+ std::array<std::thread, N> threads;
+ mapbox::base::WeakPtrFactory<Scheduler> weakFactory{this};
+ static_assert(N > 0, "Thread count must be more than zero.");
};
+using SequencedScheduler = ThreadedScheduler<1>;
+
+template <std::size_t extra>
+using ParallelScheduler = ThreadedScheduler<1 + extra>;
+
+using ThreadPool = ParallelScheduler<3>;
+
} // namespace mbgl
diff --git a/test/actor/actor.test.cpp b/test/actor/actor.test.cpp
index 6db95a83f1..c2a41fe787 100644
--- a/test/actor/actor.test.cpp
+++ b/test/actor/actor.test.cpp
@@ -90,6 +90,7 @@ TEST(Actor, DestructionBlocksOnSend) {
std::promise<void> promise;
std::future<void> future;
std::atomic<bool> waited;
+ mapbox::base::WeakPtrFactory<Scheduler> weakFactory{this};
TestScheduler(std::promise<void> promise_, std::future<void> future_)
: promise(std::move(promise_)),
@@ -101,12 +102,13 @@ TEST(Actor, DestructionBlocksOnSend) {
EXPECT_TRUE(waited.load());
}
- void schedule(std::weak_ptr<Mailbox>) final {
+ void schedule(std::function<void()>) final {
promise.set_value();
future.wait();
std::this_thread::sleep_for(1ms);
waited = true;
}
+ mapbox::base::WeakPtr<Scheduler> makeWeakPtr() override { return weakFactory.makeWeakPtr(); }
};
struct Test {
diff --git a/test/api/annotations.test.cpp b/test/api/annotations.test.cpp
index 03330dc4c6..4886d90a93 100644
--- a/test/api/annotations.test.cpp
+++ b/test/api/annotations.test.cpp
@@ -33,8 +33,7 @@ public:
MapOptions().withMapMode(MapMode::Static).withSize(frontend.getSize())};
void checkRendering(const char * name) {
- test::checkImage(std::string("test/fixtures/annotations/") + name,
- frontend.render(map), 0.0002, 0.1);
+ test::checkImage(std::string("test/fixtures/annotations/") + name, frontend.render(map).image, 0.0002, 0.1);
}
};
diff --git a/test/api/custom_geometry_source.test.cpp b/test/api/custom_geometry_source.test.cpp
index f35b4d335c..93cf6ba56e 100644
--- a/test/api/custom_geometry_source.test.cpp
+++ b/test/api/custom_geometry_source.test.cpp
@@ -64,5 +64,5 @@ TEST(CustomGeometrySource, Grid) {
layer->setLineColor(Color{ 1.0, 1.0, 1.0, 1.0 });
map.getStyle().addLayer(std::move(layer));
- test::checkImage("test/fixtures/custom_geometry_source/grid", frontend.render(map), 0.0006, 0.1);
+ test::checkImage("test/fixtures/custom_geometry_source/grid", frontend.render(map).image, 0.0006, 0.1);
}
diff --git a/test/api/custom_layer.test.cpp b/test/api/custom_layer.test.cpp
index 175d053f93..2222645147 100644
--- a/test/api/custom_layer.test.cpp
+++ b/test/api/custom_layer.test.cpp
@@ -108,5 +108,5 @@ TEST(CustomLayer, Basic) {
layer->setFillColor(Color{ 1.0, 1.0, 0.0, 1.0 });
map.getStyle().addLayer(std::move(layer));
- test::checkImage("test/fixtures/custom_layer/basic", frontend.render(map), 0.0006, 0.1);
+ test::checkImage("test/fixtures/custom_layer/basic", frontend.render(map).image, 0.0006, 0.1);
}
diff --git a/test/api/recycle_map.cpp b/test/api/recycle_map.cpp
index b3c573b1a2..be7bb77b4c 100644
--- a/test/api/recycle_map.cpp
+++ b/test/api/recycle_map.cpp
@@ -45,10 +45,10 @@ TEST(API, RecycleMapUpdateImages) {
// default marker
loadStyle("default_marker", "test/fixtures/sprites/default_marker.png");
- test::checkImage("test/fixtures/recycle_map/default_marker", frontend.render(*map), 0.0006, 0.1);
+ test::checkImage("test/fixtures/recycle_map/default_marker", frontend.render(*map).image, 0.0006, 0.1);
// flipped marker
loadStyle("flipped_marker", "test/fixtures/sprites/flipped_marker.png");
- test::checkImage("test/fixtures/recycle_map/flipped_marker", frontend.render(*map), 0.0006, 0.1);
+ test::checkImage("test/fixtures/recycle_map/flipped_marker", frontend.render(*map).image, 0.0006, 0.1);
}
diff --git a/test/fixtures/offline_database/no_auto_vacuum.db b/test/fixtures/offline_database/no_auto_vacuum.db
new file mode 100644
index 0000000000..f19e639933
--- /dev/null
+++ b/test/fixtures/offline_database/no_auto_vacuum.db
Binary files differ
diff --git a/test/geometry/line_atlas.test.cpp b/test/geometry/line_atlas.test.cpp
index 960e4ad7ad..1d817220e1 100644
--- a/test/geometry/line_atlas.test.cpp
+++ b/test/geometry/line_atlas.test.cpp
@@ -13,7 +13,7 @@ TEST(LineAtlas, Random) {
std::normal_distribution<float> lengthDistribution(3, 5);
for (size_t it = 0; it < 100; it++) {
- LineAtlas atlas{ Size{ 128, 1024 } };
+ LineAtlas atlas;
std::vector<float> dasharray;
dasharray.reserve(8);
for (size_t j = 0; j < 100; j++) {
@@ -25,7 +25,7 @@ TEST(LineAtlas, Random) {
const LinePatternCap patternCap =
capStyleDistribution(generator) > 0 ? LinePatternCap::Round : LinePatternCap::Square;
- atlas.addDash(dasharray, patternCap);
+ atlas.getDashPatternTexture(dasharray, dasharray, patternCap);
}
}
}
diff --git a/test/gl/context.test.cpp b/test/gl/context.test.cpp
index 770434c5be..9c709b7597 100644
--- a/test/gl/context.test.cpp
+++ b/test/gl/context.test.cpp
@@ -91,9 +91,10 @@ TEST(GLContextMode, Shared) {
util::RunLoop loop;
- HeadlessFrontend frontend { 1, gfx::ContextMode::Shared };
+ HeadlessFrontend frontend{1, gfx::HeadlessBackend::SwapBehaviour::NoFlush, gfx::ContextMode::Shared};
- Map map(frontend, MapObserver::nullObserver(),
+ Map map(frontend,
+ MapObserver::nullObserver(),
MapOptions().withMapMode(MapMode::Static).withSize(frontend.getSize()),
ResourceOptions().withCachePath(":memory:").withAssetPath("test/fixtures/api/assets"));
map.getStyle().loadJSON(util::read_file("test/fixtures/api/water.json"));
@@ -118,5 +119,5 @@ TEST(GLContextMode, Shared) {
MBGL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_STRIP, 0, 3));
}
- test::checkImage("test/fixtures/shared_context", frontend.render(map), 0.5, 0.1);
+ test::checkImage("test/fixtures/shared_context", frontend.render(map).image, 0.5, 0.1);
}
diff --git a/test/map/map.test.cpp b/test/map/map.test.cpp
index 09e1b92336..0eebc93f32 100644
--- a/test/map/map.test.cpp
+++ b/test/map/map.test.cpp
@@ -259,10 +259,7 @@ TEST(Map, Offline) {
test.map.getStyle().loadURL(prefix + "style.json");
- test::checkImage("test/fixtures/map/offline",
- test.frontend.render(test.map),
- 0.0015,
- 0.1);
+ test::checkImage("test/fixtures/map/offline", test.frontend.render(test.map).image, 0.0015, 0.1);
NetworkStatus::Set(NetworkStatus::Status::Online);
}
@@ -608,7 +605,7 @@ TEST(Map, AddLayer) {
layer->setBackgroundColor({ { 1, 0, 0, 1 } });
test.map.getStyle().addLayer(std::move(layer));
- test::checkImage("test/fixtures/map/add_layer", test.frontend.render(test.map));
+ test::checkImage("test/fixtures/map/add_layer", test.frontend.render(test.map).image);
}
TEST(Map, WithoutVAOExtension) {
@@ -623,7 +620,7 @@ TEST(Map, WithoutVAOExtension) {
test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/water.json"));
- test::checkImage("test/fixtures/map/no_vao", test.frontend.render(test.map), 0.002);
+ test::checkImage("test/fixtures/map/no_vao", test.frontend.render(test.map).image, 0.002);
}
TEST(Map, RemoveLayer) {
@@ -636,7 +633,7 @@ TEST(Map, RemoveLayer) {
test.map.getStyle().addLayer(std::move(layer));
test.map.getStyle().removeLayer("background");
- test::checkImage("test/fixtures/map/remove_layer", test.frontend.render(test.map));
+ test::checkImage("test/fixtures/map/remove_layer", test.frontend.render(test.map).image);
}
TEST(Map, DisabledSources) {
@@ -694,9 +691,9 @@ TEST(Map, DisabledSources) {
}
)STYLE");
- test::checkImage("test/fixtures/map/disabled_layers/first", test.frontend.render(test.map));
+ test::checkImage("test/fixtures/map/disabled_layers/first", test.frontend.render(test.map).image);
test.map.jumpTo(CameraOptions().withZoom(0.5));
- test::checkImage("test/fixtures/map/disabled_layers/second", test.frontend.render(test.map));
+ test::checkImage("test/fixtures/map/disabled_layers/second", test.frontend.render(test.map).image);
}
TEST(Map, DontLoadUnneededTiles) {
@@ -815,10 +812,7 @@ TEST(Map, NoContentTiles) {
}]
})STYLE");
- test::checkImage("test/fixtures/map/nocontent",
- test.frontend.render(test.map),
- 0.0015,
- 0.1);
+ test::checkImage("test/fixtures/map/nocontent", test.frontend.render(test.map).image, 0.0015, 0.1);
}
// https://github.com/mapbox/mapbox-gl-native/issues/12432
@@ -926,4 +920,137 @@ TEST(Map, Issue15342) {
};
test.runLoop.run();
+}
+
+TEST(Map, UniversalStyleGetter) {
+ MapTest<> test;
+
+ test.map.getStyle().loadJSON(R"STYLE({
+ "sources": {
+ "mapbox": {
+ "type": "vector",
+ "tiles": ["http://example.com/{z}-{x}-{y}.vector.pbf"]
+ }
+ },
+ "layers": [{
+ "id": "line",
+ "type": "line",
+ "source": "mapbox",
+ "paint": {
+ "line-color": "red",
+ "line-opacity": 0.5,
+ "line-width": ["get", "width"]
+ },
+ "layout": {
+ "line-cap": "butt"
+ }
+ }]
+ })STYLE");
+
+ Layer* lineLayer = test.map.getStyle().getLayer("line");
+ ASSERT_TRUE(lineLayer);
+
+ StyleProperty nonexistent = lineLayer->getProperty("nonexistent");
+ ASSERT_FALSE(nonexistent.getValue());
+ EXPECT_EQ(StyleProperty::Kind::Undefined, nonexistent.getKind());
+
+ StyleProperty undefined = lineLayer->getProperty("line-blur");
+ ASSERT_FALSE(undefined.getValue());
+ EXPECT_EQ(StyleProperty::Kind::Undefined, undefined.getKind());
+
+ StyleProperty lineColor = lineLayer->getProperty("line-color");
+ ASSERT_TRUE(lineColor.getValue());
+ EXPECT_EQ(StyleProperty::Kind::Constant, lineColor.getKind());
+ ASSERT_TRUE(lineColor.getValue().getObject());
+ const auto& color = *(lineColor.getValue().getObject());
+ EXPECT_EQ(1.0, *color.at("r").getDouble());
+ EXPECT_EQ(0.0, *color.at("g").getDouble());
+ EXPECT_EQ(0.0, *color.at("b").getDouble());
+ EXPECT_EQ(1.0, *color.at("a").getDouble());
+
+ StyleProperty lineOpacity = lineLayer->getProperty("line-opacity");
+ ASSERT_TRUE(lineOpacity.getValue());
+ EXPECT_EQ(StyleProperty::Kind::Constant, lineOpacity.getKind());
+ ASSERT_TRUE(lineOpacity.getValue().getDouble());
+ EXPECT_EQ(0.5, *lineOpacity.getValue().getDouble());
+
+ StyleProperty lineOpacityTransition = lineLayer->getProperty("line-opacity-transition");
+ ASSERT_TRUE(lineOpacityTransition.getValue());
+ EXPECT_EQ(StyleProperty::Kind::Transition, lineOpacityTransition.getKind());
+ ASSERT_TRUE(lineOpacityTransition.getValue().getArray());
+ EXPECT_EQ(3u, lineOpacityTransition.getValue().getArray()->size());
+
+ StyleProperty lineWidth = lineLayer->getProperty("line-width");
+ ASSERT_TRUE(lineWidth.getValue());
+ EXPECT_EQ(StyleProperty::Kind::Expression, lineWidth.getKind());
+ ASSERT_TRUE(lineWidth.getValue().getArray());
+
+ const auto& expression = *lineWidth.getValue().getArray();
+ EXPECT_EQ(2u, expression.size());
+ ASSERT_TRUE(expression[0].getString());
+ EXPECT_EQ("number", *expression[0].getString());
+ ASSERT_TRUE(expression[1].getArray());
+ const auto& operation = *expression[1].getArray();
+ EXPECT_EQ(2, operation.size());
+ ASSERT_TRUE(operation[0].getString());
+ EXPECT_EQ("get", *operation[0].getString());
+ ASSERT_TRUE(operation[1].getString());
+ EXPECT_EQ("width", *operation[1].getString());
+
+ StyleProperty lineCap = lineLayer->getProperty("line-cap");
+ ASSERT_TRUE(lineCap.getValue());
+ EXPECT_EQ(StyleProperty::Kind::Constant, lineCap.getKind());
+ ASSERT_TRUE(lineCap.getValue().getString());
+ EXPECT_EQ(std::string("butt"), *lineCap.getValue().getString());
+}
+
+TEST(Map, NoHangOnMissingImage) {
+ MapTest<> test;
+
+ test.fileSource->tileResponse = [&](const Resource&) {
+ Response result;
+ result.data = std::make_shared<std::string>(util::read_file("test/fixtures/map/issue12432/0-0-0.mvt"));
+ return result;
+ };
+
+ test.fileSource->spriteImageResponse = [&](const Resource&) {
+ Response result;
+ result.data = std::make_shared<std::string>(util::read_file("test/fixtures/resources/sprite.png"));
+ return result;
+ };
+
+ test.fileSource->spriteJSONResponse = [&](const Resource&) {
+ Response result;
+ result.data = std::make_shared<std::string>(util::read_file("test/fixtures/resources/sprite.json"));
+ return result;
+ };
+
+ const std::string style{R"STYLE({
+ "version": 8,
+ "sprite": "http://example.com/sprites/sprite",
+ "sources": {
+ "mapbox": {
+ "type": "vector",
+ "tiles": ["http://example.com/{z}-{x}-{y}.vector.pbf"]
+ }
+ },
+ "layers": [{
+ "id": "background",
+ "type": "background",
+ "paint": {"background-color": "white"}
+ },{
+ "id": "water",
+ "type": "fill",
+ "source": "mapbox",
+ "source-layer": "water",
+ "paint": {"fill-pattern": "missing"}
+ }]
+ })STYLE"};
+ test.map.getStyle().loadJSON(style);
+ test.frontend.render(test.map);
+
+ test.map.getStyle().loadJSON(style);
+ test.map.jumpTo(test.map.getStyle().getDefaultCamera());
+ // The test passes if the following call does not hang.
+ test.frontend.render(test.map);
} \ No newline at end of file
diff --git a/test/renderer/image_manager.test.cpp b/test/renderer/image_manager.test.cpp
index 16700d713f..d1d0a83c44 100644
--- a/test/renderer/image_manager.test.cpp
+++ b/test/renderer/image_manager.test.cpp
@@ -187,6 +187,24 @@ TEST(ImageManager, OnStyleImageMissingBeforeSpriteLoaded) {
EXPECT_EQ(observer.count, 1);
ASSERT_TRUE(notified);
+ // Request for updated dependencies must be dispatched to the
+ // observer.
+ dependencies.emplace("post", ImageType::Icon);
+ imageManager.getImages(requestor, std::make_pair(dependencies, ++imageCorrelationID));
+ ASSERT_TRUE(requestor.hasPendingRequests());
+
+ runLoop.runOnce();
+ ASSERT_FALSE(requestor.hasPendingRequests());
+
+ // Another requestor shall not have pending requests for already obtained images.
+ StubImageRequestor anotherRequestor(imageManager);
+ imageManager.getImages(anotherRequestor, std::make_pair(dependencies, ++imageCorrelationID));
+ ASSERT_FALSE(anotherRequestor.hasPendingRequests());
+
+ dependencies.emplace("unfamiliar", ImageType::Icon);
+ imageManager.getImages(anotherRequestor, std::make_pair(dependencies, ++imageCorrelationID));
+ EXPECT_TRUE(anotherRequestor.hasPendingRequests());
+ EXPECT_TRUE(anotherRequestor.hasPendingRequest("unfamiliar"));
}
TEST(ImageManager, OnStyleImageMissingAfterSpriteLoaded) {
diff --git a/test/storage/offline_database.test.cpp b/test/storage/offline_database.test.cpp
index 24234b0624..7b5255c9f5 100644
--- a/test/storage/offline_database.test.cpp
+++ b/test/storage/offline_database.test.cpp
@@ -92,6 +92,14 @@ static std::vector<std::string> databaseTableColumns(const std::string& path, co
return columns;
}
+static int databaseAutoVacuum(const std::string& path) {
+ mapbox::sqlite::Database db = mapbox::sqlite::Database::open(path, mapbox::sqlite::ReadOnly);
+ mapbox::sqlite::Statement stmt{db, "pragma auto_vacuum"};
+ mapbox::sqlite::Query query{stmt};
+ query.run();
+ return query.get<int>(0);
+}
+
namespace fixture {
const Resource resource{ Resource::Style, "mapbox://test" };
@@ -1360,13 +1368,45 @@ TEST(OfflineDatabase, MigrateFromV5Schema) {
EXPECT_EQ(6, databaseUserVersion(filename));
- EXPECT_EQ((std::vector<std::string>{ "id", "url_template", "pixel_ratio", "z", "x", "y",
- "expires", "modified", "etag", "data", "compressed",
- "accessed", "must_revalidate" }),
+ EXPECT_EQ((std::vector<std::string>{"id",
+ "url_template",
+ "pixel_ratio",
+ "z",
+ "x",
+ "y",
+ "expires",
+ "modified",
+ "etag",
+ "data",
+ "compressed",
+ "accessed",
+ "must_revalidate"}),
databaseTableColumns(filename, "tiles"));
- EXPECT_EQ((std::vector<std::string>{ "id", "url", "kind", "expires", "modified", "etag", "data",
- "compressed", "accessed", "must_revalidate" }),
- databaseTableColumns(filename, "resources"));
+ EXPECT_EQ(
+ (std::vector<std::string>{
+ "id", "url", "kind", "expires", "modified", "etag", "data", "compressed", "accessed", "must_revalidate"}),
+ databaseTableColumns(filename, "resources"));
+
+ EXPECT_EQ(0u, log.uncheckedCount());
+}
+
+TEST(OfflineDatabase, IncrementalVacuum) {
+ FixtureLog log;
+ deleteDatabaseFiles();
+ util::copyFile(filename, "test/fixtures/offline_database/no_auto_vacuum.db");
+ EXPECT_EQ(0, databaseAutoVacuum(filename));
+
+ {
+ OfflineDatabase db(filename);
+ db.setMaximumAmbientCacheSize(0);
+
+ auto regions = db.listRegions().value();
+ for (auto& region : regions) {
+ db.deleteRegion(std::move(region));
+ }
+ }
+
+ EXPECT_EQ(2, databaseAutoVacuum(filename));
EXPECT_EQ(0u, log.uncheckedCount());
}
diff --git a/test/style/source.test.cpp b/test/style/source.test.cpp
index ca0e79f46a..a08d11c7ad 100644
--- a/test/style/source.test.cpp
+++ b/test/style/source.test.cpp
@@ -35,10 +35,11 @@
#include <mbgl/util/optional.hpp>
#include <mbgl/util/range.hpp>
-#include <mbgl/map/transform.hpp>
#include <mbgl/annotation/annotation_manager.hpp>
#include <mbgl/annotation/annotation_source.hpp>
+#include <mbgl/map/transform.hpp>
#include <mbgl/renderer/image_manager.hpp>
+#include <mbgl/renderer/tile_render_data.hpp>
#include <mbgl/text/glyph_manager.hpp>
#include <cstdint>
@@ -793,6 +794,79 @@ TEST(Source, CustomGeometrySourceSetTileData) {
test.run();
}
+namespace {
+
+class FakeTileSource;
+
+class FakeTile : public Tile {
+public:
+ FakeTile(FakeTileSource& source_, const OverscaledTileID& tileID)
+ : Tile(Tile::Kind::Geometry, tileID), source(source_) {
+ renderable = true;
+ }
+ void setNecessity(TileNecessity necessity) override;
+ bool layerPropertiesUpdated(const Immutable<style::LayerProperties>&) override { return true; }
+
+ std::unique_ptr<TileRenderData> createRenderData() override { return nullptr; }
+
+private:
+ FakeTileSource& source;
+};
+
+class FakeTileSource : public RenderTileSetSource {
+public:
+ MOCK_METHOD1(tileSetNecessity, void(TileNecessity));
+
+ explicit FakeTileSource(Immutable<style::Source::Impl> impl_) : RenderTileSetSource(std::move(impl_)) {}
+ void updateInternal(const Tileset& tileset,
+ const std::vector<Immutable<style::LayerProperties>>& layers,
+ const bool needsRendering,
+ const bool needsRelayout,
+ const TileParameters& parameters) override {
+ tilePyramid.update(layers,
+ needsRendering,
+ needsRelayout,
+ parameters,
+ SourceType::Vector,
+ util::tileSize,
+ tileset.zoomRange,
+ tileset.bounds,
+ [&](const OverscaledTileID& tileID) { return std::make_unique<FakeTile>(*this, tileID); });
+ }
+
+ const optional<Tileset>& getTileset() const override {
+ return static_cast<const style::VectorSource::Impl&>(*baseImpl).tileset;
+ }
+};
+
+void FakeTile::setNecessity(TileNecessity necessity) {
+ source.tileSetNecessity(necessity);
+}
+
+} // namespace
+
+TEST(Source, InvisibleSourcesTileNecessity) {
+ SourceTest test;
+ VectorSource initialized("source", Tileset{{"tiles"}});
+ initialized.loadDescription(*test.fileSource);
+
+ FakeTileSource renderTilesetSource{initialized.baseImpl};
+ RenderSource* renderSource = &renderTilesetSource;
+ LineLayer layer("id", "source");
+ Immutable<LayerProperties> layerProperties =
+ makeMutable<LineLayerProperties>(staticImmutableCast<LineLayer::Impl>(layer.baseImpl));
+ std::vector<Immutable<LayerProperties>> layers{layerProperties};
+ EXPECT_CALL(renderTilesetSource, tileSetNecessity(TileNecessity::Required)).Times(1);
+ renderSource->update(initialized.baseImpl, layers, true, true, test.tileParameters);
+
+ // Necessity for invisible tiles must be set to `optional`.
+ EXPECT_CALL(renderTilesetSource, tileSetNecessity(TileNecessity::Optional)).Times(1);
+ renderSource->update(initialized.baseImpl, layers, false, false, test.tileParameters);
+
+ // Necessity is again `required` once tiles get back visible.
+ EXPECT_CALL(renderTilesetSource, tileSetNecessity(TileNecessity::Required)).Times(1);
+ renderSource->update(initialized.baseImpl, layers, true, false, test.tileParameters);
+}
TEST(Source, RenderTileSetSourceUpdate) {
SourceTest test;
diff --git a/test/text/local_glyph_rasterizer.test.cpp b/test/text/local_glyph_rasterizer.test.cpp
index 2722ee5849..d109b28f32 100644
--- a/test/text/local_glyph_rasterizer.test.cpp
+++ b/test/text/local_glyph_rasterizer.test.cpp
@@ -33,9 +33,7 @@ namespace {
class LocalGlyphRasterizerTest {
public:
LocalGlyphRasterizerTest(const optional<std::string> fontFamily)
- : frontend(1, gfx::ContextMode::Unique, fontFamily)
- {
- }
+ : frontend(1, gfx::HeadlessBackend::SwapBehaviour::NoFlush, gfx::ContextMode::Unique, fontFamily) {}
util::RunLoop loop;
std::shared_ptr<StubFileSource> fileSource = std::make_shared<StubFileSource>();
@@ -45,7 +43,9 @@ public:
void checkRendering(const char * name, double imageMatchPixelsThreshold = 0.015, double pixelMatchThreshold = 0.1) {
test::checkImage(std::string("test/fixtures/local_glyphs/") + name,
- frontend.render(map), imageMatchPixelsThreshold, pixelMatchThreshold);
+ frontend.render(map).image,
+ imageMatchPixelsThreshold,
+ pixelMatchThreshold);
}
};
diff --git a/test/text/quads.test.cpp b/test/text/quads.test.cpp
index 7aaeb4870d..b04617a40b 100644
--- a/test/text/quads.test.cpp
+++ b/test/text/quads.test.cpp
@@ -36,10 +36,8 @@ TEST(getIconQuads, normal) {
TEST(getIconQuads, style) {
Anchor anchor(0.0, 0.0, 0.0, 0.5f, 0);
- ImagePosition image = {
- mapbox::Bin(-1, 20, 20, 0, 0, 0, 0),
- style::Image::Impl("test", PremultipliedImage({1,1}), 1.0)
- };
+ const ImagePosition image = {mapbox::Bin(-1, 20, 20, 0, 0, 0, 0),
+ style::Image::Impl("test", PremultipliedImage({1, 1}), 1.0)};
GeometryCoordinates line;
Shaping shapedText;
@@ -51,235 +49,186 @@ TEST(getIconQuads, style) {
// none
{
- auto shapedIcon = PositionedIcon::shapeIcon(image, {{ -9.5f, -9.5f }}, SymbolAnchorType::Center, 0);
+ auto shapedIcon = PositionedIcon::shapeIcon(image, {{-9.5f, -9.5f}}, SymbolAnchorType::Center, 0);
+
+ EXPECT_FLOAT_EQ(-18.5f, shapedIcon.top());
+ EXPECT_FLOAT_EQ(-0.5f, shapedIcon.right());
+ EXPECT_FLOAT_EQ(-0.5f, shapedIcon.bottom());
+ EXPECT_FLOAT_EQ(-18.5f, shapedIcon.left());
+
SymbolLayoutProperties::Evaluated layout;
- SymbolQuad quad =
- getIconQuad(shapedIcon, WritingModeType::Horizontal);
-
- EXPECT_EQ(quad.tl.x, -19.5);
- EXPECT_EQ(quad.tl.y, -19.5);
- EXPECT_EQ(quad.tr.x, 0.5);
- EXPECT_EQ(quad.tr.y, -19.5);
- EXPECT_EQ(quad.bl.x, -19.5);
- EXPECT_EQ(quad.bl.y, 0.5);
- EXPECT_EQ(quad.br.x, 0.5);
- EXPECT_EQ(quad.br.y, 0.5);
+ SymbolQuad quad = getIconQuad(shapedIcon, WritingModeType::Horizontal);
+
+ EXPECT_FLOAT_EQ(quad.tl.x, -19.5);
+ EXPECT_FLOAT_EQ(quad.tl.y, -19.5);
+ EXPECT_FLOAT_EQ(quad.tr.x, 0.5);
+ EXPECT_FLOAT_EQ(quad.tr.y, -19.5);
+ EXPECT_FLOAT_EQ(quad.bl.x, -19.5);
+ EXPECT_FLOAT_EQ(quad.bl.y, 0.5);
+ EXPECT_FLOAT_EQ(quad.br.x, 0.5);
+ EXPECT_FLOAT_EQ(quad.br.y, 0.5);
}
// width
{
- SymbolLayoutProperties::Evaluated layout;
- layout.get<TextSize>() = 24.0f;
- layout.get<IconTextFit>() = IconTextFitType::Width;
- auto shapedIcon = PositionedIcon::shapeIcon(image, {{ -9.5f, -9.5f }}, SymbolAnchorType::Center, 0);
- shapedIcon.fitIconToText(layout, shapedText, 24.0f);
- SymbolQuad quad =
- getIconQuad(shapedIcon, WritingModeType::Horizontal);
-
- EXPECT_EQ(quad.tl.x, -61);
- EXPECT_EQ(quad.tl.y, 0);
- EXPECT_EQ(quad.tr.x, 21);
- EXPECT_EQ(quad.tr.y, 0);
- EXPECT_EQ(quad.bl.x, -61);
- EXPECT_EQ(quad.bl.y, 20);
- EXPECT_EQ(quad.br.x, 21);
- EXPECT_EQ(quad.br.y, 20);
+ auto shapedIcon = PositionedIcon::shapeIcon(image, {{-9.5f, -9.5f}}, SymbolAnchorType::Center, 0);
+ shapedIcon.fitIconToText(shapedText, IconTextFitType::Width, {{0, 0, 0, 0}}, {{0, 0}}, 24.0f / 24.0f);
+ SymbolQuad quad = getIconQuad(shapedIcon, WritingModeType::Horizontal);
+
+ EXPECT_FLOAT_EQ(quad.tl.x, -64.4444427);
+ EXPECT_FLOAT_EQ(quad.tl.y, 0);
+ EXPECT_FLOAT_EQ(quad.tr.x, 24.4444427);
+ EXPECT_FLOAT_EQ(quad.tr.y, 0);
+ EXPECT_FLOAT_EQ(quad.bl.x, -64.4444427);
+ EXPECT_FLOAT_EQ(quad.bl.y, 20);
+ EXPECT_FLOAT_EQ(quad.br.x, 24.4444427);
+ EXPECT_FLOAT_EQ(quad.br.y, 20);
}
// width x textSize
{
- SymbolLayoutProperties::Evaluated layout;
- layout.get<TextSize>() = 12.0f;
- layout.get<IconTextFit>() = IconTextFitType::Width;
- auto shapedIcon = PositionedIcon::shapeIcon(image, {{ -9.5f, -9.5f }}, SymbolAnchorType::Center, 0);
- shapedIcon.fitIconToText(layout, shapedText, 12.0f);
- SymbolQuad quad =
- getIconQuad(shapedIcon, WritingModeType::Horizontal);
-
- EXPECT_EQ(quad.tl.x, -31);
- EXPECT_EQ(quad.tl.y, -5);
- EXPECT_EQ(quad.tr.x, 11);
- EXPECT_EQ(quad.tr.y, -5);
- EXPECT_EQ(quad.bl.x, -31);
- EXPECT_EQ(quad.bl.y, 15);
- EXPECT_EQ(quad.br.x, 11);
- EXPECT_EQ(quad.br.y, 15);
+ auto shapedIcon = PositionedIcon::shapeIcon(image, {{-9.5f, -9.5f}}, SymbolAnchorType::Center, 0);
+ shapedIcon.fitIconToText(shapedText, IconTextFitType::Width, {{0, 0, 0, 0}}, {{0, 0}}, 12.0f / 24.0f);
+ SymbolQuad quad = getIconQuad(shapedIcon, WritingModeType::Horizontal);
+
+ EXPECT_FLOAT_EQ(quad.tl.x, -32.2222214);
+ EXPECT_FLOAT_EQ(quad.tl.y, -5);
+ EXPECT_FLOAT_EQ(quad.tr.x, 12.2222214);
+ EXPECT_FLOAT_EQ(quad.tr.y, -5);
+ EXPECT_FLOAT_EQ(quad.bl.x, -32.2222214);
+ EXPECT_FLOAT_EQ(quad.bl.y, 15);
+ EXPECT_FLOAT_EQ(quad.br.x, 12.2222214);
+ EXPECT_FLOAT_EQ(quad.br.y, 15);
}
// width x textSize + padding
{
- SymbolLayoutProperties::Evaluated layout;
- layout.get<TextSize>() = 12.0f;
- layout.get<IconTextFit>() = IconTextFitType::Width;
- layout.get<IconTextFitPadding>()[0] = 5.0f;
- layout.get<IconTextFitPadding>()[1] = 10.0f;
- layout.get<IconTextFitPadding>()[2] = 5.0f;
- layout.get<IconTextFitPadding>()[3] = 10.0f;
- auto shapedIcon = PositionedIcon::shapeIcon(image, {{ -9.5f, -9.5f }}, SymbolAnchorType::Center, 0);
- shapedIcon.fitIconToText(layout, shapedText, 12.0f);
- SymbolQuad quad =
- getIconQuad(shapedIcon, WritingModeType::Horizontal);
-
- EXPECT_EQ(quad.tl.x, -41);
- EXPECT_EQ(quad.tl.y, -10);
- EXPECT_EQ(quad.tr.x, 21);
- EXPECT_EQ(quad.tr.y, -10);
- EXPECT_EQ(quad.bl.x, -41);
- EXPECT_EQ(quad.bl.y, 20);
- EXPECT_EQ(quad.br.x, 21);
- EXPECT_EQ(quad.br.y, 20);
+ auto shapedIcon = PositionedIcon::shapeIcon(image, {{-9.5f, -9.5f}}, SymbolAnchorType::Center, 0);
+ shapedIcon.fitIconToText(shapedText, IconTextFitType::Width, {{5, 10, 5, 10}}, {{0, 0}}, 12.0f / 24.0f);
+ SymbolQuad quad = getIconQuad(shapedIcon, WritingModeType::Horizontal);
+
+ EXPECT_FLOAT_EQ(quad.tl.x, -43.3333321);
+ EXPECT_FLOAT_EQ(quad.tl.y, -5);
+ EXPECT_FLOAT_EQ(quad.tr.x, 23.3333321);
+ EXPECT_FLOAT_EQ(quad.tr.y, -5);
+ EXPECT_FLOAT_EQ(quad.bl.x, -43.3333321);
+ EXPECT_FLOAT_EQ(quad.bl.y, 15);
+ EXPECT_FLOAT_EQ(quad.br.x, 23.3333321);
+ EXPECT_FLOAT_EQ(quad.br.y, 15);
}
// height
{
- SymbolLayoutProperties::Evaluated layout;
- layout.get<TextSize>() = 24.0f;
- layout.get<IconTextFit>() = IconTextFitType::Height;
- auto shapedIcon = PositionedIcon::shapeIcon(image, {{ -9.5f, -9.5f }}, SymbolAnchorType::Center, 0);
- shapedIcon.fitIconToText(layout, shapedText, 24.0f);
- SymbolQuad quad =
- getIconQuad(shapedIcon, WritingModeType::Horizontal);
-
- EXPECT_EQ(quad.tl.x, -30);
- EXPECT_EQ(quad.tl.y, -11);
- EXPECT_EQ(quad.tr.x, -10);
- EXPECT_EQ(quad.tr.y, -11);
- EXPECT_EQ(quad.bl.x, -30);
- EXPECT_EQ(quad.bl.y, 31);
- EXPECT_EQ(quad.br.x, -10);
- EXPECT_EQ(quad.br.y, 31);
+ auto shapedIcon = PositionedIcon::shapeIcon(image, {{-9.5f, -9.5f}}, SymbolAnchorType::Center, 0);
+ shapedIcon.fitIconToText(shapedText, IconTextFitType::Height, {{0, 0, 0, 0}}, {{0, 0}}, 24.0f / 24.0f);
+ SymbolQuad quad = getIconQuad(shapedIcon, WritingModeType::Horizontal);
+
+ EXPECT_FLOAT_EQ(quad.tl.x, -30);
+ EXPECT_FLOAT_EQ(quad.tl.y, -12.2222214);
+ EXPECT_FLOAT_EQ(quad.tr.x, -10);
+ EXPECT_FLOAT_EQ(quad.tr.y, -12.2222214);
+ EXPECT_FLOAT_EQ(quad.bl.x, -30);
+ EXPECT_FLOAT_EQ(quad.bl.y, 32.2222214);
+ EXPECT_FLOAT_EQ(quad.br.x, -10);
+ EXPECT_FLOAT_EQ(quad.br.y, 32.2222214);
}
// height x textSize
{
SymbolLayoutProperties::Evaluated layout;
- layout.get<TextSize>() = 12.0f;
- layout.get<IconTextFit>() = IconTextFitType::Height;
- auto shapedIcon = PositionedIcon::shapeIcon(image, {{ -9.5f, -9.5f }}, SymbolAnchorType::Center, 0);
- shapedIcon.fitIconToText(layout, shapedText, 12.0f);
- SymbolQuad quad =
- getIconQuad(shapedIcon, WritingModeType::Horizontal);
-
- EXPECT_EQ(quad.tl.x, -20);
- EXPECT_EQ(quad.tl.y, -6);
- EXPECT_EQ(quad.tr.x, 0);
- EXPECT_EQ(quad.tr.y, -6);
- EXPECT_EQ(quad.bl.x, -20);
- EXPECT_EQ(quad.bl.y, 16);
- EXPECT_EQ(quad.br.x, 0);
- EXPECT_EQ(quad.br.y, 16);
+ auto shapedIcon = PositionedIcon::shapeIcon(image, {{-9.5f, -9.5f}}, SymbolAnchorType::Center, 0);
+ shapedIcon.fitIconToText(shapedText, IconTextFitType::Height, {{0, 0, 0, 0}}, {{0, 0}}, 12.0f / 24.0f);
+ SymbolQuad quad = getIconQuad(shapedIcon, WritingModeType::Horizontal);
+
+ EXPECT_FLOAT_EQ(quad.tl.x, -20);
+ EXPECT_FLOAT_EQ(quad.tl.y, -6.11111069);
+ EXPECT_FLOAT_EQ(quad.tr.x, 0);
+ EXPECT_FLOAT_EQ(quad.tr.y, -6.11111069);
+ EXPECT_FLOAT_EQ(quad.bl.x, -20);
+ EXPECT_FLOAT_EQ(quad.bl.y, 16.1111107);
+ EXPECT_FLOAT_EQ(quad.br.x, 0);
+ EXPECT_FLOAT_EQ(quad.br.y, 16.1111107);
}
// height x textSize + padding
{
- SymbolLayoutProperties::Evaluated layout;
- layout.get<TextSize>() = 12.0f;
- layout.get<IconTextFit>() = IconTextFitType::Height;
- layout.get<IconTextFitPadding>()[0] = 5.0f;
- layout.get<IconTextFitPadding>()[1] = 10.0f;
- layout.get<IconTextFitPadding>()[2] = 5.0f;
- layout.get<IconTextFitPadding>()[3] = 10.0f;
- auto shapedIcon = PositionedIcon::shapeIcon(image, {{ -9.5f, -9.5f }}, SymbolAnchorType::Center, 0);
- shapedIcon.fitIconToText(layout, shapedText, 12.0f);
- SymbolQuad quad =
- getIconQuad(shapedIcon, WritingModeType::Horizontal);
-
- EXPECT_EQ(quad.tl.x, -30);
- EXPECT_EQ(quad.tl.y, -11);
- EXPECT_EQ(quad.tr.x, 10);
- EXPECT_EQ(quad.tr.y, -11);
- EXPECT_EQ(quad.bl.x, -30);
- EXPECT_EQ(quad.bl.y, 21);
- EXPECT_EQ(quad.br.x, 10);
- EXPECT_EQ(quad.br.y, 21);
+ auto shapedIcon = PositionedIcon::shapeIcon(image, {{-9.5f, -9.5f}}, SymbolAnchorType::Center, 0);
+ shapedIcon.fitIconToText(shapedText, IconTextFitType::Height, {{5, 10, 5, 20}}, {{0, 0}}, 12.0f / 24.0f);
+ SymbolQuad quad = getIconQuad(shapedIcon, WritingModeType::Horizontal);
+
+ EXPECT_FLOAT_EQ(quad.tl.x, -20);
+ EXPECT_FLOAT_EQ(quad.tl.y, -11.666666);
+ EXPECT_FLOAT_EQ(quad.tr.x, 0);
+ EXPECT_FLOAT_EQ(quad.tr.y, -11.666666);
+ EXPECT_FLOAT_EQ(quad.bl.x, -20);
+ EXPECT_FLOAT_EQ(quad.bl.y, 21.666666);
+ EXPECT_FLOAT_EQ(quad.br.x, 0);
+ EXPECT_FLOAT_EQ(quad.br.y, 21.666666);
}
// both
{
- SymbolLayoutProperties::Evaluated layout;
- layout.get<TextSize>() = 24.0f;
- layout.get<IconTextFit>() = IconTextFitType::Both;
- auto shapedIcon = PositionedIcon::shapeIcon(image, {{ -9.5f, -9.5f }}, SymbolAnchorType::Center, 0);
- shapedIcon.fitIconToText(layout, shapedText, 24.0f);
- SymbolQuad quad =
- getIconQuad(shapedIcon, WritingModeType::Horizontal);
-
- EXPECT_EQ(quad.tl.x, -61);
- EXPECT_EQ(quad.tl.y, -11);
- EXPECT_EQ(quad.tr.x, 21);
- EXPECT_EQ(quad.tr.y, -11);
- EXPECT_EQ(quad.bl.x, -61);
- EXPECT_EQ(quad.bl.y, 31);
- EXPECT_EQ(quad.br.x, 21);
- EXPECT_EQ(quad.br.y, 31);
+ auto shapedIcon = PositionedIcon::shapeIcon(image, {{-9.5f, -9.5f}}, SymbolAnchorType::Center, 0);
+ shapedIcon.fitIconToText(shapedText, IconTextFitType::Both, {{0, 0, 0, 0}}, {{0, 0}}, 24.0f / 24.0f);
+ SymbolQuad quad = getIconQuad(shapedIcon, WritingModeType::Horizontal);
+
+ EXPECT_FLOAT_EQ(quad.tl.x, -64.4444427);
+ EXPECT_FLOAT_EQ(quad.tl.y, -12.2222214);
+ EXPECT_FLOAT_EQ(quad.tr.x, 24.4444427);
+ EXPECT_FLOAT_EQ(quad.tr.y, -12.2222214);
+ EXPECT_FLOAT_EQ(quad.bl.x, -64.4444427);
+ EXPECT_FLOAT_EQ(quad.bl.y, 32.2222214);
+ EXPECT_FLOAT_EQ(quad.br.x, 24.4444427);
+ EXPECT_FLOAT_EQ(quad.br.y, 32.2222214);
}
// both x textSize
{
- SymbolLayoutProperties::Evaluated layout;
- layout.get<TextSize>() = 12.0f;
- layout.get<IconTextFit>() = IconTextFitType::Both;
- auto shapedIcon = PositionedIcon::shapeIcon(image, {{ -9.5f, -9.5f }}, SymbolAnchorType::Center, 0);
- shapedIcon.fitIconToText(layout, shapedText, 12.0f);
- SymbolQuad quad =
- getIconQuad(shapedIcon, WritingModeType::Horizontal);
-
- EXPECT_EQ(quad.tl.x, -31);
- EXPECT_EQ(quad.tl.y, -6);
- EXPECT_EQ(quad.tr.x, 11);
- EXPECT_EQ(quad.tr.y, -6);
- EXPECT_EQ(quad.bl.x, -31);
- EXPECT_EQ(quad.bl.y, 16);
- EXPECT_EQ(quad.br.x, 11);
- EXPECT_EQ(quad.br.y, 16);
+ auto shapedIcon = PositionedIcon::shapeIcon(image, {{-9.5f, -9.5f}}, SymbolAnchorType::Center, 0);
+ shapedIcon.fitIconToText(shapedText, IconTextFitType::Both, {{0, 0, 0, 0}}, {{0, 0}}, 12.0f / 24.0f);
+ SymbolQuad quad = getIconQuad(shapedIcon, WritingModeType::Horizontal);
+
+ EXPECT_FLOAT_EQ(quad.tl.x, -32.2222214);
+ EXPECT_FLOAT_EQ(quad.tl.y, -6.11111069);
+ EXPECT_FLOAT_EQ(quad.tr.x, 12.2222214);
+ EXPECT_FLOAT_EQ(quad.tr.y, -6.11111069);
+ EXPECT_FLOAT_EQ(quad.bl.x, -32.2222214);
+ EXPECT_FLOAT_EQ(quad.bl.y, 16.1111107);
+ EXPECT_FLOAT_EQ(quad.br.x, 12.2222214);
+ EXPECT_FLOAT_EQ(quad.br.y, 16.1111107);
}
// both x textSize + padding
{
- SymbolLayoutProperties::Evaluated layout;
- layout.get<TextSize>() = 12.0f;
- layout.get<IconTextFit>() = IconTextFitType::Both;
- layout.get<IconTextFitPadding>()[0] = 5.0f;
- layout.get<IconTextFitPadding>()[1] = 10.0f;
- layout.get<IconTextFitPadding>()[2] = 5.0f;
- layout.get<IconTextFitPadding>()[3] = 10.0f;
- auto shapedIcon = PositionedIcon::shapeIcon(image, {{ -9.5f, -9.5f }}, SymbolAnchorType::Center, 0);
- shapedIcon.fitIconToText(layout, shapedText, 12.0f);
- SymbolQuad quad =
- getIconQuad(shapedIcon, WritingModeType::Horizontal);
-
- EXPECT_EQ(quad.tl.x, -41);
- EXPECT_EQ(quad.tl.y, -11);
- EXPECT_EQ(quad.tr.x, 21);
- EXPECT_EQ(quad.tr.y, -11);
- EXPECT_EQ(quad.bl.x, -41);
- EXPECT_EQ(quad.bl.y, 21);
- EXPECT_EQ(quad.br.x, 21);
- EXPECT_EQ(quad.br.y, 21);
+ auto shapedIcon = PositionedIcon::shapeIcon(image, {{-9.5f, -9.5f}}, SymbolAnchorType::Center, 0);
+ shapedIcon.fitIconToText(shapedText, IconTextFitType::Both, {{5, 10, 5, 10}}, {{0, 0}}, 12.0f / 24.0f);
+ SymbolQuad quad = getIconQuad(shapedIcon, WritingModeType::Horizontal);
+
+ EXPECT_FLOAT_EQ(quad.tl.x, -43.3333321);
+ EXPECT_FLOAT_EQ(quad.tl.y, -11.666666);
+ EXPECT_FLOAT_EQ(quad.tr.x, 23.3333321);
+ EXPECT_FLOAT_EQ(quad.tr.y, -11.666666);
+ EXPECT_FLOAT_EQ(quad.bl.x, -43.3333321);
+ EXPECT_FLOAT_EQ(quad.bl.y, 21.666666);
+ EXPECT_FLOAT_EQ(quad.br.x, 23.3333321);
+ EXPECT_FLOAT_EQ(quad.br.y, 21.666666);
}
// both x textSize + padding t/r/b/l
{
SymbolLayoutProperties::Evaluated layout;
layout.get<TextSize>() = 12.0f;
- layout.get<IconTextFit>() = IconTextFitType::Both;
- layout.get<IconTextFitPadding>()[0] = 0.0f;
- layout.get<IconTextFitPadding>()[1] = 5.0f;
- layout.get<IconTextFitPadding>()[2] = 10.0f;
- layout.get<IconTextFitPadding>()[3] = 15.0f;
- auto shapedIcon = PositionedIcon::shapeIcon(image, {{ -9.5f, -9.5f }}, SymbolAnchorType::Center, 0);
- shapedIcon.fitIconToText(layout, shapedText, 12.0f);
- SymbolQuad quad =
- getIconQuad(shapedIcon, WritingModeType::Horizontal);
-
- EXPECT_EQ(quad.tl.x, -46);
- EXPECT_EQ(quad.tl.y, -6);
- EXPECT_EQ(quad.tr.x, 16);
- EXPECT_EQ(quad.tr.y, -6);
- EXPECT_EQ(quad.bl.x, -46);
- EXPECT_EQ(quad.bl.y, 26);
- EXPECT_EQ(quad.br.x, 16);
- EXPECT_EQ(quad.br.y, 26);
+ auto shapedIcon = PositionedIcon::shapeIcon(image, {{-9.5f, -9.5f}}, SymbolAnchorType::Center, 0);
+ shapedIcon.fitIconToText(shapedText, IconTextFitType::Both, {{0, 5, 10, 15}}, {{0, 0}}, 12.0f / 24.0f);
+ SymbolQuad quad = getIconQuad(shapedIcon, WritingModeType::Horizontal);
+
+ EXPECT_FLOAT_EQ(quad.tl.x, -48.3333321);
+ EXPECT_FLOAT_EQ(quad.tl.y, -6.66666603);
+ EXPECT_FLOAT_EQ(quad.tr.x, 18.3333321);
+ EXPECT_FLOAT_EQ(quad.tr.y, -6.66666603);
+ EXPECT_FLOAT_EQ(quad.bl.x, -48.3333321);
+ EXPECT_FLOAT_EQ(quad.bl.y, 26.666666);
+ EXPECT_FLOAT_EQ(quad.br.x, 18.3333321);
+ EXPECT_FLOAT_EQ(quad.br.y, 26.666666);
}
}
-
diff --git a/vendor/mapbox-base b/vendor/mapbox-base
-Subproject 89997045511baf975dce1a3153d32b1fdd8bc69
+Subproject 52e06d824be1986643f8c058dea78223e1e5267
diff --git a/vendor/mapbox-base-files.json b/vendor/mapbox-base-files.json
index 7c638d2b80..55091e438d 100644
--- a/vendor/mapbox-base-files.json
+++ b/vendor/mapbox-base-files.json
@@ -94,6 +94,7 @@
"mapbox/pixelmatch.hpp": "vendor/mapbox-base/mapbox/pixelmatch-cpp/include/mapbox/pixelmatch.hpp",
"supercluster.hpp": "vendor/mapbox-base/mapbox/supercluster.hpp/include/supercluster.hpp",
"mapbox/type_wrapper.hpp": "vendor/mapbox-base/mapbox/typewrapper/include/mapbox/type_wrapper.hpp",
+ "mapbox/value.hpp": "vendor/mapbox-base/mapbox/value/include/mapbox/value.hpp",
"mapbox/optional.hpp": "vendor/mapbox-base/mapbox/variant/include/mapbox/optional.hpp",
"mapbox/recursive_wrapper.hpp": "vendor/mapbox-base/mapbox/variant/include/mapbox/recursive_wrapper.hpp",
"mapbox/variant.hpp": "vendor/mapbox-base/mapbox/variant/include/mapbox/variant.hpp",